google / jsinterop-generator

Generates Java annotated with JsInterop from JavaScript extern sources
Apache License 2.0
78 stars 24 forks source link

Giving a class both a static and instance members with a matching union type causes invalid java to be generated #46

Open niloc132 opened 4 years ago

niloc132 commented 4 years ago

Given two members on a class, one static and one instance, one will be generated with a _STATIC suffix. That is,

Foo.noArgMethod = function() {};
Foo.prototype.noArgMethod = function() {};

will cause this to be generated:

  @JsMethod(name = "noArgMethod")
  public static native Object noArgMethod_STATIC();

  public native Object noArgMethod();

(removed an incorrect statement, will follow up separately)

However, if both of these have a parameter which is unioned, then the generated *UnionType will be emitted twice, as the names are not distinct - unlike the actual member name, the union type's name is only based on the method name and argument name.

Additionally, once a native method is generated for this, two or more @JsOverlay methods will be generated as well, and when those are renamed to append _STATIC to them, a @JsMethod/@JsProperty annotation will also be decorated on them, which will cause a compiler error in J2CL.

Example js with a method:

/**
 * @param {string|number} arg
 */
Foo.unionArgMethod = function(arg) {};
/**
 * @param {string|number} arg
 */
Foo.prototype.unionArgMethod = function(arg) {};

Resulting java:

  @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
  public interface UnionArgMethodArgUnionType {
    @JsOverlay
    static Foo.UnionArgMethodArgUnionType of(Object o) {
      return Js.cast(o);
    }

    @JsOverlay
    default double asDouble() {
      return Js.asDouble(this);
    }

    @JsOverlay
    default String asString() {
      return Js.asString(this);
    }

    @JsOverlay
    default boolean isDouble() {
      return (Object) this instanceof Double;
    }

    @JsOverlay
    default boolean isString() {
      return (Object) this instanceof String;
    }
  }

  @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
  public interface UnionArgMethodArgUnionType {
    @JsOverlay
    static Foo.UnionArgMethodArgUnionType of(Object o) {
      return Js.cast(o);
    }

    @JsOverlay
    default double asDouble() {
      return Js.asDouble(this);
    }

    @JsOverlay
    default String asString() {
      return Js.asString(this);
    }

    @JsOverlay
    default boolean isDouble() {
      return (Object) this instanceof Double;
    }

    @JsOverlay
    default boolean isString() {
      return (Object) this instanceof String;
    }
  }

  @JsOverlay
  @JsMethod(name = "unionArgMethod")
  public static final Object unionArgMethod_STATIC(String arg) {
    return unionArgMethod(Js.<Foo.UnionArgMethodArgUnionType>uncheckedCast(arg));
  }

  @JsMethod(name = "unionArgMethod")
  public static native Object unionArgMethod_STATIC(Foo.UnionArgMethodArgUnionType arg);

  @JsOverlay
  @JsMethod(name = "unionArgMethod")
  public static final Object unionArgMethod_STATIC(double arg) {
    return unionArgMethod(Js.<Foo.UnionArgMethodArgUnionType>uncheckedCast(arg));
  }

  @JsOverlay
  public final Object unionArgMethod(String arg) {
    return unionArgMethod(Js.<Foo.UnionArgMethodArgUnionType>uncheckedCast(arg));
  }

  public native Object unionArgMethod(Foo.UnionArgMethodArgUnionType arg);

  @JsOverlay
  public final Object unionArgMethod(double arg) {
    return unionArgMethod(Js.<Foo.UnionArgMethodArgUnionType>uncheckedCast(arg));
  }

Example JS with a property:

/**
 * @type {string|number}
 */
Foo.unionProperty;
/**
 * @type {string|number}
 */
Foo.prototype.unionProperty;

Resulting Java:

  @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
  public interface UnionPropertyUnionType {
    @JsOverlay
    static Foo.UnionPropertyUnionType of(Object o) {
      return Js.cast(o);
    }

    @JsOverlay
    default double asDouble() {
      return Js.asDouble(this);
    }

    @JsOverlay
    default String asString() {
      return Js.asString(this);
    }

    @JsOverlay
    default boolean isDouble() {
      return (Object) this instanceof Double;
    }

    @JsOverlay
    default boolean isString() {
      return (Object) this instanceof String;
    }
  }

  @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
  public interface UnionPropertyUnionType {
    @JsOverlay
    static Foo.UnionPropertyUnionType of(Object o) {
      return Js.cast(o);
    }

    @JsOverlay
    default double asDouble() {
      return Js.asDouble(this);
    }

    @JsOverlay
    default String asString() {
      return Js.asString(this);
    }

    @JsOverlay
    default boolean isDouble() {
      return (Object) this instanceof Double;
    }

    @JsOverlay
    default boolean isString() {
      return (Object) this instanceof String;
    }
  }

  public static Foo.UnionPropertyUnionType unionProperty;

There is also a separate correctness issue which I'll file separately - in some cases, there is only one unionProperty, the static variant, as the non-static member is failed to be emitted.