This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY-11663
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY-11663 by this push:
new 83358811c7 GROOVY-11663: support static trait field referenced in
static context
83358811c7 is described below
commit 83358811c7514a5195935b2e75a010aacf554986
Author: Eric Milles <[email protected]>
AuthorDate: Wed May 14 09:48:57 2025 -0500
GROOVY-11663: support static trait field referenced in static context
---
.../codehaus/groovy/control/StaticVerifier.java | 42 ++++----
.../traitx/TraitASTTransformationTest.groovy | 115 ++++++++++++++++-----
2 files changed, 113 insertions(+), 44 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/control/StaticVerifier.java
b/src/main/java/org/codehaus/groovy/control/StaticVerifier.java
index 3d7d27758a..83b1646363 100644
--- a/src/main/java/org/codehaus/groovy/control/StaticVerifier.java
+++ b/src/main/java/org/codehaus/groovy/control/StaticVerifier.java
@@ -18,6 +18,7 @@
*/
package org.codehaus.groovy.control;
+import org.apache.groovy.ast.tools.ClassNodeUtils;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.DynamicVariable;
@@ -27,9 +28,8 @@ import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.transform.trait.Traits;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -88,34 +88,40 @@ public class StaticVerifier extends ClassCodeVisitorSupport
{
@Override
public void visitVariableExpression(VariableExpression ve) {
if (ve.getAccessedVariable() instanceof DynamicVariable &&
(ve.isInStaticContext() || inSpecialConstructorCall) && !inClosure) {
+ String variableName = ve.getName();
// GROOVY-5687: interface constants not visible to implementing
subclass in static context
if (methodNode != null && methodNode.isStatic()) {
- FieldNode fieldNode =
getDeclaredOrInheritedField(methodNode.getDeclaringClass(), ve.getName());
+ ClassNode classNode = methodNode.getDeclaringClass();
+ FieldNode fieldNode = getDeclaredOrInheritedField(classNode,
variableName);
if (fieldNode != null && fieldNode.isStatic()) {
return;
}
}
- addError("Apparent variable '" + ve.getName() + "' was found in a
static scope but doesn't refer to a local variable, static field or class.
Possible causes:\n" +
+ addError("Apparent variable '" + variableName + "' was found in a
static scope but doesn't refer to a local variable, static field or class.
Possible causes:\n" +
"You attempted to reference a variable in the binding or
an instance variable from a static context.\n" +
"You misspelled a classname or statically imported field.
Please check the spelling.\n" +
- "You attempted to use a method '" + ve.getName() + "' but
left out brackets in a place not allowed by the grammar.", ve);
+ "You attempted to use a method '" + variableName + "' but
left out brackets in a place not allowed by the grammar.",
+ ve);
}
}
- private static FieldNode getDeclaredOrInheritedField(ClassNode cn, String
fieldName) {
- ClassNode node = cn;
- while (node != null) {
- FieldNode fn = node.getDeclaredField(fieldName);
- if (fn != null) return fn;
- List<ClassNode> interfacesToCheck = new
ArrayList<>(Arrays.asList(node.getInterfaces()));
- while (!interfacesToCheck.isEmpty()) {
- ClassNode nextInterface = interfacesToCheck.remove(0);
- fn = nextInterface.getDeclaredField(fieldName);
- if (fn != null) return fn;
-
interfacesToCheck.addAll(Arrays.asList(nextInterface.getInterfaces()));
+ private static FieldNode getDeclaredOrInheritedField(ClassNode classNode,
String fieldName) {
+ FieldNode fieldNode = ClassNodeUtils.getField(classNode, fieldName);
+ if (fieldNode == null && fieldName.contains("__")) { // GROOVY-11663
+ List<ClassNode> traits = Traits.findTraits(classNode);
+ traits.remove(classNode); // included if it is a trait
+ for (ClassNode cn : traits) {
+ cn = Traits.findFieldHelper(cn);
+ if (cn != null) {
+ for (FieldNode fn : cn.getFields()) {
+ if (fn.getName().endsWith(fieldName)) { // prefix for
modifiers
+ fieldNode = fn;
+ break;
+ }
+ }
+ }
}
- node = node.getSuperClass();
}
- return null;
+ return fieldNode;
}
}
diff --git
a/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
b/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
index c1dd00f55e..7caea44797 100644
---
a/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
+++
b/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
@@ -1997,7 +1997,7 @@ final class TraitASTTransformationTest {
void testShouldCompileTraitMethodStatically() {
def err = shouldFail shell, '''
@CompileStatic
- trait Foo {
+ trait T {
int foo() { 1+'foo'}
}
'''
@@ -2018,10 +2018,13 @@ final class TraitASTTransformationTest {
class D extends C {
}
- assert C.foo() == 'static method'
- assert D.foo() == 'static method'
- assert new C().foo() == 'static method'
- assert new D().foo() == 'static method'
+ $mode void m() {
+ assert C.foo() == 'static method'
+ assert D.foo() == 'static method'
+ assert new C().foo() == 'static method'
+ assert new D().foo() == 'static method'
+ }
+ m()
"""
// GROOVY-7322
@@ -2038,10 +2041,13 @@ final class TraitASTTransformationTest {
class D extends C {
}
- assert C.foo() == 'static method'
- assert D.foo() == 'static method'
- assert new C().foo() == 'static method'
- assert new D().foo() == 'static method'
+ $mode void m() {
+ assert C.foo() == 'static method'
+ assert D.foo() == 'static method'
+ assert new C().foo() == 'static method'
+ assert new D().foo() == 'static method'
+ }
+ m()
"""
// GROOVY-7191
@@ -2058,8 +2064,11 @@ final class TraitASTTransformationTest {
class D extends C {
}
- assert new C().foo() == 1
- assert new D().foo() == 1
+ $mode void m() {
+ assert new C().foo() == 1
+ assert new D().foo() == 1
+ }
+ m()
"""
// GROOVY-8854
@@ -2084,11 +2093,14 @@ final class TraitASTTransformationTest {
class D extends C {
}
- def c = new C(name:'name')
- c.audit(); assert c.passes
+ $mode void m() {
+ def c = new C(name:'name')
+ c.audit(); assert c.passes
- def d = new D(name:'name')
- d.audit(); assert d.passes
+ def d = new D(name:'name')
+ d.audit(); assert d.passes
+ }
+ m()
"""
}
@@ -2103,7 +2115,10 @@ final class TraitASTTransformationTest {
class C implements T {
}
- assert C.T__VAL == 123
+ $mode void m() {
+ assert C.T__VAL == 123
+ }
+ m()
"""
assertScript shell, """
@@ -2116,9 +2131,12 @@ final class TraitASTTransformationTest {
class C implements T {
}
- assert C.T__VAL == 123
- C.update(456)
- assert C.T__VAL == 456
+ $mode void m() {
+ assert C.T__VAL == 123
+ C.update(456)
+ assert C.T__VAL == 456
+ }
+ m()
"""
}
@@ -2134,9 +2152,12 @@ final class TraitASTTransformationTest {
class C implements T {
}
- assert C.VAL == 123
- C.update(456)
- assert C.VAL == 456
+ $mode void m() {
+ assert C.VAL == 123
+ C.update(456)
+ assert C.VAL == 456
+ }
+ m()
"""
// GROOVY-7255
@@ -2153,8 +2174,11 @@ final class TraitASTTransformationTest {
class C implements T {
}
- C.initStuff([4,5,6])
- assert C.stuff == [1,2,3,4,5,6]
+ $mode void m() {
+ C.initStuff([4,5,6])
+ assert C.stuff == [1,2,3,4,5,6]
+ }
+ m()
"""
assertScript shell, """
@@ -2171,7 +2195,10 @@ final class TraitASTTransformationTest {
}
}
- assert C.m() == 3
+ $mode void m() {
+ assert C.m() == 3
+ }
+ m()
"""
// GROOVY-9678
@@ -2189,7 +2216,10 @@ final class TraitASTTransformationTest {
}
}
- assert C.m() == 3
+ $mode void m() {
+ assert C.m() == 3
+ }
+ m()
"""
}
@@ -3746,6 +3776,39 @@ final class TraitASTTransformationTest {
}
}
+ // GROOVY-11663
+ @CompileModesTest
+ void testTraitAccessToInheritedStaticConstant(String mode) {
+ assertScript shell, """
+ $mode
+ trait A {
+ public static final String BANG = '!'
+ }
+ $mode
+ trait B extends A {
+ static one(String string) {
+ string// + A__BANG
+ }
+ Object two(String string) {
+ string// + A__BANG
+ }
+ }
+ $mode
+ class C implements B {
+ static test1() {
+ assert A__BANG + one('works') == '!works'
+ }
+ void test2() {
+ assert A__BANG + one('works') == '!works'
+ assert A__BANG + two('works') == '!works'
+ }
+ }
+
+ C.test1()
+ new C().test2()
+ """
+ }
+
// GROOVY-9386
@CompileModesTest
void testTraitPropertyInitializedByTap(String mode) {