This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch GROOVY-11858 in repository https://gitbox.apache.org/repos/asf/groovy.git
commit a688de615839e5171e023855480d057531fedd19 Author: Eric Milles <[email protected]> AuthorDate: Sun Feb 15 09:41:45 2026 -0600 GROOVY-11858: defer non-closure method for in-closure implicit-this call --- src/main/java/groovy/lang/MetaClassImpl.java | 2 +- .../codehaus/groovy/runtime/metaclass/ClosureMetaClass.java | 13 +++++++++++++ src/test/groovy/gls/invocation/ClosureDelegationTest.groovy | 13 +++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java index ffeb8069c0..126509ff46 100644 --- a/src/main/java/groovy/lang/MetaClassImpl.java +++ b/src/main/java/groovy/lang/MetaClassImpl.java @@ -1265,7 +1265,7 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { return outer; } - private static Class<?> getNonClosureOuter(Class<?> c) { + protected static Class<?> getNonClosureOuter(Class<?> c) { do { c = c.getEnclosingClass(); } while (c != null && GeneratedClosure.class.isAssignableFrom(c)); diff --git a/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java b/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java index 247cbf2048..81c4733d53 100644 --- a/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java +++ b/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java @@ -261,8 +261,19 @@ public final class ClosureMetaClass extends MetaClassImpl { if (method == null) throw new MissingMethodException(methodName, theClass, theArguments, false); } + MetaMethod cmm = null; if (method == null && (resolveStrategy != Closure.DELEGATE_ONLY || !isInternalMethod(methodName))) { method = CLOSURE_METACLASS.pickMethod(methodName, argClasses); + // GROOVY-11858: delegate or owner may be desired target + if (method != null && resolveStrategy != Closure.TO_SELF + && sender == getNonClosureOuter(getTheClass())) { + CachedClass methodOwner = method.getDeclaringClass(); + if (methodOwner != getTheCachedClass() + && methodOwner.getTheClass() != Closure.class + && methodOwner.getTheClass() != GroovyObject.class) { + cmm= method; method= null; // try delegate or owner first + } + } } if (method != null) return method.doMethodInvoke(closure, theArguments); @@ -323,6 +334,8 @@ public final class ClosureMetaClass extends MetaClassImpl { } } + if (cmm != null) cmm.doMethodInvoke(closure, theArguments); + throw new MissingMethodException(methodName, theClass, theArguments, false); } diff --git a/src/test/groovy/gls/invocation/ClosureDelegationTest.groovy b/src/test/groovy/gls/invocation/ClosureDelegationTest.groovy index 77ca5d319f..3c0c8571c7 100644 --- a/src/test/groovy/gls/invocation/ClosureDelegationTest.groovy +++ b/src/test/groovy/gls/invocation/ClosureDelegationTest.groovy @@ -144,4 +144,17 @@ final class ClosureDelegationTest { assert Bar.visited == true ''' } + + // GROOVY-11858 + @Test + void testDelegateMethodVsClosureExtension() { + assertScript ''' + def array = 'a b c'.with { split() } + + assert array.length == 3 + assert array.contains('a') + assert array.contains('b') + assert array.contains('c') + ''' + } }
