Bug 413958 - Function override returning inherited Generic Type
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 87c8d60..bcc0eb7 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
@@ -13,6 +13,7 @@
  *								bug 401456 - Code compiles from javac/intellij, but fails from eclipse
  *								bug 405706 - Eclipse compiler fails to give compiler error when return type is a inferred generic
  *								Bug 408441 - Type mismatch using Arrays.asList with 3 or more implementations of an interface with the interface type as the last parameter
+ *								Bug 413958 - Function override returning inherited Generic Type
  *******************************************************************************/
 package org.eclipse.jdt.core.tests.compiler.regression;
 
@@ -33,8 +34,8 @@
 	// Static initializer to specify tests subset using TESTS_* static variables
 	// All specified tests which does not belong to the class are skipped...
 	static {
-//		TESTS_NAMES = new String[] { "testBug408441" };
-		TESTS_NAMES = new String[] { "test338350" };
+//		TESTS_NAMES = new String[] { "testBug405706" };
+//		TESTS_NAMES = new String[] { "testBug413958" };
 //		TESTS_NUMBERS = new int[] { 1465 };
 //		TESTS_RANGE = new int[] { 1097, -1 };
 	}
@@ -3123,4 +3124,146 @@
 			"}\n"
 		});
 }
+
+// https://bugs.eclipse.org/413958 - Function override returning inherited Generic Type
+public void testBug413958_1() {
+	runConformTest(
+		new String[] {
+			"TestA.java",
+			"public class TestA { }\n",
+			"TestB.java",
+			"public class TestB { }\n",
+			"ReadOnlyWrapper.java",
+			"@SuppressWarnings(\"unchecked\")\n" +
+			"public class ReadOnlyWrapper<A extends TestA, B extends TestB> {\n" +
+			"    protected A a;\n" +
+			"    protected B b;\n" +
+			"    public ReadOnlyWrapper(A ax,B bx){\n" +
+			"        this.a = ax;\n" +
+			"        this.b = bx;\n" +
+			"    }\n" +
+			"    public <X extends ReadOnlyWrapper<A,B>> X copy() {\n" +
+			"        return (X) new ReadOnlyWrapper<A,B>(a,b);\n" +
+			"    }\n" +
+			"    public <TA extends TestA,TB extends TestB,X extends ReadOnlyWrapper<TA,TB>> X icopy() {\n" +
+			"        return (X) new ReadOnlyWrapper<A,B>(a,b);\n" +
+			"    }\n" +
+			"    public A getA() {\n" +
+			"        return this.a;\n" +
+			"    }\n" +
+			"    public B getB() {\n" +
+			"        return this.b;\n" +
+			"    }\n" +
+			"}",
+			"WritableWrapper.java",
+			"@SuppressWarnings(\"unchecked\")\n" +
+			"public class WritableWrapper<A extends TestA, B extends TestB> extends ReadOnlyWrapper<A, B> {\n" +
+			"    public WritableWrapper(A ax,B bx){\n" +
+			"        super(ax,bx);\n" +
+			"    }\n" +
+			"    @Override\n" +
+			"    public <X extends ReadOnlyWrapper<A,B>> X copy() {\n" +
+			"        return (X) new WritableWrapper<A, B>(a,b);\n" +
+			"    }\n" +
+			"    @Override\n" +
+			"    public <TA extends TestA,TB extends TestB,X extends ReadOnlyWrapper<TA,TB>> X icopy() {\n" +
+			"        // Works in Indigo, Fails in Kepler\n" +
+			"        return (X) new WritableWrapper<A,B>(a,b);\n" +
+			"    }\n" +
+			"    public void setA(A ax) {\n" +
+			"        this.a = ax;\n" +
+			"    }\n" +
+			"    public void setB(B bx) {\n" +
+			"        this.b = bx;\n" +
+			"    }\n" +
+			"}\n",
+			"TestGenerics.java",
+			"public class TestGenerics {\n" +
+			"    public static void main(String [] args) {\n" +
+			"        final WritableWrapper<TestA, TestB> v1 = new WritableWrapper<TestA, TestB>(new TestA(), new TestB());\n" +
+			"        final WritableWrapper<TestA,TestB> v2 = v1.copy();\n" +
+			"        final WritableWrapper<TestA,TestB> v3 = v1.icopy();\n" +
+			"    }\n" +
+			"}\n"
+		});
+}
+// https://bugs.eclipse.org/413958 - Function override returning inherited Generic Type
+// variation showing different inference with / without a method parameter
+public void testBug413958_2() {
+	runNegativeTest(
+		new String[] {
+			"TestA.java",
+			"public class TestA { }\n",
+			"TestB.java",
+			"public class TestB { }\n",
+			"TestA2.java",
+			"public class TestA2 extends TestA { }\n",
+			"ReadOnlyWrapper.java",
+			"@SuppressWarnings(\"unchecked\")\n" +
+			"public class ReadOnlyWrapper<A extends TestA, B extends TestB> {\n" +
+			"    protected A a;\n" +
+			"    protected B b;\n" +
+			"    public ReadOnlyWrapper(A ax,B bx){\n" +
+			"        this.a = ax;\n" +
+			"        this.b = bx;\n" +
+			"    }\n" +
+			"    public <X extends ReadOnlyWrapper<A,B>> X copy() {\n" +
+			"        return (X) new ReadOnlyWrapper<A,B>(a,b);\n" +
+			"    }\n" +
+			"    public <TA extends TestA,TB extends TestB,X extends ReadOnlyWrapper<TA,TB>> X icopy() {\n" +
+			"        return (X) new ReadOnlyWrapper<A,B>(a,b);\n" +
+			"    }\n" +
+			"    public <TA extends TestA,TB extends TestB,X extends ReadOnlyWrapper<TA,TB>> X icopy2(TA in) {\n" +
+			"        return (X) new ReadOnlyWrapper<A,B>(a,b);\n" +
+			"    }\n" +
+			"    public A getA() {\n" +
+			"        return this.a;\n" +
+			"    }\n" +
+			"    public B getB() {\n" +
+			"        return this.b;\n" +
+			"    }\n" +
+			"}",
+			"WritableWrapper.java",
+			"@SuppressWarnings(\"unchecked\")\n" +
+			"public class WritableWrapper<A extends TestA, B extends TestB> extends ReadOnlyWrapper<A, B> {\n" +
+			"    public WritableWrapper(A ax,B bx){\n" +
+			"        super(ax,bx);\n" +
+			"    }\n" +
+			"    @Override\n" +
+			"    public <X extends ReadOnlyWrapper<A,B>> X copy() {\n" +
+			"        return (X) new WritableWrapper<A, B>(a,b);\n" +
+			"    }\n" +
+			"    @Override\n" +
+			"    public <TA extends TestA,TB extends TestB,X extends ReadOnlyWrapper<TA,TB>> X icopy() {\n" +
+			"        return (X) new WritableWrapper<A,B>(a,b);\n" +
+			"    }\n" +
+			"    @Override\n" +
+			"    public <TA extends TestA,TB extends TestB,X extends ReadOnlyWrapper<TA,TB>> X icopy2(TA in) {\n" +
+			"        return (X) new WritableWrapper<A,B>(a,b);\n" +
+			"    }\n" +
+			"    public void setA(A ax) {\n" +
+			"        this.a = ax;\n" +
+			"    }\n" +
+			"    public void setB(B bx) {\n" +
+			"        this.b = bx;\n" +
+			"    }\n" +
+			"}\n",
+			"TestGenerics.java",
+			"public class TestGenerics {\n" +
+			"    public static void main(String [] args) {\n" +
+			"        final WritableWrapper<TestA, TestB> v1 = new WritableWrapper<TestA, TestB>(new TestA(), new TestB());\n" +
+			"        final WritableWrapper<TestA,TestB> v2 = v1.copy();\n" +
+			"        final WritableWrapper<TestA,TestB> v3 = v1.icopy();\n" +
+			"        final WritableWrapper<TestA2,TestB> v4 = v1.icopy();\n" +
+			"        final WritableWrapper<TestA2,TestB> v5 = v1.icopy2(new TestA2());\n" +
+			"    }\n" +
+			"}\n"
+		},
+		"----------\n" +
+		"1. ERROR in TestGenerics.java (at line 6)\n" +
+		"	final WritableWrapper<TestA2,TestB> v4 = v1.icopy();\n" +
+		"	                                         ^^^^^^^^^^\n" +
+		"Type mismatch: cannot convert from ReadOnlyWrapper<TestA,TestB> to WritableWrapper<TestA2,TestB>\n" +
+		"----------\n");
+}
 }
\ No newline at end of file
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java
index 9ca94f4..e67f45f 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2012 IBM Corporation and others.
+ * Copyright (c) 2000, 2013 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
@@ -10,6 +10,7 @@
  *     Stephan Herrmann - Contributions for
  *								bug 186342 - [compiler][null] Using annotations for null checking
  *								bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation.
+ *								bug 413958 - Function override returning inherited Generic Type
  *******************************************************************************/
 package org.eclipse.jdt.internal.compiler.lookup;
 
@@ -261,7 +262,7 @@
 					if (substitute != null) continue nextTypeParameter; // already inferred previously
 					TypeBinding [] bounds = inferenceContext.getSubstitutes(current, TypeConstants.CONSTRAINT_EXTENDS);
 					if (bounds == null) continue nextTypeParameter;
-					TypeBinding[] glb = Scope.greaterLowerBound(bounds, scope);
+					TypeBinding[] glb = Scope.greaterLowerBound(bounds, scope, scope.environment());
 					TypeBinding mostSpecificSubstitute = null;
 					// https://bugs.eclipse.org/bugs/show_bug.cgi?id=341795 - Per 15.12.2.8, we should fully apply glb
 					if (glb != null) {
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 dddf2ca..1e91ba9 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
@@ -15,6 +15,7 @@
  *								bug 401271 - StackOverflowError when searching for a methods references
  *								bug 405706 - Eclipse compiler fails to give compiler error when return type is a inferred generic
  *								Bug 408441 - Type mismatch using Arrays.asList with 3 or more implementations of an interface with the interface type as the last parameter
+ *								Bug 413958 - Function override returning inherited Generic Type
  *     Jesper S Moller - Contributions for
  *								Bug 378674 - "The method can be declared as static" is wrong
  *******************************************************************************/
@@ -297,7 +298,7 @@
 	}
 
 	// 5.1.10
-	public static TypeBinding[] greaterLowerBound(TypeBinding[] types, /*@Nullable*/ Scope scope) {
+	public static TypeBinding[] greaterLowerBound(TypeBinding[] types, /*@Nullable*/ Scope scope, LookupEnvironment environment) {
 		if (types == null) return null;
 		int length = types.length;
 		if (length == 0) return null;
@@ -319,10 +320,34 @@
 				} else if (!jType.isCompatibleWith(iType, scope)) {
 					// avoid creating unsatisfiable intersection types (see https://bugs.eclipse.org/405706):
 					if (iType.isParameterizedType() && jType.isParameterizedType()) {
-						if (iType.original().isCompatibleWith(jType.original(), scope)
-								|| jType.original().isCompatibleWith(iType.original(), scope)) 
-						{
-							// parameterized types are incompatible due to incompatible type arguments => unsatisfiable
+						// if the wider of the two types (judged by originals) has type variables
+						// substitute those with their upper bounds and re-check (see https://bugs.eclipse.org/413958):
+						ParameterizedTypeBinding wideType, narrowType;
+						if (iType.original().isCompatibleWith(jType.original(), scope)) {
+							wideType = (ParameterizedTypeBinding) jType;
+							narrowType = (ParameterizedTypeBinding) iType;
+						} else if (jType.original().isCompatibleWith(iType.original(), scope)) {
+							wideType = (ParameterizedTypeBinding) iType;
+							narrowType = (ParameterizedTypeBinding) jType;
+						} else {
+							continue;
+						}
+						if (wideType.arguments == null)
+							continue; // assume we already have an error here
+						int numTypeArgs = wideType.arguments.length;
+						TypeBinding[] bounds = new TypeBinding[numTypeArgs];
+						for (int k = 0; k < numTypeArgs; k++) {
+							TypeBinding argument = wideType.arguments[k];
+							bounds[k] = argument.isTypeVariable() ? ((TypeVariableBinding)argument).upperBound() : argument;
+						}
+						ReferenceBinding wideOriginal = (ReferenceBinding) wideType.original();
+						TypeBinding substitutedWideType =
+								environment.createParameterizedType(wideOriginal, bounds, wideOriginal.enclosingType());
+						// if the narrow type is compatible with the substituted wide type, we keep silent, 
+						// substituting type variables with proper types can still satisfy all constraints,
+						// otherwise ... 
+						if (!narrowType.isCompatibleWith(substitutedWideType, scope)) {
+							// ... parameterized types are incompatible due to incompatible type arguments => unsatisfiable
 							return null;
 						}
 					}
@@ -433,7 +458,7 @@
 			    			TypeBinding [] bounds = new TypeBinding[1 + substitutedOtherBounds.length];
 			    			bounds[0] = substitutedBound;
 			    			System.arraycopy(substitutedOtherBounds, 0, bounds, 1, substitutedOtherBounds.length);
-			    			TypeBinding[] glb = Scope.greaterLowerBound(bounds, null); // re-evaluate
+			    			TypeBinding[] glb = Scope.greaterLowerBound(bounds, null, substitution.environment()); // re-evaluate
 			    			if (glb != null && glb != bounds) {
 			    				substitutedBound = glb[0];
 		    					if (glb.length == 1) {
@@ -3351,7 +3376,7 @@
 					case Wildcard.SUPER :
 						// ? super U, ? super V
 						if (wildU.boundKind == Wildcard.SUPER) {
-							TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{wildU.bound,wildV.bound}, this);
+							TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{wildU.bound,wildV.bound}, this, this.environment());
 							if (glb == null) return null;
 							return environment().createWildcard(genericType, rank, glb[0], null /*no extra bound*/, Wildcard.SUPER);	// TODO (philippe) need to capture entire bounds
 						}
@@ -3367,7 +3392,7 @@
 						return environment().createWildcard(genericType, rank, lub, null /*no extra bound*/, Wildcard.EXTENDS);
 					// U, ? super V
 					case Wildcard.SUPER :
-						TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{u,wildV.bound}, this);
+						TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{u,wildV.bound}, this, this.environment());
 						if (glb == null) return null;
 						return environment().createWildcard(genericType, rank, glb[0], null /*no extra bound*/, Wildcard.SUPER);	// TODO (philippe) need to capture entire bounds
 					case Wildcard.UNBOUND :
@@ -3385,7 +3410,7 @@
 					return environment().createWildcard(genericType, rank, lub, null /*no extra bound*/, Wildcard.EXTENDS);
 				// U, ? super V
 				case Wildcard.SUPER :
-					TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{wildU.bound, v}, this);
+					TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{wildU.bound, v}, this, this.environment());
 					if (glb == null) return null;
 					return environment().createWildcard(genericType, rank, glb[0], null /*no extra bound*/, Wildcard.SUPER); // TODO (philippe) need to capture entire bounds
 				case Wildcard.UNBOUND :