Skip to content

Commit fc050c6

Browse files
committed
GROOVY-12105: Unqualified super.m(...) from a static trait method throws MissingMethodException — should be a compile error
1 parent e4a9bb8 commit fc050c6

3 files changed

Lines changed: 145 additions & 10 deletions

File tree

src/main/java/org/codehaus/groovy/transform/trait/TraitReceiverTransformer.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.codehaus.groovy.ast.Variable;
3131
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
3232
import org.codehaus.groovy.ast.expr.BinaryExpression;
33+
import org.codehaus.groovy.ast.expr.ClassExpression;
3334
import org.codehaus.groovy.ast.expr.ClosureExpression;
3435
import org.codehaus.groovy.ast.expr.ConstantExpression;
3536
import org.codehaus.groovy.ast.expr.DeclarationExpression;
@@ -119,6 +120,23 @@ public Expression transform(final Expression exp) {
119120
return transformBinaryExpression((BinaryExpression) exp);
120121
} else if (exp instanceof MethodCallExpression mce) {
121122
String obj = mce.getObjectExpression().getText();
123+
// GROOVY-12105: in a static trait method, the parser/resolver
124+
// rewrites `super.m(...)` to a static call on the trait's
125+
// declared superclass (typically Object) before we see it. Reject
126+
// that pattern at compile time, pointing at `T.super.m(...)` as
127+
// the supported explicit form. The heuristic is narrow — it only
128+
// fires when (a) we are in a static trait-method body (weaved is
129+
// typed Class), (b) the receiver is a ClassExpression and (c) the
130+
// expression type matches the trait's declared superclass.
131+
if (ClassHelper.isClassType(weaved.getOriginType())
132+
&& mce.getObjectExpression() instanceof ClassExpression ce
133+
&& traitClass.getSuperClass() != null
134+
&& ce.getType().equals(traitClass.getSuperClass())) {
135+
unit.addError(new SyntaxException(
136+
"'super' is not allowed in a static trait method; use '" + traitClass.getNameWithoutPackage() + ".super." + mce.getMethodAsString() + "(...)' for explicit trait-anchored dispatch",
137+
mce.getLineNumber(), mce.getColumnNumber()));
138+
return mce;
139+
}
122140
if ("super".equals(obj)) {
123141
return transformSuperMethodCall(mce); // super.m(x) --> $self.Ttrait$super$m(x)
124142
} else if ("this".equals(obj)) {
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.codehaus.groovy.transform.traitx
20+
21+
import org.codehaus.groovy.control.MultipleCompilationErrorsException
22+
import org.junit.Test
23+
24+
import static groovy.test.GroovyAssert.assertScript
25+
import static groovy.test.GroovyAssert.shouldFail
26+
27+
/**
28+
* Unqualified {@code super.m(...)} from a static trait method is rejected
29+
* at compile time (GROOVY-12105). Without the reject, the call previously
30+
* compiled successfully and threw {@code MissingMethodException} at
31+
* runtime — the trait helper has no chain-walking for statics, and Java's
32+
* JLS rule "Cannot use super in a static context" already disallows the
33+
* pattern for plain classes.
34+
*
35+
* <p>The supported explicit form is {@code T.super.m(...)} (GEP-22
36+
* § this, super, and stackable traits item 3).
37+
*/
38+
final class Groovy12105 {
39+
40+
/** Unqualified super in static trait method → compile error. */
41+
@Test
42+
void unqualifiedSuper_inStaticTraitMethod_isCompileError() {
43+
def err = shouldFail(MultipleCompilationErrorsException) {
44+
assertScript '''
45+
trait Base {
46+
static String m() { 'Base' }
47+
}
48+
trait V extends Base {
49+
static String m() { 'V' }
50+
static callSuper() { super.m() } // <-- rejected
51+
}
52+
class Impl implements V { }
53+
Impl.callSuper()
54+
'''
55+
}
56+
assert err.message.contains("'super' is not allowed in a static trait method")
57+
assert err.message.contains('V.super.m(')
58+
}
59+
60+
/** Unqualified super in static trait method, no super-trait → compile error. */
61+
@Test
62+
void unqualifiedSuper_inStaticTraitMethod_noSuperTrait_isCompileError() {
63+
def err = shouldFail(MultipleCompilationErrorsException) {
64+
assertScript '''
65+
trait V {
66+
static String m() { 'V' }
67+
static callSuper() { super.m() } // <-- rejected even without a super-trait
68+
}
69+
class Impl implements V { }
70+
Impl.callSuper()
71+
'''
72+
}
73+
assert err.message.contains("'super' is not allowed in a static trait method")
74+
}
75+
76+
/** The supported explicit form T.super.m() still works for static methods. */
77+
@Test
78+
void traitQualifiedSuper_inStaticTraitMethod_works() {
79+
assertScript '''
80+
trait V {
81+
static String m() { 'V' }
82+
static callTSuper() { V.super.m() } // explicit, supported
83+
}
84+
class Impl implements V { }
85+
assert Impl.callTSuper() == 'V'
86+
'''
87+
}
88+
89+
/** Unqualified super in an INSTANCE trait method is unaffected (regression guard). */
90+
@Test
91+
void unqualifiedSuper_inInstanceTraitMethod_stillWorks() {
92+
assertScript '''
93+
trait Base {
94+
def m() { 'Base' }
95+
}
96+
trait V extends Base {
97+
def m() { 'V' }
98+
def callSuper() { super.m() } // instance method, walks chain → 'Base'
99+
}
100+
class Impl implements V { }
101+
assert new Impl().callSuper() == 'Base'
102+
'''
103+
}
104+
105+
/** Plain (non-trait) class static super.m() is unaffected (the reject is trait-only). */
106+
@Test
107+
void unqualifiedSuper_inPlainClassStatic_stillWorks() {
108+
assertScript '''
109+
class Base {
110+
static String m() { 'Base' }
111+
}
112+
class Sub extends Base {
113+
static String m() { 'Sub' }
114+
static String callSuper() { super.m() }
115+
}
116+
assert Sub.callSuper() == 'Base'
117+
'''
118+
}
119+
}

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -538,17 +538,15 @@ class TraitStaticDispatchMatrix {
538538
}
539539
}
540540

541-
// ---- Row 15 — unqualified super.m() from trait STATIC method (proposed compile error) ----
542-
// Companion to row 14, same workstream. Unqualified `super.m()` inside a
543-
// trait instance method walks the trait chain (spec item 2); from a trait
544-
// STATIC method the chain is not walked and the call throws
545-
// MissingMethodException at runtime. GEP-22 item 4 proposes rejecting
546-
// the static-context use at compile time, leaving `T.super.m()` as the
547-
// explicit form. NYI until the fix lands; flips red on a build that
548-
// implements the rejection.
541+
// ---- Row 15 — unqualified super.m() from trait STATIC method (GROOVY-12105) ----
542+
// Unqualified `super.m()` inside a trait instance method walks the trait
543+
// chain (spec item 2); from a trait STATIC method the chain is not
544+
// walked. GROOVY-12105 rejects the static-context use at compile time,
545+
// pointing at `T.super.m()` as the explicit form. See Groovy12105.groovy
546+
// for focused coverage (regression-guards for the instance-method and
547+
// plain-class super-call cases). This row is the matrix-level anchor.
549548
@Test
550-
@NotYetImplemented
551-
void row15_unqualified_static_super_inTrait_shouldBeCompileError_PROPOSED() {
549+
void row15_unqualified_static_super_inTrait_isCompileError() {
552550
GroovyAssert.shouldFail(MultipleCompilationErrorsException) {
553551
ev '''
554552
trait Base { static String m() { 'Base' } }

0 commit comments

Comments
 (0)