This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push: new 93f0d096c5 GROOVY-11403: STC: map property precedence: entry before super field 93f0d096c5 is described below commit 93f0d096c5c23256add266ff8f0f7d6a953a65ee Author: Eric Milles <eric.mil...@thomsonreuters.com> AuthorDate: Tue Jun 18 14:58:33 2024 -0500 GROOVY-11403: STC: map property precedence: entry before super field --- .../classgen/asm/sc/StaticTypesCallSiteWriter.java | 10 ++-- .../transform/stc/StaticTypeCheckingVisitor.java | 2 +- src/test/groovy/bugs/Groovy9007.groovy | 12 +++-- .../stc/FieldsAndPropertiesSTCTest.groovy | 58 ++++++++++++++++++++-- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java index 874723724b..51b7f7d358 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java @@ -53,7 +53,6 @@ import org.objectweb.asm.MethodVisitor; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.BiPredicate; import java.util.function.Predicate; @@ -190,7 +189,7 @@ public class StaticTypesCallSiteWriter extends CallSiteWriter { // GROOVY-5001, GROOVY-5491, GROOVY-5517, GROOVY-6144, GROOVY-8788: for map types, // replace "map.foo" with "map.get('foo')" -- if no public field "foo" is declared if (!isStaticProperty && isOrImplements(receiverType, MAP_TYPE) - && Optional.ofNullable(getField(receiverType, propertyName)).filter(FieldNode::isPublic).isEmpty()) { + && getField(receiverType, propertyName, FieldNode::isPublic) == null) { writeMapDotProperty(receiver, propertyName, safe); return; } @@ -416,8 +415,9 @@ public class StaticTypesCallSiteWriter extends CallSiteWriter { // GROOVY-5001, GROOVY-5491, GROOVY-5517, GROOVY-6144, GROOVY-8788: for map types, // replace "map.foo" with "map.get('foo')" -- if no public field "foo" is declared if (isMapDotProperty - && (!isThisExpression(receiver) || controller.isInGeneratedFunction()) // GROOVY-8978, GROOVY-11402 - && Optional.ofNullable(getField(receiverType, propertyName)).filter(FieldNode::isPublic).isEmpty()) { + && getField(receiverType, propertyName, FieldNode::isPublic) == null + // GROOVY-11367, GROOVY-11402, GROOVY-11403: "this.name" outside closure includes non-public fields of lexical scope + && (!isThisExpression(receiver) || controller.isInGeneratedFunction() || receiverType.getDeclaredField(propertyName) == null)) { writeMapDotProperty(receiver, propertyName, safe); return; } @@ -800,7 +800,7 @@ public class StaticTypesCallSiteWriter extends CallSiteWriter { // GROOVY-6954, GROOVY-11376: for map types, replace "map.foo = ..." // with "map.put('foo', ...)" if no public field exists if (!isClassReceiver[0] && isOrImplements(receiverType, MAP_TYPE) - && Optional.ofNullable(getField(receiverType, name)).filter(FieldNode::isPublic).isEmpty()) { + && getField(receiverType, name, FieldNode::isPublic) == null) { MethodVisitor mv = controller.getMethodVisitor(); // store value in temporary variable 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 f3195562bb..1990049d73 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -1658,7 +1658,7 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0 if (field != null && publicOnly && !field.isPublic() // GROOVY-11367, GROOVY-11402, GROOVY-11403: - && !(isThisExpression(objectExpression) && typeCheckingContext.getEnclosingClosure() == null)) { + && (!receiverType.equals(current) || !isThisExpression(objectExpression) || typeCheckingContext.getEnclosingClosure() != null)) { field = null; } // skip property/accessor checks for "field", "this.field", "this.with { field }", etc. within the declaring class of the field diff --git a/src/test/groovy/bugs/Groovy9007.groovy b/src/test/groovy/bugs/Groovy9007.groovy index 20b5e169e4..e92a7a3563 100644 --- a/src/test/groovy/bugs/Groovy9007.groovy +++ b/src/test/groovy/bugs/Groovy9007.groovy @@ -70,19 +70,21 @@ final class Groovy9007 { @CompileStatic class TaskConfig extends LazyMap implements Cloneable { - TaskConfig() { - } @Override TaskConfig clone() { def copy = (TaskConfig) super.clone() - copy.target = new HashMap<>(this.target) + copy.@target = new HashMap<>(this) return copy } } - def t1 = new TaskConfig() - t1.a = 'b' + def t1 = new TaskConfig().tap{ a = 'b' } def t2 = t1.clone() + assert t2 !== t1 + assert t2.a == 'b' + + t1.remove('a') // ensure independence + assert !t1.containsKey('a') assert t2.a == 'b' ''' } diff --git a/src/test/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy b/src/test/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy index bcf07630f1..a0ec654c90 100644 --- a/src/test/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy +++ b/src/test/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy @@ -979,6 +979,54 @@ class FieldsAndPropertiesSTCTest extends StaticTypeCheckingTestCase { // GROOVY-11403 void testMapPropertyAccess12() { + for (mode in ['','static']) { + assertScript """ + class M { + @Delegate final Map m = [:].withDefault{ 'entry' } + def $mode v = 'field' + public $mode w = 'field' + protected $mode x = 'field' + @PackageScope $mode y = 'field' + private $mode z = 'field' + void test1() { + assert this.m === this.@m + assert this.v == 'field' + assert this.w == 'field' + assert this.x == 'field' + assert this.y == 'field' + assert this.z == 'field'; + {-> + assert this.v == 'field' + assert this.w == 'field' + assert this.x == 'entry' + assert this.y == 'entry' + assert this.z == 'entry' + }() + } + } + class MM extends M { + void test2() { + assert this.v == 'field' + assert this.w == 'field' + assert this.x == 'entry' + assert this.y == 'entry' + assert this.z == 'entry'; + {-> + assert this.v == 'field' + assert this.w == 'field' + assert this.x == 'entry' + assert this.y == 'entry' + assert this.z == 'entry' + }() + } + } + new MM().with { test1(); test2() } + """ + } + } + + // GROOVY-11403 + void testMapPropertyAccess13() { for (mode in ['','static']) { assertScript """ class M { @@ -1109,7 +1157,7 @@ class FieldsAndPropertiesSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-11368 - void testMapPropertyAccess13() { + void testMapPropertyAccess14() { String type = ''' class M implements Map<String,String> { @Delegate Map<String,String> impl = [:] @@ -1134,7 +1182,7 @@ class FieldsAndPropertiesSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-11367 - void testMapPropertyAccess14() { + void testMapPropertyAccess15() { String map = "def map = new ${MapType.name}()" assertScript map + ''' map.foo = 11 // public setter @@ -1155,7 +1203,7 @@ class FieldsAndPropertiesSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-11387 - void testMapPropertyAccess15() { + void testMapPropertyAccess16() { assertScript ''' def map = new HashMap<String,String>() @ASTTest(phase=INSTRUCTION_SELECTION, value={ @@ -1177,7 +1225,7 @@ class FieldsAndPropertiesSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-11387 - void testMapPropertyAccess16() { + void testMapPropertyAccess17() { assertScript ''' class HttpHeaders extends HashMap<String,List<String>> { protected static final String ACCEPT = 'Accept' @@ -1191,7 +1239,7 @@ class FieldsAndPropertiesSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-11401 - void testMapPropertyAccess17() { + void testMapPropertyAccess18() { assertScript ''' class C { private Object obj = 'field'