From 9615572cea77e371852201799ca961ae36fa41be Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Mon, 26 Apr 2021 14:58:59 -0500 Subject: [PATCH] GROOVY-10053 --- .../core/tests/xform/TypeCheckedTests.java | 25 ++++++++++ .../stc/StaticTypeCheckingSupport.java | 9 +++- .../stc/StaticTypeCheckingVisitor.java | 47 +++++++++---------- .../stc/StaticTypeCheckingSupport.java | 9 +++- .../stc/StaticTypeCheckingVisitor.java | 27 ++++++++++- .../stc/StaticTypeCheckingSupport.java | 7 +++ .../stc/StaticTypeCheckingVisitor.java | 27 ++++++++++- 7 files changed, 121 insertions(+), 30 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 134f7d49d6..90bcb6c043 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 @@ -2310,4 +2310,29 @@ public void testTypeChecked10052() { runConformTest(sources, "xy"); } + + @Test + public void testTypeChecked10053() { + if (Float.parseFloat(System.getProperty("java.specification.version")) > 8) + vmArguments = new String[] {"--add-opens", "java.base/java.util.stream=ALL-UNNAMED"}; + + //@formatter:off + String[] sources = { + "Main.groovy", + "Set f() {\n" + + " Collections.singleton(42)\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "def Set g(Class t) {\n" + + " Set result = new HashSet<>()\n" + + " f().stream().filter{n -> t.isInstance(n)}\n" + + " .map{n -> t.cast(n)}.forEach{n -> result.add(n)}\n" + + " return result\n" + + "}\n" + + "print g(Integer)\n", + }; + //@formatter:on + + runConformTest(sources, "[42]"); + } } 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 3481b3bc5b..98c2ae9d47 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 @@ -1790,12 +1790,17 @@ static void applyGenericsConnections( // GROOVY-6787: Don't override the original if the replacement doesn't respect the bounds otherwise // the original bounds are lost, which can result in accepting an incompatible type as an argument. ClassNode replacementType = extractType(newValue); + /* GRECLIPSE edit -- GROOVY-9998, GROOVY-10053 if (oldValue.isCompatibleWith(replacementType)) { - // GRECLIPSE add -- GROOVY-9998 + */ + ClassNode suitabilityType = !replacementType.isGenericsPlaceHolder() + ? replacementType : Optional.ofNullable(replacementType.getGenericsTypes()) + .map(gts -> extractType(gts[0])).orElse(replacementType.redirect()); + if (oldValue.isCompatibleWith(suitabilityType)) { if (newValue.isWildcard() && newValue.getLowerBound() == null && newValue.getUpperBounds() == null) { entry.setValue(new GenericsType(replacementType)); } else - // GRECLIPSE end + // GRECLIPSE end entry.setValue(newValue); if (newValue.isPlaceholder()) { checkForMorePlaceholders = checkForMorePlaceholders || !equalIncludingGenerics(oldValue, newValue); 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 a39be143e9..3679daa8dc 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 @@ -2832,7 +2832,7 @@ public void visitClosureExpression(final ClosureExpression expression) { } } - // GRECLIPSE add -- GROOVY-9751 + // GRECLIPSE add -- GROOVY-9751, GROOVY-9803, GROOVY-10053 @Override public void visitMethodPointerExpression(final MethodPointerExpression expression) { super.visitMethodPointerExpression(expression); @@ -2840,21 +2840,13 @@ public void visitMethodPointerExpression(final MethodPointerExpression expressio if (nameExpr instanceof ConstantExpression && getType(nameExpr).equals(STRING_TYPE)) { String nameText = nameExpr.getText(); - - if ("new".equals(nameText)) { - ClassNode receiverType = getType(expression.getExpression()); - if (isClassClassNodeWrappingConcreteType(receiverType)) { - storeType(expression, wrapClosureType(receiverType)); - } - return; - } - List> receivers = new ArrayList<>(); addReceivers(receivers, makeOwnerList(expression.getExpression()), false); + ClassNode receiverType = null; List candidates = EMPTY_METHODNODE_LIST; for (Receiver currentReceiver : receivers) { - ClassNode receiverType = wrapTypeIfNecessary(currentReceiver.getType()); + receiverType = wrapTypeIfNecessary(currentReceiver.getType()); candidates = findMethodsWithGenerated(receiverType, nameText); if (isBeingCompiled(receiverType)) candidates.addAll(GROOVY_OBJECT_TYPE.getMethods(nameText)); @@ -2867,11 +2859,16 @@ && getType(nameExpr).equals(STRING_TYPE)) { } if (!candidates.isEmpty()) { - candidates.stream().map(MethodNode::getReturnType) - .reduce(WideningCategories::lowestUpperBound) - .filter(returnType -> !returnType.equals(OBJECT_TYPE)) - .ifPresent(returnType -> storeType(expression, wrapClosureType(returnType))); - expression.putNodeMetaData(MethodNode.class, candidates); // GROOVY-9803 + Map gts = GenericsUtils.extractPlaceholders(receiverType); + candidates.stream().map(candidate -> applyGenericsContext(gts, candidate.getReturnType())) + .reduce(WideningCategories::lowestUpperBound).ifPresent(returnType -> { + storeType(expression, wrapClosureType(returnType)); + }); + expression.putNodeMetaData(MethodNode.class, candidates); + } else { + ClassNode type = wrapTypeIfNecessary(getType(expression.getExpression())); + if (isClassClassNodeWrappingConcreteType(type)) type = type.getGenericsTypes()[0].getType(); + addStaticTypeError("Cannot find matching method " + type.getText() + "#" + nameText + ". Please check if the declared type is correct and if the method exists.", nameExpr); } } } @@ -6010,7 +6007,7 @@ private static void extractGenericsConnectionsForSuperClassAndInterfaces(final M } } - // GRECLIPSE add -- GROOVY-9803 + // GRECLIPSE add -- GROOVY-9803, GROOVY-10053 private static MethodNode chooseMethod(final MethodPointerExpression source, final Supplier samSignature) { List options = source.getNodeMetaData(MethodNode.class); if (options == null || options.isEmpty()) { @@ -6020,15 +6017,17 @@ private static MethodNode chooseMethod(final MethodPointerExpression source, fin ClassNode[] paramTypes = samSignature.get(); return options.stream().filter((MethodNode option) -> { ClassNode[] types = collateMethodReferenceParameterTypes(source, option); - if (types.length == paramTypes.length) { - for (int i = 0, n = types.length; i < n; i += 1) { - if (!types[i].isGenericsPlaceHolder() && !isAssignableTo(types[i], paramTypes[i])) { - return false; - } + final int n = types.length; + if (n != paramTypes.length) { + return false; + } + for (int i = 0; i < n; i += 1) { + // param type represents incoming argument type + if (!isAssignableTo(paramTypes[i], types[i])) { + return false; } - return true; } - return false; + return true; }).findFirst().orElse(null); // TODO: order matches by param distance } 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 073afe506d..d128dabd5b 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 @@ -1704,12 +1704,17 @@ static void applyGenericsConnections(final Map c // GROOVY-6787: Don't override the original if the replacement doesn't respect the bounds otherwise // the original bounds are lost, which can result in accepting an incompatible type as an argument. ClassNode replacementType = extractType(newValue); + /* GRECLIPSE edit -- GROOVY-9998, GROOVY-10053 if (oldValue.isCompatibleWith(replacementType)) { - // GRECLIPSE add -- GROOVY-9998 + */ + ClassNode suitabilityType = !replacementType.isGenericsPlaceHolder() + ? replacementType : Optional.ofNullable(replacementType.getGenericsTypes()) + .map(gts -> extractType(gts[0])).orElse(replacementType.redirect()); + if (oldValue.isCompatibleWith(suitabilityType)) { if (newValue.isWildcard() && newValue.getLowerBound() == null && newValue.getUpperBounds() == null) { entry.setValue(new GenericsType(replacementType)); } else - // GRECLIPSE end + // GRECLIPSE end entry.setValue(newValue); if (!checkForMorePlaceholders && newValue.isPlaceholder()) { checkForMorePlaceholders = !equalIncludingGenerics(oldValue, newValue); 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 fa9b36d894..ff5829d6f1 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 @@ -2552,9 +2552,12 @@ && getType(nameExpr).equals(STRING_TYPE)) { List> receivers = new ArrayList<>(); addReceivers(receivers, makeOwnerList(expression.getExpression()), false); + // GRECLIPSE add -- GROOVY-10053 + ClassNode receiverType = null; + // GRECLIPSE end List candidates = EMPTY_METHODNODE_LIST; for (Receiver currentReceiver : receivers) { - ClassNode receiverType = wrapTypeIfNecessary(currentReceiver.getType()); + receiverType = wrapTypeIfNecessary(currentReceiver.getType()); candidates = findMethodsWithGenerated(receiverType, nameText); if (isBeingCompiled(receiverType)) candidates.addAll(GROOVY_OBJECT_TYPE.getMethods(nameText)); @@ -2574,10 +2577,18 @@ && getType(nameExpr).equals(STRING_TYPE)) { } if (!candidates.isEmpty()) { + /* GRECLIPSE edit -- GROOVY-10053 candidates.stream().map(MethodNode::getReturnType) .reduce(WideningCategories::lowestUpperBound) .filter(returnType -> !returnType.equals(OBJECT_TYPE)) .ifPresent(returnType -> storeType(expression, wrapClosureType(returnType))); + */ + Map gts = GenericsUtils.extractPlaceholders(receiverType); + candidates.stream().map(candidate -> applyGenericsContext(gts, candidate.getReturnType())) + .reduce(WideningCategories::lowestUpperBound).ifPresent(returnType -> { + storeType(expression, wrapClosureType(returnType)); + }); + // GRECLIPSE end expression.putNodeMetaData(MethodNode.class, candidates); } else if (!(expression instanceof MethodReferenceExpression)) { ClassNode type = wrapTypeIfNecessary(getType(expression.getExpression())); @@ -5715,6 +5726,7 @@ private static MethodNode chooseMethod(final MethodPointerExpression source, fin ClassNode[] paramTypes = samSignature.get(); return options.stream().filter((MethodNode option) -> { ClassNode[] types = collateMethodReferenceParameterTypes(source, option); + /* GRECLIPSE edit -- GROOVY-10053 if (types.length == paramTypes.length) { for (int i = 0, n = types.length; i < n; i += 1) { if (!types[i].isGenericsPlaceHolder() && !isAssignableTo(types[i], paramTypes[i])) { @@ -5724,6 +5736,19 @@ private static MethodNode chooseMethod(final MethodPointerExpression source, fin return true; } return false; + */ + final int n = types.length; + if (n != paramTypes.length) { + return false; + } + for (int i = 0; i < n; i += 1) { + // param type represents incoming argument type + if (!isAssignableTo(paramTypes[i], types[i])) { + return false; + } + } + return true; + // GRECLIPSE end }).findFirst().orElse(null); // TODO: order matches by param distance } 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 4bb5e64662..1da7fd343f 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 @@ -1646,7 +1646,14 @@ static void applyGenericsConnections(final Map c // GROOVY-6787: Don't override the original if the replacement doesn't respect the bounds otherwise // the original bounds are lost, which can result in accepting an incompatible type as an argument. ClassNode replacementType = extractType(newValue); + /* GRECLIPSE edit -- GROOVY-10053: accept type parameter from context as type argument for target if (oldValue.isCompatibleWith(replacementType)) { + */ + ClassNode suitabilityType = !replacementType.isGenericsPlaceHolder() + ? replacementType : Optional.ofNullable(replacementType.getGenericsTypes()) + .map(gts -> extractType(gts[0])).orElse(replacementType.redirect()); + if (oldValue.isCompatibleWith(suitabilityType)) { + // GRECLIPSE end if (newValue.isWildcard() && newValue.getLowerBound() == null && newValue.getUpperBounds() == null) { // GROOVY-9998: apply upper/lower bound for unknown entry.setValue(new GenericsType(replacementType)); 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 39dd91c219..6e3894f617 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 @@ -2468,9 +2468,12 @@ && getType(nameExpr).equals(STRING_TYPE)) { List> receivers = new ArrayList<>(); addReceivers(receivers, makeOwnerList(expression.getExpression()), false); + // GRECLIPSE add -- GROOVY-10053 + ClassNode receiverType = null; + // GRECLIPSE end List candidates = EMPTY_METHODNODE_LIST; for (Receiver currentReceiver : receivers) { - ClassNode receiverType = wrapTypeIfNecessary(currentReceiver.getType()); + receiverType = wrapTypeIfNecessary(currentReceiver.getType()); candidates = findMethodsWithGenerated(receiverType, nameText); if (isBeingCompiled(receiverType)) candidates.addAll(GROOVY_OBJECT_TYPE.getMethods(nameText)); @@ -2490,10 +2493,18 @@ && getType(nameExpr).equals(STRING_TYPE)) { } if (!candidates.isEmpty()) { + /* GRECLIPSE edit -- GROOVY-10053 candidates.stream().map(MethodNode::getReturnType) .reduce(WideningCategories::lowestUpperBound) .filter(returnType -> !returnType.equals(OBJECT_TYPE)) .ifPresent(returnType -> storeType(expression, wrapClosureType(returnType))); + */ + Map gts = GenericsUtils.extractPlaceholders(receiverType); + candidates.stream().map(candidate -> applyGenericsContext(gts, candidate.getReturnType())) + .reduce(WideningCategories::lowestUpperBound).ifPresent(returnType -> { + storeType(expression, wrapClosureType(returnType)); + }); + // GRECLIPSE end expression.putNodeMetaData(MethodNode.class, candidates); } else if (!(expression instanceof MethodReferenceExpression)) { ClassNode type = wrapTypeIfNecessary(getType(expression.getExpression())); @@ -5493,6 +5504,7 @@ private static MethodNode chooseMethod(final MethodPointerExpression source, fin ClassNode[] paramTypes = samSignature.get(); return options.stream().filter((MethodNode option) -> { ClassNode[] types = collateMethodReferenceParameterTypes(source, option); + /* GRECLIPSE edit -- GROOVY-10053 if (types.length == paramTypes.length) { for (int i = 0, n = types.length; i < n; i += 1) { if (!types[i].isGenericsPlaceHolder() && !isAssignableTo(types[i], paramTypes[i])) { @@ -5502,6 +5514,19 @@ private static MethodNode chooseMethod(final MethodPointerExpression source, fin return true; } return false; + */ + final int n = types.length; + if (n != paramTypes.length) { + return false; + } + for (int i = 0; i < n; i += 1) { + // param type represents incoming argument type + if (!isAssignableTo(paramTypes[i], types[i])) { + return false; + } + } + return true; + // GRECLIPSE end }).findFirst().orElse(null); // TODO: order matches by param distance }