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 + ''' + } }