This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY-8283
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY-8283 by this push:
new e15ee85d30 GROOVY-8283: STC: field hides setter of super class
e15ee85d30 is described below
commit e15ee85d309ef603a160c056d368acf6a8028446
Author: Eric Milles <[email protected]>
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
+ '''
+ }
}