You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2026/04/24 00:01:34 UTC
(groovy) branch GROOVY_5_0_X updated: GROOVY-11956: STC: read-only property error for map constructor elements
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch GROOVY_5_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_5_0_X by this push:
new 8b8687aedf GROOVY-11956: STC: read-only property error for map constructor elements
8b8687aedf is described below
commit 8b8687aedffb3a6c6c79ebd510fc79262724390b
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Thu Apr 23 18:00:46 2026 -0500
GROOVY-11956: STC: read-only property error for map constructor elements
5_0_X backport
---
.../asm/sc/StaticPropertyAccessHelper.java | 42 +++++++++++++---------
.../transformers/ConstructorCallTransformer.java | 16 +++++----
.../transform/stc/StaticTypeCheckingVisitor.java | 10 +++---
.../transform/stc/ConstructorsSTCTest.groovy | 36 ++++++++++++-------
.../groovy/groovy/transform/stc/Pojo11956.java | 25 +++++++++++++
.../asm/sc/StaticCompileConstructorsTest.groovy | 10 ++++++
6 files changed, 100 insertions(+), 39 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticPropertyAccessHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticPropertyAccessHelper.java
index 6cffa34b55..841c6a8294 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticPropertyAccessHelper.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticPropertyAccessHelper.java
@@ -20,6 +20,7 @@ package org.codehaus.groovy.classgen.asm.sc;
import org.codehaus.groovy.ast.GroovyCodeVisitor;
import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ExpressionTransformer;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
@@ -45,27 +46,35 @@ public abstract class StaticPropertyAccessHelper {
final boolean spreadSafe,
final boolean returnValue,
final Expression sourceExpression) {
+ MethodCallExpression call;
+ Expression result;
if (returnValue) {
- TemporaryVariableExpression tmp = new TemporaryVariableExpression(valueExpression);
- PoppingMethodCallExpression call = new PoppingMethodCallExpression(receiver, setterMethod, tmp);
- call.setSafe(safe);
- call.setSpreadSafe(spreadSafe);
- call.setImplicitThis(implicitThis);
- call.setSourcePosition(sourceExpression);
- PoppingListOfExpressionsExpression list = new PoppingListOfExpressionsExpression(tmp, call);
- list.setSourcePosition(sourceExpression);
- return list;
+ var temp = new TemporaryVariableExpression(valueExpression);
+ temp.setSourcePosition(valueExpression);
+
+ var pmce = new PoppingMethodCallExpression(receiver, setterMethod, temp);
+ result = new PoppingListOfExpressionsExpression(temp, pmce);
+ call = pmce;
} else {
- MethodCallExpression call = new MethodCallExpression(receiver, setterMethod.getName(), valueExpression);
- call.setSafe(safe);
- call.setSpreadSafe(spreadSafe);
- call.setImplicitThis(implicitThis);
+ var args = new ArgumentListExpression(valueExpression);
+ args.setSourcePosition(valueExpression);
+
+ call = new MethodCallExpression(receiver, setterMethod.getName(), args);
call.setMethodTarget(setterMethod);
- call.setSourcePosition(sourceExpression);
- return call;
+ result = call;
}
+ call.setSafe(safe);
+ call.setSpreadSafe(spreadSafe);
+ call.setImplicitThis(implicitThis);
+ call.setSourcePosition(sourceExpression);
+ call.setLastLineNumber(valueExpression.getLastLineNumber());
+ call.setLastColumnNumber(valueExpression.getLastColumnNumber());
+
+ return result;
}
+ //--------------------------------------------------------------------------
+
private static class PoppingListOfExpressionsExpression extends ListOfExpressionsExpression {
private final TemporaryVariableExpression tmp;
@@ -97,7 +106,8 @@ public abstract class StaticPropertyAccessHelper {
private final TemporaryVariableExpression tmp;
public PoppingMethodCallExpression(final Expression receiver, final MethodNode setterMethod, final TemporaryVariableExpression tmp) {
- super(receiver, setterMethod.getName(), tmp);
+ super(receiver, setterMethod.getName(), new ArgumentListExpression(tmp));
+ getArguments().setSourcePosition(tmp);
setMethodTarget(setterMethod);
this.tmp = tmp;
}
diff --git a/src/main/java/org/codehaus/groovy/transform/sc/transformers/ConstructorCallTransformer.java b/src/main/java/org/codehaus/groovy/transform/sc/transformers/ConstructorCallTransformer.java
index 6998195031..dcb4a455dc 100644
--- a/src/main/java/org/codehaus/groovy/transform/sc/transformers/ConstructorCallTransformer.java
+++ b/src/main/java/org/codehaus/groovy/transform/sc/transformers/ConstructorCallTransformer.java
@@ -106,8 +106,8 @@ public class ConstructorCallTransformer {
this.originalCall = originalCall;
this.copyNodeMetaData(originalCall);
this.setSourcePosition(originalCall);
- Expression originalArgs = originalCall.getArguments();
- this.innerClassCall = (2 == ((TupleExpression) originalArgs).getExpressions().size());
+ var originalArgs = (TupleExpression) originalCall.getArguments();
+ this.innerClassCall = (2 == originalArgs.getExpressions().size());
}
@Override
@@ -145,20 +145,22 @@ public class ConstructorCallTransformer {
String signature = "()V";
if (innerClassCall && ctorType.getOuterClass() != null) {
acg.visitVariableExpression(varX("this")); // GROOVY-11122
- Parameter[] params = {new Parameter(ctorType.getOuterClass(), "$p$")};
+ Parameter[] params = {new Parameter(ctorType.getOuterClass(),"$p$")};
signature = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, params);
}
mv.visitMethodInsn(INVOKESPECIAL, ctorTypeName, "<init>", signature, false);
mv.visitVarInsn(ASTORE, tmpObj);
+ Expression objExpression = bytecodeX(ctorType, v -> v.visitVarInsn(ALOAD, tmpObj));
+
// process property initializers
for (MapEntryExpression entryExpression : map.getMapEntryExpressions()) {
Expression keyExpression = entryExpression.getKeyExpression();
+ Expression varExpression = propX(objExpression, keyExpression);
Expression valExpression = entryExpression.getValueExpression();
- Expression varExpression = propX(
- bytecodeX(ctorType, v -> v.visitVarInsn(ALOAD, tmpObj)),
- keyExpression
- );
+
+ objExpression.setSourcePosition(keyExpression); // GROOVY-11956
+
varExpression.putNodeMetaData(DIRECT_METHOD_CALL_TARGET,
keyExpression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET));
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 9cc82464c9..8750e5d249 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -1533,11 +1533,13 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0
String propName = keyExpression.getText();
Set<ClassNode> propertyTypes = new HashSet<>();
Expression valueExpression = entryExpression.getValueExpression();
+ PropertyExpression dummyExpression = propX(varX("", receiverType), propName);
+ dummyExpression.setSourcePosition(keyExpression);
typeCheckingContext.pushEnclosingBinaryExpression(assignX(keyExpression, valueExpression, entryExpression));
- if (!existsProperty(propX(varX("_", receiverType), propName), false, new PropertyLookup(receiverType, propertyTypes::add))) {
+ if (!existsProperty(dummyExpression, /*read*/false, new PropertyLookup(receiverType, propertyTypes::add))) {
typeCheckingContext.popEnclosingBinaryExpression();
- addStaticTypeError("No such property: " + propName + " for class: " + prettyPrintTypeName(receiverType), receiver);
- } else {
+ addStaticTypeError("No such property: " + propName + " for class: " + prettyPrintTypeName(receiverType), keyExpression);
+ } else if (receiverType.isRecord() || !addedReadOnlyPropertyError(dummyExpression)) { // GROOVY-11956
SetterInfo setterInfo = removeSetterInfo(keyExpression);
if (setterInfo != null) { // GROOVY-11451: select setter
ensureValidSetter(entryExpression, keyExpression, valueExpression, setterInfo);
@@ -1545,7 +1547,7 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0
ClassNode valueType = getType(valueExpression);
BinaryExpression kv = typeCheckingContext.popEnclosingBinaryExpression();
if (propertyTypes.stream().noneMatch(targetType -> checkCompatibleAssignmentTypes(targetType, getResultType(targetType, ASSIGN, valueType, kv), valueExpression))) {
- ClassNode targetType = propertyTypes.size() == 1 ? propertyTypes.iterator().next() : new UnionTypeClassNode(propertyTypes.toArray(ClassNode.EMPTY_ARRAY));
+ ClassNode targetType = propertyTypes.isEmpty() ? VOID_TYPE : (propertyTypes.size() == 1 ? propertyTypes.iterator().next() : new UnionTypeClassNode(propertyTypes.toArray(ClassNode[]::new)));
if (!extension.handleIncompatibleAssignment(targetType, valueType, entryExpression)) {
addAssignmentError(targetType, valueType, entryExpression);
}
diff --git a/src/test/groovy/groovy/transform/stc/ConstructorsSTCTest.groovy b/src/test/groovy/groovy/transform/stc/ConstructorsSTCTest.groovy
index 80c0bc5b53..55e1ee31f7 100644
--- a/src/test/groovy/groovy/transform/stc/ConstructorsSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/ConstructorsSTCTest.groovy
@@ -432,7 +432,7 @@ class ConstructorsSTCTest extends StaticTypeCheckingTestCase {
'''
}
- void testMapConstructorShouldFail() {
+ void testMapConstructorIncompatibleType() {
shouldFailWithMessages '''
class Foo {
ByteArrayOutputStream out
@@ -459,19 +459,12 @@ class ConstructorsSTCTest extends StaticTypeCheckingTestCase {
void testConstructorsWithVarargsAndArrayParameters() {
assertScript '''
class MultipleConstructors {
-
- public MultipleConstructors(String s, short[] arr) {}
- public MultipleConstructors(String s, int... arr) {}
- public MultipleConstructors(short[] arr) {}
- }
-
- class Clz {
- void run() {
- new MultipleConstructors('d',1)
- }
+ MultipleConstructors(String s, short[] arr) {}
+ MultipleConstructors(String s, int... arr) {}
+ MultipleConstructors( short[] arr) {}
}
- new Clz().run()
+ new MultipleConstructors('d', 1)
'''
}
@@ -485,6 +478,7 @@ class ConstructorsSTCTest extends StaticTypeCheckingTestCase {
}
String toString() { var }
}
+
def b = new MyBean(foo: 'Test')
assert b.toString() == 'Test'
'''
@@ -523,6 +517,24 @@ class ConstructorsSTCTest extends StaticTypeCheckingTestCase {
'''
}
+ // GROOVY-11956
+ void testMapStyleConstructorWithReadOnlyProperty() {
+ shouldFailWithMessages ''' // line 1
+ class C {
+ String getFoo() {}
+ }
+ new C(foo: 'bar')
+ ''',
+ 'Cannot set read-only property: foo','@ line 5, column 19'
+ }
+
+ // GROOVY-11956
+ void testMapStyleConstructorWithInaccessibleSetter() {
+ assertScript """import ${Pojo11956.canonicalName}
+ new Pojo11956(foo: 'bar')
+ """
+ }
+
// GROOVY-10787
void testMapStyleConstructorWithParameterizedProperty() {
assertScript '''
diff --git a/src/test/groovy/groovy/transform/stc/Pojo11956.java b/src/test/groovy/groovy/transform/stc/Pojo11956.java
new file mode 100644
index 0000000000..1d6de18b5d
--- /dev/null
+++ b/src/test/groovy/groovy/transform/stc/Pojo11956.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.transform.stc;
+
+public class Pojo11956 {
+ private String foo = "bar";
+ public String getFoo() { return this.foo; }
+ protected void setFoo(String foo) { this.foo = foo; }
+}
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileConstructorsTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileConstructorsTest.groovy
index c9d8050965..297975f7c0 100644
--- a/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileConstructorsTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileConstructorsTest.groovy
@@ -19,6 +19,7 @@
package org.codehaus.groovy.classgen.asm.sc
import groovy.transform.stc.ConstructorsSTCTest
+import groovy.transform.stc.Pojo11956
/**
* Unit tests for static compilation: constructors.
@@ -107,4 +108,13 @@ class StaticCompileConstructorsTest extends ConstructorsSTCTest implements Stati
assert asserts.contains('INVOKEVIRTUAL C.setP (Ljava/util/regex/Pattern;)V')
assert !asserts.contains('INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.setGroovyObjectProperty')
}
+
+ // GROOVY-11956
+ @Override
+ void testMapStyleConstructorWithInaccessibleSetter() {
+ shouldFailWithMessages """import ${Pojo11956.canonicalName}
+ new Pojo11956(foo: 'bar')
+ """,
+ "Cannot access method: setFoo(java.lang.String) of class: ${Pojo11956.canonicalName} @ line 2, column 27"
+ }
}