You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2020/07/28 00:04:34 UTC
[groovy] 01/03: GROOVY-7232: check resolve strategy of each closure
during method search
This is an automated email from the ASF dual-hosted git repository.
sunlan pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 4d04c10b5d81b7b70352cf9407ba7d664de77fb4
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Jul 27 12:03:20 2020 -0500
GROOVY-7232: check resolve strategy of each closure during method search
closes #1302
(cherry picked from commit a01d557f05310660f3168a72cfb6635bbd818596)
---
.../groovy/runtime/metaclass/ClosureMetaClass.java | 76 +++++++++++-----------
src/test/gls/closures/ResolveStrategyTest.groovy | 38 +++++------
src/test/groovy/lang/ClosureResolvingTest.groovy | 41 ++++++++++++
3 files changed, 98 insertions(+), 57 deletions(-)
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 af0bf2b..1b97ef9 100644
--- a/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java
+++ b/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java
@@ -47,7 +47,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -244,6 +243,7 @@ public final class ClosureMetaClass extends MetaClassImpl {
MetaMethod method = null;
final Closure closure = (Closure) object;
+ final int resolveStrategy = closure.getResolveStrategy();
if (CLOSURE_DO_CALL_METHOD.equals(methodName) || CLOSURE_CALL_METHOD.equals(methodName)) {
method = pickClosureMethod(argClasses);
@@ -255,7 +255,7 @@ public final class ClosureMetaClass extends MetaClassImpl {
if (method == null) throw new MissingMethodException(methodName, theClass, arguments, false);
}
- boolean shouldDefer = closure.getResolveStrategy() == Closure.DELEGATE_ONLY && isInternalMethod(methodName);
+ boolean shouldDefer = resolveStrategy == Closure.DELEGATE_ONLY && isInternalMethod(methodName);
if (method == null && !shouldDefer) {
method = CLOSURE_METACLASS.pickMethod(methodName, argClasses);
}
@@ -267,7 +267,6 @@ public final class ClosureMetaClass extends MetaClassImpl {
final Object owner = closure.getOwner();
final Object delegate = closure.getDelegate();
final Object thisObject = closure.getThisObject();
- final int resolveStrategy = closure.getResolveStrategy();
boolean invokeOnDelegate = false;
boolean invokeOnOwner = false;
boolean ownerFirst = true;
@@ -288,48 +287,21 @@ public final class ClosureMetaClass extends MetaClassImpl {
if (method == null) {
invokeOnOwner = owner != closure && (owner instanceof GroovyObject);
}
-
break;
- case Closure.DELEGATE_FIRST:
- method = getDelegateMethod(closure, delegate, methodName, argClasses);
- callObject = delegate;
- if (method == null) {
- method = getDelegateMethod(closure, owner, methodName, argClasses);
- callObject = owner;
- }
- if (method == null) {
- invokeOnDelegate = delegate != closure && (delegate instanceof GroovyObject);
- invokeOnOwner = owner != closure && (owner instanceof GroovyObject);
- ownerFirst = false;
- }
- break;
- default: // owner first
- // owner first means we start with the outer most owner that is not a generated closure
- // this owner is equal to the this object, so we check that one first.
- method = getDelegateMethod(closure, thisObject, methodName, argClasses);
- callObject = thisObject;
- if (method == null) {
- // try finding a delegate that has that method... we start from
- // outside building a stack and try each delegate
- LinkedList list = new LinkedList();
- for (Object current = closure; current != thisObject;) {
- if (!(current instanceof Closure)) break;
- Closure currentClosure = (Closure) current;
- if (currentClosure.getDelegate() != null) list.add(current);
- current = currentClosure.getOwner();
- }
-
- while (!list.isEmpty() && method == null) {
- Closure closureWithDelegate = (Closure) list.removeLast();
- Object currentDelegate = closureWithDelegate.getDelegate();
- method = getDelegateMethod(closureWithDelegate, currentDelegate, methodName, argClasses);
- callObject = currentDelegate;
+ default: // Closure.*_FIRST:
+ for (Object candidate : ownersAndDelegatesOf(closure, new ArrayList<>())) {
+ method = getDelegateMethod(closure, candidate, methodName, argClasses);
+ callObject = candidate;
+ if (method != null) {
+ break;
}
}
if (method == null) {
invokeOnDelegate = delegate != closure && (delegate instanceof GroovyObject);
invokeOnOwner = owner != closure && (owner instanceof GroovyObject);
+ ownerFirst = resolveStrategy != Closure.DELEGATE_FIRST;
}
+ break;
}
if (method == null && (invokeOnOwner || invokeOnDelegate)) {
try {
@@ -379,6 +351,34 @@ public final class ClosureMetaClass extends MetaClassImpl {
return arguments;
}
+ private static List ownersAndDelegatesOf(Closure closure, List ownersAndDelegates) {
+ switch (closure.getResolveStrategy()) {
+ case Closure.TO_SELF:
+ ownersAndDelegates.add(closure);
+ break;
+ case Closure.OWNER_ONLY:
+ ownersAndDelegates.add(closure.getOwner());
+ break;
+ case Closure.DELEGATE_ONLY:
+ ownersAndDelegates.add(closure.getDelegate());
+ break;
+ case Closure.DELEGATE_FIRST:
+ ownersAndDelegates.add(closure.getDelegate());
+ ownersAndDelegates.add(closure.getOwner());
+ if (closure.getOwner() instanceof Closure) {
+ ownersAndDelegatesOf((Closure) closure.getOwner(), ownersAndDelegates);
+ }
+ break;
+ default: // Closure.OWNER_FIRST:
+ ownersAndDelegates.add(closure.getOwner());
+ if (closure.getOwner() instanceof Closure) {
+ ownersAndDelegatesOf((Closure) closure.getOwner(), ownersAndDelegates);
+ }
+ ownersAndDelegates.add(closure.getDelegate());
+ }
+ return ownersAndDelegates;
+ }
+
private static Throwable unwrap(GroovyRuntimeException gre) {
Throwable th = gre;
if (th.getCause() != null && th.getCause() != gre) th = th.getCause();
diff --git a/src/test/gls/closures/ResolveStrategyTest.groovy b/src/test/gls/closures/ResolveStrategyTest.groovy
index e9cb637..1a8111d 100644
--- a/src/test/gls/closures/ResolveStrategyTest.groovy
+++ b/src/test/gls/closures/ResolveStrategyTest.groovy
@@ -20,30 +20,31 @@ package gls.closures
import groovy.test.GroovyTestCase
import groovy.transform.CompileStatic
+
import static groovy.lang.Closure.*
class ResolveStrategyTest extends GroovyTestCase {
void testDynamicSettingOfResolveStrategy() {
new MyClass().with {
- assert run(DELEGATE_ONLY) == 12340000 // (*)
- assert run(DELEGATE_FIRST) == 12040030
assert run(OWNER_ONLY) == 1234 // (*)
- assert run(OWNER_FIRST) == 41230
-
- assert runOwnerOnly { m1() + m2() + m3() } == 1230
assert runOwnerOnly { m1() + m2() + m3() + m4() } == 1234 // (*)
- assert runOwnerFirst { m1() + m2() + m3() + m4() } == 41230
+
+ assert run(DELEGATE_ONLY) == 12340000 // (*)
assert runDelegateOnly { m1() + m2() + m3() + m4() } == 12340000 // (*)
- assert runDelegateOnly { m1() + m2() + m4() } == 12040000
- assert runDelegateFirst { m1() + m2() + m3() + m4() } == 12340000 // (**)
+
+ // (*) involves methodMissing as forced by ONLY strategy (no equivalent CS case below)
+
+ assert run(OWNER_FIRST) == 41230
+ assert runOwnerFirst { m1() + m2() + m3() + m4() } == 41230
+
+ assert run(DELEGATE_FIRST) == 12040030
+ assert runDelegateFirst { m1() + m2() + m3() + m4() } == 12040030
// nested cases
- assert runDelegateFirst { runOwnerFirst { m1() + m2() + m3() + m4() } } == 41230
- assert runOwnerFirst { runDelegateFirst { m1() + m2() + m3() + m4() } } == 12340000 // (**)
assert runOwnerFirst { runOwnerFirst { m1() + m2() + m3() + m4() } } == 41230
- assert runDelegateFirst { runDelegateFirst { m1() + m2() + m3() + m4() } } == 12340000 // (**)
- // (*) involves methodMissing as forced by ONLY strategy (no equivalent CS case below)
- // (**) involves methodMissing since delegate methodMissing has precedence over explicit owner method
+ assert runOwnerFirst { runDelegateFirst { m1() + m2() + m3() + m4() } } == 12040030
+ assert runDelegateFirst { runOwnerFirst { m1() + m2() + m3() + m4() } } == 12040030
+ assert runDelegateFirst { runDelegateFirst { m1() + m2() + m3() + m4() } } == 12040030
}
}
@@ -53,14 +54,13 @@ class ResolveStrategyTest extends GroovyTestCase {
assert runOwnerOnly { m1() + m2() + m3() } == 1230
assert runOwnerFirst { m1() + m2() + m3() + m4() } == 41230
assert runDelegateOnly { m1() + m2() + m4() } == 12040000
- assert runDelegateFirst { m1() + m2() + m3() + m4() } == 12040030 // (*)
+ assert runDelegateFirst { m1() + m2() + m3() + m4() } == 12040030
- // nested cases (GROOVY-9086)
- assert runDelegateFirst { runOwnerFirst { m1() + m2() + m3() + m4() } } == 12040030 // (*)
- assert runOwnerFirst { runDelegateFirst { m1() + m2() + m3() + m4() } } == 12040030 // (*)
+ // GROOVY-9086: nested cases
assert runOwnerFirst { runOwnerFirst { m1() + m2() + m3() + m4() } } == 41230
- assert runDelegateFirst { runDelegateFirst { m1() + m2() + m3() + m4() } } == 12040030 // (*)
- // (*) different to dynamic since static assumes no methodMissing
+ assert runDelegateFirst { runOwnerFirst { m1() + m2() + m3() + m4() } } == 12040030
+ assert runOwnerFirst { runDelegateFirst { m1() + m2() + m3() + m4() } } == 12040030
+ assert runDelegateFirst { runDelegateFirst { m1() + m2() + m3() + m4() } } == 12040030
}
}
}
diff --git a/src/test/groovy/lang/ClosureResolvingTest.groovy b/src/test/groovy/lang/ClosureResolvingTest.groovy
index 69d2932..e266647 100644
--- a/src/test/groovy/lang/ClosureResolvingTest.groovy
+++ b/src/test/groovy/lang/ClosureResolvingTest.groovy
@@ -203,6 +203,47 @@ class ClosureResolvingTest extends GroovyTestCase {
cout()
}
+ // GROOVY-7232
+ void testOwnerDelegateChain2() {
+ assertScript '''
+ def outer = { ->
+ def inner = { ->
+ [x, keySet()]
+ }
+ inner.resolveStrategy = Closure.DELEGATE_ONLY
+ inner.delegate = [x: 1, f: 0]
+ inner.call()
+ }
+ //outer.resolveStrategy = Closure.OWNER_FIRST
+ outer.delegate = [x: 0, g: 0]
+ def result = outer.call()
+
+ assert result.flatten() == [1, 'x', 'f']
+ '''
+ }
+
+ // GROOVY-7232
+ void testOwnerDelegateChain3() {
+ assertScript '''
+ def outer = { ->
+ def inner = { ->
+ def inner_inner = { ->
+ [x, keySet()]
+ }
+ //inner_inner.resolveStrategy = Closure.OWNER_FIRST
+ return inner_inner.call()
+ }
+ inner.resolveStrategy = Closure.DELEGATE_ONLY
+ inner.delegate = [x: 1, f: 0]
+ inner()
+ }
+ //outer.resolveStrategy = Closure.OWNER_FIRST
+ outer.delegate = [x: 0, g: 0]
+ def result = outer.call()
+
+ assert result.flatten() == [1, 'x', 'f']
+ '''
+ }
}
class TestResolve1 {