jsinterop-generator
jsinterop-generator copied to clipboard
@JsEnum types cannot be used in a union
Steps to repro - use an externs file with an enum that is referenced by a union type. Roughly:
/**
* @enum {number}
*/
foo.bar.SomeEnum = {
ONE: 0,
TWO: 1,
THREE: 2,
FIVE: 3,
SEVEN: 4
};
/**
* @type {foo.bar.SomeEnum|string}
*/
foo.bar.ObjectWithProperties.prototype.thing;
The error is when j2cl tries to transpile the sources into js, it is reported that the union type is attempting an instanceof
on the enum instead of just treating it like a number (which in this case is all it is).
Easy workaround: rewrite ObjectWithProperties.thing's type to be {number|string}, though that defeats the purpose a bit.
Proposed fix (not having given it great thought): The union type should instanceof based on the type label: in this case, number
. This seems to be a bit difficult to implement though, since the original @enum
might be already made into a java type? The jsinterop.generator.model.Type
instances don't keep track of the type label at this time, so that would need to be added? It could potentially also be helpful in general, j2cl itself could implement $isInstance
with this knowledge if it were part of the @JsEnum
annotation?
Easier error reporting option: instead of waiting for j2cl to fail the generated java, something like this could throw when the generator is trying to emit the union's isFoo() method:
index b72a70a..c9d2e7b 100644
--- a/java/jsinterop/generator/visitor/UnionTypeHelperTypeCreator.java
+++ b/java/jsinterop/generator/visitor/UnionTypeHelperTypeCreator.java
@@ -262,6 +262,10 @@ public class UnionTypeHelperTypeCreator implements ModelVisitor {
return new ArrayTypeReference(OBJECT);
}
+ if (typeReference.getTypeDeclaration() != null && typeReference.getTypeDeclaration().isEnum()) {
+ throw new IllegalStateException("Can't use @enum in instanceof, so cannot construct union type");
+ }
+
// remove Type parameters
if (typeReference instanceof ParametrizedTypeReference) {
return ((ParametrizedTypeReference) typeReference).getMainType();
Riffing on this idea a bit more:
It could potentially also be helpful in general, j2cl itself could implement
$isInstance
with this knowledge if it were part of the@JsEnum
annotation?
This could be handy too, in cases like
/**
* @enum {string}
*/
MyEnum = {
"Orange":"Foo",
"Blue":"Bar"
}
where the string keys don't match the enum name or the ordinal doesn't match the value, so you can either do the "proper" java thing and use MyEnum.values()[n]
or MyEnum.valueOf(str)
, or you can do the "its JS, I do what I want" thing of Js.<MyEnum>cast("Foo")
.
IIRC, we discussed earlier that externs should not have enum definitions. if we are not handling the correctly we should probably have a good warning about it.
I think Closure is willing to accept patches for converting such enums to number.
@jDramaix please correct me if I'm wrong.
Perhaps jsinterop-generator should drop support for @enum
, as it currently (correctly) creates native @JsEnum types? I think treating those enum types as if they were the aliased type could make sense too?
Related: I'm having trouble with @typedef
as well, getting errors when those are handled. There appears to be explicit support in jsinterop-generator for this (for example, jsinterop.generator.closure.visitor.AbstractClosureVisitor#acceptTypedef
), though I don't see tests for it (so I'm unclear of the typedefs in closure's externs are handled wrong, or written wrong). That said, if this typedef mechanism worked, perhaps @enum
code could behave a bit like that?
IIRC, we discussed earlier that externs should not have enum definitions. if we are not handling the correctly we should probably have a good warning about it.
Externs used in Elemental2 should not use closure enum as they represent native api. If your extern represents a closure library, it can use closure enum.
Stupid question: If it is a closure library, then by definition it isn't an extern, right? Wouldn't it just be the closure library as input at that point?
Assuming so, could the jsinterop-generator tool detect the presence/absence of @externs
in the file to know how it should generate the enum?
Concrete example of "closure-compiler provided externs, which definitely do not refer to a closure-library": https://github.com/google/closure-compiler/blob/master/contrib/externs/maps/google_maps_api_v3.js#L4713-L4728
A closure library can provide an extern file if it does not provide the src but a binary (compiled js). The maps api is the concrete example. IF you call maps api in you code, thanks to the extern file, the js compiler can do type checking against the calls the maps api inside your code.
Can you clarify the distinction then you were making, where "externs used in elemental2 should not use closure enum"? I'm afraid I'm not understanding the point you were trying to convey.
Elemental2 is the jsinterop version of apis provided by the browser. Browser code is not closure annotated code and they never define a closure type enum.
Google maps is not an api provided by a browser but by a third party javascript library. As this library is written using closure type system, it can define closure enum type.
I don't think Google Maps vs Browser change things much. I think it is still controversial to define enum in this case; this is because the extern should never be the source of truth of those values and defining them on enum does that.
@jDramaix right - I'm filing this issue against jsinterop-generator ("Generates Java annotated with JsInterop from JavaScript extern sources"), not elemental2. My only goal in this is to understand/document/expand the limitations of jsinterop-generator when trying to generate jsinterop sources based on arbitrary externs, not to specifically tailor this work to browser apis.
So what is the resolution here? Goktug indicated that a change could be made on closure, but I don't quite follow what that change would be:
I think Closure is willing to accept patches for converting such enums to number.
Would this mean that in generating externs, you would get number
instead of the @enum
type? Would tsickle also be modified to emit number params instead of enums when externs are generated?
And for projects which have closure JS side by side with Java (see #44), is it expected then that @enum
would be outright removed? Or is this project not intended to support closure js/ts to be used with Java (not as externs, but to let j2cl java be compatible with those existing closure js types)?
I'm not following most of your questions/conclusions.
You can just send a patch to closure extern to make it a number.
@gkdn ok, that was one of the options I was trying to offer, sorry if that was not clear. Are you saying that generally when we find an @enum
in closure externs it should be a @const
with @type {number}
members?
Yes, that's what we were told earlier.
Thanks, I appreciate the clarification.