Simn / genjvm

13 stars 1 forks source link

Interfaces vs. type parameters #40

Closed Simn closed 5 years ago

Simn commented 5 years ago
interface I<T> {
    public function get(t:T):Void;
}

class Main implements I<String> {
    static public function main() {
        var i:I<String> = new Main();
        i.get("foo");
    }

    function new() { }

    public function get(s:String) { }
}
Exception in thread "main" java.lang.AbstractMethodError: Method Main.get(Ljava/lang/Object;)V is abstract
        at Main.get(src/Main.hx)
        at Main.main(src/Main.hx:8)

The problem is this invokeinterface instruction:

14: invokeinterface #17,  2           // InterfaceMethod I.get:(Ljava/lang/Object;)V

I wonder if this is supposed to use the concrete type...

Simn commented 5 years ago

Turns out javac generates a generalized function in that case...

public class Main implements I<java.lang.String>
{
    public static void main(String[] args)
    {
        I<java.lang.String> i = new Main();
        i.get("foo");
    }

    void Main() { }

    public void get(String arg) {
        System.out.println(arg);
    }
}
  public void get(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LineNumberTable:
        line 13: 0

  public void get(java.lang.Object);
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #8                  // class java/lang/String
         5: invokevirtual #9                  // Method get:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 1: 0

That makes me sad.

Simn commented 5 years ago

And yes, the run-time dispatches to the generalized function:

interface I<T> {
    public function get(t:T):Void;
}

class Main implements I<String> {
    static public function main() {
        var i:I<String> = new Main();
        i.get("foo");
    }

    function new() { }

    @:overload public function get(s:String) {
        Sys.println("get(String): " + s);
    }

    @:overload public function get(obj:Dynamic) {
        Sys.println("get(Dynamic): " + obj);
    }
}
get(Dynamic): foo

That means we have to error in this case. Java does so too:

Name clash: The method get(Object) of type Main has the same erasure as get(T) of type I<T> but does not override
Simn commented 5 years ago

Alternatively, we could treat all interfaces with type parameters as if they were @:generic. But that probably comes with its own set of issues.

Simn commented 5 years ago

Actually, this affects base-class relations too:

class Base<T> {
    public function get(t:T):Void {
        Sys.println("get(T): " + t);
    }
}

class Main extends Base<String> {
    static public function main() {
        var i:Base<String> = new Main();
        i.get("foo");
    }

    function new() { }

    override public function get(s:String) {
        Sys.println("get(String): " + s);
    }
}
get(T): foo