From a05d0593a0e8fc67d8f43f4dd1a06f9ae0f34e83 Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Thu, 13 May 2021 15:24:33 -0500 Subject: [PATCH] GROOVY-10088 --- .../core/tests/xform/TypeCheckedTests.java | 28 +++++++++++++++++++ .../stc/StaticTypeCheckingVisitor.java | 19 +++++++++---- .../stc/StaticTypeCheckingVisitor.java | 16 ++++++++--- .../stc/StaticTypeCheckingVisitor.java | 18 +++++++++--- 4 files changed, 68 insertions(+), 13 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 fa816a820a..167fd75dc2 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 @@ -2655,4 +2655,32 @@ public void testTypeChecked10067() { runConformTest(sources); } + + @Test + public void testTypeChecked10088() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " new D().p = 'x'\n" + + "}\n", + + "Types.groovy", + "class C {\n" + + " void setP(T t) { }\n" + + "}\n" + + "class D extends C {\n" + + "}\n", + }; + //@formatter:on + + runNegativeTest(sources, + "----------\n" + + "1. ERROR in Main.groovy (at line 3)\n" + + "\tnew D().p = 'x'\n" + + "\t^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Groovy:[Static type checking] - Cannot assign value of type java.lang.String to variable of type java.lang.Number\n" + + "----------\n"); + } } 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 3fccf06863..33bf96f533 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 @@ -1120,8 +1120,16 @@ 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. + // GRECLIPSE add -- GROOVY-8409, GROOVY-10088, et al. ve.setType(setterInfo.receiverType); + Function firstParamType = (method) -> { + ClassNode type = method.getParameters()[0].getOriginType(); + if (!method.isStatic() && !(method instanceof ExtensionMethodNode) && GenericsUtils.hasUnresolvedGenerics(type)) { + Map spec = extractPlaceHolders(null, setterInfo.receiverType, method.getDeclaringClass()); + type = applyGenericsContext(spec, type); + } + return type; + }; // GRECLIPSE end // for compound assignment "x op= y" find type as if it was "x = (x op y)" final Expression newRightExpression = isCompoundAssignment(expression) @@ -1135,7 +1143,7 @@ private boolean ensureValidSetter(final Expression expression, final Expression // this may happen if there's a setter of type boolean/String/Class, and that we are using the property // notation AND that the RHS is not a boolean/String/Class for (MethodNode setter : setterInfo.setters) { - ClassNode type = getWrapper(setter.getParameters()[0].getOriginType()); + ClassNode type = getWrapper(firstParamType.apply(setter)); if (Boolean_TYPE.equals(type) || STRING_TYPE.equals(type) || CLASS_Type.equals(type)) { call = callX(ve, setterInfo.name, castX(type, newRightExpression)); call.setImplicitThis(false); @@ -1150,13 +1158,14 @@ private boolean ensureValidSetter(final Expression expression, final Expression if (directSetterCandidate != null) { for (MethodNode setter : setterInfo.setters) { if (setter == directSetterCandidate) { - leftExpression.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, directSetterCandidate); - storeType(leftExpression, getType(newRightExpression)); + leftExpression.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, setter); + leftExpression.removeNodeMetaData(StaticTypesMarker.INFERRED_TYPE); + storeType(leftExpression, firstParamType.apply(setter)); break; } } } else { - ClassNode firstSetterType = setterInfo.setters.iterator().next().getParameters()[0].getOriginType(); + ClassNode firstSetterType = firstParamType.apply(setterInfo.setters.get(0)); addAssignmentError(firstSetterType, getType(newRightExpression), expression); return true; } 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 88018b72f4..bdd22f1500 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 @@ -1000,8 +1000,16 @@ 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. + // GRECLIPSE add -- GROOVY-8409, GROOVY-10088, et al. ve.setType(setterInfo.receiverType); + Function firstParamType = (method) -> { + ClassNode type = method.getParameters()[0].getOriginType(); + if (!method.isStatic() && !(method instanceof ExtensionMethodNode) && GenericsUtils.hasUnresolvedGenerics(type)) { + Map spec = extractPlaceHolders(null, setterInfo.receiverType, method.getDeclaringClass()); + type = applyGenericsContext(spec, type); + } + return type; + }; // GRECLIPSE end // for compound assignment "x op= y" find type as if it was "x = (x op y)" Expression newRightExpression = isCompoundAssignment(expression) @@ -1015,7 +1023,7 @@ private boolean ensureValidSetter(final Expression expression, final Expression // this may happen if there's a setter of type boolean/String/Class, and that we are using the property // notation AND that the RHS is not a boolean/String/Class for (MethodNode setter : setterInfo.setters) { - ClassNode type = getWrapper(setter.getParameters()[0].getOriginType()); + ClassNode type = getWrapper(firstParamType.apply(setter)); if (Boolean_TYPE.equals(type) || STRING_TYPE.equals(type) || CLASS_Type.equals(type)) { call = callX(ve, setterInfo.name, castX(type, newRightExpression)); call.setImplicitThis(false); @@ -1032,13 +1040,13 @@ private boolean ensureValidSetter(final Expression expression, final Expression if (setter == directSetterCandidate) { leftExpression.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, directSetterCandidate); leftExpression.removeNodeMetaData(INFERRED_TYPE); // clear assumption - storeType(leftExpression, getType(newRightExpression)); + storeType(leftExpression, firstParamType.apply(setter)); break; } } return false; } else { - ClassNode firstSetterType = setterInfo.setters.get(0).getParameters()[0].getOriginType(); + ClassNode firstSetterType = firstParamType.apply(setterInfo.setters.get(0)); addAssignmentError(firstSetterType, getType(newRightExpression), expression); return true; } 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 5ed79c26ef..b57fdbd615 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 @@ -1003,12 +1003,22 @@ private boolean ensureValidSetter(final Expression expression, final Expression visitMethodCallExpression(call); return call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); }; + // GRECLIPSE add -- GROOVY-10088 + Function setterType = setter -> { + ClassNode type = setter.getParameters()[0].getOriginType(); + if (!setter.isStatic() && !(setter instanceof ExtensionMethodNode) && GenericsUtils.hasUnresolvedGenerics(type)) { + Map spec = extractPlaceHolders(null, setterInfo.receiverType, setter.getDeclaringClass()); + type = applyGenericsContext(spec, type); + } + return type; + }; + // GRECLIPSE end MethodNode methodTarget = setterCall.apply(newRightExpression); if (methodTarget == null && !isCompoundAssignment(expression)) { // if no direct match, try implicit conversion for (MethodNode setter : setterInfo.setters) { - ClassNode lType = setter.getParameters()[0].getOriginType(); + ClassNode lType = setterType.apply(setter); ClassNode rType = getDeclaredOrInferredType(newRightExpression); if (checkCompatibleAssignmentTypes(lType, rType, newRightExpression, false)) { methodTarget = setterCall.apply(castX(lType, newRightExpression)); @@ -1023,14 +1033,14 @@ private boolean ensureValidSetter(final Expression expression, final Expression for (MethodNode setter : setterInfo.setters) { if (setter == methodTarget) { leftExpression.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, methodTarget); - leftExpression.removeNodeMetaData(INFERRED_TYPE); // clear the assumption - storeType(leftExpression, methodTarget.getParameters()[0].getOriginType()); + leftExpression.removeNodeMetaData(INFERRED_TYPE); // clear assumption + storeType(leftExpression, setterType.apply(methodTarget)); break; } } return false; } else { - ClassNode firstSetterType = setterInfo.setters.get(0).getParameters()[0].getOriginType(); + ClassNode firstSetterType = setterType.apply(setterInfo.setters.get(0)); addAssignmentError(firstSetterType, getType(newRightExpression), expression); return true; }