Skip to content

Commit 7851400

Browse files
committed
GROOVY-11663: support static trait field referenced in static context
1 parent b351c27 commit 7851400

2 files changed

Lines changed: 113 additions & 44 deletions

File tree

src/main/java/org/codehaus/groovy/control/StaticVerifier.java

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.codehaus.groovy.control;
2020

21+
import org.apache.groovy.ast.tools.ClassNodeUtils;
2122
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
2223
import org.codehaus.groovy.ast.ClassNode;
2324
import org.codehaus.groovy.ast.DynamicVariable;
@@ -27,9 +28,8 @@
2728
import org.codehaus.groovy.ast.expr.ClosureExpression;
2829
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
2930
import org.codehaus.groovy.ast.expr.VariableExpression;
31+
import org.codehaus.groovy.transform.trait.Traits;
3032

31-
import java.util.ArrayList;
32-
import java.util.Arrays;
3333
import java.util.List;
3434

3535
/**
@@ -88,34 +88,40 @@ public void visitConstructorOrMethod(MethodNode node, boolean isConstructor) {
8888
@Override
8989
public void visitVariableExpression(VariableExpression ve) {
9090
if (ve.getAccessedVariable() instanceof DynamicVariable && (ve.isInStaticContext() || inSpecialConstructorCall) && !inClosure) {
91+
String variableName = ve.getName();
9192
// GROOVY-5687: interface constants not visible to implementing subclass in static context
9293
if (methodNode != null && methodNode.isStatic()) {
93-
FieldNode fieldNode = getDeclaredOrInheritedField(methodNode.getDeclaringClass(), ve.getName());
94+
ClassNode classNode = methodNode.getDeclaringClass();
95+
FieldNode fieldNode = getDeclaredOrInheritedField(classNode, variableName);
9496
if (fieldNode != null && fieldNode.isStatic()) {
9597
return;
9698
}
9799
}
98-
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" +
100+
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" +
99101
"You attempted to reference a variable in the binding or an instance variable from a static context.\n" +
100102
"You misspelled a classname or statically imported field. Please check the spelling.\n" +
101-
"You attempted to use a method '" + ve.getName() + "' but left out brackets in a place not allowed by the grammar.", ve);
103+
"You attempted to use a method '" + variableName + "' but left out brackets in a place not allowed by the grammar.",
104+
ve);
102105
}
103106
}
104107

105-
private static FieldNode getDeclaredOrInheritedField(ClassNode cn, String fieldName) {
106-
ClassNode node = cn;
107-
while (node != null) {
108-
FieldNode fn = node.getDeclaredField(fieldName);
109-
if (fn != null) return fn;
110-
List<ClassNode> interfacesToCheck = new ArrayList<>(Arrays.asList(node.getInterfaces()));
111-
while (!interfacesToCheck.isEmpty()) {
112-
ClassNode nextInterface = interfacesToCheck.remove(0);
113-
fn = nextInterface.getDeclaredField(fieldName);
114-
if (fn != null) return fn;
115-
interfacesToCheck.addAll(Arrays.asList(nextInterface.getInterfaces()));
108+
private static FieldNode getDeclaredOrInheritedField(ClassNode classNode, String fieldName) {
109+
FieldNode fieldNode = ClassNodeUtils.getField(classNode, fieldName);
110+
if (fieldNode == null && fieldName.contains("__")) { // GROOVY-11663
111+
List<ClassNode> traits = Traits.findTraits(classNode);
112+
traits.remove(classNode); // included if it is a trait
113+
for (ClassNode cn : traits) {
114+
cn = Traits.findFieldHelper(cn);
115+
if (cn != null) {
116+
for (FieldNode fn : cn.getFields()) {
117+
if (fn.getName().endsWith(fieldName)) { // prefix for modifiers
118+
fieldNode = fn;
119+
break;
120+
}
121+
}
122+
}
116123
}
117-
node = node.getSuperClass();
118124
}
119-
return null;
125+
return fieldNode;
120126
}
121127
}

src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,7 +1997,7 @@ final class TraitASTTransformationTest {
19971997
void testShouldCompileTraitMethodStatically() {
19981998
def err = shouldFail shell, '''
19991999
@CompileStatic
2000-
trait Foo {
2000+
trait T {
20012001
int foo() { 1+'foo'}
20022002
}
20032003
'''
@@ -2018,10 +2018,13 @@ final class TraitASTTransformationTest {
20182018
class D extends C {
20192019
}
20202020
2021-
assert C.foo() == 'static method'
2022-
assert D.foo() == 'static method'
2023-
assert new C().foo() == 'static method'
2024-
assert new D().foo() == 'static method'
2021+
$mode void m() {
2022+
assert C.foo() == 'static method'
2023+
assert D.foo() == 'static method'
2024+
assert new C().foo() == 'static method'
2025+
assert new D().foo() == 'static method'
2026+
}
2027+
m()
20252028
"""
20262029

20272030
// GROOVY-7322
@@ -2038,10 +2041,13 @@ final class TraitASTTransformationTest {
20382041
class D extends C {
20392042
}
20402043
2041-
assert C.foo() == 'static method'
2042-
assert D.foo() == 'static method'
2043-
assert new C().foo() == 'static method'
2044-
assert new D().foo() == 'static method'
2044+
$mode void m() {
2045+
assert C.foo() == 'static method'
2046+
assert D.foo() == 'static method'
2047+
assert new C().foo() == 'static method'
2048+
assert new D().foo() == 'static method'
2049+
}
2050+
m()
20452051
"""
20462052

20472053
// GROOVY-7191
@@ -2058,8 +2064,11 @@ final class TraitASTTransformationTest {
20582064
class D extends C {
20592065
}
20602066
2061-
assert new C().foo() == 1
2062-
assert new D().foo() == 1
2067+
$mode void m() {
2068+
assert new C().foo() == 1
2069+
assert new D().foo() == 1
2070+
}
2071+
m()
20632072
"""
20642073

20652074
// GROOVY-8854
@@ -2084,11 +2093,14 @@ final class TraitASTTransformationTest {
20842093
class D extends C {
20852094
}
20862095
2087-
def c = new C(name:'name')
2088-
c.audit(); assert c.passes
2096+
$mode void m() {
2097+
def c = new C(name:'name')
2098+
c.audit(); assert c.passes
20892099
2090-
def d = new D(name:'name')
2091-
d.audit(); assert d.passes
2100+
def d = new D(name:'name')
2101+
d.audit(); assert d.passes
2102+
}
2103+
m()
20922104
"""
20932105
}
20942106

@@ -2103,7 +2115,10 @@ final class TraitASTTransformationTest {
21032115
class C implements T {
21042116
}
21052117
2106-
assert C.T__VAL == 123
2118+
$mode void m() {
2119+
assert C.T__VAL == 123
2120+
}
2121+
m()
21072122
"""
21082123

21092124
assertScript shell, """
@@ -2116,9 +2131,12 @@ final class TraitASTTransformationTest {
21162131
class C implements T {
21172132
}
21182133
2119-
assert C.T__VAL == 123
2120-
C.update(456)
2121-
assert C.T__VAL == 456
2134+
$mode void m() {
2135+
assert C.T__VAL == 123
2136+
C.update(456)
2137+
assert C.T__VAL == 456
2138+
}
2139+
m()
21222140
"""
21232141
}
21242142

@@ -2134,9 +2152,12 @@ final class TraitASTTransformationTest {
21342152
class C implements T {
21352153
}
21362154
2137-
assert C.VAL == 123
2138-
C.update(456)
2139-
assert C.VAL == 456
2155+
$mode void m() {
2156+
assert C.VAL == 123
2157+
C.update(456)
2158+
assert C.VAL == 456
2159+
}
2160+
m()
21402161
"""
21412162

21422163
// GROOVY-7255
@@ -2153,8 +2174,11 @@ final class TraitASTTransformationTest {
21532174
class C implements T {
21542175
}
21552176
2156-
C.initStuff([4,5,6])
2157-
assert C.stuff == [1,2,3,4,5,6]
2177+
$mode void m() {
2178+
C.initStuff([4,5,6])
2179+
assert C.stuff == [1,2,3,4,5,6]
2180+
}
2181+
m()
21582182
"""
21592183

21602184
assertScript shell, """
@@ -2171,7 +2195,10 @@ final class TraitASTTransformationTest {
21712195
}
21722196
}
21732197
2174-
assert C.m() == 3
2198+
$mode void m() {
2199+
assert C.m() == 3
2200+
}
2201+
m()
21752202
"""
21762203

21772204
// GROOVY-9678
@@ -2189,7 +2216,10 @@ final class TraitASTTransformationTest {
21892216
}
21902217
}
21912218
2192-
assert C.m() == 3
2219+
$mode void m() {
2220+
assert C.m() == 3
2221+
}
2222+
m()
21932223
"""
21942224
}
21952225

@@ -3746,6 +3776,39 @@ final class TraitASTTransformationTest {
37463776
}
37473777
}
37483778

3779+
// GROOVY-11663
3780+
@CompileModesTest
3781+
void testTraitAccessToInheritedStaticConstant(String mode) {
3782+
assertScript shell, """
3783+
$mode
3784+
trait A {
3785+
public static final String BANG = '!'
3786+
}
3787+
$mode
3788+
trait B extends A {
3789+
static one(String string) {
3790+
string// + A__BANG
3791+
}
3792+
Object two(String string) {
3793+
string// + A__BANG
3794+
}
3795+
}
3796+
$mode
3797+
class C implements B {
3798+
static test1() {
3799+
assert A__BANG + one('works') == '!works'
3800+
}
3801+
void test2() {
3802+
assert A__BANG + one('works') == '!works'
3803+
assert A__BANG + two('works') == '!works'
3804+
}
3805+
}
3806+
3807+
C.test1()
3808+
new C().test2()
3809+
"""
3810+
}
3811+
37493812
// GROOVY-9386
37503813
@CompileModesTest
37513814
void testTraitPropertyInitializedByTap(String mode) {

0 commit comments

Comments
 (0)