From 93eea14edfa61fb108ca141bb1c2e4bc7fa890a8 Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Thu, 16 Dec 2021 17:22:21 -0600 Subject: [PATCH] GROOVY-10419 #1320 --- .../core/tests/xform/TypeCheckedTests.java | 27 +++++++++++++ .../stc/StaticTypeCheckingVisitor.java | 12 ++++++ .../stc/StaticTypeCheckingVisitor.java | 39 ++++++++++--------- 3 files changed, 60 insertions(+), 18 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 ab30b61421..d0208aa2ac 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 @@ -4937,4 +4937,31 @@ public void testTypeChecked10414() { runConformTest(sources, "barbaz"); } + + @Test + public void testTypeChecked10419() { + assumeTrue(isParrotParser()); + + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.ToString\n" + + "class C {\n" + + " def p\n" + + " void setP(p) {\n" + + " this.p = p\n" + + " }\n" + + "}\n" + + "@groovy.transform.TypeChecked\n" + + "void test(C c) {\n" + + " c.p ?= 'x'\n" + + "}\n" + + "def c = new C()\n" + + "test(c)\n" + + "print c\n", + }; + //@formatter:on + + runConformTest(sources, "C(x)"); + } } 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 f72256b3d3..0cdfc8927e 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 @@ -1042,9 +1042,21 @@ private boolean ensureValidSetter(final Expression expression, final Expression }; // GRECLIPSE end // for compound assignment "x op= y" find type as if it was "x = (x op y)" + /* GRECLIPSE edit -- GROOVY-10419 Expression newRightExpression = isCompoundAssignment(expression) ? binX(leftExpression, getOpWithoutEqual(expression), rightExpression) : rightExpression; + */ + Expression newRightExpression = rightExpression; + if (isCompoundAssignment(expression)) { + Token op = ((BinaryExpression) expression).getOperation(); + if (op.getType() == ELVIS_EQUAL) { // GROOVY-10419: "x ?= y" + newRightExpression = new ElvisOperatorExpression(leftExpression, rightExpression); + } else { + newRightExpression = binX(leftExpression, getOpWithoutEqual(expression), rightExpression); + } + } + // GRECLIPSE end MethodCallExpression call = callX(ve, setterInfo.name, newRightExpression); call.setImplicitThis(false); visitMethodCallExpression(call); 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 c21bef6366..83077324ab 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 @@ -213,6 +213,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; import static org.codehaus.groovy.ast.tools.GeneralUtils.castX; import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.elvisX; import static org.codehaus.groovy.ast.tools.GeneralUtils.getGetterName; import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName; import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements; @@ -1014,9 +1015,16 @@ private boolean ensureValidSetter(final Expression expression, final Expression // because we need to check if a setter uses @DelegatesTo VariableExpression receiver = varX("%", setterInfo.receiverType); // for "x op= y" expression, find type as if it was "x = x op y" - Expression newRightExpression = isCompoundAssignment(expression) - ? binX(leftExpression, getOpWithoutEqual(expression), rightExpression) - : rightExpression; + Expression valueExpression = rightExpression; + if (isCompoundAssignment(expression)) { + Token op = ((BinaryExpression) expression).getOperation(); + if (op.getType() == ELVIS_EQUAL) { // GROOVY-10419: "x ?= y" + valueExpression = elvisX(leftExpression, rightExpression); + } else { + op = Token.newSymbol(TokenUtil.removeAssignment(op.getType()), op.getStartLine(), op.getStartColumn()); + valueExpression = binX(leftExpression, op, rightExpression); + } + } Function setterCall = right -> { MethodCallExpression call = callX(receiver, setterInfo.name, right); @@ -1033,14 +1041,14 @@ private boolean ensureValidSetter(final Expression expression, final Expression return type; }; - MethodNode methodTarget = setterCall.apply(newRightExpression); + MethodNode methodTarget = setterCall.apply(valueExpression); if (methodTarget == null && !isCompoundAssignment(expression)) { // if no direct match, try implicit conversion for (MethodNode setter : setterInfo.setters) { ClassNode lType = setterType.apply(setter); - ClassNode rType = getDeclaredOrInferredType(newRightExpression); - if (checkCompatibleAssignmentTypes(lType, rType, newRightExpression, false)) { - methodTarget = setterCall.apply(castX(lType, newRightExpression)); + ClassNode rType = getDeclaredOrInferredType(valueExpression); + if (checkCompatibleAssignmentTypes(lType, rType, valueExpression, false)) { + methodTarget = setterCall.apply(castX(lType, valueExpression)); if (methodTarget != null) { break; } @@ -1060,7 +1068,7 @@ private boolean ensureValidSetter(final Expression expression, final Expression return false; } else { ClassNode firstSetterType = setterType.apply(setterInfo.setters.get(0)); - addAssignmentError(firstSetterType, getType(newRightExpression), expression); + addAssignmentError(firstSetterType, getType(valueExpression), expression); return true; } } @@ -1070,16 +1078,11 @@ private static boolean isClosureWithType(final ClassNode type) { } private static boolean isCompoundAssignment(final Expression exp) { - if (!(exp instanceof BinaryExpression)) return false; - int type = ((BinaryExpression) exp).getOperation().getType(); - return isAssignment(type) && type != ASSIGN; - } - - private static Token getOpWithoutEqual(final Expression exp) { - if (!(exp instanceof BinaryExpression)) return null; // should never happen - Token op = ((BinaryExpression) exp).getOperation(); - int typeWithoutEqual = TokenUtil.removeAssignment(op.getType()); - return new Token(typeWithoutEqual, op.getText() /* will do */, op.getStartLine(), op.getStartColumn()); + if (exp instanceof BinaryExpression) { + Token op = ((BinaryExpression) exp).getOperation(); + return isAssignment(op.getType()) && op.getType() != EQUAL; + } + return false; } protected ClassNode getOriginalDeclarationType(final Expression lhs) {