diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java index d8c6fcad3a..cfcd2d5757 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java @@ -27,7 +27,6 @@ import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.groovy.tests.ReconcilerUtils; -import org.junit.Ignore; import org.junit.Test; public final class GenericInferencingTests extends InferencingTestSuite { @@ -919,26 +918,28 @@ public void testClosure6c() { @Test // GROOVY-9803 public void testClosure7() { - String contents = - "class C {\n" + - " static C of(U item) {}\n" + - " def C map(F func) {}\n" + - "}\n" + - "class D {\n" + - " static Set wrap(W o) {}\n" + - "}\n" + - "interface F {\n" + - " Y apply(X x)\n" + - "}\n" + - "@groovy.transform.TypeChecked\n" + - "void test() {\n" + - " def c = C.of(123)\n" + - " def d = c.map(D.&wrap)\n" + - " def e = d.map{x -> x.first()}\n" + - "}\n"; - assertType(contents, "wrap", "java.util.Set"); - assertType(contents, "x", "java.util.Set"); - assertType(contents, "e", "C"); + for (String toSet : new String[] {"D.&wrap", "Collections.&singleton", "{x -> [x].toSet()}", "{Collections.singleton(it)}"}) { + String contents = + "class C {\n" + + " static C of(U item) {}\n" + + " def C map(F func) {}\n" + + "}\n" + + "class D {\n" + + " static Set wrap(W o) {}\n" + + "}\n" + + "interface F {\n" + + " Y apply(X x)\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " def c = C.of(123)\n" + + " def d = c.map(" + toSet + ")\n" + + " def e = d.map{x -> x.first()}\n" + + "}\n"; + assertType(contents, "d", "C>"); + assertType(contents, "x", "java.util.Set"); + assertType(contents, "e", "C"); + } } @Test // https://github.com/groovy/groovy-eclipse/issues/1194 @@ -1572,7 +1573,7 @@ public void testCircularReference() { assertType(contents, offset, offset + "withStuff".length(), "Concrete"); } - @Test @Ignore + @Test public void testJira1718() throws Exception { createUnit("p2", "Renderer", "package p2\n" + @@ -1630,7 +1631,7 @@ public void testJira1718() throws Exception { " if (htmlRenderer == null) {\n" + " htmlRenderer = new DefaultRenderer(targetType)\n" + " }\n" + - " htmlRenderer.render(object, context)\n" + // TODO: Cannot call p2.Renderer#render(java.lang.Object, java.lang.String) with arguments [T, java.lang.String] + " htmlRenderer.render(object, context)\n" + // Cannot call p2.Renderer#render(java.lang.Object, java.lang.String) with arguments [T, java.lang.String] " }\n" + "}\n"); diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovySimpleTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovySimpleTests.java index 0ebd3a5498..b24bb52df3 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovySimpleTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovySimpleTests.java @@ -2515,8 +2515,8 @@ public void testMissingTypesForGeneratedBindingsGivesNPE_GRE273() { " \n" + " boolean isValid(String newValue) {\n" + " try {\n" + - " return validators.inject(true) {\n" + - " prev, cur -> prev && cur.call([newValue, source])\n" + + " return validators.inject(true) { valid, validator ->\n" + + " valid && validator.call([newValue, source])\n" + " }\n" + " } catch (e) {\n" + " return false\n" + diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java index 550252a919..3541c6283f 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java @@ -1834,7 +1834,7 @@ public void testCompileStatic8389a() { runConformTest(sources, "method:property"); } - @Test @Ignore("https://issues.apache.org/jira/browse/GROOVY-8409") + @Test public void testCompileStatic8409() { for (char t : new char[] {'R', 'S', 'T', 'U'}) { // BiFunction uses R, T and U //@formatter:off diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java index 79c07fa85f..fa816a820a 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java @@ -237,6 +237,148 @@ public void testTypeChecked10() { runConformTest(sources); } + @Test + public void testTypeChecked11() { + //@formatter:off + String[] sources = { + "Main.groovy", + "import static java.util.stream.Collectors.toList\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " List xxx = ['x'].collect()\n" + + " List yyy = ['y'].stream().toList()\n" + + " List zzz = ['z'].stream().collect(toList())\n" + + "}\n", + }; + //@formatter:on + + runNegativeTest(sources, + "----------\n" + + "1. ERROR in Main.groovy (at line 4)\n" + + "\tList xxx = ['x'].collect()\n" + + "\t ^^^^^^^^^^^^^^^\n" + + "Groovy:[Static type checking] - Incompatible generic argument types. Cannot assign java.util.List to: java.util.List\n" + + "----------\n" + + "2. ERROR in Main.groovy (at line 5)\n" + + "\tList yyy = ['y'].stream().toList()\n" + + "\t ^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Groovy:[Static type checking] - Incompatible generic argument types. Cannot assign java.util.List to: java.util.List\n" + + "----------\n"); + } + + @Test + public void testTypeChecked12() { + //@formatter:off + String[] sources = { + "Main.groovy", + "import org.codehaus.groovy.runtime.DefaultGroovyMethods as DGM\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " def strings = ['x','yy','zzz']\n" + + " print(strings.inject(0) { result, string -> result += string.length() })\n" + + " print(strings.inject { result, string -> result += string.toUpperCase() })\n" + + " print(DGM.inject(strings) { result, string -> result += string.toUpperCase() })\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "6xYYZZZxYYZZZ"); + } + + @Test + public void testTypeChecked13() { + //@formatter:off + String[] sources = { + "Main.groovy", + "interface OngoingStubbing /*extends IOngoingStubbing*/ {\n" + + " OngoingStubbing thenReturn(T value)\n" + + "}\n" + + "static OngoingStubbing when(T methodCall) {\n" + + " [thenReturn: { T value -> null }] as OngoingStubbing\n" + + "}\n" + + "Optional foo() {\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " when(foo()).thenReturn(Optional.empty())\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources); + } + + @Test + public void testTypeChecked14() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "void test(Pojo pojo) {\n" + + " Foo raw = pojo.getFoo('')\n" + + " raw.bar = raw.baz\n" + + "}\n" + + "test(new Pojo())\n", + + "Pojo.java", + "public class Pojo {\n" + + " public Foo getFoo(String key) {\n" + + " return new Foo<>();\n" + + " }\n" + + "}\n", + + "Types.groovy", + "interface I {\n" + + "}\n" + + "class Foo {\n" + + " T bar\n" + + " T baz\n" + + "}\n", + }; + //@formatter:on + + runConformTest(sources); + } + + @Test + public void testTypeChecked15() { + //@formatter:off + String[] sources = { + "Main.groovy", + "def > U configure(Class type, @DelegatesTo(type='T',strategy=Closure.DELEGATE_FIRST) Closure spec) {\n" + + " Configurable obj = (Configurable) type.newInstance()\n" + + " obj.configure(spec)\n" + + " obj\n" + + "}\n" + + "trait Configurable { X configObject\n" + + " void configure(Closure spec) {\n" + + " configObject.with(spec)\n" + + " }\n" + + "}\n" + + "class Item implements Configurable {\n" + + " Item() {\n" + + " configObject = new ItemConfig()\n" + + " }\n" + + "}\n" + + "class ItemConfig {\n" + + " String name, version\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "def test() {\n" + + " configure(Item) {\n" + + " name = 'test'\n" + + " version = '1'\n" + + " }\n" + + "}\n" + + "print test().configObject.name\n", + }; + //@formatter:on + + runConformTest(sources, "test"); + } + @Test public void testTypeChecked6232() { //@formatter:off @@ -597,6 +739,22 @@ public void testTypeChecked7363() { runNegativeTest(sources, ""); } + @Test + public void testTypeChecked7804() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + // Supplier also uses "T" + "def T test(java.util.function.Supplier supplier) {\n" + + " supplier.get()\n" + + "}\n" + + "print(test { -> 'foo' })\n", + }; + //@formatter:on + + runConformTest(sources, "foo"); + } + @Test public void testTypeChecked7945() { //@formatter:off @@ -1415,15 +1573,15 @@ public void testTypeChecked9902() { " def TypedProperty prop(Class clazz) {\n" + " new TypedProperty(clazz: clazz)\n" + " }\n" + - // Note: type argument of Holder cannot be supplied to value attribute of @DelegatesTo - " def T of(@DelegatesTo(value=Holder, strategy=Closure.DELEGATE_FIRST) Closure c) {\n" + + " def U of(@DelegatesTo(value=Holder, strategy=Closure.DELEGATE_FIRST) Closure c) {\n" + + // ^^^^^^ type argument cannot be supplied using value attribute " this.with(c)\n" + " }\n" + "}\n" + - "class TypedProperty {\n" + - " Class clazz\n" + - " void eq(X x) {\n" + - " assert x.class == clazz : \"x.class is ${x.class} not ${clazz}\"\n" + + "class TypedProperty {\n" + + " Class clazz\n" + + " void eq(V that) {\n" + + " assert that.class == this.lclazz : \"that.class is ${that.class} not ${this.clazz}\"\n" + " }\n" + "}\n" + "@groovy.transform.TypeChecked\n" + @@ -1448,12 +1606,12 @@ public void testTypeChecked9902() { "2. ERROR in Main.groovy (at line 21)\n" + "\tstringProperty.eq(1234)\n" + "\t^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Groovy:[Static type checking] - Cannot call TypedProperty#eq(java.lang.String) with arguments [int]\n" + + "Groovy:[Static type checking] - Cannot find matching method TypedProperty#eq(int). Please check if the declared type is correct and if the method exists.\n" + "----------\n" + "3. ERROR in Main.groovy (at line 22)\n" + "\tnumberProperty.eq('xx')\n" + "\t^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Groovy:[Static type checking] - Cannot call TypedProperty#eq(java.lang.Number) with arguments [java.lang.String]\n" + + "Groovy:[Static type checking] - Cannot find matching method TypedProperty#eq(java.lang.String). Please check if the declared type is correct and if the method exists.\n" + "----------\n"); } @@ -1504,23 +1662,25 @@ public void testTypeChecked9907() { @Test public void testTypeChecked9915() { - //@formatter:off - String[] sources = { - "Main.groovy", - "@groovy.transform.TypeChecked\n" + - "class C {\n" + - " void m() {\n" + - " init(Collections.emptyList())\n" + // Cannot call C#init(List) with arguments [List] - " }\n" + - " private static void init(List strings) {\n" + - " print strings\n" + - " }\n" + - "}\n" + - "new C().m()\n", - }; - //@formatter:on + for (String type : new String[] {"List", "Iterable", "Collection"}) { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "class C {\n" + + " void m() {\n" + + " init(Collections.emptyList())\n" + // Cannot call C#init(List) with arguments [List] + " }\n" + + " private static void init(" + type + " strings) {\n" + + " print strings\n" + + " }\n" + + "}\n" + + "new C().m()\n", + }; + //@formatter:on - runConformTest(sources, "[]"); + runConformTest(sources, "[]"); + } } @Test @@ -2483,11 +2643,11 @@ public void testTypeChecked10067() { " f((Integer) getNumber())\n" + " g((Integer) getNumber())\n" + " i = getNumber()\n" + - //" f(getNumber())\n" + - //" g(getNumber())\n" + + " f(getNumber())\n" + + " g(getNumber())\n" + " i = number\n" + - //" f(number)\n" + - //" g(number)\n" + + " f(number)\n" + + " g(number)\n" + "}\n" + "test()\n", }; diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/GenericsType.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/GenericsType.java index 9f0870033e..3b7693e4a6 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/GenericsType.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/GenericsType.java @@ -438,7 +438,8 @@ private boolean compareGenericsWithBound(final ClassNode classNode, final ClassN GenericsTypeName name = new GenericsTypeName(classNodeType.getName()); if (redirectBoundType.isPlaceholder()) { GenericsTypeName gtn = new GenericsTypeName(redirectBoundType.getName()); - match = name.equals(gtn); + match = name.equals(gtn) + || name.equals(new GenericsTypeName("#" + redirectBoundType.getName())); if (!match) { GenericsType genericsType = boundPlaceHolders.get(gtn); if (genericsType!=null) { diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/control/ResolveVisitor.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/control/ResolveVisitor.java index b036601ef7..1b99ec86d1 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/control/ResolveVisitor.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/control/ResolveVisitor.java @@ -1750,6 +1750,7 @@ private void resolveGenericsHeader(GenericsType[] types, GenericsType rootType, } else { if (!isWild) { if (toDealWithGenerics) { + /* GRECLIPSE edit GenericsType originalGt = genericParameterNames.get(gtn); genericParameterNames.put(gtn, type); type.setPlaceholder(true); @@ -1759,6 +1760,11 @@ private void resolveGenericsHeader(GenericsType[] types, GenericsType rootType, } else { classNode.setRedirect(originalGt.getType()); } + */ + type.setPlaceholder(true); + GenericsType last = genericParameterNames.put(gtn, type); + classNode.setRedirect(last != null ? last.getType().redirect() : ClassHelper.OBJECT_TYPE); + // GRECLIPSE end } } } diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java index f61a83969f..89bcb5c6f2 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java @@ -503,8 +503,11 @@ static boolean isAssignableTo(ClassNode type, ClassNode toBeAssignedTo) { // GRECLIPSE end return true; } - - //SAM check + // GRECLIPSE add -- GROOVY-10067 + if (type.isGenericsPlaceHolder() && type.getUnresolvedName().charAt(0) == '#') { + return type.getGenericsTypes()[0].isCompatibleWith(toBeAssignedTo); + } + // GRECLIPSE end if (type.isDerivedFrom(CLOSURE_TYPE) && isSAMType(toBeAssignedTo)) { return true; } @@ -1124,10 +1127,11 @@ public static List chooseBestMethod(final ClassNode receiver, final if (!asBoolean(methods)) { return Collections.emptyList(); } + /* GRECLIPSE edit -- GROOVY-8409, et al. if (isUsingUncheckedGenerics(receiver)) { return chooseBestMethod(makeRawType(receiver), methods, argumentTypes); } - + */ int bestDist = Integer.MAX_VALUE; List bestChoices = new LinkedList<>(); boolean noCulling = methods.size() <= 1 || "".equals(methods.iterator().next().getName()); @@ -1809,6 +1813,9 @@ static void applyGenericsConnections( GenericsTypeName name = new GenericsTypeName(oldValue.getName()); GenericsType newValue = connections.get(name); // find "V" in T=V if (newValue == oldValue) continue; + // GRECLIPSE add -- GROOVY-10067 + if (name.getName().charAt(0) == '#') continue; + // GRECLIPSE end if (newValue == null) { entry.setValue(newValue = applyGenericsContext(connections, oldValue)); checkForMorePlaceholders = checkForMorePlaceholders || !equalIncludingGenerics(oldValue, newValue); @@ -2047,6 +2054,7 @@ static GenericsType[] getGenericsWithoutArray(ClassNode type) { return type.getGenericsTypes(); } + /* GRECLIPSE edit static Map applyGenericsContextToParameterClass( Map spec, ClassNode parameterUsage ) { @@ -2056,23 +2064,15 @@ static Map applyGenericsContextToParameterClass( GenericsType[] newGTs = applyGenericsContext(spec, gts); ClassNode newTarget = parameterUsage.redirect().getPlainNodeReference(); newTarget.setGenericsTypes(newGTs); - /* GRECLIPSE edit -- GROOVY-9762, GROOVY-9803 return GenericsUtils.extractPlaceholders(newTarget); - */ - Map newSpec = GenericsUtils.extractPlaceholders(newTarget); - newSpec.replaceAll((xx, gt) -> - Optional.ofNullable(gt.getLowerBound()).map(GenericsType::new).orElse(gt) - ); - return newSpec; - // GRECLIPSE end } + */ - private static GenericsType[] applyGenericsContext( - Map spec, GenericsType[] gts - ) { - if (gts == null) return null; + // GRECLIPSE private->package + static GenericsType[] applyGenericsContext(final Map spec, final GenericsType[] gts) { + if (gts == null || spec == null || spec.isEmpty()) return gts; GenericsType[] newGTs = new GenericsType[gts.length]; - for (int i = 0; i < gts.length; i++) { + for (int i = 0, n = gts.length; i < n; i += 1) { GenericsType gt = gts[i]; newGTs[i] = applyGenericsContext(spec, gt); } @@ -2157,23 +2157,22 @@ static ClassNode applyGenericsContext(final Map } return newType; */ - if (!type.isGenericsPlaceHolder()) { + if (!type.isGenericsPlaceHolder()) { // convert Type to Type<...> ClassNode cn = type.getPlainNodeReference(); cn.setGenericsTypes(gt); return cn; } - if (gt[0].isPlaceholder()) { - if (type.getGenericsTypes()[0] == gt[0]) { - return type; // nothing to do - } else if (!hasNonTrivialBounds(gt[0])) { - ClassNode cn = make(gt[0].getName()); - cn.setRedirect(type); - cn.setGenericsTypes(gt); - cn.setGenericsPlaceHolder(true); - return cn; - } + if (!gt[0].isPlaceholder()) { // convert T to Type or Type<...> + return getCombinedBoundType(gt[0]); } - return getCombinedBoundType(gt[0]); + if (type.getGenericsTypes()[0] != gt[0]) { // convert T to X + ClassNode cn = make(gt[0].getName()); + cn.setRedirect(gt[0].getType()); + cn.setGenericsPlaceHolder(true); + cn.setGenericsTypes(gt); + return cn; + } + return type; // GRECLIPSE end } diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 1fbca7f663..3fccf06863 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -139,6 +139,7 @@ import java.util.function.Supplier; import java.util.stream.IntStream; +import static java.util.stream.Collectors.toMap; import static org.apache.groovy.ast.tools.ClassNodeUtils.samePackageName; import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE; import static org.codehaus.groovy.ast.ClassHelper.BigInteger_TYPE; @@ -234,7 +235,6 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatchWithDefaultParams; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsConnections; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContext; -import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContextToParameterClass; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.boundUnboundedWildcards; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.checkCompatibleAssignmentTypes; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.checkPossibleLossOfPrecision; @@ -265,7 +265,6 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isShiftOperation; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isTraitSelf; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUsingGenericsOrIsArrayUsingGenerics; -import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUsingUncheckedGenerics; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isVargs; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isWildcardLeftHandSide; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.lastArgMatchesVarg; @@ -956,6 +955,7 @@ else if (op == LOGICAL_OR) { inferDiamondType((ConstructorCallExpression) rightExpression, lType); } if (lType.isUsingGenerics() && missesGenericsTypes(resultType)) { + if (!resultType.isGenericsPlaceHolder()) // plain reference loses information resultType = GenericsUtils.parameterizeType(lType, resultType.getPlainNodeReference()); } else if (lType.equals(OBJECT_TYPE) && GenericsUtils.hasUnresolvedGenerics(resultType)) { // def list = [] Map placeholders = extractGenericsParameterMapOfThis(typeCheckingContext); @@ -1120,6 +1120,9 @@ private boolean ensureValidSetter(final Expression expression, final Expression // but we must check if the binary expression is an assignment // because we need to check if a setter uses @DelegatesTo VariableExpression ve = varX("%", setterInfo.receiverType); + // GRECLIPSE add -- GROOVY-8409, et al. + ve.setType(setterInfo.receiverType); + // GRECLIPSE end // for compound assignment "x op= y" find type as if it was "x = (x op y)" final Expression newRightExpression = isCompoundAssignment(expression) ? binX(leftExpression, getOpWithoutEqual(expression), rightExpression) @@ -3157,7 +3160,7 @@ public void visitStaticMethodCallExpression(final StaticMethodCallExpression cal if (!mn.isEmpty()) { if (mn.size() == 1) { // GRECLIPSE add -- GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915 - resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0)); + resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0).getParameters()); // GRECLIPSE end typeCheckMethodsWithGenericsOrFail(currentReceiver.getType(), args, mn.get(0), call); } @@ -4082,7 +4085,12 @@ public void visitMethodCallExpression(MethodCallExpression call) { } } // GRECLIPSE add -- GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844 - resolvePlaceholdersFromImplicitTypeHints(args, argumentList, directMethodCallCandidate); + Parameter[] parameters = directMethodCallCandidate.getParameters(); + if (chosenReceiver.getType().getGenericsTypes() != null && !directMethodCallCandidate.isStatic() && !(directMethodCallCandidate instanceof ExtensionMethodNode)) { + Map context = extractPlaceHolders(null, chosenReceiver.getType(), getDeclaringClass(directMethodCallCandidate, argumentList)); + parameters = Arrays.stream(parameters).map(param -> new Parameter(applyGenericsContext(context, param.getType()), param.getName())).toArray(Parameter[]::new); + } + resolvePlaceholdersFromImplicitTypeHints(args, argumentList, parameters); // GRECLIPSE end if (typeCheckMethodsWithGenericsOrFail(chosenReceiver.getType(), args, directMethodCallCandidate, call)) { returnType = adjustWithTraits(directMethodCallCandidate, chosenReceiver.getType(), args, returnType); @@ -5482,15 +5490,14 @@ protected List findMethod(ClassNode receiver, final String name, fin } if (receiver.equals(CLASS_Type) && receiver.getGenericsTypes() != null) { - List result = findMethod(receiver.getGenericsTypes()[0].getType(), name, args); - if (!result.isEmpty()) return result; + chosen = findMethod(receiver.getGenericsTypes()[0].getType(), name, args); + if (!chosen.isEmpty()) return chosen; + } + if (GSTRING_TYPE.equals(receiver)) { + return findMethod(STRING_TYPE, name, args); } - - if (ClassHelper.GSTRING_TYPE.equals(receiver)) return findMethod(ClassHelper.STRING_TYPE, name, args); - if (isBeingCompiled(receiver)) { - chosen = findMethod(GROOVY_OBJECT_TYPE, name, args); - if (!chosen.isEmpty()) return chosen; + return findMethod(GROOVY_OBJECT_TYPE, name, args); } return EMPTY_METHODNODE_LIST; @@ -5853,6 +5860,22 @@ protected ClassNode inferMapExpressionType(final MapExpression map) { private static class ExtensionMethodDeclaringClass { } + private static ArgumentListExpression getExtensionArguments(final ClassNode receiver, final MethodNode method, final Expression arguments) { + VariableExpression self = varX("$self", receiver); // implicit first argument + self.putNodeMetaData(ExtensionMethodDeclaringClass.class, method.getDeclaringClass()); + + ArgumentListExpression args = new ArgumentListExpression(); + args.addExpression(self); + if (arguments instanceof TupleExpression) { + for (Expression argument : (TupleExpression) arguments) { + args.addExpression(argument); + } + } else { + args.addExpression(arguments); + } + return args; + } + /** * If a method call returns a parameterized type, then we can perform additional inference on the * return type, so that the type gets actual type parameters. For example, the method @@ -5883,7 +5906,8 @@ protected ClassNode inferReturnTypeGenerics( MethodNode method, Expression arguments, GenericsType[] explicitTypeHints) { - ClassNode returnType = method instanceof ConstructorNode ? method.getDeclaringClass() : method.getReturnType(); // GRECLIPSE edit -- GROOVY-9948 + /* GRECLIPSE edit -- GROOVY-8409, GROOVY-9570, GROOVY-9735, GROOVY-9803, GROOVY-9948, GROOVY-9970, GROOVY-9996, GROOVY-10036, GROOVY-10056, GROOVY-10062 + ClassNode returnType = method.getReturnType(); if (method instanceof ExtensionMethodNode && (isUsingGenericsOrIsArrayUsingGenerics(returnType))) { // check if the placeholder corresponds to the placeholder of the first parameter @@ -5902,7 +5926,7 @@ protected ClassNode inferReturnTypeGenerics( } else { argList.addExpression(arguments); } - return inferReturnTypeGenerics(receiver, dgmMethod, argList, explicitTypeHints); // GRECLIPSE edit -- GROOVY-10036 + return inferReturnTypeGenerics(receiver, dgmMethod, argList); } if (!isUsingGenericsOrIsArrayUsingGenerics(returnType)) return returnType; if (getGenericsWithoutArray(returnType) == null) return returnType; @@ -5914,16 +5938,14 @@ protected ClassNode inferReturnTypeGenerics( if (resolvedPlaceholders.isEmpty()) { return boundUnboundedWildcards(returnType); } - /* GRECLIPSE edit -- GROOVY-9570, GROOVY-9735, GROOVY-9970 Map placeholdersFromContext = extractGenericsParameterMapOfThis(typeCheckingContext.getEnclosingMethod()); applyGenericsConnections(placeholdersFromContext, resolvedPlaceholders); - */ + // then resolve receivers from method arguments Parameter[] parameters = method.getParameters(); boolean isVargs = isVargs(parameters); ArgumentListExpression argList = InvocationWriter.makeArgumentList(arguments); List expressions = argList.getExpressions(); - /* GRECLIPSE edit -- GROOVY-9996, GROOVY-10056, GROOVY-10062 int paramLength = parameters.length; if (expressions.size() >= paramLength) { for (int i = 0; i < paramLength; i += 1) { @@ -5934,16 +5956,7 @@ protected ClassNode inferReturnTypeGenerics( type = type.getComponentType(); actualType = actualType.getComponentType(); } - */ - int nArguments = expressions.size(), nParams = parameters.length; - if (isVargs ? nArguments >= nParams - 1 : nArguments == nParams) { - for (int i = 0; i < nArguments; i += 1) { - if (isNullConstant(expressions.get(i))) continue; // GROOVY-9984 - ClassNode type = parameters[Math.min(i, nParams - 1)].getType(); - ClassNode actualType = getDeclaredOrInferredType(expressions.get(i)); - // GRECLIPSE end if (isUsingGenericsOrIsArrayUsingGenerics(type)) { - /* GRECLIPSE edit -- GROOVY-9803, GROOVY-10056, GROOVY-10062 if (implementsInterfaceOrIsSubclassOf(actualType, CLOSURE_TYPE) && isSAMType(type)) { // implicit closure coercion in action! @@ -5956,19 +5969,6 @@ protected ClassNode inferReturnTypeGenerics( if (isVargs && lastArg && type.isArray()) { type = type.getComponentType(); } - */ - if (isVargs && (i >= nParams || (i == nParams - 1 && (nArguments > nParams || !actualType.isArray())))) { - type = type.getComponentType(); - } - if (actualType.isDerivedFrom(CLOSURE_TYPE)) { - MethodNode sam = findSAM(type); - if (sam != null) { // implicit closure coercion in action! - actualType = !type.isUsingGenerics() ? type - : convertClosureTypeToSAMType(expressions.get(i), actualType, sam, type, - applyGenericsContextToParameterClass(resolvedPlaceholders, type)); - } - } - // GRECLIPSE end actualType = wrapTypeIfNecessary(actualType); Map connections = new HashMap(); @@ -5978,12 +5978,129 @@ protected ClassNode inferReturnTypeGenerics( } } } - // GRECLIPSE add -- GROOVY-9570, GROOVY-9735, GROOVY-9970 - applyGenericsConnections(extractGenericsParameterMapOfThis(typeCheckingContext), resolvedPlaceholders); - // GRECLIPSE end + return applyGenericsContext(resolvedPlaceholders, returnType); + */ + ClassNode returnType = method instanceof ConstructorNode ? method.getDeclaringClass() : method.getReturnType(); + if (!GenericsUtils.hasUnresolvedGenerics(returnType)) { + // GROOVY-7538: replace "Type" with "Type" for any "Type" + if (getGenericsWithoutArray(returnType) != null) returnType = boundUnboundedWildcards(returnType); + + return returnType; + } + + if (method instanceof ExtensionMethodNode) { + ArgumentListExpression args = getExtensionArguments(receiver, method, arguments); + MethodNode extension = ((ExtensionMethodNode) method).getExtensionMethodNode(); + return inferReturnTypeGenerics(receiver, extension, args, explicitTypeHints); + } + + Map context = method.isStatic() || method instanceof ConstructorNode + ? null : extractPlaceHolders(null, receiver, getDeclaringClass(method, arguments)); + GenericsType[] methodGenericTypes = method instanceof ConstructorNode ? method.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, method.getGenericsTypes()); + + // 1) resolve type parameters of method + + if (methodGenericTypes != null) { + Map resolvedPlaceholders = new HashMap<>(); + for (GenericsType gt : methodGenericTypes) resolvedPlaceholders.put(new GenericsTypeName(gt.getName()), gt); + applyGenericsConnections(extractGenericsConnectionsFromArguments(methodGenericTypes, Arrays.stream(method.getParameters()).map(param -> + new Parameter(applyGenericsContext(context, param.getType()), param.getName()) + ).toArray(Parameter[]::new), arguments, explicitTypeHints), resolvedPlaceholders); + + returnType = applyGenericsContext(resolvedPlaceholders, returnType); + } + + // 2) resolve type parameters of method's enclosing context + + if (context != null) { + returnType = applyGenericsContext(context, returnType); + + if (receiver.getGenericsTypes() == null && receiver.redirect().getGenericsTypes() != null && GenericsUtils.hasUnresolvedGenerics(returnType)) { + returnType = returnType.getPlainNodeReference(); // GROOVY-10049: do not return "Stream" for raw type "List#stream()" + } + } + + // 3) resolve bounds of type parameters from calling context + + returnType = applyGenericsContext(extractGenericsParameterMapOfThis(typeCheckingContext), returnType); + + return returnType; + // GRECLIPSE end + } + + /** + * Resolves type parameters declared by method from type or call arguments. + */ + private Map extractGenericsConnectionsFromArguments(final GenericsType[] methodGenericTypes, final Parameter[] parameters, final Expression arguments, final GenericsType[] explicitTypeHints) { + Map resolvedPlaceholders = new HashMap<>(); + + if (explicitTypeHints != null) { // resolve type parameters from type arguments + int n = methodGenericTypes.length; + if (n == explicitTypeHints.length) { + for (int i = 0; i < n; i += 1) { + resolvedPlaceholders.put(new GenericsTypeName(methodGenericTypes[i].getName()), explicitTypeHints[i]); + } + } + } else if (parameters.length > 0) { // resolve type parameters from call arguments + List expressions = InvocationWriter.makeArgumentList(arguments).getExpressions(); + boolean isVargs = isVargs(parameters); + int nArguments = expressions.size(); + int nParams = parameters.length; + + if (isVargs ? nArguments >= nParams - 1 : nArguments == nParams) { + for (int i = 0; i < nArguments; i += 1) { + if (isNullConstant(expressions.get(i))) + continue; // GROOVY-9984: skip null + ClassNode paramType = parameters[Math.min(i, nParams - 1)].getType(); + ClassNode argumentType = getDeclaredOrInferredType(expressions.get(i)); + + if (GenericsUtils.hasUnresolvedGenerics(paramType)) { + // if supplying array param with multiple arguments or single non-array argument, infer using element type + if (isVargs && (i >= nParams || (i == nParams - 1 && (nArguments > nParams || !argumentType.isArray())))) { + paramType = paramType.getComponentType(); + } + + if (isClosureWithType(argumentType)) { + MethodNode sam = findSAM(paramType); + if (sam != null) { // implicit closure coerce + argumentType = convertClosureTypeToSAMType(expressions.get(i), argumentType, sam, paramType); + } + } + + Map connections = new HashMap<>(); + extractGenericsConnections(connections, wrapTypeIfNecessary(argumentType), paramType); + connections.forEach((gtn, gt) -> resolvedPlaceholders.merge(gtn, gt, (gt1, gt2) -> { + return gt2; // TODO + })); + } + } + } + + // in case of ">" we can learn about "T" from resolved "U" + Map connections = Arrays.stream(methodGenericTypes) + .collect(toMap(gt -> new GenericsTypeName(gt.getName()), Function.identity())); + extractGenericsConnectionsForSuperClassAndInterfaces(connections, resolvedPlaceholders); + } + + for (GenericsType gt : methodGenericTypes) { + // GROOVY-8409, GROOVY-10067, et al.: provide "no type witness" mapping for param + resolvedPlaceholders.computeIfAbsent(new GenericsTypeName(gt.getName()), gtn -> { + GenericsType xxx = new GenericsType(ClassHelper.makeWithoutCaching("#"), + applyGenericsContext(resolvedPlaceholders, gt.getUpperBounds()), + applyGenericsContext(resolvedPlaceholders, gt.getLowerBound())); + xxx.getType().setRedirect(gt.getType().redirect()); + xxx.putNodeMetaData(GenericsType.class, gt); + xxx.setName("#" + gt.getName()); + xxx.setPlaceholder(true); + return xxx; + }); + } + + return resolvedPlaceholders; } + /* GRECLIPSE edit private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode method, final GenericsType[] explicitTypeHints, final Map resolvedPlaceholders) { if (explicitTypeHints != null) { GenericsType[] methodGenericTypes = method.getGenericsTypes(); @@ -5996,13 +6113,14 @@ private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode me } } } + */ /** * Given method call like "m(Collections.emptyList())", the type of the call * argument is {@code List} without explicit type arguments. Knowning the * method target of "m", {@code T} could be resolved. */ - private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final MethodNode inferredMethod) { + private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final Parameter[] parameterArray) { /* GRECLIPSE edit for (int i = 0, n = actuals.length; i < n; i += 1) { // check for method call with known target @@ -6022,12 +6140,13 @@ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] a ClassNode pt = p.getOriginType(); if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType(); */ - int np = inferredMethod.getParameters().length; + int np = parameterArray.length; for (int i = 0, n = actuals.length; np > 0 && i < n; i += 1) { Expression a = argumentList.getExpression(i); - Parameter p = inferredMethod.getParameters()[Math.min(i, np - 1)]; + Parameter p = parameterArray[Math.min(i, np - 1)]; ClassNode at = actuals[i], pt = p.getOriginType(); + if (!isUsingGenericsOrIsArrayUsingGenerics(pt)) continue; if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType(); if (a instanceof ListExpression) { @@ -6044,6 +6163,11 @@ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] a // and unknown generics if (!GenericsUtils.hasUnresolvedGenerics(at)) continue; + + while (!at.equals(pt) && !at.equals(OBJECT_TYPE)) { + ClassNode sc = GenericsUtils.getSuperClass(at, pt); + at = applyGenericsContext(GenericsUtils.extractPlaceholders(at), sc); + } // GRECLIPSE end // try to resolve placeholder(s) in argument type using parameter type @@ -6055,11 +6179,11 @@ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] a // connect E:T from source to E:Type from target for (GenericsType placeholder : aNode.getGenericsTypes()) { for (Map.Entry e : source.entrySet()) { - if (e.getValue() == placeholder) { + if (e.getValue().getNodeMetaData(GenericsType.class) == placeholder) { Optional.ofNullable(target.get(e.getKey())) // skip "f(g())" for "f(T)" and " U g()" .filter(gt -> isAssignableTo(gt.getType(), placeholder.getType())) - .ifPresent(gt -> linked.put(new GenericsTypeName(placeholder.getName()), gt)); + .ifPresent(gt -> linked.put(new GenericsTypeName(e.getValue().getName()), gt)); break; } } @@ -6180,36 +6304,7 @@ private static ClassNode convertClosureTypeToSAMType(final Expression expression } else if (samReturnType.isGenericsPlaceHolder()) { placeholders.put(new GenericsTypeName(samReturnType.getGenericsTypes()[0].getName()), closureType.getGenericsTypes()[0]); } - */ - private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType, final Map placeholders) { - // use the generics information from Closure to further specify the type - if (closureType.isUsingGenerics()) { - ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType(); - - Parameter[] parameters = sam.getParameters(); - if (parameters.length > 0 && expression instanceof MethodPointerExpression - && isUsingUncheckedGenerics(closureReturnType)) { // needs resolve - MethodPointerExpression mp = (MethodPointerExpression) expression; - MethodNode mn = chooseMethod(mp, () -> - applyGenericsContext(placeholders, extractTypesFromParameters(parameters)) - ); - if (mn != null) { - ClassNode[] pTypes = collateMethodReferenceParameterTypes(mp, mn); - Map connections = new HashMap<>(); - for (int i = 0, n = parameters.length; i < n; i += 1) { - // SAM parameters should align one-for-one with the referenced method's parameters - extractGenericsConnections(connections, parameters[i].getOriginType(), pTypes[i]); - } - // convert the method reference's generics into the SAM's generics domain - closureReturnType = applyGenericsContext(connections, closureReturnType); - // apply known generics connections to the placeholders of the return type - closureReturnType = applyGenericsContext(placeholders, closureReturnType); - } - } - // the SAM's return type exactly corresponds to the inferred closure return type - extractGenericsConnections(placeholders, closureReturnType, sam.getReturnType()); - // GRECLIPSE end // repeat the same for each parameter given in the ClosureExpression if (parameters.length > 0 && expression instanceof ClosureExpression) { List genericsToConnect = new LinkedList(); @@ -6255,6 +6350,47 @@ && isUsingUncheckedGenerics(closureReturnType)) { // needs resolve } ClassNode result = applyGenericsContext(placeholders, samType.redirect()); return result; + */ + private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType) { + Map samTypeConnections = GenericsUtils.extractPlaceholders(samType); + samTypeConnections.replaceAll((xx, gt) -> // GROOVY-9762, GROOVY-9803: reduce "? super T" to "T" + Optional.ofNullable(gt.getLowerBound()).map(GenericsType::new).orElse(gt) + ); + ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType(); + + Parameter[] parameters = sam.getParameters(); + if (parameters.length > 0 + && expression instanceof MethodPointerExpression + && GenericsUtils.hasUnresolvedGenerics(closureReturnType)) { + // try to resolve method reference type parameters in return type + MethodPointerExpression mp = (MethodPointerExpression) expression; + MethodNode mn = chooseMethod(mp, () -> + applyGenericsContext(samTypeConnections, extractTypesFromParameters(parameters)) + ); + if (mn != null) { + ClassNode[] pTypes = collateMethodReferenceParameterTypes(mp, mn); + Map connections = new HashMap<>(); + for (int i = 0, n = parameters.length; i < n; i += 1) { + // SAM parameters should align one-for-one with the referenced method's parameters + extractGenericsConnections(connections, parameters[i].getOriginType(), pTypes[i]); + } + // convert the method reference's generics into the SAM's generics domain + closureReturnType = applyGenericsContext(connections, closureReturnType); + // apply known generics connections to the SAM's placeholders in the return type + closureReturnType = applyGenericsContext(samTypeConnections, closureReturnType); + } + } + + // the SAM's return type exactly corresponds to the inferred closure return type + extractGenericsConnections(samTypeConnections, closureReturnType, sam.getReturnType()); + + // repeat the same for each parameter given in the ClosureExpression + if (parameters.length > 0 && expression instanceof ClosureExpression) { + return closureType; // TODO + } + + return applyGenericsContext(samTypeConnections, samType.redirect()); + // GRECLIPSE end } private ClassNode resolveGenericsWithContext(Map resolvedPlaceholders, ClassNode currentType) { diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/ast/GenericsType.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/ast/GenericsType.java index 23fe94aa98..54b5de65a3 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/ast/GenericsType.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/ast/GenericsType.java @@ -428,7 +428,8 @@ private static boolean compareGenericsWithBound(final ClassNode classNode, final GenericsTypeName name = new GenericsTypeName(classNodeType.getName()); if (redirectBoundType.isPlaceholder()) { GenericsTypeName gtn = new GenericsTypeName(redirectBoundType.getName()); - match = name.equals(gtn); + match = name.equals(gtn) + || name.equals(new GenericsTypeName("#" + redirectBoundType.getName())); if (!match) { GenericsType boundGenericsType = boundPlaceHolders.get(gtn); if (boundGenericsType != null) { diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/control/ResolveVisitor.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/control/ResolveVisitor.java index 578a7f7cea..e4ff4dd4a2 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/control/ResolveVisitor.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/control/ResolveVisitor.java @@ -1630,6 +1630,7 @@ private void resolveGenericsHeader(final GenericsType[] types, final GenericsTyp } else { if (!isWild) { if (toDealWithGenerics) { + /* GRECLIPSE edit GenericsType originalGt = genericParameterNames.get(gtn); genericParameterNames.put(gtn, type); type.setPlaceholder(true); @@ -1639,6 +1640,11 @@ private void resolveGenericsHeader(final GenericsType[] types, final GenericsTyp } else { classNode.setRedirect(originalGt.getType()); } + */ + type.setPlaceholder(true); + GenericsType last = genericParameterNames.put(gtn, type); + classNode.setRedirect(last != null ? last.getType().redirect() : ClassHelper.OBJECT_TYPE); + // GRECLIPSE end } } } diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java index 0108c9a346..2ba76e6a4e 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java @@ -467,23 +467,28 @@ public static boolean isAssignableTo(ClassNode type, ClassNode toBeAssignedTo) { if (STRING_TYPE.equals(type) && toBeAssignedTo.isDerivedFrom(GSTRING_TYPE)) { return true; } + /* GRECLIPSE edit -- GROOVY-10067 if (implementsInterfaceOrIsSubclassOf(type, toBeAssignedTo)) { - /* GRECLIPSE edit -- GROOVY-10067 if (toBeAssignedTo.getGenericsTypes() != null) { // perform additional check on generics // ? extends toBeAssignedTo GenericsType gt = GenericsUtils.buildWildcardType(toBeAssignedTo); return gt.isCompatibleWith(type); } - */ + return true; + } + */ + if (implementsInterfaceOrIsSubclassOf(type, toBeAssignedTo)) { if (toBeAssignedTo.getGenericsTypes() != null) { GenericsType gt = toBeAssignedTo.isGenericsPlaceHolder() ? toBeAssignedTo.getGenericsTypes()[0] : GenericsUtils.buildWildcardType(toBeAssignedTo); return gt.isCompatibleWith(type); } - // GRECLIPSE end return true; } - // SAM check + if (type.isGenericsPlaceHolder() && type.getUnresolvedName().charAt(0) == '#') { + return type.getGenericsTypes()[0].isCompatibleWith(toBeAssignedTo); + } + // GRECLIPSE end if (type.isDerivedFrom(CLOSURE_TYPE) && isSAMType(toBeAssignedTo)) { return true; } @@ -1065,10 +1070,11 @@ public static List chooseBestMethod(final ClassNode receiver, final if (!asBoolean(methods)) { return Collections.emptyList(); } + /* GRECLIPSE edit -- GROOVY-8409, et al. if (isUsingUncheckedGenerics(receiver)) { return chooseBestMethod(makeRawType(receiver), methods, argumentTypes); } - + */ int bestDist = Integer.MAX_VALUE; List bestChoices = new LinkedList<>(); boolean noCulling = methods.size() <= 1 || "".equals(methods.iterator().next().getName()); @@ -1725,6 +1731,9 @@ static void applyGenericsConnections(final Map c GenericsTypeName name = new GenericsTypeName(oldValue.getName()); GenericsType newValue = connections.get(name); // find "V" in T=V if (newValue == oldValue) continue; + // GRECLIPSE add -- GROOVY-10067 + if (name.getName().charAt(0) == '#') continue; + // GRECLIPSE end if (newValue == null) { entry.setValue(newValue = applyGenericsContext(connections, oldValue)); checkForMorePlaceholders = checkForMorePlaceholders || !equalIncludingGenerics(oldValue, newValue); @@ -1947,6 +1956,7 @@ static GenericsType[] getGenericsWithoutArray(final ClassNode type) { return type.getGenericsTypes(); } + /* GRECLIPSE edit static Map applyGenericsContextToParameterClass(final Map spec, final ClassNode parameterUsage) { GenericsType[] gts = parameterUsage.getGenericsTypes(); if (gts == null) return Collections.emptyMap(); @@ -1960,9 +1970,11 @@ static Map applyGenericsContextToParameterClass( ); return newSpec; } + */ - private static GenericsType[] applyGenericsContext(final Map spec, final GenericsType[] gts) { - if (gts == null) return null; + // GRECLIPSE private->package + static GenericsType[] applyGenericsContext(final Map spec, final GenericsType[] gts) { + if (gts == null || spec == null || spec.isEmpty()) return gts; GenericsType[] newGTs = new GenericsType[gts.length]; for (int i = 0, n = gts.length; i < n; i += 1) { GenericsType gt = gts[i]; @@ -2056,23 +2068,22 @@ static ClassNode applyGenericsContext(final Map } return newType; */ - if (!type.isGenericsPlaceHolder()) { + if (!type.isGenericsPlaceHolder()) { // convert Type to Type<...> ClassNode cn = type.getPlainNodeReference(); cn.setGenericsTypes(gt); return cn; } - if (gt[0].isPlaceholder()) { - if (type.getGenericsTypes()[0] == gt[0]) { - return type; // nothing to do - } else if (!hasNonTrivialBounds(gt[0])) { - ClassNode cn = make(gt[0].getName()); - cn.setRedirect(type); - cn.setGenericsTypes(gt); - cn.setGenericsPlaceHolder(true); - return cn; - } + if (!gt[0].isPlaceholder()) { // convert T to Type or Type<...> + return getCombinedBoundType(gt[0]); } - return getCombinedBoundType(gt[0]); + if (type.getGenericsTypes()[0] != gt[0]) { // convert T to X + ClassNode cn = make(gt[0].getName()); + cn.setRedirect(gt[0].getType()); + cn.setGenericsPlaceHolder(true); + cn.setGenericsTypes(gt); + return cn; + } + return type; // GRECLIPSE end } diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 6c81fdbb4c..88018b72f4 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -134,9 +134,11 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiPredicate; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.IntStream; +import static java.util.stream.Collectors.*; import static org.apache.groovy.util.BeanUtils.capitalize; import static org.apache.groovy.util.BeanUtils.decapitalize; import static org.codehaus.groovy.ast.ClassHelper.AUTOCLOSEABLE_TYPE; @@ -239,7 +241,6 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatchWithDefaultParams; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsConnections; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContext; -import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContextToParameterClass; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.boundUnboundedWildcards; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.checkCompatibleAssignmentTypes; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.checkPossibleLossOfPrecision; @@ -273,7 +274,6 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isShiftOperation; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isTraitSelf; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUsingGenericsOrIsArrayUsingGenerics; -import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUsingUncheckedGenerics; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isVargs; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isWildcardLeftHandSide; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.lastArgMatchesVarg; @@ -837,6 +837,7 @@ else if (op == LOGICAL_OR) { inferDiamondType((ConstructorCallExpression) rightExpression, lType); } if (lType.isUsingGenerics() && missesGenericsTypes(resultType)) { + if (!resultType.isGenericsPlaceHolder()) // plain reference loses information resultType = GenericsUtils.parameterizeType(lType, resultType.getPlainNodeReference()); } else if (lType.equals(OBJECT_TYPE) && GenericsUtils.hasUnresolvedGenerics(resultType)) { // def list = [] Map placeholders = extractGenericsParameterMapOfThis(typeCheckingContext); @@ -999,6 +1000,9 @@ private boolean ensureValidSetter(final Expression expression, final Expression // but we must check if the binary expression is an assignment // because we need to check if a setter uses @DelegatesTo VariableExpression ve = varX("%", setterInfo.receiverType); + // GRECLIPSE add -- GROOVY-8409, et al. + ve.setType(setterInfo.receiverType); + // GRECLIPSE end // for compound assignment "x op= y" find type as if it was "x = (x op y)" Expression newRightExpression = isCompoundAssignment(expression) ? binX(leftExpression, getOpWithoutEqual(expression), rightExpression) @@ -1230,7 +1234,7 @@ private boolean typeCheckMultipleAssignmentAndContinue(final Expression leftExpr return true; } - private static final List TUPLE_TYPES = Collections.unmodifiableList(Arrays.stream(TUPLE_CLASSES).map(ClassHelper::makeWithoutCaching).collect(java.util.stream.Collectors.toList())); + private static final List TUPLE_TYPES = Collections.unmodifiableList(Arrays.stream(TUPLE_CLASSES).map(ClassHelper::makeWithoutCaching).collect(toList())); /* GRECLIPSE edit -- GROOVY-10063 private Expression transformRightExpressionToSupportMultipleAssignment(final Expression rightExpression) { @@ -2867,7 +2871,7 @@ public void visitStaticMethodCallExpression(final StaticMethodCallExpression cal if (!mn.isEmpty()) { if (mn.size() == 1) { // GRECLIPSE add -- GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915 - resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0)); + resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0).getParameters()); // GRECLIPSE end typeCheckMethodsWithGenericsOrFail(currentReceiver.getType(), args, mn.get(0), call); } @@ -3777,8 +3781,14 @@ public void visitMethodCallExpression(final MethodCallExpression call) { returnType = typeCheckingContext.getEnclosingClassNode(); } } - // GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, et al. - resolvePlaceholdersFromImplicitTypeHints(args, argumentList, directMethodCallCandidate); + // GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915, et al. + Parameter[] parameters = directMethodCallCandidate.getParameters(); + if (chosenReceiver.getType().getGenericsTypes() != null && !directMethodCallCandidate.isStatic() && !(directMethodCallCandidate instanceof ExtensionMethodNode)) { + Map context = extractPlaceHolders(null, chosenReceiver.getType(), getDeclaringClass(directMethodCallCandidate, argumentList)); + parameters = Arrays.stream(parameters).map(param -> new Parameter(applyGenericsContext(context, param.getType()), param.getName())).toArray(Parameter[]::new); + } + resolvePlaceholdersFromImplicitTypeHints(args, argumentList, parameters); + // GRECLIPSE end if (typeCheckMethodsWithGenericsOrFail(chosenReceiver.getType(), args, directMethodCallCandidate, call)) { returnType = adjustWithTraits(directMethodCallCandidate, chosenReceiver.getType(), args, returnType); @@ -5247,15 +5257,14 @@ protected List findMethod(ClassNode receiver, final String name, fin } if (receiver.equals(CLASS_Type) && receiver.getGenericsTypes() != null) { - List result = findMethod(receiver.getGenericsTypes()[0].getType(), name, args); - if (!result.isEmpty()) return result; + chosen = findMethod(receiver.getGenericsTypes()[0].getType(), name, args); + if (!chosen.isEmpty()) return chosen; + } + if (GSTRING_TYPE.equals(receiver)) { + return findMethod(STRING_TYPE, name, args); } - - if (GSTRING_TYPE.equals(receiver)) return findMethod(STRING_TYPE, name, args); - if (isBeingCompiled(receiver)) { - chosen = findMethod(GROOVY_OBJECT_TYPE, name, args); - if (!chosen.isEmpty()) return chosen; + return findMethod(GROOVY_OBJECT_TYPE, name, args); } return EMPTY_METHODNODE_LIST; @@ -5578,6 +5587,22 @@ protected ClassNode inferMapExpressionType(final MapExpression map) { private static class ExtensionMethodDeclaringClass { } + private static ArgumentListExpression getExtensionArguments(final ClassNode receiver, final MethodNode method, final Expression arguments) { + VariableExpression self = varX("$self", receiver); // implicit first argument + self.putNodeMetaData(ExtensionMethodDeclaringClass.class, method.getDeclaringClass()); + + ArgumentListExpression args = new ArgumentListExpression(); + args.addExpression(self); + if (arguments instanceof TupleExpression) { + for (Expression argument : (TupleExpression) arguments) { + args.addExpression(argument); + } + } else { + args.addExpression(arguments); + } + return args; + } + /** * If a method call returns a parameterized type, then we can perform additional inference on the * return type, so that the type gets actual type parameters. For example, the method @@ -5604,7 +5629,8 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth * @return parameterized, infered, class node */ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final MethodNode method, final Expression arguments, final GenericsType[] explicitTypeHints) { - ClassNode returnType = method instanceof ConstructorNode ? method.getDeclaringClass() : method.getReturnType(); // GRECLIPSE edit -- GROOVY-9948 + /* GRECLIPSE edit -- GROOVY-8409, GROOVY-9948, GROOVY-9970, GROOVY-10036, GROOVY-10056, GROOVY-10062 + ClassNode returnType = method.getReturnType(); if (getGenericsWithoutArray(returnType) == null) { return returnType; } @@ -5623,7 +5649,7 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth } else { args.addExpression(arguments); } - return inferReturnTypeGenerics(receiver, dgm, args, explicitTypeHints); // GRECLIPSE edit -- GROOVY-10036 + return inferReturnTypeGenerics(receiver, dgm, args); } Map resolvedPlaceholders = resolvePlaceHoldersFromDeclaration(receiver, getDeclaringClass(method, arguments), method, method.isStatic()); resolvePlaceholdersFromExplicitTypeHints(method, explicitTypeHints, resolvedPlaceholders); @@ -5631,14 +5657,12 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth return boundUnboundedWildcards(returnType); } Map placeholdersFromContext = extractGenericsParameterMapOfThis(typeCheckingContext); - /* GRECLIPSE edit -- GROOVY-9970 applyGenericsConnections(placeholdersFromContext, resolvedPlaceholders); - */ + // then resolve receivers from method arguments List expressions = InvocationWriter.makeArgumentList(arguments).getExpressions(); Parameter[] parameters = method.getParameters(); boolean isVargs = isVargs(parameters); - /* GRECLIPSE edit -- GROOVY-10056, GROOVY-10062 int paramLength = parameters.length; if (expressions.size() >= paramLength) { for (int i = 0; i < paramLength; i += 1) { @@ -5649,20 +5673,7 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth paramType = paramType.getComponentType(); argumentType = argumentType.getComponentType(); } - */ - int nArguments = expressions.size(), nParams = parameters.length; - if (isVargs ? nArguments >= nParams - 1 : nArguments == nParams) { - for (int i = 0; i < nArguments; i += 1) { - if (isNullConstant(expressions.get(i))) continue; // GROOVY-9984 - ClassNode paramType = parameters[Math.min(i, nParams - 1)].getType(); - ClassNode argumentType = getDeclaredOrInferredType(expressions.get(i)); - // GRECLIPSE end if (isUsingGenericsOrIsArrayUsingGenerics(paramType)) { - // GRECLIPSE add -- use element type if supplying array param with multiple arguments or single non-array argument - if (isVargs && (i >= nParams || (i == nParams - 1 && (nArguments > nParams || !argumentType.isArray())))) { - paramType = paramType.getComponentType(); - } - // GRECLIPSE end if (argumentType.isDerivedFrom(CLOSURE_TYPE)) { MethodNode sam = findSAM(paramType); if (sam != null) { // implicit closure coercion in action! @@ -5671,14 +5682,12 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth applyGenericsContextToParameterClass(resolvedPlaceholders, paramType)); } } - /* GRECLIPSE edit -- GROOVY-10056, GROOVY-10062 if (isVargs && lastArg && argumentType.isArray()) { argumentType = argumentType.getComponentType(); } if (isVargs && lastArg && paramType.isArray()) { paramType = paramType.getComponentType(); } - */ argumentType = wrapTypeIfNecessary(argumentType); Map connections = new HashMap<>(); @@ -5686,18 +5695,132 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth extractGenericsConnectionsForSuperClassAndInterfaces(resolvedPlaceholders, connections); applyGenericsConnections(connections, resolvedPlaceholders); - /* GRECLIPSE edit -- GROOVY-9970 applyGenericsConnections(placeholdersFromContext, resolvedPlaceholders); - */ } } } - // GRECLIPSE add -- GROOVY-9970 - applyGenericsConnections(placeholdersFromContext, resolvedPlaceholders); - // GRECLIPSE end return applyGenericsContext(resolvedPlaceholders, returnType); + */ + ClassNode returnType = method instanceof ConstructorNode ? method.getDeclaringClass() : method.getReturnType(); + if (!GenericsUtils.hasUnresolvedGenerics(returnType)) { + // GROOVY-7538: replace "Type" with "Type" for any "Type" + if (getGenericsWithoutArray(returnType) != null) returnType = boundUnboundedWildcards(returnType); + + return returnType; + } + + if (method instanceof ExtensionMethodNode) { + ArgumentListExpression args = getExtensionArguments(receiver, method, arguments); + MethodNode extension = ((ExtensionMethodNode) method).getExtensionMethodNode(); + return inferReturnTypeGenerics(receiver, extension, args, explicitTypeHints); + } + + Map context = method.isStatic() || method instanceof ConstructorNode + ? null : extractPlaceHolders(null, receiver, getDeclaringClass(method, arguments)); + GenericsType[] methodGenericTypes = method instanceof ConstructorNode ? method.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, method.getGenericsTypes()); + + // 1) resolve type parameters of method + + if (methodGenericTypes != null) { + Map resolvedPlaceholders = new HashMap<>(); + for (GenericsType gt : methodGenericTypes) resolvedPlaceholders.put(new GenericsTypeName(gt.getName()), gt); + applyGenericsConnections(extractGenericsConnectionsFromArguments(methodGenericTypes, Arrays.stream(method.getParameters()).map(param -> + new Parameter(applyGenericsContext(context, param.getType()), param.getName()) + ).toArray(Parameter[]::new), arguments, explicitTypeHints), resolvedPlaceholders); + + returnType = applyGenericsContext(resolvedPlaceholders, returnType); + } + + // 2) resolve type parameters of method's enclosing context + + if (context != null) { + returnType = applyGenericsContext(context, returnType); + + if (receiver.getGenericsTypes() == null && receiver.redirect().getGenericsTypes() != null && GenericsUtils.hasUnresolvedGenerics(returnType)) { + returnType = returnType.getPlainNodeReference(); // GROOVY-10049: do not return "Stream" for raw type "List#stream()" + } + } + + // 3) resolve bounds of type parameters from calling context + + returnType = applyGenericsContext(extractGenericsParameterMapOfThis(typeCheckingContext), returnType); + + return returnType; + // GRECLIPSE end } + /** + * Resolves type parameters declared by method from type or call arguments. + */ + private Map extractGenericsConnectionsFromArguments(final GenericsType[] methodGenericTypes, final Parameter[] parameters, final Expression arguments, final GenericsType[] explicitTypeHints) { + Map resolvedPlaceholders = new HashMap<>(); + + if (explicitTypeHints != null) { // resolve type parameters from type arguments + int n = methodGenericTypes.length; + if (n == explicitTypeHints.length) { + for (int i = 0; i < n; i += 1) { + resolvedPlaceholders.put(new GenericsTypeName(methodGenericTypes[i].getName()), explicitTypeHints[i]); + } + } + } else if (parameters.length > 0) { // resolve type parameters from call arguments + List expressions = InvocationWriter.makeArgumentList(arguments).getExpressions(); + boolean isVargs = isVargs(parameters); + int nArguments = expressions.size(); + int nParams = parameters.length; + + if (isVargs ? nArguments >= nParams - 1 : nArguments == nParams) { + for (int i = 0; i < nArguments; i += 1) { + if (isNullConstant(expressions.get(i))) + continue; // GROOVY-9984: skip null + ClassNode paramType = parameters[Math.min(i, nParams - 1)].getType(); + ClassNode argumentType = getDeclaredOrInferredType(expressions.get(i)); + + if (GenericsUtils.hasUnresolvedGenerics(paramType)) { + // if supplying array param with multiple arguments or single non-array argument, infer using element type + if (isVargs && (i >= nParams || (i == nParams - 1 && (nArguments > nParams || !argumentType.isArray())))) { + paramType = paramType.getComponentType(); + } + + if (isClosureWithType(argumentType)) { + MethodNode sam = findSAM(paramType); + if (sam != null) { // implicit closure coerce + argumentType = convertClosureTypeToSAMType(expressions.get(i), argumentType, sam, paramType, null); + } + } + + Map connections = new HashMap<>(); + extractGenericsConnections(connections, wrapTypeIfNecessary(argumentType), paramType); + connections.forEach((gtn, gt) -> resolvedPlaceholders.merge(gtn, gt, (gt1, gt2) -> { + return gt2; // TODO + })); + } + } + } + + // in case of ">" we can learn about "T" from resolved "U" + Map connections = Arrays.stream(methodGenericTypes) + .collect(toMap(gt -> new GenericsTypeName(gt.getName()), Function.identity())); + extractGenericsConnectionsForSuperClassAndInterfaces(connections, resolvedPlaceholders); + } + + for (GenericsType gt : methodGenericTypes) { + // GROOVY-8409, GROOVY-10067, et al.: provide "no type witness" mapping for param + resolvedPlaceholders.computeIfAbsent(new GenericsTypeName(gt.getName()), gtn -> { + GenericsType xxx = new GenericsType(ClassHelper.makeWithoutCaching("#"), + applyGenericsContext(resolvedPlaceholders, gt.getUpperBounds()), + applyGenericsContext(resolvedPlaceholders, gt.getLowerBound())); + xxx.getType().setRedirect(gt.getType().redirect()); + xxx.putNodeMetaData(GenericsType.class, gt); + xxx.setName("#" + gt.getName()); + xxx.setPlaceholder(true); + return xxx; + }); + } + + return resolvedPlaceholders; + } + + /* GRECLIPSE edit private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode method, final GenericsType[] explicitTypeHints, final Map resolvedPlaceholders) { if (explicitTypeHints != null) { GenericsType[] methodGenericTypes = method.getGenericsTypes(); @@ -5710,13 +5833,14 @@ private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode me } } } + */ /** * Given method call like "m(Collections.emptyList())", the type of the call * argument is {@code List} without explicit type arguments. Knowning the * method target of "m", {@code T} could be resolved. */ - private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final MethodNode inferredMethod) { + private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final Parameter[] parameterArray) { /* GRECLIPSE edit for (int i = 0, n = actuals.length; i < n; i += 1) { // check for method call with known target @@ -5736,12 +5860,13 @@ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] a ClassNode pt = p.getOriginType(); if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType(); */ - int np = inferredMethod.getParameters().length; + int np = parameterArray.length; for (int i = 0, n = actuals.length; np > 0 && i < n; i += 1) { Expression a = argumentList.getExpression(i); - Parameter p = inferredMethod.getParameters()[Math.min(i, np - 1)]; + Parameter p = parameterArray[Math.min(i, np - 1)]; ClassNode at = actuals[i], pt = p.getOriginType(); + if (!isUsingGenericsOrIsArrayUsingGenerics(pt)) continue; if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType(); if (a instanceof ListExpression) { @@ -5758,6 +5883,11 @@ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] a // and unknown generics if (!GenericsUtils.hasUnresolvedGenerics(at)) continue; + + while (!at.equals(pt) && !at.equals(OBJECT_TYPE)) { + ClassNode sc = GenericsUtils.getSuperClass(at, pt); + at = applyGenericsContext(GenericsUtils.extractPlaceholders(at), sc); + } // GRECLIPSE end // try to resolve placeholder(s) in argument type using parameter type @@ -5769,11 +5899,11 @@ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] a // connect E:T from source to E:Type from target for (GenericsType placeholder : aNode.getGenericsTypes()) { for (Map.Entry e : source.entrySet()) { - if (e.getValue() == placeholder) { + if (e.getValue().getNodeMetaData(GenericsType.class) == placeholder) { Optional.ofNullable(target.get(e.getKey())) // skip "f(g())" for "f(T)" and " U g()" .filter(gt -> isAssignableTo(gt.getType(), placeholder.getType())) - .ifPresent(gt -> linked.put(new GenericsTypeName(placeholder.getName()), gt)); + .ifPresent(gt -> linked.put(new GenericsTypeName(e.getValue().getName()), gt)); break; } } @@ -5886,6 +6016,7 @@ private static ClassNode[] collateMethodReferenceParameterTypes(final MethodPoin * @return SAM type augmented using information from the argument expression */ private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType, final Map placeholders) { + /* GRECLIPSE edit -- GROOVY-9762, GROOVY-9803 // use the generics information from Closure to further specify the type if (closureType.isUsingGenerics()) { ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType(); @@ -5958,6 +6089,46 @@ && isUsingUncheckedGenerics(closureReturnType)) { // needs resolve } return applyGenericsContext(placeholders, samType.redirect()); + */ + Map samTypeConnections = GenericsUtils.extractPlaceholders(samType); + samTypeConnections.replaceAll((xx, gt) -> // GROOVY-9762, GROOVY-9803: reduce "? super T" to "T" + Optional.ofNullable(gt.getLowerBound()).map(GenericsType::new).orElse(gt) + ); + ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType(); + + Parameter[] parameters = sam.getParameters(); + if (parameters.length > 0 + && expression instanceof MethodPointerExpression + && GenericsUtils.hasUnresolvedGenerics(closureReturnType)) { + // try to resolve method reference type parameters in return type + MethodPointerExpression mp = (MethodPointerExpression) expression; + MethodNode mn = chooseMethod(mp, () -> + applyGenericsContext(samTypeConnections, extractTypesFromParameters(parameters)) + ); + if (mn != null) { + ClassNode[] pTypes = collateMethodReferenceParameterTypes(mp, mn); + Map connections = new HashMap<>(); + for (int i = 0, n = parameters.length; i < n; i += 1) { + // SAM parameters should align one-for-one with the referenced method's parameters + extractGenericsConnections(connections, parameters[i].getOriginType(), pTypes[i]); + } + // convert the method reference's generics into the SAM's generics domain + closureReturnType = applyGenericsContext(connections, closureReturnType); + // apply known generics connections to the SAM's placeholders in the return type + closureReturnType = applyGenericsContext(samTypeConnections, closureReturnType); + } + } + + // the SAM's return type exactly corresponds to the inferred closure return type + extractGenericsConnections(samTypeConnections, closureReturnType, sam.getReturnType()); + + // repeat the same for each parameter given in the ClosureExpression + if (parameters.length > 0 && expression instanceof ClosureExpression) { + return closureType; // TODO + } + + return applyGenericsContext(samTypeConnections, samType.redirect()); + // GRECLIPSE end } private ClassNode resolveGenericsWithContext(final Map resolvedPlaceholders, final ClassNode currentType) { diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/GenericsType.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/GenericsType.java index 9b9e792e08..24b3c65d8f 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/GenericsType.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/GenericsType.java @@ -420,7 +420,8 @@ private static boolean compareGenericsWithBound(final ClassNode classNode, final GenericsTypeName name = new GenericsTypeName(classNodeType.getName()); if (redirectBoundType.isPlaceholder()) { GenericsTypeName gtn = new GenericsTypeName(redirectBoundType.getName()); - match = name.equals(gtn); + match = name.equals(gtn) + || name.equals(new GenericsTypeName("#" + redirectBoundType.getName())); if (!match) { GenericsType boundGenericsType = boundPlaceHolders.get(gtn); if (boundGenericsType != null) { diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/control/ResolveVisitor.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/control/ResolveVisitor.java index c442f7da64..8a1532494f 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/control/ResolveVisitor.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/control/ResolveVisitor.java @@ -1625,6 +1625,7 @@ private void resolveGenericsHeader(final GenericsType[] types, final GenericsTyp } else { if (!isWild) { if (toDealWithGenerics) { + /* GRECLIPSE edit GenericsType originalGt = genericParameterNames.get(gtn); genericParameterNames.put(gtn, type); type.setPlaceholder(true); @@ -1634,6 +1635,11 @@ private void resolveGenericsHeader(final GenericsType[] types, final GenericsTyp } else { classNode.setRedirect(originalGt.getType()); } + */ + type.setPlaceholder(true); + GenericsType last = genericParameterNames.put(gtn, type); + classNode.setRedirect(last != null ? last.getType().redirect() : ClassHelper.OBJECT_TYPE); + // GRECLIPSE end } } } diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java index 6a9655acdb..7b43c9416c 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java @@ -485,12 +485,11 @@ public static boolean isAssignableTo(ClassNode type, ClassNode toBeAssignedTo) { // GRECLIPSE end return true; } - // SAM check - if (type.isDerivedFrom(CLOSURE_TYPE) && isSAMType(toBeAssignedTo)) { - return true; + // GROOVY-10067: unresolved argument like "N extends Number" for parameter like "Integer" + if (type.isGenericsPlaceHolder() && type.getUnresolvedName().charAt(0) == '#') { + return type.getGenericsTypes()[0].isCompatibleWith(toBeAssignedTo); } - - return false; + return (type.isDerivedFrom(CLOSURE_TYPE) && isSAMType(toBeAssignedTo)); } static boolean isVargs(final Parameter[] parameters) { @@ -1043,10 +1042,11 @@ public static List chooseBestMethod(final ClassNode receiver, final if (!asBoolean(methods)) { return Collections.emptyList(); } + /* GRECLIPSE edit -- GROOVY-8409, et al. if (isUsingUncheckedGenerics(receiver)) { return chooseBestMethod(makeRawType(receiver), methods, argumentTypes); } - + */ int bestDist = Integer.MAX_VALUE; List bestChoices = new LinkedList<>(); boolean noCulling = methods.size() <= 1 || "".equals(methods.iterator().next().getName()); @@ -1664,7 +1664,7 @@ static void applyGenericsConnections(final Map c if (oldValue.isPlaceholder()) { // T=T or V, not T=String or ? ... GenericsTypeName name = new GenericsTypeName(oldValue.getName()); GenericsType newValue = connections.get(name); // find "V" in T=V - if (newValue == oldValue) continue; + if (newValue == oldValue || name.getName().charAt(0) == '#') continue; if (newValue == null) { entry.setValue(newValue = applyGenericsContext(connections, oldValue)); if (!checkForMorePlaceholders) { @@ -1879,6 +1879,7 @@ static GenericsType[] getGenericsWithoutArray(final ClassNode type) { return type.getGenericsTypes(); } + /* GRECLIPSE edit static Map applyGenericsContextToParameterClass(final Map spec, final ClassNode parameterUsage) { GenericsType[] gts = parameterUsage.getGenericsTypes(); if (gts == null) return Collections.emptyMap(); @@ -1892,9 +1893,11 @@ static Map applyGenericsContextToParameterClass( ); return newSpec; } + */ - private static GenericsType[] applyGenericsContext(final Map spec, final GenericsType[] gts) { - if (gts == null) return null; + // GRECLIPSE private->package + static GenericsType[] applyGenericsContext(final Map spec, final GenericsType[] gts) { + if (gts == null || spec == null || spec.isEmpty()) return gts; int n = gts.length; if (n == 0) return gts; @@ -1990,23 +1993,22 @@ static ClassNode applyGenericsContext(final Map } return newType; */ - if (!type.isGenericsPlaceHolder()) { + if (!type.isGenericsPlaceHolder()) { // convert Type to Type<...> ClassNode cn = type.getPlainNodeReference(); cn.setGenericsTypes(gt); return cn; } - if (gt[0].isPlaceholder()) { - if (type.getGenericsTypes()[0] == gt[0]) { - return type; // nothing to do - } else if (!hasNonTrivialBounds(gt[0])) { - ClassNode cn = make(gt[0].getName()); - cn.setRedirect(type); - cn.setGenericsTypes(gt); - cn.setGenericsPlaceHolder(true); - return cn; - } + if (!gt[0].isPlaceholder()) { // convert T to Type or Type<...> + return getCombinedBoundType(gt[0]); } - return getCombinedBoundType(gt[0]); + if (type.getGenericsTypes()[0] != gt[0]) { // convert T to X + ClassNode cn = make(gt[0].getName()); + cn.setRedirect(gt[0].getType()); + cn.setGenericsPlaceHolder(true); + cn.setGenericsTypes(gt); + return cn; + } + return type; // GRECLIPSE end } diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 4606924f08..5ed79c26ef 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -138,6 +138,7 @@ import java.util.function.Supplier; import java.util.stream.IntStream; +import static java.util.stream.Collectors.*; import static org.apache.groovy.util.BeanUtils.capitalize; import static org.apache.groovy.util.BeanUtils.decapitalize; import static org.codehaus.groovy.ast.ClassHelper.AUTOCLOSEABLE_TYPE; @@ -241,7 +242,6 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatchWithDefaultParams; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsConnections; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContext; -import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContextToParameterClass; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.boundUnboundedWildcards; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.checkCompatibleAssignmentTypes; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.checkPossibleLossOfPrecision; @@ -276,7 +276,6 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isShiftOperation; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isTraitSelf; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUsingGenericsOrIsArrayUsingGenerics; -import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUsingUncheckedGenerics; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isVargs; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isWildcardLeftHandSide; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.lastArgMatchesVarg; @@ -825,6 +824,7 @@ else if (op == LOGICAL_OR) { inferDiamondType((ConstructorCallExpression) rightExpression, lType); } if (lType.isUsingGenerics() && missesGenericsTypes(resultType)) { + if (!resultType.isGenericsPlaceHolder()) // plain reference loses information resultType = GenericsUtils.parameterizeType(lType, resultType.getPlainNodeReference()); } else if (lType.equals(OBJECT_TYPE) && GenericsUtils.hasUnresolvedGenerics(resultType)) { // def list = [] Map placeholders = extractGenericsParameterMapOfThis(typeCheckingContext); @@ -989,6 +989,9 @@ private boolean ensureValidSetter(final Expression expression, final Expression // but we must check if the binary expression is an assignment // because we need to check if a setter uses @DelegatesTo VariableExpression receiver = varX("%", setterInfo.receiverType); + // GRECLIPSE add -- GROOVY-8409, et al. + receiver.setType(setterInfo.receiverType); + // GRECLIPSE end // for "x op= y" expression, find type as if it was "x = x op y" Expression newRightExpression = isCompoundAssignment(expression) ? binX(leftExpression, getOpWithoutEqual(expression), rightExpression) @@ -1207,7 +1210,7 @@ private boolean typeCheckMultipleAssignmentAndContinue(final Expression leftExpr return true; } - private static final List TUPLE_TYPES = Collections.unmodifiableList(Arrays.stream(TUPLE_CLASSES).map(ClassHelper::makeWithoutCaching).collect(java.util.stream.Collectors.toList())); + private static final List TUPLE_TYPES = Collections.unmodifiableList(Arrays.stream(TUPLE_CLASSES).map(ClassHelper::makeWithoutCaching).collect(toList())); /* GRECLIPSE edit private Expression transformRightExpressionToSupportMultipleAssignment(final Expression rightExpression) { @@ -2781,8 +2784,8 @@ public void visitStaticMethodCallExpression(final StaticMethodCallExpression cal mn = findMethod(currentReceiver.getType(), name, args); if (!mn.isEmpty()) { if (mn.size() == 1) { - // GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915, etc. - resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0)); + // GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915, et al. + resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0).getParameters()); typeCheckMethodsWithGenericsOrFail(currentReceiver.getType(), args, mn.get(0), call); } chosenReceiver = currentReceiver; @@ -3676,8 +3679,14 @@ && implementsInterfaceOrIsSubclassOf(receiverType, node.getDeclaringClass()))) { returnType = typeCheckingContext.getEnclosingClassNode(); } } - // GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, et al. - resolvePlaceholdersFromImplicitTypeHints(args, argumentList, directMethodCallCandidate); + // GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915, et al. + Parameter[] parameters = directMethodCallCandidate.getParameters(); + if (chosenReceiver.getType().getGenericsTypes() != null && !directMethodCallCandidate.isStatic() && !(directMethodCallCandidate instanceof ExtensionMethodNode)) { + Map context = extractPlaceHolders(null, chosenReceiver.getType(), getDeclaringClass(directMethodCallCandidate, argumentList)); + parameters = Arrays.stream(parameters).map(param -> new Parameter(applyGenericsContext(context, param.getType()), param.getName())).toArray(Parameter[]::new); + } + resolvePlaceholdersFromImplicitTypeHints(args, argumentList, parameters); + // GRECLIPSE end if (typeCheckMethodsWithGenericsOrFail(chosenReceiver.getType(), args, directMethodCallCandidate, call)) { returnType = adjustWithTraits(directMethodCallCandidate, chosenReceiver.getType(), args, returnType); @@ -5060,15 +5069,14 @@ protected List findMethod(ClassNode receiver, final String name, fin } if (receiver.equals(CLASS_Type) && receiver.getGenericsTypes() != null) { - List result = findMethod(receiver.getGenericsTypes()[0].getType(), name, args); - if (!result.isEmpty()) return result; + chosen = findMethod(receiver.getGenericsTypes()[0].getType(), name, args); + if (!chosen.isEmpty()) return chosen; + } + if (GSTRING_TYPE.equals(receiver)) { + return findMethod(STRING_TYPE, name, args); } - - if (GSTRING_TYPE.equals(receiver)) return findMethod(STRING_TYPE, name, args); - if (isBeingCompiled(receiver)) { - chosen = findMethod(GROOVY_OBJECT_TYPE, name, args); - if (!chosen.isEmpty()) return chosen; + return findMethod(GROOVY_OBJECT_TYPE, name, args); } return EMPTY_METHODNODE_LIST; @@ -5381,6 +5389,22 @@ protected ClassNode inferMapExpressionType(final MapExpression map) { private static class ExtensionMethodDeclaringClass { } + private static ArgumentListExpression getExtensionArguments(final ClassNode receiver, final MethodNode method, final Expression arguments) { + VariableExpression self = varX("$self", receiver); // implicit first argument + self.putNodeMetaData(ExtensionMethodDeclaringClass.class, method.getDeclaringClass()); + + ArgumentListExpression args = new ArgumentListExpression(); + args.addExpression(self); + if (arguments instanceof TupleExpression) { + for (Expression argument : (TupleExpression) arguments) { + args.addExpression(argument); + } + } else { + args.addExpression(arguments); + } + return args; + } + /** * If a method call returns a parameterized type, then we can perform additional inference on the * return type, so that the type gets actual type parameters. For example, the method @@ -5408,6 +5432,7 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth */ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final MethodNode method, final Expression arguments, final GenericsType[] explicitTypeHints) { ClassNode returnType = method instanceof ConstructorNode ? method.getDeclaringClass() : method.getReturnType(); + /* GRECLIPSE edit -- GROOVY-8409, GROOVY-10036, GROOVY-10056, GROOVY-10062 if (getGenericsWithoutArray(returnType) == null) { return returnType; } @@ -5426,7 +5451,7 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth } else { args.addExpression(arguments); } - return inferReturnTypeGenerics(receiver, dgm, args, explicitTypeHints); // GRECLIPSE edit -- GROOVY-10036 + return inferReturnTypeGenerics(receiver, dgm, args); } Map resolvedPlaceholders = resolvePlaceHoldersFromDeclaration(receiver, getDeclaringClass(method, arguments), method, method.isStatic()); resolvePlaceholdersFromExplicitTypeHints(method, explicitTypeHints, resolvedPlaceholders); @@ -5438,7 +5463,6 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth List expressions = InvocationWriter.makeArgumentList(arguments).getExpressions(); Parameter[] parameters = method.getParameters(); boolean isVargs = isVargs(parameters); - /* GRECLIPSE edit -- GROOVY-10056, GROOVY-10062 int paramLength = parameters.length; if (expressions.size() >= paramLength) { for (int i = 0; i < paramLength; i += 1) { @@ -5451,20 +5475,7 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth paramType = paramType.getComponentType(); argumentType = argumentType.getComponentType(); } - */ - int nArguments = expressions.size(), nParams = parameters.length; - if (isVargs ? nArguments >= nParams - 1 : nArguments == nParams) { - for (int i = 0; i < nArguments; i += 1) { - if (isNullConstant(expressions.get(i))) continue; // GROOVY-9984 - ClassNode paramType = parameters[Math.min(i, nParams - 1)].getType(); - ClassNode argumentType = getDeclaredOrInferredType(expressions.get(i)); - // GRECLIPSE end if (isUsingGenericsOrIsArrayUsingGenerics(paramType)) { - // GRECLIPSE add -- use element type if supplying array param with multiple arguments or single non-array argument - if (isVargs && (i >= nParams || (i == nParams - 1 && (nArguments > nParams || !argumentType.isArray())))) { - paramType = paramType.getComponentType(); - } - // GRECLIPSE end if (argumentType.isDerivedFrom(CLOSURE_TYPE)) { MethodNode sam = findSAM(paramType); if (sam != null) { // implicit closure coercion in action! @@ -5473,14 +5484,12 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth applyGenericsContextToParameterClass(resolvedPlaceholders, paramType)); } } - /* GRECLIPSE edit -- GROOVY-10056, GROOVY-10062 if (isVargs && lastArg && argumentType.isArray()) { argumentType = argumentType.getComponentType(); } if (isVargs && lastArg && paramType.isArray()) { paramType = paramType.getComponentType(); } - */ argumentType = wrapTypeIfNecessary(argumentType); Map connections = new HashMap<>(); @@ -5496,8 +5505,126 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth applyGenericsConnections(extractGenericsParameterMapOfThis(typeCheckingContext), resolvedPlaceholders); return applyGenericsContext(resolvedPlaceholders, returnType); + */ + if (!GenericsUtils.hasUnresolvedGenerics(returnType)) { + // GROOVY-7538: replace "Type" with "Type" for any "Type" + if (getGenericsWithoutArray(returnType) != null) returnType = boundUnboundedWildcards(returnType); + + return returnType; + } + + if (method instanceof ExtensionMethodNode) { + ArgumentListExpression args = getExtensionArguments(receiver, method, arguments); + MethodNode extension = ((ExtensionMethodNode) method).getExtensionMethodNode(); + return inferReturnTypeGenerics(receiver, extension, args, explicitTypeHints); + } + + Map context = method.isStatic() || method instanceof ConstructorNode + ? null : extractPlaceHolders(null, receiver, getDeclaringClass(method, arguments)); + GenericsType[] methodGenericTypes = method instanceof ConstructorNode ? method.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, method.getGenericsTypes()); + + // 1) resolve type parameters of method + + if (methodGenericTypes != null) { + Map resolvedPlaceholders = new HashMap<>(); + for (GenericsType gt : methodGenericTypes) resolvedPlaceholders.put(new GenericsTypeName(gt.getName()), gt); + applyGenericsConnections(extractGenericsConnectionsFromArguments(methodGenericTypes, Arrays.stream(method.getParameters()).map(param -> + new Parameter(applyGenericsContext(context, param.getType()), param.getName()) + ).toArray(Parameter[]::new), arguments, explicitTypeHints), resolvedPlaceholders); + + returnType = applyGenericsContext(resolvedPlaceholders, returnType); + } + + // 2) resolve type parameters of method's enclosing context + + if (context != null) { + returnType = applyGenericsContext(context, returnType); + + if (receiver.getGenericsTypes() == null && receiver.redirect().getGenericsTypes() != null && GenericsUtils.hasUnresolvedGenerics(returnType)) { + returnType = returnType.getPlainNodeReference(); // GROOVY-10049: do not return "Stream" for raw type "List#stream()" + } + } + + // 3) resolve bounds of type parameters from calling context + + returnType = applyGenericsContext(extractGenericsParameterMapOfThis(typeCheckingContext), returnType); + + return returnType; + // GRECLIPSE end } + /** + * Resolves type parameters declared by method from type or call arguments. + */ + private Map extractGenericsConnectionsFromArguments(final GenericsType[] methodGenericTypes, final Parameter[] parameters, final Expression arguments, final GenericsType[] explicitTypeHints) { + Map resolvedPlaceholders = new HashMap<>(); + + if (explicitTypeHints != null) { // resolve type parameters from type arguments + int n = methodGenericTypes.length; + if (n == explicitTypeHints.length) { + for (int i = 0; i < n; i += 1) { + resolvedPlaceholders.put(new GenericsTypeName(methodGenericTypes[i].getName()), explicitTypeHints[i]); + } + } + } else if (parameters.length > 0) { // resolve type parameters from call arguments + List expressions = InvocationWriter.makeArgumentList(arguments).getExpressions(); + boolean isVargs = isVargs(parameters); + int nArguments = expressions.size(); + int nParams = parameters.length; + + if (isVargs ? nArguments >= nParams - 1 : nArguments == nParams) { + for (int i = 0; i < nArguments; i += 1) { + if (isNullConstant(expressions.get(i))) + continue; // GROOVY-9984: skip null + ClassNode paramType = parameters[Math.min(i, nParams - 1)].getType(); + ClassNode argumentType = getDeclaredOrInferredType(expressions.get(i)); + + if (GenericsUtils.hasUnresolvedGenerics(paramType)) { + // if supplying array param with multiple arguments or single non-array argument, infer using element type + if (isVargs && (i >= nParams || (i == nParams - 1 && (nArguments > nParams || !argumentType.isArray())))) { + paramType = paramType.getComponentType(); + } + + if (isClosureWithType(argumentType)) { + MethodNode sam = findSAM(paramType); + if (sam != null) { // implicit closure coerce + argumentType = convertClosureTypeToSAMType(expressions.get(i), argumentType, sam, paramType); + } + } + + Map connections = new HashMap<>(); + extractGenericsConnections(connections, wrapTypeIfNecessary(argumentType), paramType); + connections.forEach((gtn, gt) -> resolvedPlaceholders.merge(gtn, gt, (gt1, gt2) -> { + return gt2; // TODO + })); + } + } + } + + // in case of ">" we can learn about "T" from resolved "U" + Map connections = Arrays.stream(methodGenericTypes) + .collect(toMap(gt -> new GenericsTypeName(gt.getName()), Function.identity())); + extractGenericsConnectionsForSuperClassAndInterfaces(connections, resolvedPlaceholders); + } + + for (GenericsType gt : methodGenericTypes) { + // GROOVY-8409, GROOVY-10067, et al.: provide "no type witness" mapping for param + resolvedPlaceholders.computeIfAbsent(new GenericsTypeName(gt.getName()), gtn -> { + GenericsType xxx = new GenericsType(ClassHelper.makeWithoutCaching("#"), + applyGenericsContext(resolvedPlaceholders, gt.getUpperBounds()), + applyGenericsContext(resolvedPlaceholders, gt.getLowerBound())); + xxx.getType().setRedirect(gt.getType().redirect()); + xxx.putNodeMetaData(GenericsType.class, gt); + xxx.setName("#" + gt.getName()); + xxx.setPlaceholder(true); + return xxx; + }); + } + + return resolvedPlaceholders; + } + + /* GRECLIPSE edit private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode method, final GenericsType[] explicitTypeHints, final Map resolvedPlaceholders) { if (explicitTypeHints != null) { GenericsType[] methodGenericTypes = method.getGenericsTypes(); @@ -5510,19 +5637,21 @@ private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode me } } } + */ /** * Given method call like "m(Collections.emptyList())", the type of the call * argument is {@code List} without explicit type arguments. Knowning the * method target of "m", {@code T} could be resolved. */ - private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final MethodNode inferredMethod) { - int np = inferredMethod.getParameters().length; + private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final Parameter[] parameterArray) { + int np = parameterArray.length; for (int i = 0, n = actuals.length; np > 0 && i < n; i += 1) { Expression a = argumentList.getExpression(i); - Parameter p = inferredMethod.getParameters()[Math.min(i, np - 1)]; + Parameter p = parameterArray[Math.min(i, np - 1)]; ClassNode at = actuals[i], pt = p.getOriginType(); + if (!isUsingGenericsOrIsArrayUsingGenerics(pt)) continue; if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType(); if (a instanceof ListExpression) { @@ -5540,6 +5669,11 @@ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] a // and unknown generics if (!GenericsUtils.hasUnresolvedGenerics(at)) continue; + while (!at.equals(pt) && !at.equals(OBJECT_TYPE)) { + ClassNode sc = GenericsUtils.getSuperClass(at, pt); + at = applyGenericsContext(GenericsUtils.extractPlaceholders(at), sc); + } + // try to resolve placeholder(s) in argument type using parameter type Map linked = new HashMap<>(); @@ -5549,11 +5683,11 @@ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] a // connect E:T from source to E:Type from target for (GenericsType placeholder : aNode.getGenericsTypes()) { for (Map.Entry e : source.entrySet()) { - if (e.getValue() == placeholder) { + if (e.getValue().getNodeMetaData(GenericsType.class) == placeholder) { Optional.ofNullable(target.get(e.getKey())) // skip "f(g())" for "f(T)" and " U g()" .filter(gt -> isAssignableTo(gt.getType(), placeholder.getType())) - .ifPresent(gt -> linked.put(new GenericsTypeName(placeholder.getName()), gt)); + .ifPresent(gt -> linked.put(new GenericsTypeName(e.getValue().getName()), gt)); break; } } @@ -5665,79 +5799,45 @@ private static ClassNode[] collateMethodReferenceParameterTypes(final MethodPoin * @param samType the type into which the closure is coerced into * @return SAM type augmented using information from the argument expression */ - private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType, final Map placeholders) { - // use the generics information from Closure to further specify the type - if (closureType.isUsingGenerics()) { - ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType(); - - Parameter[] parameters = sam.getParameters(); - if (parameters.length > 0 && expression instanceof MethodPointerExpression - && isUsingUncheckedGenerics(closureReturnType)) { // needs resolve - MethodPointerExpression mp = (MethodPointerExpression) expression; - MethodNode mn = chooseMethod(mp, () -> - applyGenericsContext(placeholders, extractTypesFromParameters(parameters)) - ); - if (mn != null) { - ClassNode[] pTypes = collateMethodReferenceParameterTypes(mp, mn); - Map connections = new HashMap<>(); - for (int i = 0, n = parameters.length; i < n; i += 1) { - // SAM parameters should align one-for-one with the referenced method's parameters - extractGenericsConnections(connections, parameters[i].getOriginType(), pTypes[i]); - } - // convert the method reference's generics into the SAM's generics domain - closureReturnType = applyGenericsContext(connections, closureReturnType); - // apply known generics connections to the placeholders of the return type - closureReturnType = applyGenericsContext(placeholders, closureReturnType); + private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType) { + Map samTypeConnections = GenericsUtils.extractPlaceholders(samType); + samTypeConnections.replaceAll((xx, gt) -> // GROOVY-9762, GROOVY-9803: reduce "? super T" to "T" + Optional.ofNullable(gt.getLowerBound()).map(GenericsType::new).orElse(gt) + ); + ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType(); + + Parameter[] parameters = sam.getParameters(); + if (parameters.length > 0 + && expression instanceof MethodPointerExpression + && GenericsUtils.hasUnresolvedGenerics(closureReturnType)) { + // try to resolve method reference type parameters in return type + MethodPointerExpression mp = (MethodPointerExpression) expression; + MethodNode mn = chooseMethod(mp, () -> + applyGenericsContext(samTypeConnections, extractTypesFromParameters(parameters)) + ); + if (mn != null) { + ClassNode[] pTypes = collateMethodReferenceParameterTypes(mp, mn); + Map connections = new HashMap<>(); + for (int i = 0, n = parameters.length; i < n; i += 1) { + // SAM parameters should align one-for-one with the referenced method's parameters + extractGenericsConnections(connections, parameters[i].getOriginType(), pTypes[i]); } + // convert the method reference's generics into the SAM's generics domain + closureReturnType = applyGenericsContext(connections, closureReturnType); + // apply known generics connections to the SAM's placeholders in the return type + closureReturnType = applyGenericsContext(samTypeConnections, closureReturnType); } + } - // the SAM's return type exactly corresponds to the inferred closure return type - extractGenericsConnections(placeholders, closureReturnType, sam.getReturnType()); - - // repeat the same for each parameter given in the ClosureExpression - if (parameters.length > 0 && expression instanceof ClosureExpression) { - List genericsToConnect = new ArrayList<>(); - Parameter[] closureParams = ((ClosureExpression) expression).getParameters(); - ClassNode[] closureParamTypes = expression.getNodeMetaData(CLOSURE_ARGUMENTS); - if (closureParamTypes == null) closureParamTypes = extractTypesFromParameters(closureParams); + // the SAM's return type exactly corresponds to the inferred closure return type + extractGenericsConnections(samTypeConnections, closureReturnType, sam.getReturnType()); - for (int i = 0, n = parameters.length; i < n; i += 1) { - Parameter parameter = parameters[i]; - if (parameter.getOriginType().isUsingGenerics() && closureParamTypes.length > i) { - genericsToConnect.add(new ClassNode[]{closureParamTypes[i], parameter.getOriginType()}); - } - } - for (ClassNode[] classNodes : genericsToConnect) { - ClassNode found = classNodes[0]; - ClassNode expected = classNodes[1]; - if (!isAssignableTo(found, expected)) { - // probably facing a type mismatch - continue; - } - ClassNode generifiedType = GenericsUtils.parameterizeType(found, expected); - while (expected.isArray()) { - expected = expected.getComponentType(); - generifiedType = generifiedType.getComponentType(); - } - if (expected.isGenericsPlaceHolder()) { - placeholders.put(new GenericsTypeName(expected.getGenericsTypes()[0].getName()), new GenericsType(generifiedType)); - } else { - GenericsType[] expectedGenericsTypes = expected.getGenericsTypes(); - GenericsType[] foundGenericsTypes = generifiedType.getGenericsTypes(); - - for (int i = 0, n = expectedGenericsTypes.length; i < n; i += 1) { - GenericsType type = expectedGenericsTypes[i]; - if (type.isPlaceholder()) { - String name = type.getName(); - placeholders.put(new GenericsTypeName(name), foundGenericsTypes[i]); - } - } - } - } - } + // repeat the same for each parameter given in the ClosureExpression + if (parameters.length > 0 && expression instanceof ClosureExpression) { + return closureType; // TODO } - return applyGenericsContext(placeholders, samType.redirect()); + return applyGenericsContext(samTypeConnections, samType.redirect()); } private ClassNode resolveGenericsWithContext(final Map resolvedPlaceholders, final ClassNode currentType) {