[ 
https://issues.apache.org/jira/browse/GROOVY-12106?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18092072#comment-18092072
 ] 

Paul King edited comment on GROOVY-12106 at 6/27/26 10:59 PM:
--------------------------------------------------------------

Scenario: child trait extends parent trait, calling the inherited parent-trait 
static, with a subtype argument, under @CompileStatic

|| Row || Form || Case || 4.0.32 || 5.0.6 || 5.0.7-SNAPSHOT || master || GEP-22 
says ||
| 16 | unqualified {{m(...)}} | (b) | (/) | (x) | (x) | (x) | Silent |
| 16b | {{this.m(...)}} | (b) | (/) | (x) | (x) | (x) | Silent |
| 16q | qualified {{Parent.m(...)}} | (a) | (x) ^1^ | (x) | (x) | (/) | Covered 
— supported |
| - | {{Parent.super.m(...)}} | escape | (/) | (/) | (/) | (/) | Covered |
| - | unqualified, exact {{Object}} arg | - | (/) | (/) | (/) | (/) | Silent |
| D | plain class {{Child extends Parent}} | control | (/) | (/) | (/) | (/) | 
n/a |

^1^ {{Parent.m(...)}} on 4.0.32 compiles but throws {{MissingMethodException}} 
at runtime — so the qualified form never fully worked before master (4.0.x 
runtime-fails, 5.0.x compile-fails, only master both compiles and runs). All 
other cells are compile + runtime.

  What the spec says for each verdict:

  - (b) — Silent. Every static-dispatch rule in § Static members is phrased for 
a static "declared in a trait" called from "the body of a trait method" 
resolving to "the trait's own copy." None address a static inherited from a 
super-trait called from a sub-trait body. But the model implies they should 
work: "a trait should be reasoned about as if it were a superclass of the 
implementing class" + "declarer-bound by default: a call of the form {{m(...)}} 
or {{this.m(...)}} ... resolves to the trait's own copy." So (b) is a 
spec-violation-by-implication, not intended behaviour — and 4.0.32 already did 
it correctly.                                                                   
                                                                                
                                                                              
  - exact-{{Object}} arg — Silent (and the split is the bug). The spec never 
makes static resolution depend on argument subtyping; that the exact type 
resolves while a subtype fails is nowhere in the spec.
  - (a) qualified — Covered/supported. "Public static methods declared in a 
trait are promoted onto the generated trait interface as JVM-native interface 
statics ... External calls of both forms — {{Trait.staticMethod(...)}} ... — 
are supported" (GROOVY-12111). Master matches the spec; 4.0.32 and 5.0.7 
predate the promotion (runtime-fail / compile-fail respectively) → a backport 
gap, not a spec question.
  - {{Parent.super.m(...)}} — Covered. "A qualified {{T.super.m(...)}} call 
resolves to trait {{T}}'s implementation ... the explicit override form" (item 
3); item 4 names it as the form to use for a trait's own static. Works on every 
version.
  - plain class (row D) — n/a. GEP-22 governs traits; the plain-class control 
is outside its scope (and works everywhere — localising the defect to trait 
machinery).

Net: 4.0.32 is the clean reference for (b) — unqualified/{{this.}} resolved 
there and regressed in 5.0.x. The spec is silent exactly where the bug lives, 
explicitly supports the qualified form (only ever delivered on master), and is 
n/a for the control.


was (Author: paulk):
Scenario: child trait extends parent trait, calling the inherited parent-trait 
static, with a subtype argument, under @CompileStatic

|| Row || Form || Case || 4.0.32 || 5.0.7-SNAPSHOT || master || GEP-22 says ||
| 16 | unqualified {{m(...)}} | (b) | (/) | (x) | (x) | Silent |
| 16b | {{this.m(...)}} | (b) | (/) | (x) | (x) | Silent |
| 16q | qualified {{Parent.m(...)}} | (a) | (x) ^1^ | (x) | (/) | Covered — 
supported |
| - | {{Parent.super.m(...)}} | escape | (/) | (/) | (/) | Covered |
| - | unqualified, exact {{Object}} arg | - | (/) | (/) | (/) | Silent |
| D | plain class {{Child extends Parent}} | control | (/) | (/) | (/) | n/a |

^1^ {{Parent.m(...)}} on 4.0.32 compiles but throws {{MissingMethodException}} 
at runtime — so the qualified form never fully worked before master (4.0.x 
runtime-fails, 5.0.x compile-fails, only master both compiles and runs). All 
other cells are compile + runtime.

  What the spec says for each verdict:

  - (b) — Silent. Every static-dispatch rule in § Static members is phrased for 
a static "declared in a trait" called from "the body of a trait method" 
resolving to "the trait's own copy." None address a static inherited from a 
super-trait called from a sub-trait body. But the model implies they should 
work: "a trait should be reasoned about as if it were a superclass of the 
implementing class" + "declarer-bound by default: a call of the form {{m(...)}} 
or {{this.m(...)}} ... resolves to the trait's own copy." So (b) is a 
spec-violation-by-implication, not intended behaviour — and 4.0.32 already did 
it correctly.                                                                   
                                                                                
                                                                              
  - exact-{{Object}} arg — Silent (and the split is the bug). The spec never 
makes static resolution depend on argument subtyping; that the exact type 
resolves while a subtype fails is nowhere in the spec.
  - (a) qualified — Covered/supported. "Public static methods declared in a 
trait are promoted onto the generated trait interface as JVM-native interface 
statics ... External calls of both forms — {{Trait.staticMethod(...)}} ... — 
are supported" (GROOVY-12111). Master matches the spec; 4.0.32 and 5.0.7 
predate the promotion (runtime-fail / compile-fail respectively) → a backport 
gap, not a spec question.
  - {{Parent.super.m(...)}} — Covered. "A qualified {{T.super.m(...)}} call 
resolves to trait {{T}}'s implementation ... the explicit override form" (item 
3); item 4 names it as the form to use for a trait's own static. Works on every 
version.
  - plain class (row D) — n/a. GEP-22 governs traits; the plain-class control 
is outside its scope (and works everywhere — localising the defect to trait 
machinery).

Net: 4.0.32 is the clean reference for (b) — unqualified/{{this.}} resolved 
there and regressed in 5.0.x. The spec is silent exactly where the bug lives, 
explicitly supports the qualified form (only ever delivered on master), and is 
n/a for the control.

> STC cannot resolve inherited static trait method from sub-trait body
> --------------------------------------------------------------------
>
>                 Key: GROOVY-12106
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12106
>             Project: Groovy
>          Issue Type: Bug
>          Components: Static Type Checker
>    Affects Versions: 5.0.7
>            Reporter: James Fredley
>            Assignee: Paul King
>            Priority: Major
>              Labels: static-method, sts, traits
>
> h2. Summary
> Groovy 5 static type checking does not resolve a static method declared by a 
> parent trait when the method is called from a sub-trait body.
> This is independent of Grails and can be reproduced with plain Groovy traits 
> under @CompileStatic.
> This was re-tested on Groovy 5.0.7-SNAPSHOT. Both an unqualified call and a 
> qualified call fail static type checking:
> {code:groovy}
> inheritedStaticMethod(value)
> {code}
> {code:groovy}
> ParentTrait.inheritedStaticMethod(value)
> {code}
> The failure is distinct from GROOVY-11985, which fixed 
> static-override-via-this. This issue is about resolving a parent trait's 
> static method from a sub-trait body.
> h2. Standalone reproducer
> {code:groovy}
> import groovy.transform.CompileStatic
> @CompileStatic
> trait ParentTrait {
>     static void configure(Closure closure, Object target) {
>         if (closure != null) {
>             closure.delegate = target
>             closure.resolveStrategy = Closure.DELEGATE_ONLY
>             closure.call()
>         }
>     }
> }
> @CompileStatic
> trait ChildTrait extends ParentTrait {
>     void applyConfig(Closure closure, Object target) {
>         configure(closure, target)
>     }
> }
> @CompileStatic
> class Example implements ChildTrait {
> }
> {code}
> h2. Expected result
> The code should pass static type checking. A sub-trait should be able to 
> resolve a static method declared by a parent trait.
> h2. Actual result
> Static type checking fails on the call from the sub-trait body:
> {code}
> Cannot find matching method ChildTrait#configure(groovy.lang.Closure, 
> java.lang.Object)
> {code}
> Using a qualified call also fails:
> {code:groovy}
> ParentTrait.configure(closure, target)
> {code}
> h2. Real-world use case
> Apache Grails has the same pattern in the GraphQL DSL helper traits.
> Parent trait method:
> {code}
> grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/ExecutesClosures.groovy
> line 34
> {code}
> {code:groovy}
> @CompileStatic
> trait ExecutesClosures {
>     static void withDelegate(@DelegatesTo(strategy = Closure.DELEGATE_ONLY) 
> Closure closure, Object delegate) {
>         ...
>     }
> }
> {code}
> Sub-traits that should be able to call the inherited helper:
> {code}
> grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/Arguable.groovy
> grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/ComplexTyped.groovy
> {code}
> Grails currently works around this by inlining the parent trait helper body 
> into each sub-trait. Once this Groovy STC issue is fixed, that inline 
> workaround can be removed.
> h2. Workaround
> Inline the static helper body in each sub-trait, or avoid calling the 
> inherited static trait method from the sub-trait.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to