Fix bad code generation involving method references and generics.
When a method reference is assigned to a generic functional interface
like:
interface I<T> { boolean m(T o); }
public class C { public boolean f(); }
....
I<C> methodReference = C::f;
The implementation of the method reference is roughly
class SynthesizedImplementation implements I {
public boolean m(Object o) {
o.f();
}
Note the lack of generics (internally GWT does all the transformations
suing raw types).
Because the type of "o" is object, the above code is not type correct and
some of the compiler passes get confused (in the small repro example it
is the Devirtualizer that gets confused and devitualizes that method as
if it were an Object method).
This patch correctly inserts the required cast.
class SynthesizedImplementation implements I {
public boolean m(Object o) {
((C) o).f();
}
Change-Id: I2286516157aa58181987629891c3c2c00951bdd1
Bug-Link:https://github.com/gwtproject/gwt/issues/9214
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 cde69dd..274627a 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
@@ -1742,7 +1742,7 @@
// Calculate what type this reference is going to bind to, and what single abstract method
TypeBinding binding = x.expectedType();
- MethodBinding samBinding = binding.getSingleAbstractMethod(blockScope, false);
+ MethodBinding samBinding = binding.getSingleAbstractMethod(blockScope, false).original();
// Get the interface method is binds to
JMethod interfaceMethod = typeMap.get(samBinding);
@@ -1845,7 +1845,9 @@
if (!haveReceiver && !referredMethod.isStatic() && instance == null &&
samMethod.getParams().size() == referredMethod.getParams().size() + 1) {
// the instance qualifier is the first parameter in this case.
- instance = new JParameterRef(info, paramIt.next());
+ // Needs to be cast the actual type due to generics.
+ instance = new JCastOperation(info, typeMap.get(referredMethodBinding.declaringClass),
+ new JParameterRef(info, paramIt.next()));
}
JMethodCall samCall = null;
@@ -1871,13 +1873,13 @@
// need to build up an array of passed parameters if we have varargs
List<JExpression> varArgInitializers = null;
- int varArg = x.binding.parameters.length - 1;
+ int varArg = referredMethodBinding.parameters.length - 1;
// interface Foo { m(int x, int y); } bound to reference foo(int... args)
// if varargs and incoming param is not already a var-arg, we'll need to convert
// trailing args of the target interface into an array
- if (x.binding.isVarargs() && !samBinding.parameters[varArg].isArrayType()) {
- varArgInitializers = new ArrayList<JExpression>();
+ if (referredMethodBinding.isVarargs() && !samBinding.parameters[varArg].isArrayType()) {
+ varArgInitializers = Lists.newArrayList();
}
while (paramIt.hasNext()) {
@@ -1886,14 +1888,14 @@
// params may need to be boxed or unboxed
TypeBinding destParam = null;
// if it is not the trailing param or varargs, or interface method is already varargs
- if (varArgInitializers == null || !x.binding.isVarargs() || (paramNumber < varArg)) {
- destParam = x.binding.parameters[paramNumber];
+ if (varArgInitializers == null || !referredMethodBinding.isVarargs() || (paramNumber < varArg)) {
+ destParam = referredMethodBinding.parameters[paramNumber];
paramExpr = boxOrUnboxExpression(paramExpr, samBinding.parameters[paramNumber],
destParam);
samCall.addArg(paramExpr);
} else if (!samBinding.parameters[paramNumber].isArrayType()) {
// else add trailing parameters to var-args initializer list for an array
- destParam = x.binding.parameters[varArg].leafComponentType();
+ destParam = referredMethodBinding.parameters[varArg].leafComponentType();
paramExpr = boxOrUnboxExpression(paramExpr, samBinding.parameters[paramNumber],
destParam);
varArgInitializers.add(paramExpr);
@@ -1904,7 +1906,8 @@
// add trailing new T[] { initializers } var-arg array
if (varArgInitializers != null) {
JArrayType lastParamType =
- (JArrayType) typeMap.get(x.binding.parameters[x.binding.parameters.length - 1]);
+ (JArrayType) typeMap.get(
+ referredMethodBinding.parameters[referredMethodBinding.parameters.length - 1]);
JNewArray newArray =
JNewArray.createArrayWithInitializers(info, lastParamType, varArgInitializers);
samCall.addArg(newArray);
@@ -1913,7 +1916,7 @@
// TODO(rluble): Make this a call to JjsUtils.makeMethodEndStatement once boxing/unboxing
// is handled there.
if (samMethod.getType() != JPrimitiveType.VOID) {
- JExpression samExpression = boxOrUnboxExpression(samCall, x.binding.returnType,
+ JExpression samExpression = boxOrUnboxExpression(samCall, referredMethodBinding.returnType,
samBinding.returnType);
samMethodBody.getBlock().addStmt(simplify(samExpression, x).makeReturnStatement());
} else {
@@ -1983,7 +1986,11 @@
return unbox(expr, implicitConversion);
}
- return expr;
+ TypeBinding castToType = fromType.genericCast(toType);
+ if (castToType == null) {
+ return expr;
+ }
+ return new JCastOperation(expr.getSourceInfo(), typeMap.get(castToType), expr);
}
@Override
@@ -3649,6 +3656,9 @@
* Reflective access to {@link ForeachStatement#collectionElementType}.
*/
private static final Field collectionElementTypeField;
+ /**
+ * Reflective access to {@link ReferenceExpression#haveReceiver}.
+ */
private static final Field haveReceiverField;
private static final TypeBinding[] NO_TYPES = new TypeBinding[0];
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 584fab5..90f3728 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
@@ -55,8 +55,8 @@
));
addAll(JavaResourceBase.createMockJavaResource("test.Lambda2",
"package test;",
- "public interface Lambda2<String> {",
- " boolean run(String a, String b);",
+ "public interface Lambda2<T> {",
+ " boolean run(T a, T b);",
"}"
));
addAll(JavaResourceBase.createMockJavaResource("test.AcceptsLambda",
@@ -936,7 +936,8 @@
// should implement run method and invoke lambda via captured instance
JMethod samMethod = findMethod(lambdaInnerClass, "run");
assertEquals(
- "public final boolean run(Object arg0,Object arg1){return arg0.equalsIgnoreCase(arg1);}",
+ "public final boolean run(Object arg0,Object arg1)"
+ + "{return((String)arg0).equalsIgnoreCase((String)arg1);}",
formatSource(samMethod.toSource()));
}
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 50afc72..9702bece 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
@@ -1255,6 +1255,24 @@
assertContentsInOrder(initializationOrder, "A1", "B1", "A2", "C", "B2", "A3", "D");
}
+ /**
+ * Regression test for issue 9214.
+ */
+ interface P<T> {
+ boolean apply(T obj);
+ }
+
+ static class B {
+ public boolean getTrue() {
+ return true;
+ }
+ }
+
+ public void testMethodReference_generics() {
+ P<B> p = B::getTrue;
+ assertTrue(p.apply(new B()));
+ }
+
private void assertContentsInOrder(Iterable<String> contents, String... elements) {
assertEquals(Arrays.asList(elements).toString(), contents.toString());
}
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 894370c..adec54d 100644
--- a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
+++ b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -37,7 +37,7 @@
@Override
public void runTest() throws Throwable {
// Only run these tests if -sourceLevel 8 (or greater) is enabled.
- if (JUnitShell.getCompilerOptions().getSourceLevel().compareTo(SourceLevel.JAVA8) >= 0) {
+ if (isGwtSourceLevel8()) {
super.runTest();
}
}
@@ -45,167 +45,218 @@
public void testLambdaNoCapture() {
// Make sure we are using the right Java8Test if the source compatibility level is set to Java 8
// or above.
- assertFalse(JUnitShell.getCompilerOptions().getSourceLevel().compareTo(SourceLevel.JAVA8) >= 0);
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaCaptureLocal() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaCaptureLocalWithInnerClass() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaCaptureLocalAndField() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaCaptureLocalAndFieldWithInnerClass() {
+ assertFalse(isGwtSourceLevel8());
}
public void testCompileLambdaCaptureOuterInnerField() throws Exception {
+ assertFalse(isGwtSourceLevel8());
}
public void testStaticReferenceBinding() throws Exception {
- }
-
- public static Integer foo(int x, int y) {
- return x + y;
- }
-
- public Integer fooInstance(int x, int y) {
- return x + y + 1;
+ assertFalse(isGwtSourceLevel8());
}
public void testInstanceReferenceBinding() throws Exception {
+ assertFalse(isGwtSourceLevel8());
}
public void testImplicitQualifierReferenceBinding() throws Exception {
+ assertFalse(isGwtSourceLevel8());
}
public void testConstructorReferenceBinding() {
+ assertFalse(isGwtSourceLevel8());
}
public void testStaticInterfaceMethod() {
+ assertFalse(isGwtSourceLevel8());
}
public void testArrayConstructorReference() {
+ assertFalse(isGwtSourceLevel8());
}
public void testArrayConstructorReferenceBoxed() {
+ assertFalse(isGwtSourceLevel8());
}
public void testVarArgsReferenceBinding() {
+ assertFalse(isGwtSourceLevel8());
}
public void testVarArgsPassthroughReferenceBinding() {
+ assertFalse(isGwtSourceLevel8());
}
public void testVarArgsPassthroughReferenceBindingProvidedArray() {
+ assertFalse(isGwtSourceLevel8());
}
public void testSuperReferenceExpression() {
+ assertFalse(isGwtSourceLevel8());
}
public void testSuperReferenceExpressionWithVarArgs() {
+ assertFalse(isGwtSourceLevel8());
}
public void testPrivateConstructorReference() {
+ assertFalse(isGwtSourceLevel8());
}
public void testDefaultInterfaceMethod() {
+ assertFalse(isGwtSourceLevel8());
}
public void testDefaultInterfaceMethodVirtualUpRef() {
+ assertFalse(isGwtSourceLevel8());
}
public void testInterfaceWithDefaultMethodsInitialization() {
+ assertFalse(isGwtSourceLevel8());
}
public void testDefaultInterfaceMethodMultiple() {
+ assertFalse(isGwtSourceLevel8());
}
public void testDefaultMethodReference() {
+ assertFalse(isGwtSourceLevel8());
}
public void testDefenderMethodByInterfaceInstance() {
+ assertFalse(isGwtSourceLevel8());
}
public void testThisRefInDefenderMethod() {
+ assertFalse(isGwtSourceLevel8());
}
public void testClassImplementsTwoInterfacesWithSameDefenderMethod() {
+ assertFalse(isGwtSourceLevel8());
}
public void testAbstractClassImplementsInterface() {
+ assertFalse(isGwtSourceLevel8());
}
public void testSuperRefInDefenderMethod() {
+ assertFalse(isGwtSourceLevel8());
}
public void testSuperThisRefsInDefenderMethod() {
+ assertFalse(isGwtSourceLevel8());
}
public void testNestedInterfaceClass() {
+ assertFalse(isGwtSourceLevel8());
}
public void testBaseIntersectionCast() {
+ assertFalse(isGwtSourceLevel8());
}
public void testIntersectionCastWithLambdaExpr() {
+ assertFalse(isGwtSourceLevel8());
}
public void testIntersectionCastPolymorphism() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaCaptureParameter() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaNestingCaptureLocal() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaNestingInAnonymousCaptureLocal() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaNestingInMultipleMixedAnonymousCaptureLocal() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaNestingInMultipleMixedAnonymousCaptureLocal_withInterference() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaNestingInMultipleMixedAnonymousCaptureLocalAndField() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaNestingInMultipleAnonymousCaptureLocal() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaNestingCaptureField_InnerClassCapturingOuterClassVariable() {
+ assertFalse(isGwtSourceLevel8());
}
public void testInnerClassCaptureLocalFromOuterLambda() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaNestingCaptureField() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaMultipleNestingCaptureFieldAndLocal() {
+ assertFalse(isGwtSourceLevel8());
}
public void testLambdaMultipleNestingCaptureFieldAndLocalInnerClass() {
+ assertFalse(isGwtSourceLevel8());
}
public void testMethodRefWithSameName() {
+ assertFalse(isGwtSourceLevel8());
}
public void testMultipleDefaults_fromInterfaces_left() {
+ assertFalse(isGwtSourceLevel8());
}
public void testMultipleDefaults_fromInterfaces_right() {
+ assertFalse(isGwtSourceLevel8());
}
public void testMultipleDefaults_superclass_left() {
+ assertFalse(isGwtSourceLevel8());
}
public void testMultipleDefaults_superclass_right() {
+ assertFalse(isGwtSourceLevel8());
}
public void testInterfaceThis() {
+ assertFalse(isGwtSourceLevel8());
+ }
+
+ public void testMethodReference_generics() {
+ assertFalse(isGwtSourceLevel8());
+ }
+
+ private boolean isGwtSourceLevel8() {
+ return JUnitShell.getCompilerOptions().getSourceLevel().compareTo(SourceLevel.JAVA8) >= 0;
}
}
\ No newline at end of file