You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by "Paul King (Jira)" <ji...@apache.org> on 2026/10/20 01:27:02 UTC

[jira] [Closed] (GROOVY-12045) Calling an enclosing-class instance method on a static nested class instance throws IllegalArgumentException instead of MissingMethodException

     [ https://issues.apache.org/jira/browse/GROOVY-12045?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Paul King closed GROOVY-12045.
------------------------------

> Calling an enclosing-class instance method on a static nested class instance throws IllegalArgumentException instead of MissingMethodException
> ----------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12045
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12045
>             Project: Groovy
>          Issue Type: Bug
>          Components: groovy-runtime
>    Affects Versions: 6.0.0-alpha-1
>            Reporter: Leonard Brünings
>            Assignee: Eric Milles
>            Priority: Major
>             Fix For: 6.0.0-alpha-2
>
>
> This was discovered in the Spock Groovy 6 [integration branch|https://github.com/spockframework/spock/pull/2356].
> The analysis was done by Claude, but it seems reasonable to me. 
> I've verified that the reproducers work.
> h2. Summary
> Invoking an enclosing-class **instance** method on an instance of a **static nested class**
> throws `IllegalArgumentException: object is not an instance of declaring class` instead of the
> expected `MissingMethodException`. Regression introduced by the outer-method resolution added in
> GROOVY-11823 / GROOVY-11858 (the new `getNonClosureOuter` helper, `@since 6.0.0`).
> h2. Environment
> Groovy 6.0.0-alpha-1 and current master (`f6e2248d12`), JDK 17.
> h2. Description
> When a method is not found on a static nested class, Groovy now tries to resolve it on the
> *enclosing* class. For a static nested class there is no enclosing **instance**, so the runtime
> falls back to the enclosing **Class** object and then tries to invoke the (instance) method on
> that Class — which fails with a raw `IllegalArgumentException` from reflection rather than a
> clean `MissingMethodException`.
> This also breaks delegate-based dispatch: a closure with {{resolveStrategy = DELEGATE_FIRST}}
> whose delegate is a static nested class no longer falls through to the owner, because the
> exception thrown while probing the delegate is not a {{MissingMethodException}}.
> h3. Steps to reproduce
> {code:groovy}
> class Outer {
>   void foo() { println "foo() on ${this.class.simpleName}" }
>   static class StaticInner {}
>   void run() {
>     // (1) direct call of an outer *instance* method on a static-nested-class instance
>     println "--- direct ---"
>     try { new StaticInner().foo() } catch (Throwable t) { println "  ${t.class.simpleName}: ${t.message}" }
>     // (2) same thing via a DELEGATE_FIRST closure (delegate = static nested class, owner = Outer)
>     println "--- closure DELEGATE_FIRST ---"
>     Closure c = { foo() }
>     c.delegate = new StaticInner()
>     c.resolveStrategy = Closure.DELEGATE_FIRST
>     try { c() } catch (Throwable t) { println "  ${t.class.simpleName}: ${t.message}" }
>   }
> }
> new Outer().run()
> {code}
> h3. Expected (Groovy 5.0.x)
> {noformat}
> --- direct ---
>   MissingMethodException: No signature of method: foo for class: Outer$StaticInner ...
> --- closure DELEGATE_FIRST ---
> foo() on Outer
> {noformat}
> The static nested class has no {{foo}}, so the direct call misses cleanly, and the
> DELEGATE_FIRST closure falls through from the delegate to the owner ({{Outer}}).
> ### Actual (Groovy 6.0.0-alpha-1 and master)
> {noformat}
> --- direct ---
>   IllegalArgumentException: object is not an instance of declaring class
> --- closure DELEGATE_FIRST ---
>   IllegalArgumentException: object is not an instance of declaring class
> {noformat}
> h2. Root cause
> In {{groovy.lang.MetaClassImpl}} (master `f6e2248d12`):
> * {{invokeOuterMethod(...)}} (line ~1328) resolves a not-found method against the enclosing class
>   and invokes it via {{omc.invokeMethod(outerClass, target, methodName, ...)}} where
>   {{target = getOuterReference(sender, object)}}.
> * {{getOuterReference(Class innerClass, Object object)}} (line ~1344): for a **static** nested
>   class the {{this$0}} branch is skipped (line ~1347 guards on {{(modifiers & ACC_STATIC) == 0}}),
>   so {{outer}} stays {{null}} and it falls back to
>   {{outer = getNonClosureOuter(innerClass)}} (line ~1361), which returns the enclosing
>   **`Class`** object (line ~1369, {{@since 6.0.0}}).
> * {{invokeOuterMethod}} then invokes the enclosing **instance** method with the `Class` object as
>   the receiver, so reflection throws
>   {{IllegalArgumentException: object is not an instance of declaring class}}.
> A static nested class has no enclosing instance, so an enclosing **instance** method is simply not
> applicable and should yield a {{MissingMethodException}} (allowing DELEGATE_FIRST/owner fallback to
> proceed). Only enclosing **static** methods are legitimately callable via the `Class` target.
> h2. Impact
> Breaks frameworks that rely on closure delegate/owner fallback. Spock's {{with}} / {{verifyAll}}
> blocks dispatch implicit-`this` method conditions on the block closure (DELEGATE_FIRST). When the
> {{with}} target is a static nested class and the condition calls a method declared on the spec
> (the owner), Groovy 6 throws instead of resolving the owner method.
> Spock reproducer: `org.spockframework.smoke.WithBlocks."with works with void methods"`.



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