From 81f32c2f7e2c779a0825a861b41178c43ce99680 Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Thu, 1 Apr 2021 18:16:45 -0500 Subject: [PATCH] GROOVY-6912, GROOVY-7106, GROOVY-7128, GROOVY-7274, GROOVY-8001, GROOVY-8909, GROOVY-9844, GROOVY-10002 --- .../core/tests/xform/TypeCheckedTests.java | 277 ++++++++++++++++++ .../stc/StaticTypeCheckingVisitor.java | 157 +++++++++- .../stc/StaticTypeCheckingVisitor.java | 159 +++++++++- .../stc/StaticTypeCheckingVisitor.java | 157 +++++++++- .../quickfix/test/QuickFixTestSuite.groovy | 11 +- 5 files changed, 736 insertions(+), 25 deletions(-) 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 2b78560cea..167c65c611 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 @@ -316,6 +316,52 @@ public void testTypeChecked6882() { runConformTest(sources, "CC"); } + @Test + public void testTypeChecked6912() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " def a = []\n" + + " assert a instanceof List\n" + + " List b = []\n" + + " assert b instanceof List\n" + + " Object c = []\n" + + " assert c instanceof List\n" + + " Iterable d = []\n" + + " assert d instanceof Iterable\n" + + " ArrayList e = []\n" + + " assert e instanceof ArrayList\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, ""); + } + + @Test + public void testTypeChecked6912a() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " Set a = []\n" + + " assert a instanceof Set\n" + + " HashSet b = []\n" + + " assert b instanceof HashSet\n" + + " LinkedHashSet c = []\n" + + " assert c instanceof LinkedHashSet\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, ""); + } + @Test public void testTypeChecked6938() { //@formatter:off @@ -357,6 +403,151 @@ public void testTypeChecked6938() { runConformTest(sources, "null"); } + @Test + public void testTypeChecked7106() { + //@formatter:off + String[] sources = { + "Main.groovy", + "void m(Map map) {\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " ['x'].each {\n" + + " m([(it): it.toLowerCase()])\n" + + " }\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, ""); + } + + @Test + public void testTypeChecked7128() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " List list = [1,2,3]\n" + + " Set set = [1,2,3,3]\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, ""); + } + + @Test + public void testTypeChecked7128a() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " def a = [:]\n" + + " assert a instanceof Map\n" + + " Map b = [:]\n" + + " assert b instanceof Map\n" + + " Object c = [:]\n" + + " assert c instanceof Map\n" + + " HashMap d = [:]\n" + + " assert d instanceof HashMap\n" + + " LinkedHashMap e = [:]\n" + + " assert e instanceof LinkedHashMap\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, ""); + } + + @Test + public void testTypeChecked7128b() { + for (String spec : new String[] {"CharSequence,Integer", "String,Number", "CharSequence,Number"}) { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " Map<" + spec + "> map = [a:1,b:2,c:3]\n" + + " assert map.size() == 3\n" + + " assert map['c'] == 3\n" + + " assert !('x' in map)\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, ""); + } + } + + @Test + public void testTypeChecked7128c() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " Map map = [1:2]\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runNegativeTest(sources, + "----------\n" + + "1. ERROR in Main.groovy (at line 3)\n" + + "\tMap map = [1:2]\n" + + "\t ^^^^^\n" + + "Groovy:[Static type checking] - Incompatible generic argument types. Cannot assign java.util.LinkedHashMap to: java.util.Map \n" + + "----------\n"); + } + + @Test + public void testTypeChecked7128d() { + //@formatter:off + String[] sources = { + "Main.groovy", + "class A {\n" + + " int x\n" + + " int y\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " A a = [x:100, y:200]\n" + + " assert a.x == 100\n" + + " assert a.y == 200\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, ""); + } + + @Test + public void testTypeChecked7274() { + //@formatter:off + String[] sources = { + "Main.groovy", + "void m(Map map) {\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " m([d:new Date(), i:1, s:''])\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, ""); + } + @Test public void testTypeChecked7333() { //@formatter:off @@ -437,6 +628,28 @@ public void testTypeChecked7945() { "----------\n"); } + @Test + public void testTypeChecked8001() { + //@formatter:off + String[] sources = { + "Main.groovy", + "class C {\n" + + " Map map\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " int value = 42\n" + + " def c = new C()\n" + + " c.map = [key:\"$value\"]\n" + + " print c.map\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "[key:42]"); + } + @Test public void testTypeChecked8034() { //@formatter:off @@ -510,6 +723,49 @@ public void testTypeChecked8103() { runNegativeTest(sources, ""); } + @Test + public void testTypeChecked8909() { + //@formatter:off + String[] sources = { + "Main.groovy", + "void m(List list) {\n" + + " assert list.size() == 3\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " m([1,2,3])\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, ""); + } + + @Test + public void testTypeChecked8909a() { + //@formatter:off + String[] sources = { + "Main.groovy", + "void m(Set set) {\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " m([1,2,3])\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runNegativeTest(sources, + "----------\n" + + "1. ERROR in Main.groovy (at line 5)\n" + + "\tm([1,2,3])\n" + + "\t^^^^^^^^^^\n" + + "Groovy:[Static type checking] - Cannot find matching method Main#m(java.util.List ). Please check if the declared type is correct and if the method exists.\n" + + "----------\n"); + } + @Test public void testTypeChecked9460() { //@formatter:off @@ -785,6 +1041,27 @@ public void testTypeChecked9822() { runNegativeTest(sources, ""); } + @Test + public void testTypeChecked9844() { + //@formatter:off + String[] sources = { + "Main.groovy", + "void m(Map map) {\n" + + " print map\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " Map map = [key: 'val']\n" + + " m([key: 'val'])\n" + + " m(key: 'val')\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "[key:val][key:val]"); + } + @Ignore @Test public void testTypeChecked9873() { Map options = getCompilerOptions(); 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 d29ef31cd8..0db03cbb5c 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 @@ -133,8 +133,10 @@ import java.util.Set; 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 org.apache.groovy.ast.tools.ClassNodeUtils.samePackageName; import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE; @@ -301,6 +303,10 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { protected static final ClassNode NAMED_PARAMS_CLASSNODE = make(NamedParams.class); protected static final ClassNode MAP_ENTRY_TYPE = make(Map.Entry.class); protected static final ClassNode ENUMERATION_TYPE = make(Enumeration.class); + // GRECLIPSE add + private static final ClassNode SET_TYPE = ClassHelper.makeWithoutCaching(Set.class); + private static final ClassNode LinkedHashSet_TYPE = ClassHelper.makeWithoutCaching(LinkedHashSet.class); + // GRECLIPSE end public static final Statement GENERATED_EMPTY_STATEMENT = new EmptyStatement(); @@ -1338,6 +1344,7 @@ private void addListAssignmentConstructorErrors( // if left type is not a list but right type is a list, then we're in the case of a groovy // constructor type : Dimension d = [100,200] // In that case, more checks can be performed + /* GRECLIPSE edit if (rightExpression instanceof ListExpression && !implementsInterfaceOrIsSubclassOf(LIST_TYPE, leftRedirect)) { ArgumentListExpression argList = args(((ListExpression) rightExpression).getExpressions()); ClassNode[] args = getArgumentTypes(argList); @@ -1352,10 +1359,27 @@ && implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, LIST_TYPE) addAssignmentError(leftExpressionType, inferredRightExpressionType, assignmentExpression); } } + */ + if (!implementsInterfaceOrIsSubclassOf(LIST_TYPE, leftRedirect) + && (!leftRedirect.isAbstract() || leftRedirect.isArray()) && !leftRedirect.equals(OBJECT_TYPE) + && !ArrayList_TYPE.isDerivedFrom(leftRedirect) && !LinkedHashSet_TYPE.isDerivedFrom(leftRedirect)) { + ClassNode[] types = getArgumentTypes(args(((ListExpression) rightExpression).getExpressions())); + MethodNode methodNode = checkGroovyStyleConstructor(leftRedirect, types, assignmentExpression); + if (methodNode != null) { + rightExpression.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, methodNode); + } + } else if (implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, LIST_TYPE) + && !implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, leftRedirect) + && !isWildcardLeftHandSide(leftRedirect)) { + if (!extension.handleIncompatibleAssignment(leftExpressionType, inferredRightExpressionType, assignmentExpression)) { + addAssignmentError(leftExpressionType, inferredRightExpressionType, assignmentExpression); + } + } + // GRECLIPSE end } private void addMapAssignmentConstructorErrors(final ClassNode leftRedirect, final Expression leftExpression, final Expression rightExpression) { - if (!(rightExpression instanceof MapExpression) || (leftExpression instanceof VariableExpression && ((VariableExpression) leftExpression).isDynamicTyped()) + if (/* GRECLIPSE edit !(rightExpression instanceof MapExpression) || */(leftExpression instanceof VariableExpression && ((VariableExpression) leftExpression).isDynamicTyped()) || leftRedirect.equals(OBJECT_TYPE) || implementsInterfaceOrIsSubclassOf(leftRedirect, MAP_TYPE)) { return; } @@ -1394,12 +1418,26 @@ private boolean hasGStringStringError(ClassNode leftExpressionType, ClassNode wr return false; } + // GRECLIPSE add + private static boolean isConstructorAbbreviation(final ClassNode leftType, final Expression rightExpression) { + if (rightExpression instanceof ListExpression) { + return !(ArrayList_TYPE.isDerivedFrom(leftType) || ArrayList_TYPE.implementsInterface(leftType) + || LinkedHashSet_TYPE.isDerivedFrom(leftType) || LinkedHashSet_TYPE.implementsInterface(leftType)); + } + if (rightExpression instanceof MapExpression) { + return !(LINKEDHASHMAP_CLASSNODE.isDerivedFrom(leftType) || LINKEDHASHMAP_CLASSNODE.implementsInterface(leftType)); + } + return false; + } + // GRECLIPSE end + protected void typeCheckAssignment( final BinaryExpression assignmentExpression, final Expression leftExpression, final ClassNode leftExpressionType, final Expression rightExpression, - final ClassNode inferredRightExpressionTypeOrig) { + final ClassNode rightExpressionType) { + /* GRECLIPSE edit ClassNode inferredRightExpressionType = inferredRightExpressionTypeOrig; if (!typeCheckMultipleAssignmentAndContinue(leftExpression, rightExpression)) return; @@ -1428,6 +1466,37 @@ protected void typeCheckAssignment( if (hasGStringStringError(leftExpressionType, wrappedRHS, rightExpression)) return; checkTypeGenerics(leftExpressionType, wrappedRHS, rightExpression); } + */ + if (!typeCheckMultipleAssignmentAndContinue(leftExpression, rightExpression)) return; + + if (addedReadOnlyPropertyError(leftExpression)) return; + + ClassNode rTypeInferred, rTypeWrapped; // check for instanceof and spreading + if (rightExpression instanceof VariableExpression && hasInferredReturnType(rightExpression) && assignmentExpression.getOperation().getType() == ASSIGN) { + rTypeInferred = getInferredReturnType(rightExpression); + } else { + rTypeInferred = rightExpressionType; + } + rTypeWrapped = adjustTypeForSpreading(rTypeInferred, leftExpression); + + if (!checkCompatibleAssignmentTypes(leftExpressionType, rTypeWrapped, rightExpression)) { + if (!extension.handleIncompatibleAssignment(leftExpressionType, rTypeInferred, assignmentExpression)) { + addAssignmentError(leftExpressionType, rTypeInferred, rightExpression); + } + } else { + ClassNode lTypeRedirect = leftExpressionType.redirect(); + addPrecisionErrors(lTypeRedirect, leftExpressionType, rTypeInferred, rightExpression); + if (rightExpression instanceof ListExpression) { + addListAssignmentConstructorErrors(lTypeRedirect, leftExpressionType, rTypeInferred, rightExpression, assignmentExpression); + } else if (rightExpression instanceof MapExpression) { + addMapAssignmentConstructorErrors(lTypeRedirect, leftExpression, rightExpression); + } + if (!hasGStringStringError(leftExpressionType, rTypeWrapped, rightExpression) + && !isConstructorAbbreviation(leftExpressionType, rightExpression)) { + checkTypeGenerics(leftExpressionType, rTypeWrapped, rightExpression); + } + } + // GRECLIPSE end } protected void checkGroovyConstructorMap(final Expression receiver, final ClassNode receiverType, final MapExpression mapExpression) { @@ -2985,7 +3054,7 @@ public void visitStaticMethodCallExpression(final StaticMethodCallExpression cal mn = findMethod(currentReceiver.getType(), name, args); if (!mn.isEmpty()) { if (mn.size() == 1) { - // GRECLIPSE add -- GROOVY-8961, GROOVY-9734, GROOVY-9915 + // GRECLIPSE add -- GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915 resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0)); // GRECLIPSE end typeCheckMethodsWithGenericsOrFail(currentReceiver.getType(), args, mn.get(0), call); @@ -3893,7 +3962,7 @@ public void visitMethodCallExpression(MethodCallExpression call) { returnType = md.getType(); } } - // GRECLIPSE add -- GROOVY-8961, GROOVY-9734 + // GRECLIPSE add -- GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844 resolvePlaceholdersFromImplicitTypeHints(args, argumentList, directMethodCallCandidate); // GRECLIPSE end if (typeCheckMethodsWithGenericsOrFail(chosenReceiver.getType(), args, directMethodCallCandidate, call)) { @@ -4792,13 +4861,24 @@ protected ClassNode getResultType(ClassNode left, int op, ClassNode right, Binar } } // GRECLIPSE add - if (isOrImplements(rightRedirect, Collection_TYPE)) { - if (leftRedirect.isArray()) { - return leftRedirect; + if (!leftRedirect.equals(OBJECT_TYPE)) { + if (rightExpression instanceof ListExpression) { + if (LIST_TYPE.equals(leftRedirect) + || ITERABLE_TYPE.equals(leftRedirect) + || Collection_TYPE.equals(leftRedirect) + || ArrayList_TYPE.isDerivedFrom(leftRedirect)) { // GROOVY-6912 + return getLiteralResultType(left, right, ArrayList_TYPE); // GROOVY-7128 + } + if (SET_TYPE.equals(leftRedirect) + || LinkedHashSet_TYPE.isDerivedFrom(leftRedirect)) { // GROOVY-6912 + return getLiteralResultType(left, right, LinkedHashSet_TYPE); // GROOVY-7128 + } } - if (isOrImplements(leftRedirect, Collection_TYPE) - && rightExpression instanceof ListExpression && isEmptyCollection(rightExpression)) { - return left; + if (rightExpression instanceof MapExpression) { + if (MAP_TYPE.equals(leftRedirect) + || LINKEDHASHMAP_CLASSNODE.isDerivedFrom(leftRedirect)) { + return getLiteralResultType(left, right, LINKEDHASHMAP_CLASSNODE); // GROOVY-7128, GROOVY-9844 + } } } // GRECLIPSE end @@ -4849,6 +4929,38 @@ && rightExpression instanceof ListExpression && isEmptyCollection(rightExpressio return null; } + // GRECLIPSE add + /** + * For "{@code List x = [...]}" or "{@code Set y = [...]}", etc. + * the literal may be composed of sub-types of {@code Type}. In these cases, + * {@code ArrayList} is an appropriate result type for the expression. + */ + private static ClassNode getLiteralResultType(final ClassNode targetType, final ClassNode sourceType, final ClassNode baseType) { + ClassNode resultType = sourceType.equals(baseType) ? sourceType + : GenericsUtils.parameterizeType(sourceType, baseType.getPlainNodeReference()); + + if (targetType.getGenericsTypes() != null + && !GenericsUtils.buildWildcardType(targetType).isCompatibleWith(resultType)) { + BiPredicate isEqualOrSuper = (target, source) -> { + if (target.isCompatibleWith(source.getType())) { + return true; + } + if (!target.isPlaceholder() && !target.isWildcard()) { + return GenericsUtils.buildWildcardType(getCombinedBoundType(target)).isCompatibleWith(source.getType()); + } + return false; + }; + + GenericsType[] lgt = targetType.getGenericsTypes(), rgt = resultType.getGenericsTypes(); + if (IntStream.range(0, lgt.length).allMatch(i -> isEqualOrSuper.test(lgt[i], rgt[i]))) { + resultType = GenericsUtils.parameterizeType(targetType, baseType.getPlainNodeReference()); + } + } + + return resultType; + } + // GRECLIPSE end + private ClassNode getMathResultType(int op, ClassNode leftRedirect, ClassNode rightRedirect, String operationName) { if (isNumberType(leftRedirect) && isNumberType(rightRedirect)) { if (isOperationInGroup(op)) { @@ -5761,6 +5873,7 @@ private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode me * method target of "m", {@code T} could be resolved. */ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final MethodNode inferredMethod) { + /* GRECLIPSE edit for (int i = 0, n = actuals.length; i < n; i += 1) { // check for method call with known target Expression a = argumentList.getExpression(i); @@ -5778,6 +5891,30 @@ 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; + 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)]; + + ClassNode at = actuals[i], pt = p.getOriginType(); + if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType(); + + if (a instanceof ListExpression) { + actuals[i] = getLiteralResultType(pt, at, ArrayList_TYPE); + } else if (a instanceof MapExpression) { + actuals[i] = getLiteralResultType(pt, at, LINKEDHASHMAP_CLASSNODE); + } + + // check for method call with known target + if (!(a instanceof MethodCallExpression)) continue; + if (((MethodCallExpression) a).isUsingGenerics()) continue; + MethodNode aNode = a.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET); + if (aNode == null || aNode.getGenericsTypes() == null) continue; + + // and unknown generics + if (!GenericsUtils.hasUnresolvedGenerics(at)) continue; + // GRECLIPSE end // try to resolve placeholder(s) in argument type using parameter type 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 8b9e6142e0..8d558a7ef8 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 @@ -132,7 +132,9 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiPredicate; import java.util.function.Supplier; +import java.util.stream.IntStream; import static org.apache.groovy.util.BeanUtils.capitalize; import static org.apache.groovy.util.BeanUtils.decapitalize; @@ -324,6 +326,10 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { protected static final ClassNode NAMED_PARAMS_CLASSNODE = ClassHelper.make(NamedParams.class); protected static final ClassNode MAP_ENTRY_TYPE = ClassHelper.make(Map.Entry.class); protected static final ClassNode ENUMERATION_TYPE = ClassHelper.make(Enumeration.class); + // GRECLIPSE add + private static final ClassNode SET_TYPE = ClassHelper.makeWithoutCaching(Set.class); + private static final ClassNode LinkedHashSet_TYPE = ClassHelper.makeWithoutCaching(LinkedHashSet.class); + // GRECLIPSE end public static final Statement GENERATED_EMPTY_STATEMENT = EmptyStatement.INSTANCE; @@ -1294,6 +1300,7 @@ private void addListAssignmentConstructorErrors(final ClassNode leftRedirect, fi // if left type is not a list but right type is a list, then we're in the case of a groovy // constructor type : Dimension d = [100,200] // In that case, more checks can be performed + /* GRECLIPSE edit if (rightExpression instanceof ListExpression && !implementsInterfaceOrIsSubclassOf(LIST_TYPE, leftRedirect)) { ArgumentListExpression argList = args(((ListExpression) rightExpression).getExpressions()); ClassNode[] args = getArgumentTypes(argList); @@ -1308,10 +1315,27 @@ && implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, LIST_TYPE) addAssignmentError(leftExpressionType, inferredRightExpressionType, assignmentExpression); } } + */ + if (!implementsInterfaceOrIsSubclassOf(LIST_TYPE, leftRedirect) + && (!leftRedirect.isAbstract() || leftRedirect.isArray()) && !leftRedirect.equals(OBJECT_TYPE) + && !ArrayList_TYPE.isDerivedFrom(leftRedirect) && !LinkedHashSet_TYPE.isDerivedFrom(leftRedirect)) { + ClassNode[] types = getArgumentTypes(args(((ListExpression) rightExpression).getExpressions())); + MethodNode methodNode = checkGroovyStyleConstructor(leftRedirect, types, assignmentExpression); + if (methodNode != null) { + rightExpression.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, methodNode); + } + } else if (implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, LIST_TYPE) + && !implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, leftRedirect) + && !isWildcardLeftHandSide(leftRedirect)) { + if (!extension.handleIncompatibleAssignment(leftExpressionType, inferredRightExpressionType, assignmentExpression)) { + addAssignmentError(leftExpressionType, inferredRightExpressionType, assignmentExpression); + } + } + // GRECLIPSE end } private void addMapAssignmentConstructorErrors(final ClassNode leftRedirect, final Expression leftExpression, final Expression rightExpression) { - if (!(rightExpression instanceof MapExpression) || (leftExpression instanceof VariableExpression && ((VariableExpression) leftExpression).isDynamicTyped()) + if (/* GRECLIPSE edit !(rightExpression instanceof MapExpression) || */(leftExpression instanceof VariableExpression && ((VariableExpression) leftExpression).isDynamicTyped()) || leftRedirect.equals(OBJECT_TYPE) || implementsInterfaceOrIsSubclassOf(leftRedirect, MAP_TYPE)) { return; } @@ -1350,7 +1374,21 @@ private boolean hasGStringStringError(final ClassNode leftExpressionType, final return false; } - protected void typeCheckAssignment(final BinaryExpression assignmentExpression, final Expression leftExpression, final ClassNode leftExpressionType, final Expression rightExpression, final ClassNode inferredRightExpressionTypeOrig) { + // GRECLIPSE add + private static boolean isConstructorAbbreviation(final ClassNode leftType, final Expression rightExpression) { + if (rightExpression instanceof ListExpression) { + return !(ArrayList_TYPE.isDerivedFrom(leftType) || ArrayList_TYPE.implementsInterface(leftType) + || LinkedHashSet_TYPE.isDerivedFrom(leftType) || LinkedHashSet_TYPE.implementsInterface(leftType)); + } + if (rightExpression instanceof MapExpression) { + return !(LINKEDHASHMAP_CLASSNODE.isDerivedFrom(leftType) || LINKEDHASHMAP_CLASSNODE.implementsInterface(leftType)); + } + return false; + } + // GRECLIPSE end + + protected void typeCheckAssignment(final BinaryExpression assignmentExpression, final Expression leftExpression, final ClassNode leftExpressionType, final Expression rightExpression, final ClassNode rightExpressionType) { + /* GRECLIPSE edit ClassNode inferredRightExpressionType = inferredRightExpressionTypeOrig; if (!typeCheckMultipleAssignmentAndContinue(leftExpression, rightExpression)) return; @@ -1379,6 +1417,38 @@ protected void typeCheckAssignment(final BinaryExpression assignmentExpression, if (hasGStringStringError(leftExpressionType, wrappedRHS, rightExpression)) return; checkTypeGenerics(leftExpressionType, wrappedRHS, rightExpression); } + */ + if (!typeCheckMultipleAssignmentAndContinue(leftExpression, rightExpression)) return; + + // TODO: need errors for write-only too! + if (addedReadOnlyPropertyError(leftExpression)) return; + + ClassNode rTypeInferred, rTypeWrapped; // check for instanceof and spreading + if (rightExpression instanceof VariableExpression && hasInferredReturnType(rightExpression) && assignmentExpression.getOperation().getType() == ASSIGN) { + rTypeInferred = getInferredReturnType(rightExpression); + } else { + rTypeInferred = rightExpressionType; + } + rTypeWrapped = adjustTypeForSpreading(rTypeInferred, leftExpression); + + if (!checkCompatibleAssignmentTypes(leftExpressionType, rTypeWrapped, rightExpression)) { + if (!extension.handleIncompatibleAssignment(leftExpressionType, rTypeInferred, assignmentExpression)) { + addAssignmentError(leftExpressionType, rTypeInferred, rightExpression); + } + } else { + ClassNode lTypeRedirect = leftExpressionType.redirect(); + addPrecisionErrors(lTypeRedirect, leftExpressionType, rTypeInferred, rightExpression); + if (rightExpression instanceof ListExpression) { + addListAssignmentConstructorErrors(lTypeRedirect, leftExpressionType, rTypeInferred, rightExpression, assignmentExpression); + } else if (rightExpression instanceof MapExpression) { + addMapAssignmentConstructorErrors(lTypeRedirect, leftExpression, rightExpression); + } + if (!hasGStringStringError(leftExpressionType, rTypeWrapped, rightExpression) + && !isConstructorAbbreviation(leftExpressionType, rightExpression)) { + checkTypeGenerics(leftExpressionType, rTypeWrapped, rightExpression); + } + } + // GRECLIPSE end } protected void checkGroovyConstructorMap(final Expression receiver, final ClassNode receiverType, final MapExpression mapExpression) { @@ -2766,7 +2836,7 @@ public void visitStaticMethodCallExpression(final StaticMethodCallExpression cal mn = findMethod(currentReceiver.getType(), name, args); if (!mn.isEmpty()) { if (mn.size() == 1) { - // GRECLIPSE add -- GROOVY-8961, GROOVY-9734, GROOVY-9915 + // GRECLIPSE add -- GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915 resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0)); // GRECLIPSE end typeCheckMethodsWithGenericsOrFail(currentReceiver.getType(), args, mn.get(0), call); @@ -3652,7 +3722,7 @@ public void visitMethodCallExpression(final MethodCallExpression call) { returnType = typeCheckingContext.getEnclosingClassNode(); } } - // GROOVY-8961, GROOVY-9734 + // GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, et al. resolvePlaceholdersFromImplicitTypeHints(args, argumentList, directMethodCallCandidate); if (typeCheckMethodsWithGenericsOrFail(chosenReceiver.getType(), args, directMethodCallCandidate, call)) { returnType = adjustWithTraits(directMethodCallCandidate, chosenReceiver.getType(), args, returnType); @@ -4591,7 +4661,7 @@ protected ClassNode getResultType(ClassNode left, final int op, final ClassNode return initialType; } } - + /* GRECLIPSE edit if (isOrImplements(rightRedirect, Collection_TYPE)) { if (leftRedirect.isArray()) { return leftRedirect; @@ -4601,6 +4671,28 @@ rightExpression instanceof ListExpression && isEmptyCollection(rightExpression)) return left; } } + */ + if (!leftRedirect.equals(OBJECT_TYPE)) { + if (rightExpression instanceof ListExpression) { + if (LIST_TYPE.equals(leftRedirect) + || ITERABLE_TYPE.equals(leftRedirect) + || Collection_TYPE.equals(leftRedirect) + || ArrayList_TYPE.isDerivedFrom(leftRedirect)) { // GROOVY-6912 + return getLiteralResultType(left, right, ArrayList_TYPE); // GROOVY-7128 + } + if (SET_TYPE.equals(leftRedirect) + || LinkedHashSet_TYPE.isDerivedFrom(leftRedirect)) { // GROOVY-6912 + return getLiteralResultType(left, right, LinkedHashSet_TYPE); // GROOVY-7128 + } + } + if (rightExpression instanceof MapExpression) { + if (MAP_TYPE.equals(leftRedirect) + || LINKEDHASHMAP_CLASSNODE.isDerivedFrom(leftRedirect)) { + return getLiteralResultType(left, right, LINKEDHASHMAP_CLASSNODE); // GROOVY-7128, GROOVY-9844 + } + } + } + // GRECLIPSE end return right; } @@ -4649,6 +4741,38 @@ rightExpression instanceof ListExpression && isEmptyCollection(rightExpression)) return null; } + // GRECLIPSE add + /** + * For "{@code List x = [...]}" or "{@code Set y = [...]}", etc. + * the literal may be composed of sub-types of {@code Type}. In these cases, + * {@code ArrayList} is an appropriate result type for the expression. + */ + private static ClassNode getLiteralResultType(final ClassNode targetType, final ClassNode sourceType, final ClassNode baseType) { + ClassNode resultType = sourceType.equals(baseType) ? sourceType + : GenericsUtils.parameterizeType(sourceType, baseType.getPlainNodeReference()); + + if (targetType.getGenericsTypes() != null + && !GenericsUtils.buildWildcardType(targetType).isCompatibleWith(resultType)) { + BiPredicate isEqualOrSuper = (target, source) -> { + if (target.isCompatibleWith(source.getType())) { + return true; + } + if (!target.isPlaceholder() && !target.isWildcard()) { + return GenericsUtils.buildWildcardType(getCombinedBoundType(target)).isCompatibleWith(source.getType()); + } + return false; + }; + + GenericsType[] lgt = targetType.getGenericsTypes(), rgt = resultType.getGenericsTypes(); + if (IntStream.range(0, lgt.length).allMatch(i -> isEqualOrSuper.test(lgt[i], rgt[i]))) { + resultType = GenericsUtils.parameterizeType(targetType, baseType.getPlainNodeReference()); + } + } + + return resultType; + } + // GRECLIPSE end + private ClassNode getMathResultType(final int op, final ClassNode leftRedirect, final ClassNode rightRedirect, final String operationName) { if (isNumberType(leftRedirect) && isNumberType(rightRedirect)) { if (isOperationInGroup(op)) { @@ -5534,6 +5658,7 @@ private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode me * method target of "m", {@code T} could be resolved. */ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final MethodNode inferredMethod) { + /* GRECLIPSE edit for (int i = 0, n = actuals.length; i < n; i += 1) { // check for method call with known target Expression a = argumentList.getExpression(i); @@ -5551,6 +5676,30 @@ 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; + 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)]; + + ClassNode at = actuals[i], pt = p.getOriginType(); + if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType(); + + if (a instanceof ListExpression) { + actuals[i] = getLiteralResultType(pt, at, ArrayList_TYPE); + } else if (a instanceof MapExpression) { + actuals[i] = getLiteralResultType(pt, at, LINKEDHASHMAP_CLASSNODE); + } + + // check for method call with known target + if (!(a instanceof MethodCallExpression)) continue; + if (((MethodCallExpression) a).isUsingGenerics()) continue; + MethodNode aNode = a.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); + if (aNode == null || aNode.getGenericsTypes() == null) continue; + + // and unknown generics + if (!GenericsUtils.hasUnresolvedGenerics(at)) continue; + // GRECLIPSE end // try to resolve placeholder(s) in argument type using parameter type 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 a3975bee54..bf7d5b60b2 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 @@ -132,7 +132,9 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiPredicate; import java.util.function.Supplier; +import java.util.stream.IntStream; import static org.apache.groovy.util.BeanUtils.capitalize; import static org.apache.groovy.util.BeanUtils.decapitalize; @@ -324,6 +326,10 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { protected static final ClassNode NAMED_PARAMS_CLASSNODE = ClassHelper.make(NamedParams.class); protected static final ClassNode MAP_ENTRY_TYPE = ClassHelper.make(Map.Entry.class); protected static final ClassNode ENUMERATION_TYPE = ClassHelper.make(Enumeration.class); + // GRECLIPSE add + private static final ClassNode SET_TYPE = ClassHelper.makeWithoutCaching(Set.class); + private static final ClassNode LinkedHashSet_TYPE = ClassHelper.makeWithoutCaching(LinkedHashSet.class); + // GRECLIPSE end public static final Statement GENERATED_EMPTY_STATEMENT = EmptyStatement.INSTANCE; @@ -1289,6 +1295,7 @@ private void addListAssignmentConstructorErrors(final ClassNode leftRedirect, fi // if left type is not a list but right type is a list, then we're in the case of a groovy // constructor type : Dimension d = [100,200] // In that case, more checks can be performed + /* GRECLIPSE edit if (rightExpression instanceof ListExpression && !implementsInterfaceOrIsSubclassOf(LIST_TYPE, leftRedirect)) { ArgumentListExpression argList = args(((ListExpression) rightExpression).getExpressions()); ClassNode[] args = getArgumentTypes(argList); @@ -1303,10 +1310,27 @@ && implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, LIST_TYPE) addAssignmentError(leftExpressionType, inferredRightExpressionType, assignmentExpression); } } + */ + if (!implementsInterfaceOrIsSubclassOf(LIST_TYPE, leftRedirect) + && (!leftRedirect.isAbstract() || leftRedirect.isArray()) && !leftRedirect.equals(OBJECT_TYPE) + && !ArrayList_TYPE.isDerivedFrom(leftRedirect) && !LinkedHashSet_TYPE.isDerivedFrom(leftRedirect)) { + ClassNode[] types = getArgumentTypes(args(((ListExpression) rightExpression).getExpressions())); + MethodNode methodNode = checkGroovyStyleConstructor(leftRedirect, types, assignmentExpression); + if (methodNode != null) { + rightExpression.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, methodNode); + } + } else if (implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, LIST_TYPE) + && !implementsInterfaceOrIsSubclassOf(inferredRightExpressionType, leftRedirect) + && !isWildcardLeftHandSide(leftRedirect)) { + if (!extension.handleIncompatibleAssignment(leftExpressionType, inferredRightExpressionType, assignmentExpression)) { + addAssignmentError(leftExpressionType, inferredRightExpressionType, assignmentExpression); + } + } + // GRECLIPSE end } private void addMapAssignmentConstructorErrors(final ClassNode leftRedirect, final Expression leftExpression, final Expression rightExpression) { - if (!(rightExpression instanceof MapExpression) || (leftExpression instanceof VariableExpression && ((VariableExpression) leftExpression).isDynamicTyped()) + if (/* GRECLIPSE edit !(rightExpression instanceof MapExpression) || */(leftExpression instanceof VariableExpression && ((VariableExpression) leftExpression).isDynamicTyped()) || leftRedirect.equals(OBJECT_TYPE) || implementsInterfaceOrIsSubclassOf(leftRedirect, MAP_TYPE)) { return; } @@ -1345,7 +1369,21 @@ private boolean hasGStringStringError(final ClassNode leftExpressionType, final return false; } - protected void typeCheckAssignment(final BinaryExpression assignmentExpression, final Expression leftExpression, final ClassNode leftExpressionType, final Expression rightExpression, final ClassNode inferredRightExpressionTypeOrig) { + // GRECLIPSE add + private static boolean isConstructorAbbreviation(final ClassNode leftType, final Expression rightExpression) { + if (rightExpression instanceof ListExpression) { + return !(ArrayList_TYPE.isDerivedFrom(leftType) || ArrayList_TYPE.implementsInterface(leftType) + || LinkedHashSet_TYPE.isDerivedFrom(leftType) || LinkedHashSet_TYPE.implementsInterface(leftType)); + } + if (rightExpression instanceof MapExpression) { + return !(LINKEDHASHMAP_CLASSNODE.isDerivedFrom(leftType) || LINKEDHASHMAP_CLASSNODE.implementsInterface(leftType)); + } + return false; + } + // GRECLIPSE end + + protected void typeCheckAssignment(final BinaryExpression assignmentExpression, final Expression leftExpression, final ClassNode leftExpressionType, final Expression rightExpression, final ClassNode rightExpressionType) { + /* GRECLIPSE edit ClassNode inferredRightExpressionType = inferredRightExpressionTypeOrig; if (!typeCheckMultipleAssignmentAndContinue(leftExpression, rightExpression)) return; @@ -1371,6 +1409,38 @@ protected void typeCheckAssignment(final BinaryExpression assignmentExpression, if (hasGStringStringError(leftExpressionType, wrappedRHS, rightExpression)) return; checkTypeGenerics(leftExpressionType, wrappedRHS, rightExpression); } + */ + if (!typeCheckMultipleAssignmentAndContinue(leftExpression, rightExpression)) return; + + // TODO: need errors for write-only too! + if (addedReadOnlyPropertyError(leftExpression)) return; + + ClassNode rTypeInferred, rTypeWrapped; // check for instanceof and spreading + if (rightExpression instanceof VariableExpression && hasInferredReturnType(rightExpression) && assignmentExpression.getOperation().getType() == ASSIGN) { + rTypeInferred = getInferredReturnType(rightExpression); + } else { + rTypeInferred = rightExpressionType; + } + rTypeWrapped = adjustTypeForSpreading(rTypeInferred, leftExpression); + + if (!checkCompatibleAssignmentTypes(leftExpressionType, rTypeWrapped, rightExpression)) { + if (!extension.handleIncompatibleAssignment(leftExpressionType, rTypeInferred, assignmentExpression)) { + addAssignmentError(leftExpressionType, rTypeInferred, rightExpression); + } + } else { + ClassNode lTypeRedirect = leftExpressionType.redirect(); + addPrecisionErrors(lTypeRedirect, leftExpressionType, rTypeInferred, rightExpression); + if (rightExpression instanceof ListExpression) { + addListAssignmentConstructorErrors(lTypeRedirect, leftExpressionType, rTypeInferred, rightExpression, assignmentExpression); + } else if (rightExpression instanceof MapExpression) { + addMapAssignmentConstructorErrors(lTypeRedirect, leftExpression, rightExpression); + } + if (!hasGStringStringError(leftExpressionType, rTypeWrapped, rightExpression) + && !isConstructorAbbreviation(leftExpressionType, rightExpression)) { + checkTypeGenerics(leftExpressionType, rTypeWrapped, rightExpression); + } + } + // GRECLIPSE end } protected void checkGroovyConstructorMap(final Expression receiver, final ClassNode receiverType, final MapExpression mapExpression) { @@ -2751,7 +2821,7 @@ public void visitStaticMethodCallExpression(final StaticMethodCallExpression cal mn = findMethod(currentReceiver.getType(), name, args); if (!mn.isEmpty()) { if (mn.size() == 1) { - // GRECLIPSE add -- GROOVY-8961, GROOVY-9734, GROOVY-9915 + // GRECLIPSE add -- GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915 resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0)); // GRECLIPSE end typeCheckMethodsWithGenericsOrFail(currentReceiver.getType(), args, mn.get(0), call); @@ -3632,7 +3702,7 @@ && implementsInterfaceOrIsSubclassOf(receiverType, node.getDeclaringClass()))) { returnType = typeCheckingContext.getEnclosingClassNode(); } } - // GROOVY-8961, GROOVY-9734 + // GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, et al. resolvePlaceholdersFromImplicitTypeHints(args, argumentList, directMethodCallCandidate); if (typeCheckMethodsWithGenericsOrFail(chosenReceiver.getType(), args, directMethodCallCandidate, call)) { returnType = adjustWithTraits(directMethodCallCandidate, chosenReceiver.getType(), args, returnType); @@ -4557,7 +4627,7 @@ protected ClassNode getResultType(ClassNode left, final int op, final ClassNode return initialType; } } - + /* GRECLIPSE edit if (isOrImplements(rightRedirect, Collection_TYPE)) { if (leftRedirect.isArray()) { return leftRedirect; @@ -4567,6 +4637,28 @@ rightExpression instanceof ListExpression && isEmptyCollection(rightExpression)) return left; } } + */ + if (!leftRedirect.equals(OBJECT_TYPE)) { + if (rightExpression instanceof ListExpression) { + if (LIST_TYPE.equals(leftRedirect) + || ITERABLE_TYPE.equals(leftRedirect) + || Collection_TYPE.equals(leftRedirect) + || ArrayList_TYPE.isDerivedFrom(leftRedirect)) { // GROOVY-6912 + return getLiteralResultType(left, right, ArrayList_TYPE); // GROOVY-7128 + } + if (SET_TYPE.equals(leftRedirect) + || LinkedHashSet_TYPE.isDerivedFrom(leftRedirect)) { // GROOVY-6912 + return getLiteralResultType(left, right, LinkedHashSet_TYPE); // GROOVY-7128 + } + } + if (rightExpression instanceof MapExpression) { + if (MAP_TYPE.equals(leftRedirect) + || LINKEDHASHMAP_CLASSNODE.isDerivedFrom(leftRedirect)) { + return getLiteralResultType(left, right, LINKEDHASHMAP_CLASSNODE); // GROOVY-7128, GROOVY-9844 + } + } + } + // GRECLIPSE end return right; } @@ -4615,6 +4707,36 @@ rightExpression instanceof ListExpression && isEmptyCollection(rightExpression)) return null; } + /** + * For "{@code List x = [...]}" or "{@code Set y = [...]}", etc. + * the literal may be composed of sub-types of {@code Type}. In these cases, + * {@code ArrayList} is an appropriate result type for the expression. + */ + private static ClassNode getLiteralResultType(final ClassNode targetType, final ClassNode sourceType, final ClassNode baseType) { + ClassNode resultType = sourceType.equals(baseType) ? sourceType + : GenericsUtils.parameterizeType(sourceType, baseType.getPlainNodeReference()); + + if (targetType.getGenericsTypes() != null + && !GenericsUtils.buildWildcardType(targetType).isCompatibleWith(resultType)) { + BiPredicate isEqualOrSuper = (target, source) -> { + if (target.isCompatibleWith(source.getType())) { + return true; + } + if (!target.isPlaceholder() && !target.isWildcard()) { + return GenericsUtils.buildWildcardType(getCombinedBoundType(target)).isCompatibleWith(source.getType()); + } + return false; + }; + + GenericsType[] lgt = targetType.getGenericsTypes(), rgt = resultType.getGenericsTypes(); + if (IntStream.range(0, lgt.length).allMatch(i -> isEqualOrSuper.test(lgt[i], rgt[i]))) { + resultType = GenericsUtils.parameterizeType(targetType, baseType.getPlainNodeReference()); + } + } + + return resultType; + } + private ClassNode getMathResultType(final int op, final ClassNode leftRedirect, final ClassNode rightRedirect, final String operationName) { if (isNumberType(leftRedirect) && isNumberType(rightRedirect)) { if (isOperationInGroup(op)) { @@ -5492,6 +5614,7 @@ private static void resolvePlaceholdersFromExplicitTypeHints(final MethodNode me * method target of "m", {@code T} could be resolved. */ private static void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final MethodNode inferredMethod) { + /* GRECLIPSE edit for (int i = 0, n = actuals.length; i < n; i += 1) { // check for method call with known target Expression a = argumentList.getExpression(i); @@ -5509,6 +5632,30 @@ 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; + 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)]; + + ClassNode at = actuals[i], pt = p.getOriginType(); + if (i >= (np - 1) && pt.isArray() && !at.isArray()) pt = pt.getComponentType(); + + if (a instanceof ListExpression) { + actuals[i] = getLiteralResultType(pt, at, ArrayList_TYPE); + } else if (a instanceof MapExpression) { + actuals[i] = getLiteralResultType(pt, at, LINKEDHASHMAP_CLASSNODE); + } + + // check for method call with known target + if (!(a instanceof MethodCallExpression)) continue; + if (((MethodCallExpression) a).isUsingGenerics()) continue; + MethodNode aNode = a.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); + if (aNode == null || aNode.getGenericsTypes() == null) continue; + + // and unknown generics + if (!GenericsUtils.hasUnresolvedGenerics(at)) continue; + // GRECLIPSE end // try to resolve placeholder(s) in argument type using parameter type diff --git a/ide-test/org.codehaus.groovy.eclipse.quickfix.test/src/org/codehaus/groovy/eclipse/quickfix/test/QuickFixTestSuite.groovy b/ide-test/org.codehaus.groovy.eclipse.quickfix.test/src/org/codehaus/groovy/eclipse/quickfix/test/QuickFixTestSuite.groovy index d027fe9f77..93ad6ca27a 100644 --- a/ide-test/org.codehaus.groovy.eclipse.quickfix.test/src/org/codehaus/groovy/eclipse/quickfix/test/QuickFixTestSuite.groovy +++ b/ide-test/org.codehaus.groovy.eclipse.quickfix.test/src/org/codehaus/groovy/eclipse/quickfix/test/QuickFixTestSuite.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2009-2020 the original author or authors. + * Copyright 2009-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.codehaus.groovy.eclipse.quickfix.test import static org.eclipse.jdt.internal.core.util.Util.getProblemArgumentsFromMarker +import groovy.transform.AutoFinal import groovy.transform.CompileStatic import org.codehaus.groovy.eclipse.quickfix.GroovyQuickFixProcessor @@ -33,7 +34,7 @@ import org.eclipse.jdt.ui.text.java.IInvocationContext import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal import org.eclipse.jdt.ui.text.java.IProblemLocation -@CompileStatic +@AutoFinal @CompileStatic abstract class QuickFixTestSuite extends GroovyEclipseTestSuite { @Override @@ -47,9 +48,9 @@ abstract class QuickFixTestSuite extends GroovyEclipseTestSuite { } protected IJavaCompletionProposal[] getGroovyQuickFixes(CompilationUnit unit) throws CoreException { - IMarker[] markers = getJavaProblemMarkers(unit.resource) + def markers = getJavaProblemMarkers(unit.resource) - IProblemLocation[] locations = markers.collect { IMarker marker -> + def locations = markers.collect { marker -> int offset = marker.getAttribute(IMarker.CHAR_START, -1) int length = marker.getAttribute(IMarker.CHAR_END, -1) - offset def isError = marker.getAttribute(IMarker.SEVERITY) == IMarker.SEVERITY_ERROR @@ -59,7 +60,7 @@ abstract class QuickFixTestSuite extends GroovyEclipseTestSuite { IInvocationContext context = new AssistContext(unit, 0, 0) // TODO: pass offset and length? - return new GroovyQuickFixProcessor().getCorrections(context, locations) + return new GroovyQuickFixProcessor().getCorrections(context, locations as IProblemLocation[]) } protected IMarker[] getJavaProblemMarkers(IResource resource) {