Unlike many of the other ideas in JS, closure js, and typescript, Java doesn't have a type union feature, with the exception of in catch() expressions. JsInterop handles this case by generating new types which express each of the possible unioned types, with 'isFoo' methods that return boolean, and 'asFoo' that return the value cast to that type. The @JsType for those union types incorrectly states that they map to ? in closure JS, so there is no way from jsinterop alone to work backwards to closure JS here.
Instead, I propose that we author union types in Java, with specific annotations to explain that this is a union between several other types. We can't simply annotate the type with @TsUnion(String.class, Number.class) or the like, since it wouldn't be possible to pass JsArray<String> in there as well, so actual members that match the possible types are required.
Example of jsinterop and elemental2, using fetch(resource) (omitting options for the sake of simplicity):
As can be seen from the overloads, either a String or a Request can be specified to this method, but the actual underlying JS method is only defined once - the implementation will have to test which object is passed. In the same way, if a Java method were defined that takes some union type, JS could call it with either type, and the Java code would have to test the type of the parameter.
In closure JS, this is defined as (again, omitting the options/init):
Since this tool starts only from java and we can control the TS output, I suggest we use a union type in this way to describe an API that is flexible in what types can be used. The example input Java here creates a native type mapped to ?, and provides only the required convenience methods to read the values (writing them is optional, and need only exist if other Java would call this code), but still has to behave in the jsinterop type system as a native type. Unioning with null is still supported via @JsNullable. This means all methods on the union type are @JsOverlay.
@JsType
public interface Api {
ResultUnion<String> someFunction(ParamUnion param1, @JsNullable ParamUnion param2);
}
@JsType(isNative=true, name="?", namespace=JsPackage.GLOBAL)
@TsUnion// Proposed annotation on the type itself
public interface ResultUnion<T> {//generics can be supported, but need not be used on each constituent type
// one optional way for java to be able to directly create these, has no effect on ts output
@JsOverlay
static <T> ResultUnion<T> of(Object o) { return Js.cast(o); }
@JsOverlay
@TsUnionMember// Proposed annotation for methods to indicate that this return type is only of the possible types
default Double asNumber() {...}
@JsOverlay
@TsUnionMember
default JsArray<T> asArray() {...}
}
@JsType(isNative=true, name="?", namespace=JsPackage.GLOBAL)
@TsUnion// Proposed annotation on the type itself
public interface ParamUnion {
// since this is a param type, java has no real need for creator methods
@JsOverlay
@TsUnionMember
default Double asNumber() {...}
// another demonstration of a complex type
@JsOverlay
@TsUnionMember
default JsArray<@JsNullable String> asNullableStringArray() {...}
}
Note that both TsUnion and TsUnionMember are probably not both needed, but might make for added clarity.
There is some flexibility in what we can generate here, but at least for now I suggest emitting the union inline, without a named typedef:
interface Api {
someFunction(param1:Array<string | null>|number, param2:Array<string | null>|number|null|undefined):number|Array<string>;
}
Unlike many of the other ideas in JS, closure js, and typescript, Java doesn't have a type union feature, with the exception of in
catch()
expressions. JsInterop handles this case by generating new types which express each of the possible unioned types, with 'isFoo' methods that return boolean, and 'asFoo' that return the value cast to that type. The@JsType
for those union types incorrectly states that they map to?
in closure JS, so there is no way from jsinterop alone to work backwards to closure JS here.Instead, I propose that we author union types in Java, with specific annotations to explain that this is a union between several other types. We can't simply annotate the type with
@TsUnion(String.class, Number.class)
or the like, since it wouldn't be possible to passJsArray<String>
in there as well, so actual members that match the possible types are required.Example of jsinterop and elemental2, using
fetch(resource)
(omitting options for the sake of simplicity):As can be seen from the overloads, either a String or a Request can be specified to this method, but the actual underlying JS method is only defined once - the implementation will have to test which object is passed. In the same way, if a Java method were defined that takes some union type, JS could call it with either type, and the Java code would have to test the type of the parameter.
In closure JS, this is defined as (again, omitting the options/init):
In typescript, we might define this instead as
Since this tool starts only from java and we can control the TS output, I suggest we use a union type in this way to describe an API that is flexible in what types can be used. The example input Java here creates a native type mapped to
?
, and provides only the required convenience methods to read the values (writing them is optional, and need only exist if other Java would call this code), but still has to behave in the jsinterop type system as a native type. Unioning with null is still supported via@JsNullable
. This means all methods on the union type are@JsOverlay
.Note that both TsUnion and TsUnionMember are probably not both needed, but might make for added clarity.
There is some flexibility in what we can generate here, but at least for now I suggest emitting the union inline, without a named typedef: