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', ')'