Skip to content

GROOVY-12117: replace super trait search with more complete helper stub#2678

Open
eric-milles wants to merge 1 commit into
masterfrom
GROOVY-12117
Open

GROOVY-12117: replace super trait search with more complete helper stub#2678
eric-milles wants to merge 1 commit into
masterfrom
GROOVY-12117

Conversation

@eric-milles

Copy link
Copy Markdown
Member

In order to support some code assist or type inferencing for traits, I needed to fill out the trait helper stub a little more. This solution also works for GROOVY-12117 instead of the super trait method search. That is why I could not get the 12117 test case to trip locally.

@eric-milles eric-milles requested a review from paulk-asert July 8, 2026 15:46

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.

Benchmark suite Current: 6c85475 Previous: 23780cf Ratio
org.apache.groovy.bench.CalibrationBench.memoryPointerChase 975.0959011983615 us/op 569.5932258985365 us/op 1.71

This comment was automatically generated by workflow using github-action-benchmark.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.47619% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.7007%. Comparing base (23780cf) to head (e197b22).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...va/org/codehaus/groovy/transform/trait/Traits.java 90.4762% 0 Missing and 2 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##               master      #2678        +/-   ##
==================================================
- Coverage     68.7019%   68.7007%   -0.0012%     
+ Complexity      34123      34119         -4     
==================================================
  Files            1536       1536                
  Lines          128912     128923        +11     
  Branches        23369      23370         +1     
==================================================
+ Hits            88565      88571         +6     
- Misses          32517      32521         +4     
- Partials         7830       7831         +1     
Files with missing lines Coverage Δ
...oovy/transform/trait/TraitReceiverTransformer.java 88.8325% <ø> (+0.2753%) ⬆️
...va/org/codehaus/groovy/transform/trait/Traits.java 86.8966% <90.4762%> (-0.0265%) ⬇️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sonarqubecloud

sonarqubecloud Bot commented Jul 8, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

JMH summary — classic (commit 7ecccc9)

Speedup vs trailing 90-day baseline on gh-pages. Higher = faster.
1.00 = in line with history. Per-benchmark ratio, geomean within group.
Time-per-op units inverted so direction is consistent. The calibrated
column divides out this runner's speed vs the baseline hardware, as
measured by Groovy-independent pure-Java ruler benchmarks.

Group Speedup Calibrated n
bench 1.033 × 1.032 × 78
core 1.023 × 1.044 × 77
grails 0.988 × 1.029 × 80

Runner calibration (this run vs baseline hardware): bench 1.01× (26 rulers) · core-ag 0.96× (3 rulers) · core-hz 1.01× (3 rulers) · grails-ad 0.90× (3 rulers) · grails-ez 1.01× (3 rulers)

Baseline: dev/bench/jmh/<part>/classic/data.js on gh-pages, trailing 90 days. Daily dashboard · Per-suite raw data

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

JMH summary — indy (commit 7ecccc9)

Speedup vs trailing 90-day baseline on gh-pages. Higher = faster.
1.00 = in line with history. Per-benchmark ratio, geomean within group.
Time-per-op units inverted so direction is consistent. The calibrated
column divides out this runner's speed vs the baseline hardware, as
measured by Groovy-independent pure-Java ruler benchmarks.

Group Speedup Calibrated n
bench 0.992 × 1.021 × 78
core 3.227 × 3.067 × 77
grails 2.075 × 2.373 × 80

⚠️ Runner speed differs ≥15% from the historical baseline hardware for: grails-ad. Raw speedups are not meaningful for those parts — use the calibrated column.

Runner calibration (this run vs baseline hardware): bench 0.97× (26 rulers) · core-ag 1.11× (3 rulers) · core-hz 0.99× (3 rulers) · grails-ad 0.87× (3 rulers) · grails-ez 0.88× (3 rulers)

Baseline: dev/bench/jmh/<part>/indy/data.js on gh-pages, trailing 90 days. Daily dashboard · Per-suite raw data

@testlens-app

testlens-app Bot commented Jul 9, 2026

Copy link
Copy Markdown

🚨 TestLens detected 5 failed tests 🚨

Here is what you can do:

  1. Inspect the test failures carefully.
  2. If you are convinced that some of the tests are flaky, you can mute them below.
  3. Finally, trigger a rerun by checking the rerun checkbox.

Test Summary

Build and test / lts (17, macos-latest) > :test

Test Runs
Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst()

Build and test / lts (17, ubuntu-latest) > :test

Test Runs
Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst()

Build and test / lts (17, windows-latest, 1) > :test

Test Runs
Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst()

Build and test / lts (21, ubuntu-latest) > :test

Test Runs
Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst()

Build and test / lts (25, ubuntu-latest) > :test

Test Runs
Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst()

🏷️ Commit: 6c85475
▶️ Tests: 103045 executed
⚪️ Checks: 31/31 completed

Test Failures

Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst() (:test in Build and test / lts (17, macos-latest))
Assertion failed: 

assert D.greet() == 'override'
         |       |
         'base'  false

	at org.codehaus.groovy.runtime.InvokerHelper.createAssertError(InvokerHelper.java:400)
	at TestScript22.run(TestScript22.groovy:8)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:553)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:588)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:572)
	at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:106)
	at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:96)
	at org.codehaus.groovy.transform.traitx.Groovy11985.testSuperTraitPublicStaticIsPolymorphicSubTraitFirst(Groovy11985.groovy:140)
Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst() (:test in Build and test / lts (17, ubuntu-latest))
Assertion failed: 

assert D.greet() == 'override'
         |       |
         'base'  false

	at org.codehaus.groovy.runtime.InvokerHelper.createAssertError(InvokerHelper.java:400)
	at TestScript22.run(TestScript22.groovy:8)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:553)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:588)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:572)
	at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:106)
	at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:96)
	at org.codehaus.groovy.transform.traitx.Groovy11985.testSuperTraitPublicStaticIsPolymorphicSubTraitFirst(Groovy11985.groovy:140)
Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst() (:test in Build and test / lts (17, windows-latest, 1))
Assertion failed: 

assert D.greet() == 'override'
         |       |
         'base'  false

	at org.codehaus.groovy.runtime.InvokerHelper.createAssertError(InvokerHelper.java:400)
	at TestScript30.run(TestScript30.groovy:8)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:553)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:588)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:572)
	at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:106)
	at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:96)
	at org.codehaus.groovy.transform.traitx.Groovy11985.testSuperTraitPublicStaticIsPolymorphicSubTraitFirst(Groovy11985.groovy:140)
Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst() (:test in Build and test / lts (21, ubuntu-latest))
Assertion failed: 

assert D.greet() == 'override'
         |       |
         'base'  false

	at org.codehaus.groovy.runtime.InvokerHelper.createAssertError(InvokerHelper.java:400)
	at TestScript22.run(TestScript22.groovy:8)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:553)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:588)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:572)
	at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:106)
	at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:96)
	at org.codehaus.groovy.transform.traitx.Groovy11985.testSuperTraitPublicStaticIsPolymorphicSubTraitFirst(Groovy11985.groovy:140)
Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst() (:test in Build and test / lts (25, ubuntu-latest))
Assertion failed: 

assert D.greet() == 'override'
         |       |
         'base'  false

	at org.codehaus.groovy.runtime.InvokerHelper.createAssertError(InvokerHelper.java:400)
	at TestScript8.run(TestScript8.groovy:8)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:553)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:588)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:572)
	at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:106)
	at groovy.test.GroovyAssert.assertScript(GroovyAssert.java:96)
	at org.codehaus.groovy.transform.traitx.Groovy11985.testSuperTraitPublicStaticIsPolymorphicSubTraitFirst(Groovy11985.groovy:140)

Muted Tests

Select tests to mute in this pull request:

  • Groovy11985 > testSuperTraitPublicStaticIsPolymorphicSubTraitFirst()

Reuse successful test results:

  • ♻️ Only rerun the tests that failed or were muted before

Click the checkbox to trigger a rerun:

  • Rerun jobs

Learn more about TestLens at testlens.app.

@paulk-asert

Copy link
Copy Markdown
Contributor

AI said the PR broke ordering, so I added an existing test to show that - and the PR breaks now - but the fix should be easy.

AI read below:


Thanks for this — replacing the super-trait search with a fuller helper stub is a nice simplification, and modelling the not-yet-lowered helper as a real stub (rather than the empty GROOVY-7909 one) is the right direction for the code-assist/inferencing goal. It also happens to fix a latent bug the old empty stub had: in SuperCallTraitTransformer, isStatic is derived from the helper's methods, so T.super.staticMethod() in the not-yet-lowered window previously received this instead of this.class; a populated stub makes that window behave like the lowered case.

One behavioural concern before this lands, though: as written, the stub drops the original method's annotations, which makes @Virtual static dispatch depend on trait transform order — exactly the order-independence this PR's own javadoc says it preserves.

Why

HelperClassStub.helperMethod adds only @Implemented, whereas the real helper method (TraitASTTransformation.processMethod) copies all of the original method's annotations, including @Virtual. TraitReceiverTransformer.findConcreteMethod's result is then branched on by hasAnnotation(methodNode, VIRTUAL_TYPE) to choose dynamic dispatch vs. declarer-bound dispatch. So for this.m(x) where m is a @Virtual public static on a super trait:

  • super trait already lowered → real helper method carries @Virtual → dynamic-dispatch path ✔
  • super trait not yet lowered (stub window) → stub method lacks @Virtual → declarer-bound path ✘

The removed getDeclaredMethods fallback returned the original MethodNode with @Virtual intact, so it was order-independent here. The failure is silent — the implementer's static override just stops being visible from trait code depending on sibling-trait declaration order.

Reproducer

This is the sub-trait-first counterpart of the existing Groovy11985.testSuperTraitPublicStaticIsPolymorphic:

@Test
void testSuperTraitPublicStaticIsPolymorphicSubTraitFirst() {
    GroovyAssert.assertScript '''
        import groovy.transform.Virtual
        trait Mid extends Base { static String greet() { hello() } }   // sub-trait declared FIRST
        trait Base { @Virtual static String hello() { 'base' } }
        class C implements Mid {}
        class D implements Mid { static String hello() { 'override' } }
        assert C.greet() == 'base'
        assert D.greet() == 'override'
    '''
}

Suggested fix

Copy the original method's annotations onto the stub method, mirroring processMethod (which excludes @Override). No new imports needed — AnnotationNode and ClassHelper are already imported in Traits.java.

             var m = new MethodNode(method.getName(), mods, method.getReturnType(), helperParams, method.getExceptions(), null);
-            m.addAnnotation(Traits.IMPLEMENTED_CLASSNODE);
+            // Mirror processMethod's annotation copy: findConcreteMethod dispatches on
+            // @Virtual, so a stub method missing it silently picks declarer-bound dispatch.
+            for (AnnotationNode annotation : method.getAnnotations()) {
+                if (!annotation.getClassNode().equals(ClassHelper.OVERRIDE_TYPE)) {
+                    m.addAnnotation(annotation);
+                }
+            }
             m.setGenericsTypes(method.getGenericsTypes());

(Dropping the explicit @Implemented is intentional: the real helper methods don't carry it either — processMethod adds @Implemented to the original interface method, not to the helper copy.)

With this change the full trait suite still passes (TraitASTTransformationTest, TraitStaticDispatchMatrix, TraitGenericsMatrix, VirtualAnnotationTest, Groovy11985/12104/12105/12106/12112), and the reproducer above goes green.

Optional fidelity tweaks (not required to fix the bug)

Since the stub now feeds code-assist/inferencing, a couple of small alignments with processMethod/createSelfParameter would make stub signatures match the lowered ones:

  • self parameter: use trait.getPlainNodeReference() and the names $self / $static$self (Traits.THIS_OBJECT / Traits.STATIC_THIS_OBJECT) instead of the raw generic node and "traitImplementer" — avoids leaking unbound placeholders into Class<T<X>>;
  • add ACC_FINAL for final instance methods, as processMethod does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants