Bug 509324: [1.8][inference] jdt generics regression in neon.2

Change-Id: Iab0852112ebb3880725ba76b64f28691d4e2424f
Signed-off-by: Stephan Herrmann <stephan.herrmann@berlin.de>
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 7583065..7907c77 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2016 GK Software AG, and others.
+ * Copyright (c) 2013, 2017 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
@@ -7349,4 +7349,54 @@
 			"}\n"
 		});
 }
+	public void testBug509324() {
+		runConformTest(
+			new String[] {
+				"testgenerics/TestGenerics.java",
+				"package testgenerics;\n" + 
+				"\n" + 
+				"import java.time.Duration;\n" + 
+				"import java.util.function.Function;\n" + 
+				"import java.util.function.Supplier;\n" + 
+				"\n" + 
+				"interface Publisher<T> {}\n" + 
+				"\n" + 
+				"abstract class Mono<T> implements Publisher<T> {\n" + 
+				"	public static <T> Mono<T> just(T data) { return null; }\n" + 
+				"	public static <T> Mono<T> empty() { return null; }\n" + 
+				"	public final <R> Mono<R> then(Function<? super T, ? extends Mono<? extends R>> transformer) {\n" + 
+				"		return null;\n" + 
+				"	}\n" + 
+				"	public T block() { return null; }\n" + 
+				"	public final T block(Duration timeout) { return null; }\n" + 
+				"}\n" + 
+				"class Info {\n" + 
+				"	public String getApplicationSshEndpoint() { return null; }\n" + 
+				"}\n" + 
+				"class SshHost {\n" + 
+				"	public SshHost(String host, int port, String fingerPrint) { }\n" + 
+				"}\n" + 
+				"\n" + 
+				"public class TestGenerics {\n" + 
+				"\n" + 
+				"	private Mono<Info> info = Mono.just(new Info());\n" + 
+				"\n" + 
+				"	public static <T> T ru_get(Mono<T> mono) throws Exception {\n" + 
+				"		return mono.block();\n" + 
+				"	}\n" + 
+				"\n" + 
+				"	public SshHost getSshHost() throws Exception {\n" + 
+				"		return ru_get(\n" + 
+				"			info.then((i) -> {\n" + 
+				"				String host = i.getApplicationSshEndpoint();\n" + 
+				"				if (host!=null) {\n" + 
+				"					return Mono.just(new SshHost(host, 0, host));\n" + 
+				"				}\n" + 
+				"				return Mono.empty();\n" + 
+				"			})\n" + 
+				"		);\n" + 
+				"	}\n" +
+				"}\n"
+			});
+	}
 }
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 1998a94..8454e95 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2016 GK Software AG, and others.
+ * Copyright (c) 2013, 2017 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
@@ -403,7 +403,7 @@
 			}
 
 			if (SHOULD_WORKAROUND_BUG_JDK_8153748) { // "before 18.5.2", but should not spill into b3 ... (heuristically)
-				ReductionResult jdk8153748result = addJDK_8153748ConstraintsFromInvocation(this.invocationArguments, method);
+				ReductionResult jdk8153748result = addJDK_8153748ConstraintsFromInvocation(this.invocationArguments, method, new InferenceSubstitution(this));
 				if (jdk8153748result != null) {
 					this.currentBounds.incorporate(this);
 				}
@@ -521,15 +521,17 @@
 	}
 	// ---
 
-	private ReductionResult addJDK_8153748ConstraintsFromInvocation(Expression[] arguments, MethodBinding method) throws InferenceFailureException {
+	private ReductionResult addJDK_8153748ConstraintsFromInvocation(Expression[] arguments, MethodBinding method, InferenceSubstitution substitution)
+			throws InferenceFailureException
+	{
 		// not per JLS, trying to mimic javac behavior
 		boolean constraintAdded = false;
 		if (arguments != null) {
 			for (int i = 0; i < arguments.length; i++) {
 				Expression argument = arguments[i];
 				TypeBinding parameter = getParameter(method.parameters, i, method.isVarargs());
-				parameter = this.substitute(parameter);
-				ReductionResult result = addJDK_8153748ConstraintsFromExpression(argument, parameter, method);
+				parameter = substitution.substitute(substitution, parameter); 
+				ReductionResult result = addJDK_8153748ConstraintsFromExpression(argument, parameter, method, substitution);
 				if (result == ReductionResult.FALSE)
 					return ReductionResult.FALSE;
 				if (result == ReductionResult.TRUE)
@@ -539,7 +541,10 @@
 		return constraintAdded ? ReductionResult.TRUE : null;
 	}
 
-	private ReductionResult addJDK_8153748ConstraintsFromExpression(Expression argument, TypeBinding parameter, MethodBinding method) throws InferenceFailureException {
+	private ReductionResult addJDK_8153748ConstraintsFromExpression(Expression argument, TypeBinding parameter, MethodBinding method,
+			InferenceSubstitution substitution)
+			throws InferenceFailureException
+	{
 		if (argument instanceof FunctionalExpression) {
 			return addJDK_8153748ConstraintsFromFunctionalExpr((FunctionalExpression) argument, parameter, method);
 		} else if (argument instanceof Invocation && argument.isPolyExpression(method)) {
@@ -547,13 +552,14 @@
 			Expression[] innerArgs = invocation.arguments();
 			MethodBinding innerMethod = invocation.binding();
 			if (innerMethod != null && innerMethod.isValidBinding()) {
-				return addJDK_8153748ConstraintsFromInvocation(innerArgs, innerMethod.original());
+				substitution = enrichSubstitution(substitution, invocation, innerMethod);
+				return addJDK_8153748ConstraintsFromInvocation(innerArgs, innerMethod.original(), substitution);
 			}
 		} else if (argument instanceof ConditionalExpression) {
 			ConditionalExpression ce = (ConditionalExpression) argument;
-			if (addJDK_8153748ConstraintsFromExpression(ce.valueIfTrue, parameter, method) == ReductionResult.FALSE)
+			if (addJDK_8153748ConstraintsFromExpression(ce.valueIfTrue, parameter, method, substitution) == ReductionResult.FALSE)
 				return ReductionResult.FALSE;
-			return addJDK_8153748ConstraintsFromExpression(ce.valueIfFalse, parameter, method);
+			return addJDK_8153748ConstraintsFromExpression(ce.valueIfFalse, parameter, method, substitution);
 		}
 		return null;
 	}
@@ -587,6 +593,15 @@
 		}
 		return null;
 	}
+	
+	InferenceSubstitution enrichSubstitution(InferenceSubstitution substitution, Invocation innerInvocation, MethodBinding innerMethod) {
+		if (innerMethod instanceof ParameterizedGenericMethodBinding) {
+			InferenceContext18 innerContext = innerInvocation.getInferenceContext((ParameterizedMethodBinding) innerMethod);
+			if (innerContext != null)
+				return substitution.addContext(innerContext);
+		}
+		return substitution;
+	}
 
 	private boolean addConstraintsToC(Expression[] exprs, Set<ConstraintFormula> c, MethodBinding method, int inferenceKindForMethod, InvocationSite site)
 			throws InferenceFailureException
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceSubstitution.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceSubstitution.java
index d482c7a..fa208b6 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceSubstitution.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceSubstitution.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2015 GK Software AG.
+ * Copyright (c) 2013, 2017 GK Software AG.
  * 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
@@ -17,18 +17,58 @@
 
 	private LookupEnvironment environment;
 	private InferenceVariable[] variables;
-	private InvocationSite site;
+	private InvocationSite[] sites;
 
 	public InferenceSubstitution(LookupEnvironment environment, InferenceVariable[] variables, InvocationSite site) {
 		this.environment = environment;
 		this.variables = variables;
-		this.site = site;
+		this.sites = new InvocationSite[] {site};
 	}
 
 	public InferenceSubstitution(InferenceContext18 context) {
 		this(context.environment, context.inferenceVariables, context.currentInvocation);
 	}
 
+	/** Answer a substitution that is able to substitute into inference variables of several inference contexts (outer & inner) */
+	public InferenceSubstitution addContext(InferenceContext18 otherContext) {
+		InferenceSubstitution subst = new InferenceSubstitution(this.environment, null, null) {
+
+			protected boolean isSameParameter(TypeBinding p1, TypeBinding originalType) {
+				if (TypeBinding.equalsEquals(p1, originalType))
+					return true;
+				if (p1 instanceof TypeVariableBinding && originalType instanceof TypeVariableBinding) {
+					// may need to 'normalize' if inner & outer have different degree of parameterization / original:
+					TypeVariableBinding var1= (TypeVariableBinding) p1, var2 = (TypeVariableBinding) originalType;
+					Binding declaring1 = var1.declaringElement;
+					Binding declaring2 = var2.declaringElement;
+					if (declaring1 instanceof MethodBinding && declaring2 instanceof MethodBinding) {
+						declaring1 = ((MethodBinding) declaring1).original();
+						declaring2 = ((MethodBinding) declaring2).original();
+					}
+					// TODO: handle TypeBinding if needed
+					return declaring1 == declaring2 && var1.rank == var2.rank;
+				}
+				return false;
+			}
+		};
+		
+		int l1 = this.sites.length;
+		subst.sites = new InvocationSite[l1+1];
+		System.arraycopy(this.sites, 0, subst.sites, 0, l1);
+		subst.sites[l1] = otherContext.currentInvocation;
+
+		subst.variables = this.variables;
+
+// TODO: switch to also combining variables, if needed (filter duplicates?):
+//		l1 = this.variables.length;
+//		int l2 = otherContext.inferenceVariables.length;
+//		subst.variables = new InferenceVariable[l1+l2];
+//		System.arraycopy(this.variables, 0, subst.variables, 0, l1);
+//		System.arraycopy(otherContext.inferenceVariables, 0, subst.variables, l1, l2);
+
+		return subst;
+	}
+
 	/**
 	 * Override method {@link Scope.Substitutor#substitute(Substitution, TypeBinding)}, 
 	 * to add substitution of types other than type variables.
@@ -36,7 +76,7 @@
 	public TypeBinding substitute(Substitution substitution, TypeBinding originalType) {
 		for (int i = 0; i < this.variables.length; i++) {
 			InferenceVariable variable = this.variables[i];
-			if (InferenceContext18.isSameSite(this.site, variable.site) && TypeBinding.equalsEquals(getP(i), originalType)) {
+			if (isInSites(variable.site) && isSameParameter(getP(i), originalType)) {
 				if (this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled && originalType.hasNullTypeAnnotations())
 					return this.environment.createAnnotatedType(variable.withoutToplevelNullAnnotation(), originalType.getTypeAnnotations());
 				return variable;
@@ -46,6 +86,17 @@
 		return super.substitute(substitution, originalType);
 	}
 
+	private boolean isInSites(InvocationSite otherSite) {
+		for (int i = 0; i < this.sites.length; i++)
+			if (InferenceContext18.isSameSite(this.sites[i], otherSite))
+				return true;
+		return false;
+	}
+
+	protected boolean isSameParameter(TypeBinding p1, TypeBinding originalType) {
+		return TypeBinding.equalsEquals(p1, originalType);
+	}
+
 	/**
 	 * Get the type corresponding to the ith inference variable.
 	 * Default behavior is to answer the inference variable's type parameter.