[groovy] branch GROOVY_3_0_X updated: GROOVY-9779: before missing method, try "call" method of property value (closes #1403)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[groovy] branch GROOVY_3_0_X updated: GROOVY-9779: before missing method, try "call" method of property value (closes #1403)

paulk
This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push:
     new 9f773c6  GROOVY-9779: before missing method, try "call" method of property value (closes #1403)
9f773c6 is described below

commit 9f773c65b6a095252ead5da816366022762d0bb2
Author: Eric Milles <[hidden email]>
AuthorDate: Mon Oct 12 12:01:12 2020 -0500

    GROOVY-9779: before missing method, try "call" method of property value (closes #1403)
   
    - this change expands upon the check for Script binding variable "call"
---
 src/main/java/groovy/lang/MetaClassImpl.java | 55 ++++++++++++++++------------
 src/test/groovy/bugs/Groovy9779.groovy       | 43 ++++++++++++++++++++++
 2 files changed, 74 insertions(+), 24 deletions(-)

diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java
index 019130b..aebd79c 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -133,10 +133,11 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
     protected static final String PROPERTY_MISSING = "propertyMissing";
     protected static final String INVOKE_METHOD_METHOD = "invokeMethod";
 
-    private static final String CLOSURE_CALL_METHOD = "call";
-    private static final String CLOSURE_DO_CALL_METHOD = "doCall";
+    private static final String CALL_METHOD = "call";
+    private static final String DO_CALL_METHOD = "doCall";
     private static final String GET_PROPERTY_METHOD = "getProperty";
     private static final String SET_PROPERTY_METHOD = "setProperty";
+
     private static final Class[] METHOD_MISSING_ARGS = new Class[]{String.class, Object.class};
     private static final Class[] GETTER_MISSING_ARGS = new Class[]{String.class};
     private static final Class[] SETTER_MISSING_ARGS = METHOD_MISSING_ARGS;
@@ -1135,7 +1136,7 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
             final Closure closure = (Closure) object;
             final Object owner = closure.getOwner();
 
-            if (CLOSURE_CALL_METHOD.equals(methodName) || CLOSURE_DO_CALL_METHOD.equals(methodName)) {
+            if (CALL_METHOD.equals(methodName) || DO_CALL_METHOD.equals(methodName)) {
                 final Class objectClass = object.getClass();
                 if (objectClass == MethodClosure.class) {
                     return this.invokeMethodClosure(object, arguments);
@@ -1272,8 +1273,8 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
 
     private MetaMethod getMetaMethod(Class sender, Object object, String methodName, boolean isCallToSuper, Object... arguments) {
         MetaMethod method = null;
-        if (CLOSURE_CALL_METHOD.equals(methodName) && object instanceof GeneratedClosure) {
-            method = getMethodWithCaching(sender, "doCall", arguments, isCallToSuper);
+        if (CALL_METHOD.equals(methodName) && object instanceof GeneratedClosure) {
+            method = getMethodWithCaching(sender, DO_CALL_METHOD, arguments, isCallToSuper);
         }
         if (method == null) {
             method = getMethodWithCaching(sender, methodName, arguments, isCallToSuper);
@@ -1309,30 +1310,36 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
         };
     }
 
-    private Object invokePropertyOrMissing(Object object, String methodName, Object[] originalArguments, boolean fromInsideClass, boolean isCallToSuper) {
-        // if no method was found, try to find a closure defined as a field of the class and run it
+    /**
+     * Tries to find a callable property and make the call.
+     */
+    private Object invokePropertyOrMissing(final Object object, final String methodName, final Object[] originalArguments, final boolean fromInsideClass, final boolean isCallToSuper) {
+        MetaProperty metaProperty = this.getMetaProperty(methodName, false);
+
         Object value = null;
-        final MetaProperty metaProperty = this.getMetaProperty(methodName, false);
         if (metaProperty != null) {
             value = metaProperty.getProperty(object);
-        } else {
-            if (object instanceof Map)
-                value = ((Map) object).get(methodName);
+        } else if (object instanceof Map) {
+            value = ((Map<?, ?>) object).get(methodName);
+        } else if (object instanceof Script) {
+            value = ((Script) object).getBinding().getVariables().get(methodName);
         }
 
-        if (value instanceof Closure) {  // This test ensures that value != this If you ever change this ensure that value != this
-            Closure closure = (Closure) value;
-            MetaClass delegateMetaClass = closure.getMetaClass();
-            return delegateMetaClass.invokeMethod(closure.getClass(), closure, CLOSURE_DO_CALL_METHOD, originalArguments, false, fromInsideClass);
+        if (value instanceof Closure) {
+            Closure<?> closure = (Closure<?>) value;
+            MetaClass metaClass = closure.getMetaClass();
+            return metaClass.invokeMethod(closure.getClass(), closure, DO_CALL_METHOD, originalArguments, false, fromInsideClass);
         }
 
-        if (object instanceof Script) {
-            Object bindingVar = ((Script) object).getBinding().getVariables().get(methodName);
-            if (bindingVar != null) {
-                MetaClass bindingVarMC = ((MetaClassRegistryImpl) registry).getMetaClass(bindingVar);
-                return bindingVarMC.invokeMethod(bindingVar, CLOSURE_CALL_METHOD, originalArguments);
+        if (value != null && !(value instanceof Map)) {
+            try {
+                MetaClass metaClass = ((MetaClassRegistryImpl) registry).getMetaClass(value);
+                return metaClass.invokeMethod(value, CALL_METHOD, originalArguments); // delegate to call method of property value
+            } catch (MissingMethodException mme) {
+                // ignore
             }
         }
+
         return invokeMissingMethod(object, methodName, originalArguments, null, isCallToSuper);
     }
 
@@ -1553,7 +1560,7 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
 
         if (prop != null) {
             MetaClass propMC = registry.getMetaClass(prop.getClass());
-            return propMC.invokeMethod(prop, CLOSURE_CALL_METHOD, arguments);
+            return propMC.invokeMethod(prop, CALL_METHOD, arguments);
         }
 
         return invokeStaticMissingMethod(sender, methodName, arguments);
@@ -1562,7 +1569,7 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
     private static Object invokeStaticClosureProperty(Object[] originalArguments, Object prop) {
         Closure closure = (Closure) prop;
         MetaClass delegateMetaClass = closure.getMetaClass();
-        return delegateMetaClass.invokeMethod(closure.getClass(), closure, CLOSURE_DO_CALL_METHOD, originalArguments, false, false);
+        return delegateMetaClass.invokeMethod(closure.getClass(), closure, DO_CALL_METHOD, originalArguments, false, false);
     }
 
     private Object invokeStaticMissingMethod(Class sender, String methodName, Object[] arguments) {
@@ -3547,13 +3554,13 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
         if (!GroovyCategorySupport.hasCategoryInCurrentThread() && !(this instanceof AdaptingMetaClass)) {
             Class[] params = MetaClassHelper.convertToTypeArray(args);
             CallSite tempSite = site;
-            if (site.getName().equals("call") && GeneratedClosure.class.isAssignableFrom(theClass)) {
+            if (site.getName().equals(CALL_METHOD) && GeneratedClosure.class.isAssignableFrom(theClass)) {
                 // here, we want to point to a method named "doCall" instead of "call"
                 // but we don't want to replace the original call site name, otherwise
                 // we loose the fact that the original method name was "call" so instead
                 // we will point to a metamethod called "doCall"
                 // see GROOVY-5806 for details
-                tempSite = new AbstractCallSite(site.getArray(), site.getIndex(), "doCall");
+                tempSite = new AbstractCallSite(site.getArray(), site.getIndex(), DO_CALL_METHOD);
             }
             MetaMethod metaMethod = getMethodWithCachingInternal(theClass, tempSite, params);
             if (metaMethod != null)
diff --git a/src/test/groovy/bugs/Groovy9779.groovy b/src/test/groovy/bugs/Groovy9779.groovy
new file mode 100644
index 0000000..c2b0623
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy9779.groovy
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.bugs
+
+import groovy.transform.CompileStatic
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+@CompileStatic
+final class Groovy9779 {
+    @Test
+    void testCallOperatorOnDynamicProperties() {
+        assertScript '''
+            class C {
+                def call() { return 42 }
+            }
+            class D {
+                static final x = new C()
+                       final y = new C()
+            }
+            assert D.x() == 42
+            assert new D().x() == 42
+            assert new D().y() == 42
+        '''
+    }
+}