Skip to content
Permalink
Browse files
GROOVY-10599: STC: support spread list elements: ['a',*letters(),'z']
  • Loading branch information
eric-milles committed Apr 26, 2022
1 parent 8fa6d43 commit 5c468cd352f37fb5c599a3f51534ffcc55b339ed
Showing 5 changed files with 149 additions and 135 deletions.
@@ -40,7 +40,6 @@
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.SpreadExpression;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.ForStatement;
@@ -575,8 +574,4 @@ public void visitPropertyExpression(final PropertyExpression expression) {
expression.getObjectExpression().putNodeMetaData(RECEIVER_OF_DYNAMIC_PROPERTY, dynamic);
}
}

@Override
public void visitSpreadExpression(final SpreadExpression expression) {
}
}
@@ -4979,24 +4979,22 @@ private static void collectAllInterfaceMethodsByName(final ClassNode type, final
}
}

protected ClassNode getType(final ASTNode exp) {
ClassNode cn = exp.getNodeMetaData(INFERRED_TYPE);
if (cn != null) {
return cn;
protected ClassNode getType(final ASTNode node) {
ClassNode type = node.getNodeMetaData(INFERRED_TYPE);
if (type != null) {
return type;
}
if (exp instanceof ClassExpression) {
ClassNode node = CLASS_Type.getPlainNodeReference();
node.setGenericsTypes(new GenericsType[]{
new GenericsType(((ClassExpression) exp).getType())
});
return node;
if (node instanceof ClassExpression) {
type = ((ClassExpression) node).getType();
return makeClassSafe0(CLASS_Type, new GenericsType(type));
}
if (exp instanceof VariableExpression) {
VariableExpression vexp = (VariableExpression) exp;
ClassNode selfTrait = isTraitSelf(vexp);
if (selfTrait != null) return makeSelf(selfTrait);
if (node instanceof VariableExpression) {
VariableExpression vexp = (VariableExpression) node;
type = isTraitSelf(vexp);
if (type != null) return makeSelf(type);
if (vexp.isThisExpression()) return makeThis();
if (vexp.isSuperExpression()) return makeSuper();

Variable variable = vexp.getAccessedVariable();
if (variable instanceof FieldNode) {
FieldNode fieldNode = (FieldNode) variable;
@@ -5012,7 +5010,6 @@ protected ClassNode getType(final ASTNode exp) {
}
if (variable instanceof Parameter) {
Parameter parameter = (Parameter) variable;
ClassNode type = null;
// check if param part of control structure - but not if inside instanceof
List<ClassNode> temporaryTypesForExpression = getTemporaryTypesForExpression(vexp);
if (temporaryTypesForExpression == null || temporaryTypesForExpression.isEmpty()) {
@@ -5031,74 +5028,73 @@ protected ClassNode getType(final ASTNode exp) {
}
return vexp.getOriginType();
}

if (exp instanceof ListExpression) {
return inferListExpressionType((ListExpression) exp);
}
if (exp instanceof MapExpression) {
return inferMapExpressionType((MapExpression) exp);
}
if (exp instanceof ConstructorCallExpression) {
return ((ConstructorCallExpression) exp).getType();
if (node instanceof Parameter || node instanceof FieldNode || node instanceof PropertyNode) {
return ((Variable) node).getOriginType();
}
if (exp instanceof MethodNode) {
if ((exp == GET_DELEGATE || exp == GET_OWNER || exp == GET_THISOBJECT) && typeCheckingContext.getEnclosingClosure() != null) {

if (node instanceof MethodNode) {
if ((node == GET_DELEGATE || node == GET_OWNER || node == GET_THISOBJECT)
&& typeCheckingContext.getEnclosingClosure() != null) {
return typeCheckingContext.getEnclosingClassNode();
}
ClassNode ret = getInferredReturnType(exp);
return ret != null ? ret : ((MethodNode) exp).getReturnType();
type = ((MethodNode) node).getReturnType();
return Optional.ofNullable(getInferredReturnType(node)).orElse(type);
}
if (exp instanceof FieldNode || exp instanceof PropertyNode) {
return ((Variable) exp).getOriginType();
}
if (exp instanceof RangeExpression) {
ClassNode plain = RANGE_TYPE.getPlainNodeReference();
RangeExpression re = (RangeExpression) exp;
ClassNode fromType = getType(re.getFrom());
ClassNode toType = getType(re.getTo());
if (fromType.equals(toType)) {
plain.setGenericsTypes(new GenericsType[]{
new GenericsType(wrapTypeIfNecessary(fromType))
});
} else {
plain.setGenericsTypes(new GenericsType[]{
new GenericsType(wrapTypeIfNecessary(lowestUpperBound(fromType, toType)))
});
if (node instanceof MethodCall) {
if (node instanceof ConstructorCallExpression) {
return ((ConstructorCallExpression) node).getType();
}
MethodNode target = node.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
if (target != null) {
return getType(target);
}
return plain;
}
if (exp instanceof UnaryPlusExpression) {
return getType(((UnaryPlusExpression) exp).getExpression());
}
if (exp instanceof UnaryMinusExpression) {
return getType(((UnaryMinusExpression) exp).getExpression());
}
if (exp instanceof BitwiseNegationExpression) {
return getType(((BitwiseNegationExpression) exp).getExpression());
}
if (exp instanceof Parameter) {
return ((Parameter) exp).getOriginType();
}
if (exp instanceof ClosureExpression) {
ClassNode type = CLOSURE_TYPE.getPlainNodeReference();
ClassNode returnType = getInferredReturnType(exp);
if (node instanceof ClosureExpression) {
type = CLOSURE_TYPE.getPlainNodeReference();
ClassNode returnType = getInferredReturnType(node);
if (returnType != null) {
type.setGenericsTypes(new GenericsType[]{
new GenericsType(wrapTypeIfNecessary(returnType))
});
}
Parameter[] parameters = ((ClosureExpression) exp).getParameters();
Parameter[] parameters = ((ClosureExpression) node).getParameters();
int nParameters = parameters == null ? 0
: parameters.length == 0 ? -1 : parameters.length;
type.putNodeMetaData(CLOSURE_ARGUMENTS, nParameters);
return type;
} else if (exp instanceof MethodCall) {
MethodNode target = exp.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
if (target != null) {
return getType(target);
}

if (node instanceof ListExpression) {
return inferListExpressionType((ListExpression) node);
}
if (node instanceof MapExpression) {
return inferMapExpressionType((MapExpression) node);
}
if (node instanceof RangeExpression) {
RangeExpression re = (RangeExpression) node;
ClassNode fromType = getType(re.getFrom());
ClassNode toType = getType(re.getTo());
if (fromType.equals(toType)) {
type = wrapTypeIfNecessary(fromType);
} else {
type = wrapTypeIfNecessary(lowestUpperBound(fromType, toType));
}
return makeClassSafe0(RANGE_TYPE, new GenericsType(type));
}
if (node instanceof SpreadExpression) {
type = getType(((SpreadExpression) node).getExpression());
return inferComponentType(type, null); // for list literal
}
if (node instanceof UnaryPlusExpression) {
return getType(((UnaryPlusExpression) node).getExpression());
}
if (node instanceof UnaryMinusExpression) {
return getType(((UnaryMinusExpression) node).getExpression());
}
if (node instanceof BitwiseNegationExpression) {
return getType(((BitwiseNegationExpression) node).getExpression());
}
return ((Expression) exp).getType();
return ((Expression) node).getType();
}

private ClassNode getTypeFromClosureArguments(final Parameter parameter, final TypeCheckingContext.EnclosingClosure enclosingClosure) {
@@ -18,6 +18,8 @@
*/
package groovy.transform.stc

import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer

/**
* Unit tests for static type checking : arrays and collections.
*/
@@ -222,6 +224,31 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
List classes = list*.toUpperCase()
assert classes == ['A','B','C']
'''

assertScript '''
def list = 'a,b,c'.split(',')*.toUpperCase()
assert list == ['A', 'B', 'C']
'''

// GROOVY-8133
assertScript '''
def list = ['a','b','c'].stream()*.toUpperCase()
assert list == ['A', 'B', 'C']
'''

shouldFailWithMessages '''
def list = 'abc'*.toUpperCase()
assert list == ['A', 'B', 'C']
''',
'Spread-dot operator can only be used on iterable types'

config.compilationCustomizers
.find { it instanceof ASTTransformationCustomizer }
.annotationParameters = [extensions: PrecompiledExtensionNotExtendingDSL.name]
assertScript '''
def list = 'abc'*.toUpperCase()
assert list == ['A', 'B', 'C']
'''
}

void testInferredMapDotProperty() {
@@ -299,7 +326,7 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {

void testForInLoopWithRange() {
assertScript '''
for (int i in 1..10) { i*2 }
for (int i in 1..10) { i * 2 }
'''
}

@@ -755,33 +782,67 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {

// GROOVY-6311
void testSetSpread() {
assertScript """
assertScript '''
class Inner {Set<String> strings}
class Outer {Set<Inner> inners}
Outer outer = new Outer(inners: [ new Inner(strings: ['abc', 'def'] as Set), new Inner(strings: ['ghi'] as Set) ] as Set)
def res = outer.inners*.strings
assert res[1].contains('ghi')
assert res[0].contains('abc')
assert res[0].contains('def')
"""
'''
}

// GROOVY-8033
void testSetSpreadPropertyInStaticContext() {
assertScript '''
class Foo {
String name
}
static List<String> meth() {
Set<Foo> foos = [new Foo(name: 'pls'), new Foo(name: 'bar')].toSet()
foos*.name
}
assert meth().toSet() == ['pls', 'bar'].toSet()
'''
}

// GROOVY-10599
void testListExpressionWithSpreadExpression() {
assertScript '''
void test(List<String> list) {
assert list == ['x','y','z']
}
List<String> strings = ['y','z']
test(['x', *strings])
'''
assertScript '''
void test(List<String> list) {
assert list == ['x','y','z']
}
List<String> getStrings() {
return ['y','z']
}
test(['x', *strings])
'''
}

// GROOVY-6241
void testAsImmutable() {
assertScript """
assertScript '''
List<Integer> list = [1, 2, 3]
List<Integer> immutableList = [1, 2, 3].asImmutable()
Map<String, Integer> map = [foo: 123, bar: 456]
Map<String, Integer> immutableMap = [foo: 123, bar: 456].asImmutable()
"""
'''
}

// GROOVY-6350
void testListPlusList() {
assertScript """
assertScript '''
def foo = [] + []
assert foo==[]
"""
'''
}

// GROOVY-7122
@@ -796,20 +857,6 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
'''
}

// GROOVY-8033
void testSetSpreadPropertyInStaticContext() {
assertScript '''
class Foo {
String name
}
static List<String> meth() {
Set<Foo> foos = [new Foo(name: 'pls'), new Foo(name: 'bar')].toSet()
foos*.name
}
assert meth().toSet() == ['pls', 'bar'].toSet()
'''
}

void testAbstractTypeInitializedByListLiteral() {
shouldFailWithMessages '''
abstract class A {

0 comments on commit 5c468cd

Please sign in to comment.