Skip to content

GROOVY-12581: SC: make dynamic calls on a closure delegate as normal method calls - #3107

Open
codeconsole wants to merge 1 commit into
apache:masterfrom
codeconsole:trait-sc-init-delegate
Open

codeconsole wants to merge 1 commit into
apache:masterfrom
codeconsole:trait-sc-init-delegate

Conversation

@codeconsole

Copy link
Copy Markdown
Contributor

{ log << new StringBuilder('a').with { append('b') }.toString() } in a @CompileStatic class now works, as it did in 4.0. Before, it failed with IncompatibleClassChangeError: Class java.lang.StringBuilder does not implement the requested interface groovy.lang.GroovyObject for any delegate that is not a Groovy object.

The type checker records when an implicit-this call in a closure goes to the delegate, and the static invocation writer then replaces the receiver with this.delegate, but kept the call adapter chosen for a call on this. The instance initializer blocks of a @CompileStatic class are type checked, but their calls get no method targets, so the call was made dynamically with ScriptBytecodeAdapter.invokeMethodOnCurrent, which takes a GroovyObject. Once the receiver has been replaced, the writer now makes such a call with the invokeMethod adapter, as for any other receiver. A toString() call inside such a closure goes to the closure itself under static compilation until GROOVY-12576 (#3102) is merged.

Tests: ClosuresSTCTest.testClosureDelegateInInstanceInitializer (also run statically compiled by ClosuresStaticCompileTest), which fails without the change. The groovy, gls, bugs and org.codehaus.groovy packages pass (15,512 tests), apart from three @Grab tests (CoercionSTCTest.testCoerceToFunctionalInterface14, also run by CoercionStaticCompileTests, and GenericsSTCTest.testSelfReferentialTypeParameter5) whose download failed while other builds ran; they pass when rerun. The Checkstyle and CodeNarc gates pass.

A regression in 5.0.0: 4.0.33 is correct, 5.0.8 and 6.0.0 are not.

…od calls

The type checker records when an implicit-this call in a closure goes to
the delegate, and the static invocation writer then replaces the
receiver with this.delegate. It kept the call adapter that was chosen
for a call on this, though. When the call has no method target and is
made dynamically, that adapter is ScriptBytecodeAdapter's
invokeMethodOnCurrent, which takes a GroovyObject, so a delegate of a
Java class failed at run time:

    @CompileStatic
    class C {
        List<String> log = [];
        { log << new StringBuilder('a').with { append('b'); toString() } }
    }

new C() failed with "IncompatibleClassChangeError: Class
java.lang.StringBuilder does not implement the requested interface
groovy.lang.GroovyObject". The instance initializer blocks of a
@CompileStatic class are type checked but their calls get no method
targets, so with, tap and other delegating closures in them hit this
with any delegate that is not a Groovy object.

Once the receiver has been replaced, the writer now makes such a call
with the invokeMethod adapter, as for any other receiver.
@testlens-app

testlens-app Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

🚨 All tests passed but jobs failed 🚨

Failed Jobs without Test Failures

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

🏷️ Commit: ac5dcd7
▶️ Tests: 102081 executed
⚪️ Checks: 26/26 completed

Rerun Controls

Click the checkbox to trigger a rerun:

  • Rerun jobs

Learn more about TestLens at testlens.app/docs.

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

GROOVY-12581: when SC rewrites an implicit-this call onto the closure delegate (tryImplicitReceiver), the call must not keep invokeMethodOnCurrent — that assumes a GroovyObject receiver and blows up with IncompatibleClassChangeError on StringBuilder / ArrayList. Remapping to invokeMethod restores Groovy 4 behavior.

testClosureDelegateInInstanceInitializer covers with/tap on Java types inside an instance initializer. Small, right layer.

CI: lts (17, windows-latest, 1) 60m timeout — re-run then APPROVE. Same StaticInvocationWriter conflict surface as #3102/#3103/#3106.

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.

2 participants