GROOVY-11060: support spread array elements: `[1, *numbers, n] as int[]`
diff --git a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
index e8205bf..2049d46 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
@@ -96,6 +96,7 @@
 import org.codehaus.groovy.ast.tools.WideningCategories;
 import org.codehaus.groovy.classgen.asm.BytecodeHelper;
 import org.codehaus.groovy.classgen.asm.BytecodeVariable;
+import org.codehaus.groovy.classgen.asm.CompileStack;
 import org.codehaus.groovy.classgen.asm.MethodCaller;
 import org.codehaus.groovy.classgen.asm.MethodCallerMultiAdapter;
 import org.codehaus.groovy.classgen.asm.MopWriter;
@@ -158,6 +159,7 @@
 import static org.codehaus.groovy.transform.SealedASTTransformation.sealedNative;
 import static org.codehaus.groovy.transform.SealedASTTransformation.sealedSkipAnnotation;
 import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PROPERTY_OWNER;
+import static org.objectweb.asm.Opcodes.AALOAD;
 import static org.objectweb.asm.Opcodes.AASTORE;
 import static org.objectweb.asm.Opcodes.ACC_ENUM;
 import static org.objectweb.asm.Opcodes.ACC_FINAL;
@@ -172,6 +174,7 @@
 import static org.objectweb.asm.Opcodes.ALOAD;
 import static org.objectweb.asm.Opcodes.ANEWARRAY;
 import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.ARRAYLENGTH;
 import static org.objectweb.asm.Opcodes.ASTORE;
 import static org.objectweb.asm.Opcodes.ATHROW;
 import static org.objectweb.asm.Opcodes.BASTORE;
@@ -187,6 +190,7 @@
 import static org.objectweb.asm.Opcodes.IASTORE;
 import static org.objectweb.asm.Opcodes.ICONST_0;
 import static org.objectweb.asm.Opcodes.ICONST_1;
+import static org.objectweb.asm.Opcodes.IFNE;
 import static org.objectweb.asm.Opcodes.IFNONNULL;
 import static org.objectweb.asm.Opcodes.ILOAD;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
@@ -1660,27 +1664,41 @@
     @Override
     public void visitArrayExpression(final ArrayExpression expression) {
         MethodVisitor mv = controller.getMethodVisitor();
-
-        int size = 0;
-        int dimensions = 0;
-        OperandStack operandStack = controller.getOperandStack();
-        if (expression.hasInitializer()) {
-            size = expression.getExpressions().size();
-            BytecodeHelper.pushConstant(mv, size);
-        } else {
-            for (Expression element : expression.getSizeExpression()) {
-                if (element == ConstantExpression.EMPTY_EXPRESSION) break;
-                dimensions += 1;
-                // convert to an int
-                element.visit(this);
-                operandStack.doGroovyCast(ClassHelper.int_TYPE);
-            }
-            operandStack.remove(dimensions);
-        }
+        CompileStack  compileStack = controller.getCompileStack();
+        OperandStack  operandStack = controller.getOperandStack();
 
         ClassNode arrayType = expression.getType();
         ClassNode elementType = arrayType.getComponentType();
 
+        int size = 0, dimensions = 0;
+        if (expression.hasInitializer()) {
+            if (containsSpreadExpression(expression)) {
+                despreadList(expression.getExpressions(), false);
+                if (elementType.equals(ClassHelper.OBJECT_TYPE)){
+                    operandStack.push(arrayType);
+                    return;
+                }
+                mv.visitInsn(DUP); // Object[] from despreadList
+                mv.visitInsn(ARRAYLENGTH);
+                mv.visitInsn(DUP); // store value count
+                operandStack.push(ClassHelper.int_TYPE);
+                size = -compileStack.defineTemporaryVariable("value$count", ClassHelper.int_TYPE, true);
+            } else {
+                size = expression.getExpressions().size();
+                BytecodeHelper.pushConstant(mv, size);
+            }
+            // stack: ..., size
+        } else {
+            for (final Expression sizeExpr : expression.getSizeExpression()) {
+                if (sizeExpr == ConstantExpression.EMPTY_EXPRESSION) break;
+                dimensions += 1;
+                sizeExpr.visit(this);
+                operandStack.doGroovyCast(ClassHelper.int_TYPE);
+            }
+            operandStack.remove(dimensions);
+            // stack: ..., size (one per dimension)
+        }
+
         int storeIns = AASTORE;
         if (!elementType.isArray() || expression.hasInitializer()) {
             if (isPrimitiveType(elementType)) {
@@ -1718,19 +1736,58 @@
         } else {
             mv.visitMultiANewArrayInsn(BytecodeHelper.getTypeDescription(arrayType), dimensions);
         }
+        // stack: ..., array
 
-        for (int i = 0; i < size; i += 1) {
-            mv.visitInsn(DUP);
-            BytecodeHelper.pushConstant(mv, i);
-            Expression elementExpression = expression.getExpression(i);
-            if (elementExpression == null) {
-                ConstantExpression.NULL.visit(this);
-            } else {
-                elementExpression.visit(this);
+        if (size >= 0) {
+            for (int i = 0; i < size; i += 1) {
+                mv.visitInsn(DUP); // array ref
+                BytecodeHelper.pushConstant(mv, i);
+                Optional.ofNullable(expression.getExpression(i))
+                        .orElse(ConstantExpression.NULL)
+                        .visit(this);
                 operandStack.doGroovyCast(elementType);
+                mv.visitInsn(storeIns);
+                operandStack.remove(1);
             }
-            mv.visitInsn(storeIns);
-            operandStack.remove(1);
+        } else {
+            // stack: ..., source, target
+            Label top = new Label();
+            mv.visitLabel(top);
+
+            {
+                final int idx = -size;
+                mv.visitIincInsn(idx, -1);
+
+                mv.visitInsn(DUP2);
+                mv.visitInsn(SWAP);
+                // stack: ..., target, source
+                mv.visitVarInsn(ILOAD, idx);
+                // stack: ..., target, source, index
+                mv.visitInsn(AALOAD);
+                // stack: ..., target, value
+                operandStack.push(ClassHelper.OBJECT_TYPE);
+                operandStack.doGroovyCast(elementType);
+
+                mv.visitVarInsn(ILOAD, idx);
+                // stack: ..., target, value, index
+                operandStack.push(ClassHelper.int_TYPE);
+                operandStack.swap();
+                // stack: ..., target, index, value
+                mv.visitInsn(storeIns);
+                operandStack.remove(2);
+                // stack: ...
+
+                mv.visitVarInsn(ILOAD, idx);
+                mv.visitJumpInsn(IFNE, top);
+
+                compileStack.removeVar(idx);
+            }
+
+            // stack: ..., source, target
+            mv.visitInsn(SWAP);
+            // stack: ..., target, source
+            mv.visitInsn(POP);
+            // stack: ..., target
         }
 
         operandStack.push(arrayType);
@@ -2280,19 +2337,19 @@
         return true;
     }
 
-    public static boolean containsSpreadExpression(final Expression arguments) {
-        List<Expression> args;
-        if (arguments instanceof TupleExpression) {
-            TupleExpression tupleExpression = (TupleExpression) arguments;
-            args = tupleExpression.getExpressions();
-        } else if (arguments instanceof ListExpression) {
-            ListExpression le = (ListExpression) arguments;
-            args = le.getExpressions();
+    public static boolean containsSpreadExpression(final Expression expression) {
+        List<Expression> expressions;
+        if (expression instanceof TupleExpression) {
+            expressions = ((TupleExpression) expression).getExpressions();
+        } else if (expression instanceof ListExpression) {
+            expressions = ((ListExpression)  expression).getExpressions();
+        } else if (expression instanceof ArrayExpression) {
+            expressions = ((ArrayExpression) expression).getExpressions();
         } else {
-            return arguments instanceof SpreadExpression;
+            return expression instanceof SpreadExpression;
         }
-        for (Expression arg : args) {
-            if (arg instanceof SpreadExpression) return true;
+        for (Expression expr : expressions) {
+            if (expr instanceof SpreadExpression) return true;
         }
         return false;
     }
diff --git a/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy b/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
index 228faa8..5656ab3 100644
--- a/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
@@ -828,14 +828,14 @@
         '''
     }
 
-    // GROOVY-10599
+    // GROOVY-10599, GROOVY-11060
     void testListExpressionWithSpreadExpression() {
         assertScript '''
             void test(List<String> list) {
                 assert list == ['x','y','z']
             }
             List<String> strings = ['y','z']
-            test(['x', *strings])
+            test(['x',*strings])
         '''
 
         assertScript '''
@@ -845,7 +845,23 @@
             List<String> getStrings() {
                 return ['y','z']
             }
-            test(['x', *strings])
+            test(['x',*strings])
+        '''
+
+        assertScript '''
+            void test(String[] array) {
+                assert array.toString() == '[x, y, z]'
+            }
+            List<String> strings = ['y','z']
+            test(['x',*strings] as String[])
+        '''
+
+        assertScript '''
+            void test(long[] array) {
+                assert array.toString() == '[1, 2, 3]'
+            }
+            List<Number> numbers = [2, 3]
+            test([1L,*numbers] as long[])
         '''
     }
 
@@ -1094,8 +1110,9 @@
         'Cannot assign value of type groovy.lang.ListWithDefault<java.lang.Integer> to variable of type java.util.Set<java.lang.Integer>'
     }
 
+    // GROOVY-8001, GROOVY-11028
     void testMapWithTypeArgumentsInitializedByMapLiteral() {
-        ['CharSequence,Integer', 'String,Number', 'CharSequence,Number'].each { spec ->
+        for (spec in ['CharSequence,Integer', 'String,Number', 'CharSequence,Number']) {
             assertScript """
                 Map<$spec> map = [a:1,b:2,c:3]
                 assert map.size() == 3
@@ -1104,7 +1121,6 @@
             """
         }
 
-        // GROOVY-8001
         assertScript '''
             class C {
                 Map<String,Object> map
@@ -1115,7 +1131,6 @@
             assert c.map['key'] == '42'
         '''
 
-        // GROOVY-11028
         assertScript '''
             Map<String,Integer> map = [:].withDefault { 0 }
             assert map.size() == 0
@@ -1140,7 +1155,7 @@
     // GROOVY-8136
     void testAbstractClassThatImplementsMapInitializedByMapLiteral() {
         shouldFailWithMessages '''
-            abstract class MVM<K, V> extends Map<K, List<V>> { }
+            abstract class MVM<K, V> implements Map<K, List<V>> { }
             MVM map = [:] // no STC error; fails at runtime
         ''',
         'Cannot find matching constructor MVM(', 'Map', ')'