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 78514001bbea4f5358d1ccedb7e80bc6db613cfd
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) {

Reply via email to