Skip to content

GROOVY-12519: resolve super.name() and super.name to a static member the class hides - #3045

Open
codeconsole wants to merge 5 commits into
apache:masterfrom
codeconsole:super-hidden-static-member
Open

codeconsole wants to merge 5 commits into
apache:masterfrom
codeconsole:super-hidden-static-member

Conversation

@codeconsole

@codeconsole codeconsole commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

When a class declares a static method or property with the same signature as its superclass, super.sm() threw MissingMethodException in dynamic Groovy and under @TypeChecked, or called another overload with null, and super.sp threw MissingPropertyException. Under @CompileStatic, super.sm() in a closure or lambda failed with IncompatibleClassChangeError. All of them now reach the superclass's static member, as in javac. Private static methods of the superclass stay out of reach. The same change fixes super.first() in a closure or lambda of an ArrayList subclass under @CompileStatic, which failed with NoSuchMethodError: an extension method is a static method too, so it needs no super$ helper either.

class Base { static String sm() { 'base' } }
class Sub extends Base {
    static String sm() { 'sub' }
    String test() { super.sm() }  // threw MissingMethodException; now 'base'
}

How

The meta-class drops a method the class declares itself from the methods available to super calls, and nothing took the place of a static method that hides one of the superclass. A super call now finds the hidden static method of the nearest superclass that has one; the super property index is built from the same methods, so the static property accessors resolve too. Under @CompileStatic, a static target needs no super$ helper in a closure: the call is an invokestatic, valid from any class.

Behaviour change

super.sm() and super.sp for a static member the class hides reach the superclass's member in dynamic Groovy and under @TypeChecked, where they failed or picked another overload before.

Tests

groovy.ThisAndSuperTest, in all three modes, including closures and lambdas, a static property read and written through super, a hidden method two levels up, the private case, and extension methods called through super from a closure and a lambda. They fail without the change. 14,938 tests in the groovy, gls, bugs, org.codehaus.groovy.classgen, transform, runtime, reflection, vmplugin, control and ast packages and the stub generator tests pass, apart from bugs.Groovy12363, which fails on master too when it runs after bugs.Groovy12142 in the same JVM (#3096 fixes it). That run predates the last commit, which only adds the extension method test; groovy.ThisAndSuperTest passes with it. groovy-contracts, groovy-typecheckers, groovy-ginq, groovy-macro-library, groovy-json, groovy-xml and groovy-templates pass their tests, and every subproject compiles its main and test sources. The Checkstyle and CodeNarc gates pass.

…the class hides

When a class declares a static method with the signature of a static
method of its superclass, the meta-class dropped the subclass method from
the methods available to super calls, as it does for any method declared
by the class itself, and nothing took its place: super.sm() threw
MissingMethodException, or picked another overload, and super.sp threw
MissingPropertyException for a static property. A super call now finds
the hidden static method of the nearest superclass that has one, as javac
does; the super property index is built from the same methods, so the
static property accessors resolve too. Private static methods of the
superclass stay out of reach.

Under static compilation, super.sm() in a closure or lambda went through a
super$ helper that called the static method with invokespecial
(IncompatibleClassChangeError). A static target needs no helper: the call
is an invokestatic, valid from any class.
… lambdas

The type checker no longer asks for a super$ helper for a static
target, and an extension method is a static method too. Statically
compiled, super.first() in a closure or super.last() in a lambda of an
ArrayList subclass called the extension method through a helper that
invoked ArrayList.first() with invokespecial, and failed with
NoSuchMethodError; 6.0.0 and master still do. The call is now a direct
static call.
@codecov-commenter

codecov-commenter commented Oct 26, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.6811%. Comparing base (3869743) to head (61bd8cf).

Files with missing lines Patch % Lines
src/main/java/groovy/lang/MetaClassImpl.java 84.6154% 0 Missing and 2 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##               master      #3045        +/-   ##
==================================================
+ Coverage     73.6728%   73.6811%   +0.0083%     
- Complexity      42587      42594         +7     
==================================================
  Files            1678       1678                
  Lines          146913     146925        +12     
  Branches        27792      27800         +8     
==================================================
+ Hits           108235     108256        +21     
+ Misses          28526      28519         -7     
+ Partials        10152      10150         -2     
Files with missing lines Coverage Δ
...roovy/transform/stc/StaticTypeCheckingVisitor.java 87.4916% <100.0000%> (+0.0253%) ⬆️
src/main/java/groovy/lang/MetaClassImpl.java 79.1805% <84.6154%> (+0.0162%) ⬆️

... and 9 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.

@daniellansun daniellansun 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.

Thanks for aligning dynamic super resolution and the SC closure path with javac for hidden static members.

Observations:

  1. Replacing a not-super-useful static declared on c with getHiddenStaticMethod in the MOP rewrite is the right place: it feeds both call sites and the super property index without a separate property path.

  2. Accessibility (public / protected / package-private same package, private out of reach) matches what we want for super static lookup. The private-static test is a useful guard.

  3. Short-circuiting checkSuperCallFromClosure for static targets avoids emitting a useless super$ helper and fixes the IncompatibleClassChangeError / extension-method NoSuchMethodError cases. Treating DGM as static here is consistent.

  4. The parameterized ThisAndSuperTest coverage (methods, properties, Mid→Base vs Low→Hider, closures/lambdas) makes the intended matrix easy to re-check.

Optional (non-blocking): a note in release notes that super.sm() / super.sp for a hidden static now succeed where they previously threw or picked another overload would help anyone who relied on the old failure. I have no blocking concerns on this tip.

@codeconsole

Copy link
Copy Markdown
Contributor Author

Added the note to COMPATIBILITY.md in 838b579, after merging master in. Thanks.

…-member

# Conflicts:
#	src/test/groovy/groovy/ThisAndSuperTest.groovy
@testlens-app

testlens-app Bot commented Oct 30, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 61bd8cf
▶️ Tests: 124191 executed
⚪️ Checks: 27/27 completed


Learn more about TestLens at testlens.app/docs.

This branch has not been deployed

No deployments
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