Bug 521212: [compiler] generic type bound compilation - ecj passes

Change-Id: Ib080877356b8a84d38cbed1d507185769fe2f634
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java
index 288f391..2fcca7e 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java
@@ -196,6 +196,9 @@
 			if ("1.6.0_10-beta".equals(rawVersion)) { // b24
 				return 1010;
 			}
+			if ("1.6.0_45".equals(rawVersion)) {
+				return 1045;
+			}
 		}
 		if (version == JavaCore.VERSION_1_7) {
 			if ("1.7.0-ea".equals(rawVersion)) {
@@ -207,6 +210,9 @@
 			if ("1.7.0_25".equals(rawVersion)) {
 				return 2500;
 			}
+			if ("1.7.0_80".equals(rawVersion)) {
+				return 8000;
+			}
 		}
 		if (version == JavaCore.VERSION_1_8) {
 			if ("1.8.0-ea".equals(rawVersion) || ("1.8.0".equals(rawVersion))) {
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java
index 94b0540..6b1cc57 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java
@@ -21875,6 +21875,11 @@
 		"	Store<? extends Key<T>> store1;\n" +
 		"	                    ^\n" +
 		"Bound mismatch: The type T is not a valid substitute for the bounded parameter <E extends Key<E>> of the type Key<E>\n" +
+		"----------\n" + 
+		"2. ERROR in X.java (at line 6)\n" + 
+		"	Store<? extends Key<? extends T>> store2;\n" + 
+		"	                    ^^^^^^^^^^^\n" + 
+		"Bound mismatch: The type ? extends T is not a valid substitute for the bounded parameter <E extends Key<E>> of the type Key<E>\n" + 
 		"----------\n",
 		// javac options
 		JavacTestOptions.JavacHasABug.JavacBugFixed_6_10 /* javac test options */);
@@ -40821,9 +40826,8 @@
 		"");
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=179902
-// FIXME javac8 rejects
 public void test1167() {
-	this.runConformTest(
+	this.runNegativeTest(
 		new String[] {
 			"Foo.java",
 			"public class Foo<F extends Enum<F>> {\n" +
@@ -40832,7 +40836,12 @@
 			"  }\n" +
 			"}\n", // =================
 		},
-		"");
+		"----------\n" + 
+	"1. ERROR in Foo.java (at line 3)\n" + 
+	"	Bar(Foo<? extends B> bar) {}\n" + 
+	"	        ^^^^^^^^^^^\n" + 
+	"Bound mismatch: The type ? extends B is not a valid substitute for the bounded parameter <F extends Enum<F>> of the type Foo<F>\n" + 
+	"----------\n");
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=169049
 public void test1168() {
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java
index 1a014e7..4f53f63 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java
@@ -5659,8 +5659,6 @@
 // original test case, documenting existing compiler behavior
 public void testBug456459a() {
 	runNegativeTest(
-		false /*skipJavac */,
-		JavacTestOptions.Excuse.JavacHasErrorsEclipseHasWarnings,
 		new String[] {
 			"EnumTest.java",
 			"import java.util.EnumSet;\n" + 
@@ -5682,22 +5680,25 @@
 		"	                                      ^^^^^\n" + 
 		"Class is a raw type. References to generic type Class<T> should be parameterized\n" + 
 		"----------\n" + 
-		"2. WARNING in EnumTest.java (at line 9)\n" + 
+		"2. ERROR in EnumTest.java (at line 9)\n" + 
 		"	EnumSet<? extends T> set = EnumSet.allOf(enumType);\n" + 
-		"	                           ^^^^^^^^^^^^^^^^^^^^^^^\n" + 
-		"Type safety: Unchecked invocation allOf(Class) of the generic method allOf(Class<E>) of type EnumSet\n" + 
+		"	        ^^^^^^^^^^^\n" + 
+		"Bound mismatch: The type ? extends T is not a valid substitute for the bounded parameter <E extends Enum<E>> of the type EnumSet<E>\n" + 
 		"----------\n" + 
 		"3. WARNING in EnumTest.java (at line 9)\n" + 
 		"	EnumSet<? extends T> set = EnumSet.allOf(enumType);\n" + 
 		"	                           ^^^^^^^^^^^^^^^^^^^^^^^\n" + 
-		"Type safety: The expression of type EnumSet needs unchecked conversion to conform to EnumSet<? extends T>\n" + 
+		"Type safety: Unchecked invocation allOf(Class) of the generic method allOf(Class<E>) of type EnumSet\n" + 
 		"----------\n" + 
 		"4. WARNING in EnumTest.java (at line 9)\n" + 
 		"	EnumSet<? extends T> set = EnumSet.allOf(enumType);\n" + 
+		"	                           ^^^^^^^^^^^^^^^^^^^^^^^\n" + 
+		"Type safety: The expression of type EnumSet needs unchecked conversion to conform to EnumSet<? extends T>\n" + 
+		"----------\n" + 
+		"5. WARNING in EnumTest.java (at line 9)\n" + 
+		"	EnumSet<? extends T> set = EnumSet.allOf(enumType);\n" + 
 		"	                                         ^^^^^^^^\n" +
-		(this.complianceLevel < ClassFileConstants.JDK1_8
-		? "Type safety: The expression of type Class needs unchecked conversion to conform to Class<T&Enum<T&Enum<E>>>\n"
-		: "Type safety: The expression of type Class needs unchecked conversion to conform to Class<Enum<Enum<E>>>\n") +
+		"Type safety: The expression of type Class needs unchecked conversion to conform to Class<Enum<Enum<E>>>\n" +
 		"----------\n");
 }
 // simple conflict introduced by additional wildcard bound
@@ -6125,5 +6126,24 @@
 		}
 	);
 }
+public void testBug521212() {
+	runNegativeTest(
+		new String[] {
+			"X.java",
+			"class Y<U extends Z> {}\n" + 
+			"class Z {}\n" + 
+			"public class X<T> {\n" + 
+			"    public static <V> Y<? extends V> one() {\n" + 
+			"        return null;\n" + 
+			"    }\n" + 
+			"}\n"
+		},
+		"----------\n" + 
+		"1. ERROR in X.java (at line 4)\n" + 
+		"	public static <V> Y<? extends V> one() {\n" + 
+		"	                    ^^^^^^^^^^^\n" + 
+		"Bound mismatch: The type ? extends V is not a valid substitute for the bounded parameter <U extends Z> of the type Y<U>\n" + 
+		"----------\n");
+}
 }
 
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CaptureBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CaptureBinding.java
index bf7a147..3ff29b6 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CaptureBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CaptureBinding.java
@@ -242,6 +242,8 @@
 						if (this.superclass.isSuperclassOf(substitutedVariableSuperclass)) {
 							this.setSuperClass(substitutedVariableSuperclass);
 						}
+						// TODO: there are cases were we need to compute glb(capturedWildcardBound, substitutedVariableSuperclass)
+						//       but then when glb (perhaps triggered inside setFirstBound()) fails, how to report the error??
 					}
 					this.setSuperInterfaces(substitutedVariableInterfaces);
 				}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java
index 1a04f46..8243867 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java
@@ -115,6 +115,7 @@
 			boolean hasErrors = false;
 			TypeVariableBinding[] typeVariables = this.type.typeVariables();
 			if (this.arguments != null && typeVariables != null) { // arguments may be null in error cases
+				// per JLS 4.5 we should capture 'this'
 				for (int i = 0, length = typeVariables.length; i < length; i++) {
 				    BoundCheckStatus checkStatus = typeVariables[i].boundCheck(this, this.arguments[i], scope, argumentReferences[i]);
 				    hasErrors |= checkStatus != BoundCheckStatus.OK;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java
index 8701d39..d54bd44 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java
@@ -347,6 +347,9 @@
 				if (i == j) continue;
 				ReferenceBinding jType = result[j];
 				if (jType == null) continue;
+				if (isMalformedPair(iType, jType, null)) {
+					return null;
+				}
 				if (iType.isCompatibleWith(jType)) { // if Vi <: Vj, Vj is removed
 					if (result == types) { // defensive copy
 						System.arraycopy(result, 0, result = new ReferenceBinding[length], 0, length);
@@ -382,6 +385,9 @@
 				if (i == j) continue;
 				TypeBinding jType = result[j];
 				if (jType == null) continue;
+				if (isMalformedPair(iType, jType, scope)) {
+					return null;
+				}
 				if (iType.isCompatibleWith(jType, scope)) { // if Vi <: Vj, Vj is removed
 					if (result == types) { // defensive copy
 						System.arraycopy(result, 0, result = new TypeBinding[length], 0, length);
@@ -442,6 +448,24 @@
 		return trimmedResult;
 	}
 
+	static boolean isMalformedPair(TypeBinding t1, TypeBinding t2, Scope scope) {
+		// not spec-ed in JLS, but per email communication (2017-09-13) it should be
+		switch (t1.kind()) {
+			case Binding.TYPE:
+			case Binding.GENERIC_TYPE:
+			case Binding.PARAMETERIZED_TYPE:
+			case Binding.RAW_TYPE:
+				if (t1.isClass()) {
+					if (t2.getClass() == TypeVariableBinding.class) {
+						TypeBinding bound = ((TypeVariableBinding) t2).firstBound;
+						if (bound == null || !bound.erasure().isCompatibleWith(t1.erasure())) { // use of erasure is heuristic-based
+							return true; // malformed, because substitution could create a contradiction.
+						}
+					}
+				}
+		}
+		return false;
+	}
 	/**
 	 * Returns an array of types, where original types got substituted given a substitution.
 	 * Only allocate an array if anything is different.
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java
index f0dd9b2..1195ff7 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java
@@ -176,6 +176,16 @@
 												// non-object real superclass should have produced a valid 'match' above
 												return BoundCheckStatus.MISMATCH;
 											}
+											// not fully spec-ed in JLS, but based on email communication (2017-09-13):
+											// (a) bound check should apply capture
+											// (b) capture applies glb
+											// (c) and then the glb should be checked for well-formedness (see Scope.isMalformedPair() - this part missing in JLS).
+											// Since we don't do (a), nor (b) for this case, we just directly proceed to (b) here.
+											// For (a) see ParameterizedTypeBinding.boundCheck() - comment added as of this commit
+											// for (b) see CaptureBinding.initializeBounds()  - comment added as of this commit
+											if (Scope.greaterLowerBound(new TypeBinding[] {substitutedSuperType, wildcardBound}, scope, this.environment) == null) {
+												return BoundCheckStatus.MISMATCH;
+											}
 										}
 									}
 								}