Method references should correctly implement their intersection cast Bug: #9653 Change-Id: Icbbc62154fccd673f6eaf9d9c2a55847a58d43a5 Bug-Link: https://github.com/gwtproject/gwt/issues/9653
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java index 656fc3a..240860b 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -1214,14 +1214,7 @@ // And its JInterface container we must implement // There may be more than more JInterface containers to be implemented // if the lambda expression is cast to a IntersectionCastType. - JInterfaceType[] lambdaInterfaces; - if (binding instanceof IntersectionTypeBinding18) { - IntersectionTypeBinding18 type = (IntersectionTypeBinding18) binding; - lambdaInterfaces = - processIntersectionType(type, new JInterfaceType[type.intersectingTypes.length]); - } else { - lambdaInterfaces = new JInterfaceType[] {(JInterfaceType) typeMap.get(binding)}; - } + JInterfaceType[] lambdaInterfaces = getInterfacesToImplement(binding); SourceInfo info = makeSourceInfo(x); // Create an inner class to implement the interface and SAM method. @@ -1270,6 +1263,14 @@ newTypes.add(innerLambdaClass); } + private JInterfaceType[] getInterfacesToImplement(TypeBinding binding) { + if (binding instanceof IntersectionTypeBinding18) { + IntersectionTypeBinding18 type = (IntersectionTypeBinding18) binding; + return processIntersectionType(type, new JInterfaceType[type.intersectingTypes.length]); + } + return new JInterfaceType[]{(JInterfaceType) typeMap.get(binding)}; + } + private void createFunctionalExpressionBridges( JClassType functionalExpressionImplementationClass, FunctionalExpression functionalExpression, @@ -1771,7 +1772,8 @@ binding.getSingleAbstractMethod(blockScope, false).original(); // Get the interface method is binds to JMethod interfaceMethod = typeMap.get(declarationSamBinding); - JInterfaceType funcType = (JInterfaceType) typeMap.get(binding); + + JInterfaceType[] funcType = getInterfacesToImplement(binding); SourceInfo info = makeSourceInfo(x); // Get the method that the Type::method is actually referring to
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java index a85cb10..ac51d85 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java +++ b/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java
@@ -1117,7 +1117,7 @@ formatSource(samMethod.toSource())); } - public void testIntersectionCastMultipleAbstractMethods() throws Exception { + public void testIntersectionCastOfLambdaMultipleAbstractMethods() throws Exception { addSnippetClassDecl("interface I1 { public void foo(); }"); addSnippetClassDecl("interface I2 { public void foo(); }"); String lambda = "Object o = (I1 & I2) () -> {};"; @@ -1140,6 +1140,166 @@ formatSource(samMethod.toSource())); } + public void testIntersectionCastOfLambdaMultipleAbstractMethodsWithGenerics() throws Exception { + addSnippetClassDecl("interface I1 extends I2<String> { public void foo(String arg0); }"); + addSnippetClassDecl("interface I2<T> { public void foo(T arg); }"); + String lambda = "Object o = (I1 & I2<String>) str -> {};"; + assertEqualBlock("Object o=(EntryPoint$I1)new EntryPoint$lambda$0$Type();", lambda); + + JProgram program = compileSnippet("void", lambda, false); + + assertNotNull(getMethod(program, "lambda$0")); + + JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$lambda$0$Type"); + assertNotNull(lambdaInnerClass); + assertEquals("java.lang.Object", lambdaInnerClass.getSuperClass().getName()); + assertEquals(1, lambdaInnerClass.getImplements().size()); + assertTrue( + lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I1"))); + // should implement foo method + JMethod samMethod = findMethod(lambdaInnerClass, "foo(Ljava/lang/String;)V"); + assertEquals("public final void foo(String arg0){EntryPoint.lambda$0(arg0);}", + formatSource(samMethod.toSource())); + } + public void testIntersectionCastOfMethodReference() throws Exception { + addSnippetClassDecl("static class C { public static void go() {} }"); + addSnippetClassDecl("interface I1 { public void foo(); }"); + addSnippetClassDecl("interface I2 { }"); + String methodReference = "Object o = (I2 & I1) C::go;"; + assertEqualBlock("Object o=(EntryPoint$I1)(EntryPoint$I2)new EntryPoint$0methodref$go$Type();", + methodReference); + JProgram program = compileSnippet("void", methodReference, false); + + // created by GwtAstBuilder + JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$0methodref$go$Type"); + assertNotNull(lambdaInnerClass); + + // no fields + assertEquals(0, lambdaInnerClass.getFields().size()); + + // should have constructor taking no args + JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$0methodref$go$Type"); + assertTrue(ctor instanceof JConstructor); + assertEquals(0, ctor.getParams().size()); + + // should implements I1 and I2 + assertTrue( + lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I1"))); + assertTrue( + lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I2"))); + // should implement foo method + JMethod samMethod = findMethod(lambdaInnerClass, "foo"); + assertEquals("public final void foo(){EntryPoint$C.go();}", + formatSource(samMethod.toSource())); + } + + public void testMultipleIntersectionCastOfMethodReference() throws Exception { + addSnippetClassDecl("static class C { public static void go() {} }"); + addSnippetClassDecl("interface I1 { public void foo(); }"); + addSnippetClassDecl("interface I2 { }"); + addSnippetClassDecl("interface I3 { }"); + String methodReference = "I2 o = (I3 & I2 & I1) C::go;"; + assertEqualBlock( + "EntryPoint$I2 o=(EntryPoint$I1)(EntryPoint$I2)(EntryPoint$I3)new EntryPoint$0methodref$go$Type();", + methodReference); + + JProgram program = compileSnippet("void", methodReference, false); + + // created by GwtAstBuilder + JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$0methodref$go$Type"); + assertNotNull(lambdaInnerClass); + + // no fields + assertEquals(0, lambdaInnerClass.getFields().size()); + + // should have constructor taking no args + JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$0methodref$go$Type"); + assertTrue(ctor instanceof JConstructor); + assertEquals(0, ctor.getParams().size()); + + // should extends java.lang.Object, implements I1, I2 and I3 + assertEquals("java.lang.Object", lambdaInnerClass.getSuperClass().getName()); + assertTrue( + lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I1"))); + assertTrue( + lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I2"))); + assertTrue( + lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I3"))); + // should implement foo method + JMethod samMethod = findMethod(lambdaInnerClass, "foo"); + assertEquals("public final void foo(){EntryPoint$C.go();}", + formatSource(samMethod.toSource())); + } + + public void testIntersectionCastOfMethodReferenceOneAbstractMethod() throws Exception { + addSnippetClassDecl("static class C { public static void go() {} }"); + addSnippetClassDecl("interface I1 { public void foo(); }"); + addSnippetClassDecl("interface I2 extends I1{ public void foo();}"); + String lambda = "Object o = (I1 & I2) C::go;"; + // (I1 & I2) is resolved to I2 by JDT. + assertEqualBlock("Object o=(EntryPoint$I2)new EntryPoint$0methodref$go$Type();", + lambda); + + JProgram program = compileSnippet("void", lambda, false); + + JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$0methodref$go$Type"); + assertNotNull(lambdaInnerClass); + assertEquals("java.lang.Object", lambdaInnerClass.getSuperClass().getName()); + assertEquals(1, lambdaInnerClass.getImplements().size()); // only implements I2. + assertTrue( + lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I2"))); + // should implement foo method + JMethod samMethod = findMethod(lambdaInnerClass, "foo"); + assertEquals("public final void foo(){EntryPoint$C.go();}", + formatSource(samMethod.toSource())); + } + + public void testIntersectionCastOfMethodReferenceMultipleAbstractMethods() throws Exception { + addSnippetClassDecl("static class C { public static void go() {} }"); + addSnippetClassDecl("interface I1 { public void foo(); }"); + addSnippetClassDecl("interface I2 { public void foo(); }"); + String methodReference = "Object o = (I1 & I2) C::go;"; + assertEqualBlock("Object o=(EntryPoint$I1)(EntryPoint$I2)new EntryPoint$0methodref$go$Type();", + methodReference); + + JProgram program = compileSnippet("void", methodReference, false); + + JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$0methodref$go$Type"); + assertNotNull(lambdaInnerClass); + assertEquals("java.lang.Object", lambdaInnerClass.getSuperClass().getName()); + assertEquals(2, lambdaInnerClass.getImplements().size()); + assertTrue( + lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I1"))); + assertTrue( + lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I2"))); + // should implement foo method + JMethod samMethod = findMethod(lambdaInnerClass, "foo"); + assertEquals("public final void foo(){EntryPoint$C.go();}", + formatSource(samMethod.toSource())); + } + + public void testIntersectionCastOfMethodReferenceMultipleAbstractMethodsWithGenerics() throws Exception { + addSnippetClassDecl("static class C { public static void go(String arg) {} }"); + addSnippetClassDecl("interface I1 extends I2<String> { public void foo(String arg); }"); + addSnippetClassDecl("interface I2<T> { public void foo(T arg); }"); + String methodReference = "Object o = (I1 & I2<String>) C::go;"; + assertEqualBlock("Object o=(EntryPoint$I1)new EntryPoint$0methodref$go$Type();", + methodReference); + + JProgram program = compileSnippet("void", methodReference, false); + + JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$0methodref$go$Type"); + assertNotNull(lambdaInnerClass); + assertEquals("java.lang.Object", lambdaInnerClass.getSuperClass().getName()); + assertEquals(1, lambdaInnerClass.getImplements().size()); + assertTrue( + lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I1"))); + // should implement foo method + JMethod samMethod = findMethod(lambdaInnerClass, "foo(Ljava/lang/String;)V"); + assertEquals("public final void foo(String arg){EntryPoint$C.go(arg);}", + formatSource(samMethod.toSource())); + } + private static final MockJavaResource LAMBDA_METAFACTORY = JavaResourceBase.createMockJavaResource("java.lang.invoke.LambdaMetafactory", "package java.lang.invoke;",
diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java index 907d9f3..a251c73 100644 --- a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java +++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -1617,7 +1617,7 @@ assertEquals(4, outer.createInner2Param().apply(1, 2).sum); assertEquals(7, outer.createInner3Param().apply(1, 2, 3).sum); assertEquals(7, outer.createInner2ParamArray().apply(1, new Integer[] {2, 3}).sum); - + // inner class constructor varargs + autoboxing assertEquals(2, outer.createInner1IntParam().apply(1).sum); assertEquals(4, outer.createInner2IntParam().apply(1, 2).sum); @@ -2006,7 +2006,7 @@ //////////////////////////////////////////////////////////// // // Tests for language features introduced in Java 9 - + class Resource implements AutoCloseable { boolean isOpen = true; @@ -2080,4 +2080,37 @@ Predicate p = o -> true; assertTrue(p.test(null)); } + + interface I2<T> { public T foo(T arg); } + + interface I1 extends I2<String> { public String foo(String arg0); } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public void testIntersectionCastLambda() { + + Object instance = (I1 & I2<String>) val -> "#" + val; + + assertTrue(instance instanceof I1); + assertTrue(instance instanceof I2); + + I1 lambda = (I1) instance; + I2 raw = lambda; + assertEquals("#1", raw.foo("1")); // tests that the bridge exists and is correct + assertEquals("#2", lambda.foo("2")); + } + + static class C { public static String append(String str) { return "#" + str; } } + @SuppressWarnings({"rawtypes", "unchecked"}) + public void testIntersectionCastMethodReference() { + + Object instance = (I1 & I2<String>) C::append; + + assertTrue(instance instanceof I1); + assertTrue(instance instanceof I2); + + I1 lambda = (I1) instance; + I2 raw = lambda; + assertEquals("#1", raw.foo("1")); // tests that the bridge exists and is correct + assertEquals("#2", lambda.foo("2")); + } }
diff --git a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java index 312d70c..5e4c773 100644 --- a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java +++ b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -360,6 +360,14 @@ assertFalse(isGwtSourceLevel9()); } + public void testIntersectionCastLambda() { + assertFalse(isGwtSourceLevel9()); + } + + public void testIntersectionCastMethodReference() { + assertFalse(isGwtSourceLevel9()); + } + private boolean isGwtSourceLevel9() { return JUnitShell.getCompilerOptions().getSourceLevel().compareTo(SourceLevel.JAVA9) >= 0; }