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() {