Bug 477894 - [1.8][inference] Cannot resolve lambda argument with
anonymous class
Change-Id: Ie74a4aefd299a20c2035ab6d39814e1451713db7
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java
index 8eb270a..d846c92 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java
@@ -8982,4 +8982,25 @@
"}"
});
}
+ public void testBug535969() {
+ runConformTest(
+ new String[] {
+ "Test.java",
+ "import java.util.List;\n" +
+ "import java.util.stream.Collectors;\n" +
+ "import java.util.stream.Stream;\n" +
+ "\n" +
+ "public class Test {\n" +
+ " public static void main(String[] args) {\n" +
+ " List<String> strings = Stream.of(1, 2, 3).map(i -> {\n" +
+ " return new Object() {\n" +
+ " final Integer myInt = i;\n" +
+ " };\n" +
+ " }).map(o -> {\n" +
+ " return o.myInt.toString();\n" +
+ " }).collect(Collectors.toList());\n" +
+ " }\n" +
+ "}\n"
+ });
+ }
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java
index e3edb55..22a0f2d 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java
@@ -703,6 +703,8 @@
// additional checks, because LE.resolveType may return a valid binding even in the presence of structural errors
if (!lambda.isCompatibleWith(parameterType, scope) || lambda.hasErrors())
continue;
+ // avoid that preliminary local type bindings escape beyond this point:
+ lambda.updateLocalTypesInMethod(candidateMethod);
} else {
updatedArgumentType = argument.resolveType(scope);
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java
index 45190e7..717084e 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java
@@ -48,6 +48,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
@@ -75,6 +76,7 @@
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.eclipse.jdt.internal.compiler.lookup.InferenceContext18;
import org.eclipse.jdt.internal.compiler.lookup.IntersectionTypeBinding18;
+import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
@@ -86,6 +88,8 @@
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.Substitution;
+import org.eclipse.jdt.internal.compiler.lookup.Substitution.NullSubstitution;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;
@@ -94,6 +98,7 @@
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding;
+import org.eclipse.jdt.internal.compiler.lookup.Scope.Substitutor;
import org.eclipse.jdt.internal.compiler.parser.Parser;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilationUnit;
@@ -127,6 +132,8 @@
private HashMap<TypeBinding, LambdaExpression> copiesPerTargetType;
protected Expression [] resultExpressions = NO_EXPRESSIONS;
public InferenceContext18 inferenceContext; // when performing tentative resolve keep a back reference to the driving context
+ private Map<Integer/*sourceStart*/, LocalTypeBinding> localTypes; // support look-up of a local type from this lambda copy
+
public LambdaExpression(CompilationResult compilationResult, boolean assistNode, boolean requiresGenericSignature) {
super(compilationResult);
@@ -450,6 +457,8 @@
if (this.shouldCaptureInstance && this.scope.isConstructorCall) {
this.scope.problemReporter().fieldsOrThisBeforeConstructorInvocation(this);
}
+ // beyond this point ensure that all local type bindings are their final binding:
+ updateLocalTypes();
return (argumentsHaveErrors|parametersHaveErrors) ? null : this.resolvedType;
}
@@ -1400,4 +1409,74 @@
}
return this.classType = new LambdaTypeBinding();
}
+
+ public void addLocalType(LocalTypeBinding localTypeBinding) {
+ if (this.localTypes == null)
+ this.localTypes = new HashMap<>();
+ this.localTypes.put(localTypeBinding.sourceStart, localTypeBinding);
+ }
+
+ /**
+ * During inference, several copies of a lambda may be created.
+ * If a lambda body contains a local type declaration, one binding may be created
+ * within each of the lambda copies. Once inference finished, we need to map all
+ * such local type bindings to the instance from the correct lambda copy.
+ * <p>
+ * When a local type binding occurs as a field of another type binding (e.g.,
+ * type argument), the local type will be replaced in-place, assuming that the
+ * previous binding should never escape the context of resolving this lambda.
+ * </p>
+ */
+ class LocalTypeSubstitutor extends Substitutor {
+ Map<Integer,LocalTypeBinding> localTypes2;
+
+ public LocalTypeSubstitutor(Map<Integer, LocalTypeBinding> localTypes) {
+ this.localTypes2 = localTypes;
+ }
+
+ @Override
+ public TypeBinding substitute(Substitution substitution, TypeBinding originalType) {
+ if (originalType.isLocalType()) {
+ LocalTypeBinding orgLocal = (LocalTypeBinding) originalType;
+ MethodScope lambdaScope2 = orgLocal.scope.enclosingLambdaScope();
+ if (lambdaScope2 != null) {
+ if (((LambdaExpression) lambdaScope2.referenceContext).sourceStart == LambdaExpression.this.sourceStart) {
+ // local type within this lambda needs replacement:
+ TypeBinding substType = this.localTypes2.get(orgLocal.sourceStart);
+ if (substType != null)
+ return substType;
+ }
+ }
+ return originalType;
+ }
+ return super.substitute(substitution, originalType);
+ }
+ }
+
+ private void updateLocalTypes() {
+ if (this.descriptor == null || this.localTypes == null)
+ return;
+ LocalTypeSubstitutor substor = new LocalTypeSubstitutor(this.localTypes);
+ NullSubstitution subst = new NullSubstitution(this.scope.environment());
+ updateLocalTypesInMethod(this.binding, substor, subst);
+ updateLocalTypesInMethod(this.descriptor, substor, subst);
+ this.resolvedType = substor.substitute(subst, this.resolvedType);
+ }
+
+ /**
+ * Perform substitution with a {@link LocalTypeSubstitutor} on all types mentioned in the given method binding.
+ */
+ void updateLocalTypesInMethod(MethodBinding method) {
+ if (this.localTypes == null)
+ return;
+ updateLocalTypesInMethod(method, new LocalTypeSubstitutor(this.localTypes), new NullSubstitution(this.scope.environment()));
+ }
+
+ private void updateLocalTypesInMethod(MethodBinding method, Substitutor substor, Substitution subst) {
+ method.declaringClass = (ReferenceBinding) substor.substitute(subst, method.declaringClass);
+ method.returnType = substor.substitute(subst, method.returnType);
+ for (int i = 0; i < method.parameters.length; i++) {
+ method.parameters[i] = substor.substitute(subst, method.parameters[i]);
+ }
+ }
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalTypeBinding.java
index 2c65366..35d7872 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalTypeBinding.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -18,6 +18,7 @@
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.CaseStatement;
+import org.eclipse.jdt.internal.compiler.ast.LambdaExpression;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
@@ -47,6 +48,10 @@
if (methodBinding != null) {
this.enclosingMethod = methodBinding;
}
+ MethodScope lambdaScope = scope.enclosingLambdaScope();
+ if (lambdaScope != null) {
+ ((LambdaExpression) lambdaScope.referenceContext).addLocalType(this);
+ }
}
public LocalTypeBinding(LocalTypeBinding prototype) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Substitution.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Substitution.java
index bdd7cb9..95642e9 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Substitution.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Substitution.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2009 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -16,6 +16,30 @@
public interface Substitution {
/**
+ * Don't substitute any type variables.
+ * Enables the use of {@link Scope.Substitutor} for other purposes.
+ */
+ public static class NullSubstitution implements Substitution {
+ LookupEnvironment environment;
+
+ public NullSubstitution(LookupEnvironment environment) {
+ this.environment = environment;
+ }
+ @Override
+ public TypeBinding substitute(TypeVariableBinding typeVariable) {
+ return typeVariable;
+ }
+ @Override
+ public boolean isRawSubstitution() {
+ return false;
+ }
+ @Override
+ public LookupEnvironment environment() {
+ return this.environment;
+ }
+ }
+
+ /**
* Returns the type substitute for a given type variable, or itself
* if no substitution got performed.
*/
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java
index 347995b..7dbd365 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java
@@ -1612,6 +1612,10 @@
return false;
if (that.id != TypeIds.NoId && that.id == other.id)
return true;
+ if (that instanceof LocalTypeBinding && other instanceof LocalTypeBinding) {
+ // while a lambda is being resolved, consider a local type as equal to its variant from another lambda copy
+ return ((LocalTypeBinding) that).sourceStart == ((LocalTypeBinding) other).sourceStart;
+ }
return false;
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBound.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBound.java
index 35b012c..2006074 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBound.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBound.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2013, 2015 GK Software AG and others.
+ * Copyright (c) 2013, 2018 GK Software AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -11,7 +11,6 @@
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
-import org.eclipse.jdt.core.compiler.CharOperation;
/**
* Implementation of 18.1.3 in JLS8
@@ -38,7 +37,7 @@
TypeBound(InferenceVariable inferenceVariable, TypeBinding typeBinding, int relation, boolean isSoft) {
this.left = inferenceVariable;
- this.right = safeType(typeBinding);
+ this.right = typeBinding;
if (((inferenceVariable.tagBits | this.right.tagBits) & TagBits.AnnotationNullMASK) != 0) {
if ((inferenceVariable.tagBits & TagBits.AnnotationNullMASK) == (this.right.tagBits & TagBits.AnnotationNullMASK)) {
// strip off identical nullness on both sides:
@@ -58,20 +57,6 @@
this.relation = relation;
this.isSoft = isSoft;
}
-
- private TypeBinding safeType(TypeBinding type) {
- if (type != null && type.isLocalType()) {
- MethodBinding enclosingMethod = ((LocalTypeBinding) type.original()).enclosingMethod;
- if (enclosingMethod != null && CharOperation.prefixEquals(TypeConstants.ANONYMOUS_METHOD, enclosingMethod.selector)) {
- // don't use local class inside lambda: lambda is copied, type will be re-created and thus is unmatchable
- if (type.superclass().id == TypeIds.T_JavaLangObject && type.superInterfaces().length > 0)
- return type.superInterfaces()[0];
- return type.superclass();
- }
- }
- return type;
- }
-
/** distinguish bounds from dependencies. */
boolean isBound() {