Bug 499725: Failure to infer type in lambdas

Change-Id: I83ffe3f1eb9285f6d28dfa29f8119891b7284133
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 c82d1c2..5b96590 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
@@ -7253,4 +7253,60 @@
 			"}\n"
 		});
 }
+public void testBug499725() {
+	runConformTest(
+		new String[] {
+			"Try22.java",
+			"import java.rmi.RemoteException;\n" + 
+			"import java.util.Arrays;\n" + 
+			"import java.util.Collection;\n" + 
+			"import java.util.Collections;\n" + 
+			"import java.util.List;\n" + 
+			"import java.util.function.Function;\n" + 
+			"import java.util.stream.Collectors;\n" + 
+			"\n" + 
+			"\n" + 
+			"public class Try22 {\n" + 
+			"    public static class RemoteExceptionWrapper {\n" + 
+			"        @FunctionalInterface\n" + 
+			"        public static interface FunctionRemote<T, R> {\n" + 
+			"            R apply(T t) throws RemoteException;\n" + 
+			"        }\n" + 
+			"        \n" + 
+			"        public static <T, R> Function<T, R> wrapFunction(FunctionRemote<T, R> f) {\n" + 
+			"            return x -> {\n" + 
+			"                try {\n" + 
+			"                    return f.apply(x);\n" + 
+			"                }\n" + 
+			"                catch(RemoteException  e) {\n" + 
+			"                    throw new RuntimeException(e);\n" + 
+			"                }\n" + 
+			"            };\n" + 
+			"        }\n" + 
+			"    }\n" + 
+			"\n" + 
+			"\n" + 
+			"    private static class ThrowingThingy {\n" + 
+			"        public Collection<String> listStuff(String in) throws RemoteException {\n" + 
+			"            return Collections.emptyList();\n" + 
+			"        }\n" + 
+			"    }\n" + 
+			"\n" + 
+			"    \n" + 
+			"    public static void main(String[] args) {\n" + 
+			"        List<String> stagedNodes = Arrays.asList(\"a\", \"b\", \"c\");\n" + 
+			"        ThrowingThingy remoteThing = new ThrowingThingy();  // simulation of a rmi remote, hence the exceptio\n" + 
+			"        \n" + 
+			"        List<String> resultingStuff = stagedNodes.stream()\n" + 
+			"            .flatMap(RemoteExceptionWrapper.wrapFunction(\n" + 
+			"                node -> remoteThing.listStuff(node)    // HERE\n" + 
+			"                    .stream()\n" + 
+			"                    .map(sub -> node + \"/\" + sub)))\n" + 
+			"            .collect(Collectors.toList());\n" + 
+			"        \n" + 
+			"        System.out.println(resultingStuff);\n" + 
+			"    }\n" + 
+			"}\n"
+		});
+}
 }
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NegativeLambdaExpressionsTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NegativeLambdaExpressionsTest.java
index 799b42e..75eb903 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NegativeLambdaExpressionsTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NegativeLambdaExpressionsTest.java
@@ -8576,7 +8576,7 @@
 		"5. ERROR in X.java (at line 38)\n" + 
 		"	return stream.collect(Collectors.toList()); // NO ERROR\n" + 
 		"	       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + 
-		"Type mismatch: cannot convert from List<capture#18-of ? extends String> to Stream<String>\n" + 
+		"Type mismatch: cannot convert from List<capture#19-of ? extends String> to Stream<String>\n" + 
 		"----------\n");
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=428795, - [1.8]Internal compiler error: java.lang.NullPointerException at org.eclipse.jdt.internal.compiler.ast.MessageSend.analyseCode
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExpressionFormula.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExpressionFormula.java
index 6432e23..f903676 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExpressionFormula.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintExpressionFormula.java
@@ -121,7 +121,7 @@
 						inferInvocationApplicability(inferenceContext, method, argumentTypes, isDiamond, inferenceContext.inferenceKind);
 						// b2 has been lifted, inferring poly invocation type amounts to lifting b3.
 					}
-					if (!inferPolyInvocationType(inferenceContext, invocation, this.right, method))
+					if (!inferenceContext.computeB3(invocation, this.right, method))
 						return FALSE;
 					return null; // already incorporated
 				} finally {
@@ -296,7 +296,7 @@
 					InferenceContext18 innerContext = reference.getInferenceContext((ParameterizedMethodBinding) compileTimeDecl);
 					int innerInferenceKind = determineInferenceKind(compileTimeDecl, argumentTypes, innerContext);
 					inferInvocationApplicability(inferenceContext, original, argumentTypes, original.isConstructor()/*mimic a diamond?*/, innerInferenceKind);
-					if (!inferPolyInvocationType(inferenceContext, reference, r, original))
+					if (!inferenceContext.computeB3(reference, r, original))
 						return FALSE;
 					return null; // already incorporated
 				} catch (InferenceFailureException e) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java
index 0710d9b..07851ae 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java
@@ -159,6 +159,10 @@
 	ReferenceBinding object; // java.lang.Object
 	public BoundSet b2;
 	private BoundSet b3;
+	/** Not per JLS: inbox for emulation of how javac passes type bounds from inner to outer */
+	private BoundSet innerInbox; 
+	/** Not per JLS: signal when current is ready to directly merge all bounds from inner. */
+	private boolean directlyAcceptingInnerBounds = false;
 	
 	// InferenceVariable interning:
 	private InferenceVariable[] internedVariables;
@@ -394,6 +398,19 @@
 		ConstraintExpressionFormula.inferInvocationApplicability(this, method, arguments, isDiamond, this.inferenceKind);
 	}
 
+	/** Perform steps from JLS 18.5.2. needed for computing the bound set B3. */
+	boolean computeB3(InvocationSite invocationSite, TypeBinding targetType, MethodBinding method) 
+				throws InferenceFailureException 
+	{
+		boolean result = ConstraintExpressionFormula.inferPolyInvocationType(this, invocationSite, targetType, method);
+		if (result) {
+			mergeInnerBounds();
+			if (this.b3 == null)
+				this.b3 = this.currentBounds.copy();
+		}
+		return result;
+	}
+
 	/** JLS 18.5.2 Invocation Type Inference 
 	 */
 	public BoundSet inferInvocationType(TypeBinding expectedType, InvocationSite invocationSite, MethodBinding method) throws InferenceFailureException 
@@ -412,11 +429,13 @@
 					&& ((Expression)invocationSite).isPolyExpression(method)) 
 			{
 				// 3. bullet: special treatment for poly expressions
-				if (!ConstraintExpressionFormula.inferPolyInvocationType(this, invocationSite, expectedType, method)) {
+				if (!computeB3(invocationSite, expectedType, method)) {
 					return null;
 				}
+			} else {
+				mergeInnerBounds();
+				this.b3 = this.currentBounds.copy();
 			}
-			this.b3 = this.currentBounds.copy();
 
 			if (SHOULD_WORKAROUND_BUG_JDK_8153748) { // "before 18.5.2", but should not spill into b3 ... (heuristically)
 				ReductionResult jdk8153748result = addJDK_8153748ConstraintsFromInvocation(this.invocationArguments, method);
@@ -425,9 +444,12 @@
 				}
 			}
 
+			pushBoundsToOuter();
+			this.directlyAcceptingInnerBounds = true;
+
 			// 4. bullet: assemble C:
 			Set<ConstraintFormula> c = new HashSet<ConstraintFormula>();
-			if (!addConstraintsToC(this.invocationArguments, c, method, this.inferenceKind, false, invocationSite))
+			if (!addConstraintsToC(this.invocationArguments, c, method, this.inferenceKind, invocationSite))
 				return null;
 			// 5. bullet: determine B4 from C
 			List<Set<InferenceVariable>> components = this.currentBounds.computeConnectedComponents(this.inferenceVariables);
@@ -480,6 +502,42 @@
 		}
 	}
 
+	// ---  not per JLS: emulate how javac passes type bounds from inner to outer: ---
+	/** Not per JLS: push current bounds to outer inference if outer is ready for it. */
+	private void pushBoundsToOuter() {
+		InferenceContext18 outer = this.outerContext;
+		if (outer != null && outer.stepCompleted >= APPLICABILITY_INFERRED) {
+			if (outer.directlyAcceptingInnerBounds) {
+				outer.currentBounds.addBounds(this.currentBounds, this.environment);
+			} else if (outer.innerInbox == null) {
+				outer.innerInbox = this.currentBounds.copy();
+			} else {
+				outer.innerInbox.addBounds(this.currentBounds, this.environment);
+			}
+		}
+	}
+	/** Not JLS: merge pending bounds of inner inference into current. */
+	private void mergeInnerBounds() {
+		if (this.innerInbox != null) {
+			this.currentBounds.addBounds(this.innerInbox, this.environment);
+			this.innerInbox = null;
+		}
+	}
+
+	interface InferenceOperation {
+		boolean perform() throws InferenceFailureException;
+	}
+	/** Not per JLS: if operation succeeds merge new bounds from inner into current. */
+	private boolean collectingInnerBounds(InferenceOperation operation) throws InferenceFailureException {
+		boolean result = operation.perform();
+		if (result)
+			mergeInnerBounds();
+		else
+			this.innerInbox = null;
+		return result;
+	}
+	// ---
+
 	private ReductionResult addJDK_8153748ConstraintsFromInvocation(Expression[] arguments, MethodBinding method) throws InferenceFailureException {
 		// not per JLS, trying to mimic javac behavior
 		boolean constraintAdded = false;
@@ -519,12 +577,12 @@
 
 	private ReductionResult addJDK_8153748ConstraintsFromFunctionalExpr(FunctionalExpression functionalExpr, TypeBinding targetType, MethodBinding method) throws InferenceFailureException {
 		if (!functionalExpr.isPertinentToApplicability(targetType, method)) {
-			ConstraintFormula newConstraint = new ConstraintExpressionFormula(functionalExpr, targetType, ReductionResult.COMPATIBLE, ARGUMENT_CONSTRAINTS_ARE_SOFT);
-			if (newConstraint.inputVariables(this).isEmpty()) { // input variable would signal: not ready for inference
-				if (!reduceAndIncorporate(newConstraint))
+			ConstraintFormula exprConstraint = new ConstraintExpressionFormula(functionalExpr, targetType, ReductionResult.COMPATIBLE, ARGUMENT_CONSTRAINTS_ARE_SOFT);
+			if (collectingInnerBounds(() -> exprConstraint.inputVariables(this).isEmpty())) { // input variable would signal: not ready for inference
+				if (!collectingInnerBounds(() -> reduceAndIncorporate(exprConstraint)))
 					return ReductionResult.FALSE;
-				newConstraint = new ConstraintExceptionFormula(functionalExpr, targetType); // ??
-				if (!reduceAndIncorporate(newConstraint))
+				ConstraintFormula excConstraint = new ConstraintExceptionFormula(functionalExpr, targetType); // ??
+				if (!collectingInnerBounds(() -> reduceAndIncorporate(excConstraint)))
 					return ReductionResult.FALSE;
 				return ReductionResult.TRUE;
 			}
@@ -532,7 +590,7 @@
 		return null;
 	}
 
-	private boolean addConstraintsToC(Expression[] exprs, Set<ConstraintFormula> c, MethodBinding method, int inferenceKindForMethod, boolean interleaved, InvocationSite site)
+	private boolean addConstraintsToC(Expression[] exprs, Set<ConstraintFormula> c, MethodBinding method, int inferenceKindForMethod, InvocationSite site)
 			throws InferenceFailureException
 	{
 		TypeBinding[] fs;
@@ -559,14 +617,14 @@
 				TypeBinding fsi = fs[Math.min(i, p-1)];
 				InferenceSubstitution inferenceSubstitution = new InferenceSubstitution(this.environment, this.inferenceVariables, site);
 				TypeBinding substF = inferenceSubstitution.substitute(inferenceSubstitution,fsi);
-				if (!addConstraintsToC_OneExpr(exprs[i], c, fsi, substF, method, interleaved))
+				if (!addConstraintsToC_OneExpr(exprs[i], c, fsi, substF, method))
 					return false;
 	        }
 		}
 		return true;
 	}
 
-	private boolean addConstraintsToC_OneExpr(Expression expri, Set<ConstraintFormula> c, TypeBinding fsi, TypeBinding substF, MethodBinding method, boolean interleaved)
+	private boolean addConstraintsToC_OneExpr(Expression expri, Set<ConstraintFormula> c, TypeBinding fsi, TypeBinding substF, MethodBinding method)
 			throws InferenceFailureException
 	{
 		// -- not per JLS, emulate javac behavior:
@@ -595,7 +653,7 @@
 						Expression[] resultExpressions = lambda.resultExpressions();
 						for (int i = 0, length = resultExpressions == null ? 0 : resultExpressions.length; i < length; i++) {
 							Expression resultExpression = resultExpressions[i];
-							if (!addConstraintsToC_OneExpr(resultExpression, c, r.original(), r, method, true))
+							if (!addConstraintsToC_OneExpr(resultExpression, c, r.original(), r, method))
 								return false;
 						}
 					}
@@ -619,22 +677,22 @@
 			if (innerMethod instanceof ParameterizedGenericMethodBinding)
 				 innerContext = invocation.getInferenceContext((ParameterizedGenericMethodBinding) innerMethod);
 			
-			if (interleaved && innerContext != null) {
+			if (innerContext != null) {
 				MethodBinding shallowMethod = innerMethod.shallowOriginal();
 				innerContext.outerContext = this;
 				if (innerContext.stepCompleted < InferenceContext18.APPLICABILITY_INFERRED) // shouldn't happen, but let's play safe
 					innerContext.inferInvocationApplicability(shallowMethod, argumentTypes, shallowMethod.isConstructor());
-				if (!ConstraintExpressionFormula.inferPolyInvocationType(innerContext, invocation, substF, shallowMethod))
+				if (!innerContext.computeB3(invocation, substF, shallowMethod))
 					return false;
-				return innerContext.addConstraintsToC(arguments, c, innerMethod.genericMethod(), innerContext.inferenceKind, interleaved, invocation);
+				return innerContext.addConstraintsToC(arguments, c, innerMethod.genericMethod(), innerContext.inferenceKind, invocation);
 			} else {
 				int applicabilityKind = getInferenceKind(innerMethod, argumentTypes);
-				return this.addConstraintsToC(arguments, c, innerMethod.genericMethod(), applicabilityKind, interleaved, invocation);
+				return this.addConstraintsToC(arguments, c, innerMethod.genericMethod(), applicabilityKind, invocation);
 			}
 		} else if (expri instanceof ConditionalExpression) {
 			ConditionalExpression ce = (ConditionalExpression) expri;
-			return addConstraintsToC_OneExpr(ce.valueIfTrue, c, fsi, substF, method, interleaved)
-					&& addConstraintsToC_OneExpr(ce.valueIfFalse, c, fsi, substF, method, interleaved);
+			return addConstraintsToC_OneExpr(ce.valueIfTrue, c, fsi, substF, method)
+					&& addConstraintsToC_OneExpr(ce.valueIfFalse, c, fsi, substF, method);
 		}
 		return true;
 	}
@@ -1504,7 +1562,8 @@
 		this.currentBounds.addBounds(innerCtx.b2, this.environment);
 		this.inferenceVariables = innerCtx.inferenceVariables;
 		this.inferenceKind = innerCtx.inferenceKind;
-		innerCtx.outerContext = this;
+		if (!isSameSite(innerCtx.currentInvocation, this.currentInvocation))
+			innerCtx.outerContext = this;
 		this.usesUncheckedConversion = innerCtx.usesUncheckedConversion;
 		for (InferenceVariable variable : this.inferenceVariables)
 			if (!isInterned(variable))