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