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

commit 6f370933132cacfb1ac79672c26ead11ce3f219f
Author: Eric Milles <eric.mil...@thomsonreuters.com>
AuthorDate: Wed Jul 16 12:35:45 2025 -0500

    GROOVY-8283: STC: field hides setter of super class
---
 .../transform/stc/StaticTypeCheckingVisitor.java   |  15 +-
 src/test/groovy/bugs/Groovy8283.groovy             | 183 +++++++++++++++++++--
 2 files changed, 176 insertions(+), 22 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 b087852c08..61b4c7e6bd 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -1575,7 +1575,7 @@ out:    if ((samParameterTypes.length == 1 && 
isOrImplements(samParameterTypes[0
         
enclosingTypes.addAll(enclosingTypes.iterator().next().getOuterClasses());
 
         if (objectExpression instanceof ClassExpression) {
-            if ("this".equals(propertyName)) {
+            if (propertyName.equals("this")) {
                 // handle "Outer.this" for any level of nesting
                 ClassNode outer = 
getType(objectExpression).getGenericsTypes()[0].getType();
 
@@ -1590,7 +1590,7 @@ out:    if ((samParameterTypes.length == 1 && 
isOrImplements(samParameterTypes[0
                     storeType(pexp, outer);
                     return true;
                 }
-            } else if ("super".equals(propertyName)) {
+            } else if (propertyName.equals("super")) {
                 // GROOVY-8299: handle "Iface.super" for interface default 
methods
                 ClassNode enclosingType = 
typeCheckingContext.getEnclosingClassNode();
                 ClassNode accessingType = 
getType(objectExpression).getGenericsTypes()[0].getType();
@@ -1614,7 +1614,7 @@ out:    if ((samParameterTypes.length == 1 && 
isOrImplements(samParameterTypes[0
         for (Receiver<String> receiver : receivers) {
             ClassNode receiverType = receiver.getType();
 
-            if (receiverType.isArray() && "length".equals(propertyName)) {
+            if (receiverType.isArray() && propertyName.equals("length")) {
                 pexp.putNodeMetaData(READONLY_PROPERTY, Boolean.TRUE);
                 storeType(pexp, int_TYPE);
                 if (visitor != null) {
@@ -1694,7 +1694,7 @@ out:    if ((samParameterTypes.length == 1 && 
isOrImplements(samParameterTypes[0
                     getter = getGetterMethod(current, getterName, checkUp);
                     getter = allowStaticAccessToMember(getter, staticOnly);
                 }
-                if (getter != null && ((publicOnly && (!getter.isPublic() || 
"class".equals(propertyName) || "empty".equals(propertyName)))
+                if (getter != null && ((publicOnly && (!getter.isPublic() || 
propertyName.equals("class") || propertyName.equals("empty")))
                         // GROOVY-11319:
                         || 
!hasAccessToMember(typeCheckingContext.getEnclosingClassNode(), 
getter.getDeclaringClass(), getter.getModifiers()))) {
                     getter = null;
@@ -1718,7 +1718,7 @@ out:    if ((samParameterTypes.length == 1 && 
isOrImplements(samParameterTypes[0
                             return true;
                         }
                     } else {
-                        if (!setters.isEmpty()) {
+                        if (!setters.isEmpty() && (checkUp || 
setters.stream().map(MethodNode::getDeclaringClass).anyMatch(current::equals))) 
{
                             if (visitor != null) {
                                 for (MethodNode setter : setters) {
                                     // visiting setter will not infer the 
property type since return type is void, so visit a dummy field instead
@@ -1727,10 +1727,9 @@ out:    if ((samParameterTypes.length == 1 && 
isOrImplements(samParameterTypes[0
                                     visitor.visitField(virtual);
                                 }
                             }
-                            SetterInfo info = new SetterInfo(current, 
setterName, setters);
                             BinaryExpression enclosingBinaryExpression = 
typeCheckingContext.getEnclosingBinaryExpression();
                             if (enclosingBinaryExpression != null) {
-                                
putSetterInfo(enclosingBinaryExpression.getLeftExpression(), info);
+                                
putSetterInfo(enclosingBinaryExpression.getLeftExpression(), new 
SetterInfo(receiverType, setterName, setters));
                             }
                             String delegationData = receiver.getData();
                             if (delegationData != null) {
@@ -1738,7 +1737,7 @@ out:    if ((samParameterTypes.length == 1 && 
isOrImplements(samParameterTypes[0
                             }
                             pexp.removeNodeMetaData(READONLY_PROPERTY);
                             return true;
-                        } else if (getter != null && (field == null || 
field.isFinal())) {
+                        } else if (getter != null && (field == null || 
field.isFinal()) && setters.isEmpty()) {
                             pexp.putNodeMetaData(READONLY_PROPERTY, 
Boolean.TRUE); // GROOVY-9127
                         }
                     }
diff --git a/src/test/groovy/bugs/Groovy8283.groovy 
b/src/test/groovy/bugs/Groovy8283.groovy
index 2d7f8b3dbb..5afbe01d3f 100644
--- a/src/test/groovy/bugs/Groovy8283.groovy
+++ b/src/test/groovy/bugs/Groovy8283.groovy
@@ -16,9 +16,9 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package groovy.bugs
+package bugs
 
-import org.junit.Test
+import org.junit.jupiter.api.Test
 
 import static groovy.test.GroovyAssert.assertScript
 
@@ -60,7 +60,14 @@ final class Groovy8283 {
 
     @Test
     void testReadFieldPropertyShadowing2() {
-        def shell = new GroovyShell()
+        def shell = GroovyShell.withConfig {
+            ast(groovy.transform.TypeChecked)
+            imports {
+                normal 'groovy.transform.ASTTest'
+                staticStar 'org.codehaus.groovy.control.CompilePhase'
+                staticStar 
'org.codehaus.groovy.transform.stc.StaticTypesMarker'
+            }
+        }
         shell.parse '''package p
             class A {}
             class B {}
@@ -74,8 +81,8 @@ final class Groovy8283 {
         '''
         assertScript shell, '''import p.*
             class E extends D {
-                
@groovy.transform.ASTTest(phase=org.codehaus.groovy.control.CompilePhase.INSTRUCTION_SELECTION,
 value={
-                    def typeof = { label -> 
lookup(label)[0].getExpression().getNodeMetaData(org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_TYPE).toString(false)
 }
+                @ASTTest(phase=INSTRUCTION_SELECTION, value={
+                    def typeof = { label -> 
lookup(label)[0].getExpression().getNodeMetaData(INFERRED_TYPE).getName() }
 
                     assert typeof('implicit'   ) == 'p.B'
                     assert typeof('explicit'   ) == 'p.B'
@@ -86,7 +93,6 @@ final class Groovy8283 {
                     assert typeof('attribute2' ) == 'p.B'
                     assert typeof('methodCall2') == 'p.A'
                 })
-                @groovy.transform.TypeChecked
                 void test() {
                   implicit:
                     def a = foo
@@ -107,14 +113,11 @@ final class Groovy8283 {
                 }
             }
 
-            @groovy.transform.TypeChecked
-            void test() {
-                
@groovy.transform.ASTTest(phase=org.codehaus.groovy.control.CompilePhase.INSTRUCTION_SELECTION,
 value={
-                    def type = 
node.getNodeMetaData(org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_TYPE)
-                    assert type.toString(false) == 'p.A'
-                })
-                def a = new E().foo
-            }
+            @ASTTest(phase=INSTRUCTION_SELECTION, value={
+                def type = node.getNodeMetaData(INFERRED_TYPE)
+                assert type.getName() == 'p.A'
+            })
+            def a = new E().foo // not the field from this perspective
         '''
     }
 
@@ -231,4 +234,156 @@ final class Groovy8283 {
             assert e.fooB != null
         '''
     }
+
+    @Test
+    void testWriteFieldPropertyShadowing2() {
+        def shell = GroovyShell.withConfig {
+            ast(groovy.transform.TypeChecked)
+            imports {
+                normal 'groovy.transform.ASTTest'
+                staticStar 'org.codehaus.groovy.control.CompilePhase'
+                staticStar 
'org.codehaus.groovy.transform.stc.StaticTypesMarker'
+            }
+        }
+        shell.parse '''package p
+            class A {}
+            class B {}
+            class C {
+                boolean setter
+                protected A foo = new A()
+                A getFooA() { return this.@foo }
+                A setFoo(A a) { setter = true; this.@foo = a }
+            }
+            class D extends C {
+                protected B foo = new B() // hides A#foo; should hide A#setFoo 
in subclasses
+                B getFooB() { return this.@foo }
+            }
+        '''
+        assertScript shell, '''import p.*
+            class E extends D {
+                @ASTTest(phase=INSTRUCTION_SELECTION, value={
+                    def typeof = { label ->
+                        def expr = lookup(label)[0].getExpression()
+                        try { expr = expr.getLeftExpression() } catch (e) {}
+                        return expr.getNodeMetaData(INFERRED_TYPE).getName()
+                    }
+
+                    assert typeof('implicit'   ) == 'p.B'
+                    assert typeof('explicit'   ) == 'p.B'
+                    assert typeof('attribute'  ) == 'p.B'
+                    assert typeof('methodCall' ) == 'p.A'
+
+                    assert typeof('property'   ) == 'p.B'
+                    assert typeof('attribute2' ) == 'p.B'
+                    assert typeof('methodCall2') == 'p.A'
+                })
+                void test1() {
+                  implicit:
+                    foo = null
+                  explicit:
+                    this.foo = null
+                  attribute:
+                    this.@foo = null
+                  methodCall:
+                    this.setFoo(null)
+
+                    def that = new E()
+                  property:
+                    that.foo = null
+                  attribute2:
+                    that.@foo = null
+                  methodCall2:
+                    that.setFoo(null)
+                }
+            }
+
+            @ASTTest(phase=INSTRUCTION_SELECTION, value={
+                node = node.getRightExpression().getLeftExpression()
+                assert node.getNodeMetaData(INFERRED_TYPE).getName() == 'p.A'
+            })
+            def a = (new E().foo = null) // not the field from this perspective
+        '''
+    }
+
+    @Test
+    void testWriteFieldPropertyShadowing3() {
+        def shell = new GroovyShell()
+        shell.parse '''package p
+            class A {}
+            class B {}
+            class C {
+                boolean setter
+                protected A foo = new A()
+                A getFooA() { return this.@foo }
+                void setFoo(A a) { setter = true; this.@foo = a }
+            }
+            class D extends C {
+                protected B foo = new B() // hides A#foo; should hide A#setFoo 
in subclasses
+                B getFooB() { return this.@foo }
+            }
+        '''
+        assertScript shell, '''import p.*
+            class E extends D {
+                void test1() {
+                    foo = null
+                    assert !setter
+                    assert fooA != null
+                    assert fooB == null
+                }
+                void test2() {
+                    /* TODO
+                    this.foo = null
+                    assert !setter
+                    assert fooA != null
+                    assert fooB == null
+                    */
+                }
+                void test3() {
+                    this.@foo = null
+                    assert !setter
+                    assert fooA != null
+                    assert fooB == null
+                }
+                void test4() {
+                    this.setFoo(null)
+                    assert setter
+                    assert fooA == null
+                    assert fooB != null
+                }
+                void test5() {
+                    def that = new E()
+                    /* TODO
+                    that.foo = null
+                    assert !that.setter
+                    assert that.fooA != null
+                    assert that.fooB == null
+
+                    that = new E()
+                    */
+                    that.@foo = null
+                    assert !that.setter
+                    assert that.fooA != null
+                    assert that.fooB == null
+
+                    that = new E()
+                    that.setFoo(null)
+                    assert that.setter
+                    assert that.fooA == null
+                    assert that.fooB != null
+                }
+            }
+
+            new E().test1()
+            new E().test2()
+            new E().test3()
+            new E().test4()
+            new E().test5()
+
+            def e = new E()
+            e.foo = null // not the field from this perspective
+            assert e.setter
+            assert e.fooA == null
+            assert e.fooB != null
+        '''
+    }
 }

Reply via email to