-
Notifications
You must be signed in to change notification settings - Fork 332
Description
I had to dig into the compiler to understand why this works the way it does, and haven't found it yet documented anywhere else, so starting here.
While Java has both classes and interfaces, and both can be used nearly interchangably in the type system, JS has a keyword class that isn't entirely aligned with documented js classes, and JS spec IDL files describe "interfaces" (as well as "enum", "dictionary", and other kinds of types) that aren't really represented in the JS language itself.
One might assume from this that the choice between interface and class in some jsinterop exported code is effectively dependent on what the Java developer wants to express (NOTE, this is WRONG):
- create something native, Java can now reference it directly
- create something non-native, ensure it is exported, assume that JS will be able to "extend" and use it.
In JS, extending a class is done in a variety of waits - but implementing an interface is merely matching its contract. Quite frequently in JS it is even sufficient to "extend" a class by matching its contract as well - TS's use of "structural typing" has reinforced this idea in many parts of the JS ecosystem.
However, this isn't how GWT views the world here - there is a subtle difference, and changing it risks breaking existing GWT projects out in the wild. GWT assumes that non-native types will have members exposed to JS, but under the hood, they are still modeled as Java rather than JS. This means they have typeIds and castable type maps rather than using js instanceof and constructor functions. As such, a JS type cannot implement a non-native interface, since it won't be castable to that type by any check. From there, it is clear that the compiler is now free to prune the entire interface if it never sees an implementation of it.
Instead, developers must model "interfaces" that are part of the application but expected to be implemented by external JS as if they are native types. Thus, the decision for the developer actually is closer to
- Am I modeling a function? JsFunction.
- Am I modeling a type defined in the browser or by some external code? isNative=true on class or interface.
- Am I modeling a type defined in my application with a constructor defined in Java? isNative=false on class.
- Am I modeling a Java interface that a JS consumer will implement? isNative=true on interface.
- Am I modeling a Java interface that only Java classes can implement? isNative=true on interface.