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

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


The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
     new 4668a7681a GROOVY-4843, GROOVY-8560: box primitive array for spread 
invocation
4668a7681a is described below

commit 4668a7681a9b7ba26591bdfcec145d20b5f02eb4
Author: Eric Milles <[email protected]>
AuthorDate: Sat Mar 15 12:50:33 2025 -0500

    GROOVY-4843, GROOVY-8560: box primitive array for spread invocation
    
    4_0_X backport
---
 .../groovy/runtime/ScriptBytecodeAdapter.java      |   3 +-
 .../org/codehaus/groovy/vmplugin/v8/Selector.java  |  35 ++++---
 src/test/groovy/SpreadArgTest.groovy               | 106 +++++++++++++++++++++
 .../groovy/mock/interceptor/MockForJavaTest.groovy |  38 ++++++--
 4 files changed, 161 insertions(+), 21 deletions(-)

diff --git 
a/src/main/java/org/codehaus/groovy/runtime/ScriptBytecodeAdapter.java 
b/src/main/java/org/codehaus/groovy/runtime/ScriptBytecodeAdapter.java
index 9ff0f761e1..c044f08e97 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ScriptBytecodeAdapter.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ScriptBytecodeAdapter.java
@@ -911,7 +911,8 @@ public class ScriptBytecodeAdapter {
             } else if (value instanceof List) {
                 ret.addAll((List) value);
             } else if (value.getClass().isArray()) {
-                Collections.addAll(ret, 
DefaultTypeTransformation.primitiveArrayBox(value));
+                Collections.addAll(ret, 
value.getClass().getComponentType().isPrimitive()
+                        ? DefaultTypeTransformation.primitiveArrayBox(value) : 
(Object[]) value);
             } else {
                 String error = "Cannot spread the type " + 
value.getClass().getName() + " with value " + value;
                 if (value instanceof Map) {
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java 
b/src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java
index c0fa515a74..30c6434346 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java
@@ -50,6 +50,7 @@ import 
org.codehaus.groovy.runtime.metaclass.MethodMetaProperty;
 import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod;
 import org.codehaus.groovy.runtime.metaclass.NewStaticMetaMethod;
 import org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod;
+import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
 import org.codehaus.groovy.runtime.wrappers.Wrapper;
 import org.codehaus.groovy.vmplugin.VMPluginFactory;
 
@@ -154,12 +155,24 @@ public abstract class Selector {
      * number arguments.
      */
     private static Object[] spread(Object[] args, boolean spreadCall) {
-        if (!spreadCall) return args;
-        Object[] normalArguments = (Object[]) args[1];
-        Object[] ret = new Object[normalArguments.length + 1];
-        ret[0] = args[0];
-        System.arraycopy(normalArguments, 0, ret, 1, ret.length - 1);
-        return ret;
+        Object[] result = args;
+        if (spreadCall) {
+            Object[] arguments = (Object[]) args[1];
+            final int nArguments = arguments.length;
+
+            result = new Object[nArguments + 1];
+            result[0] = args[0]; // the receiver
+            System.arraycopy(arguments, 0, result, 1, nArguments);
+
+            // accommodate Object[] spreader m. handle
+            for (int i = 1; i <= nArguments; i += 1) {
+                Class argumentType = result[i] != null ? result[i].getClass() 
: Object.class;
+                if (argumentType.isArray() && 
argumentType.getComponentType().isPrimitive()) {
+                    result[i] = 
DefaultTypeTransformation.primitiveArrayBox(result[i]); // GROOVY-4843, 
GROOVY-8560
+                }
+            }
+        }
+        return result;
     }
 
     private static class CastSelector extends MethodSelector {
@@ -523,7 +536,7 @@ public abstract class Selector {
             this.safeNavigation = safeNavigation && arguments[0] == null;
             this.thisCall = thisCall;
             this.spread = spreadCall;
-            this.cache = !spread;
+            this.cache = !spreadCall;
 
             if (LOG_ENABLED) {
                 StringBuilder msg =
@@ -762,8 +775,7 @@ public abstract class Selector {
             Class<?>[] params = handle.type().parameterArray();
             if (currentType != null) params = currentType.parameterArray();
             if (!isVargs) {
-                if (spread && useMetaClass) return;
-                if (params.length == 2 && args.length == 1) {
+                if (!(spread && useMetaClass) && params.length == 2 && 
args.length == 1) {
                     handle = MethodHandles.insertArguments(handle, 1, 
SINGLE_NULL_ARRAY);
                 }
                 return;
@@ -860,8 +872,9 @@ public abstract class Selector {
         }
 
         public void correctSpreading() {
-            if (!spread || useMetaClass || skipSpreadCollector) return;
-            handle = handle.asSpreader(Object[].class, args.length - 1);
+            if (spread && !useMetaClass && !skipSpreadCollector) {
+                handle = handle.asSpreader(Object[].class, args.length - 1);
+            }
         }
 
         /**
diff --git a/src/test/groovy/SpreadArgTest.groovy 
b/src/test/groovy/SpreadArgTest.groovy
new file mode 100644
index 0000000000..d08cdd3814
--- /dev/null
+++ b/src/test/groovy/SpreadArgTest.groovy
@@ -0,0 +1,106 @@
+/*
+ *  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
+
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+/**
+ * Tests for the spread arg(s) operator "m(*x)".
+ */
+final class SpreadArgTest {
+
+    // GROOVY-9515
+    @Test
+    void testSpreadList() {
+        assertScript '''
+            int f(int x, int y) { x + y }
+            int f(int x) { x }
+            int g(x) { f(*x) }
+
+            assert g([1]) == 1
+            assert g([1, 2]) == 3
+        '''
+    }
+
+    @Test
+    void testSpreadArray() {
+        assertScript '''
+            int f(int x, int y, int z) {
+                x + y + z
+            }
+
+            Number[] nums = [1, 2, 39]
+            assert f(*nums) == 42
+        '''
+    }
+
+    // GROOVY-8560
+    @Test
+    void testSpreadArray2() {
+        assertScript '''
+            def f(byte[] bytes) {
+                assert bytes.length == 2
+            }
+
+            def ba = new byte[]{0, 0}
+            def oa = new Object[]{ba}
+            f(oa[0])
+            f( *oa )
+        '''
+    }
+
+    // GROOVY-5647
+    @Test
+    void testSpreadSkipSTC() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+            import static groovy.transform.TypeCheckingMode.SKIP
+
+            @CompileStatic
+            class C {
+                @CompileStatic(SKIP)
+                def foo(fun, args) {
+                    new Runnable() { // create an anonymous class which should 
*not* be visited
+                        void run() {
+                            fun(*args) // spread operator is disallowed with 
STC/SC, but SKIP should prevent from an error
+                        }
+                    }
+                }
+            }
+
+            new C()
+        '''
+    }
+
+    @Test
+    void testSpreadVarargs() {
+        assertScript '''
+            int f(String... strings) {
+                g(*strings)
+            }
+            int g(String... strings) {
+                strings.length
+            }
+
+            assert f("1","2") == 2
+        '''
+    }
+}
diff --git a/src/test/groovy/mock/interceptor/MockForJavaTest.groovy 
b/src/test/groovy/mock/interceptor/MockForJavaTest.groovy
index 40d2e3c978..be617e88ff 100644
--- a/src/test/groovy/mock/interceptor/MockForJavaTest.groovy
+++ b/src/test/groovy/mock/interceptor/MockForJavaTest.groovy
@@ -18,20 +18,22 @@
  */
 package groovy.mock.interceptor
 
-import groovy.test.GroovyTestCase
+import org.junit.Test
 
-class MockForJavaTest extends GroovyTestCase {
+final class MockForJavaTest {
+
+    @Test
     void testIterator() {
         def iteratorContext = new MockFor(Iterator)
         iteratorContext.demand.hasNext() { true }
         iteratorContext.demand.hasNext() { true }
         iteratorContext.demand.hasNext() { false }
         def iterator = iteratorContext.proxyDelegateInstance()
-        iteratorContext.demand.next() { "foo" }
+        iteratorContext.demand.next() { 'foo' }
         def iterator2 = iteratorContext.proxyDelegateInstance()
 
         assert new IteratorCounter().count(iterator2) == 2
-        assert iterator2.next() == "foo"
+        assert iterator2.next() == 'foo'
         iteratorContext.verify(iterator2)
 
         assert new IteratorCounter().count(iterator) == 2
@@ -45,13 +47,31 @@ class MockForJavaTest extends GroovyTestCase {
         iteratorContext.verify(iterator3)
     }
 
+    // GROOVY-4843
+    @Test
+    void testStream() {
+        def streamContext = new MockFor(FileInputStream)
+        streamContext.demand.read(1..1) { byte[] b ->
+            b[0] = 1
+            b[1] = 2
+            return 2
+        }
+        def p = System.getProperty('os.name').contains('Windows') ? 'NUL' : 
'/dev/null'
+        def s = streamContext.proxyDelegateInstance(p)
+        byte[] buffer = new byte[2]
+        assert s.read(buffer) == 2
+        assert buffer[0] == 1
+        assert buffer[1] == 2
+        streamContext.verify(s)
+    }
+
+    @Test
     void testString() {
         def stringContext = new MockFor(String)
-        stringContext.demand.endsWith(2..2) { String arg -> arg == "foo" }
+        stringContext.demand.endsWith(2..2) { String arg -> arg == 'foo' }
         def s = stringContext.proxyDelegateInstance()
-        assert !s.endsWith("bar")
-        assert s.endsWith("foo")
+        assert !s.endsWith('bar')
+        assert s.endsWith('foo')
         stringContext.verify(s)
     }
-
-}
\ No newline at end of file
+}

Reply via email to