From a05304a534446298a99906b13eb376834efd5ef6 Mon Sep 17 00:00:00 2001 From: Paul King Date: Tue, 4 Aug 2026 07:44:06 +1000 Subject: [PATCH] GROOVY-12228: STC: multiple assignment ignores the declared type of the target variables --- .../stc/StaticTypeCheckingVisitor.java | 11 ++++- .../transform/stc/STCAssignmentTest.groovy | 40 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 8261cfafc5e..3f16865efc8 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -1521,13 +1521,20 @@ private boolean typeCheckMultipleAssignmentPositional(final Expression leftExpre } for (int i = 0, n = tupleExpressions.size(); i < n; i += 1) { + Expression tupleExpression = tupleExpressions.get(i); ClassNode valueType = getType(valueExpressions.get(i)); - ClassNode targetType = getType(tupleExpressions.get(i)); + ClassNode targetType = getType(tupleExpression); if (!isAssignableTo(valueType, targetType)) { addStaticTypeError("Cannot assign value of type " + prettyPrintType(valueType) + " to variable of type " + prettyPrintType(targetType), rightExpression); return false; } - storeType(tupleExpressions.get(i), valueType); + // GROOVY-12228: check for implicit conversion like "String a = 123" or "Long b = 1"; + // as for single assignment, the target then holds its declared type, not the value type + ClassNode originType = getOriginalDeclarationType(tupleExpression); + if (!implementsInterfaceOrIsSubclassOf(wrapTypeIfNecessary(valueType), wrapTypeIfNecessary(originType))) { + valueType = originType; + } + storeType(tupleExpression, valueType); } return true; diff --git a/src/test/groovy/groovy/transform/stc/STCAssignmentTest.groovy b/src/test/groovy/groovy/transform/stc/STCAssignmentTest.groovy index f2708ed78e2..fd07f4c7e02 100644 --- a/src/test/groovy/groovy/transform/stc/STCAssignmentTest.groovy +++ b/src/test/groovy/groovy/transform/stc/STCAssignmentTest.groovy @@ -1467,6 +1467,46 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase { ''' } + // GROOVY-12228 + @Test + void testMultiAssignRetainsDeclaredType1() { + assertScript ''' + def m() { + String a, b + (a, b) = ["x${1}", "y${2}"] + assert a instanceof String + assert a.class === String && b.class === String + assert a == 'x1' && b == 'y2' + } + m() + ''' + } + + // GROOVY-12228 + @Test + void testMultiAssignRetainsDeclaredType2() { + assertScript ''' + def m() { + def (String a, String b) = ["x${1}", "y${2}"] + assert a.class === String && b.class === String + } + m() + ''' + } + + // GROOVY-12228 + @Test + void testMultiAssignRetainsDeclaredType3() { + assertScript ''' + def m() { + Long a, b + (a, b) = [1, 2] + assert a.class === Long && b.class === Long + } + m() + ''' + } + // GROOVY-10943 @Test void testMultiAssignUnderscorePlaceholder1() {