Skip to content

Commit a35832d

Browse files
committed
GROOVY-5523, GROOVY-7753, GROOVY-9972, GROOVY-9983
1 parent 08a300c commit a35832d

File tree

4 files changed

+228
-51
lines changed

4 files changed

+228
-51
lines changed

base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java

+58
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,26 @@ public void testTypeChecked15() {
379379
runConformTest(sources, "test");
380380
}
381381

382+
@Test
383+
public void testTypeChecked5523() {
384+
//@formatter:off
385+
String[] sources = {
386+
"Main.groovy",
387+
"import static org.codehaus.groovy.ast.ClassHelper.*\n" +
388+
"import static org.codehaus.groovy.transform.stc.StaticTypesMarker.*\n" +
389+
"@groovy.transform.TypeChecked\n" +
390+
"def findFile(String path) {\n" +
391+
" @groovy.transform.ASTTest(phase=INSTRUCTION_SELECTION, value={\n" +
392+
" assert node.getNodeMetaData(INFERRED_TYPE) == make(File)\n" +
393+
" })\n" +
394+
" File file = path ? null : null\n" + // edge case
395+
"}",
396+
};
397+
//@formatter:on
398+
399+
runConformTest(sources);
400+
}
401+
382402
@Test
383403
public void testTypeChecked6232() {
384404
//@formatter:off
@@ -2081,6 +2101,44 @@ public void testTypeChecked9977() {
20812101
runConformTest(sources, "-1");
20822102
}
20832103

2104+
@Test
2105+
public void testTypeChecked9983() {
2106+
//@formatter:off
2107+
String[] sources = {
2108+
"Main.groovy",
2109+
"@groovy.transform.TupleConstructor(defaults=false)\n" +
2110+
"class A<T> {\n" +
2111+
" T p\n" +
2112+
"}\n" +
2113+
"class B {\n" +
2114+
"}\n" +
2115+
"class C {\n" +
2116+
" static m(A<B> a_of_b) {\n" +
2117+
" }\n" +
2118+
"}\n" +
2119+
"class D extends B {\n" +
2120+
"}\n" +
2121+
"@groovy.transform.TypeChecked\n" +
2122+
"void test() {\n" +
2123+
" boolean flag = true\n" +
2124+
" A<B> v = new A<>(null)\n" + // Cannot call C#m(A<B>) with arguments [A<#>]
2125+
" A<B> w = new A<>(new B())\n" +
2126+
" A<B> x = new A<>(new D())\n" +
2127+
" A<B> y = flag ? new A<>(new B()) : new A<>(new B())\n" +
2128+
" A<B> z = flag ? new A<>(new B()) : new A<>(new D())\n" +
2129+
" C.m(new A<>(null))\n" +
2130+
" C.m(new A<>(new B()))\n" +
2131+
" C.m(new A<>(new D()))\n" +
2132+
" C.m(flag ? new A<>(new B()) : new A<>(new B()))\n" +
2133+
" C.m(flag ? new A<>(new B()) : new A<>(new D()))\n" + // Cannot call m(A<B>) with arguments [A<? extends B>]\n" +
2134+
"}\n" +
2135+
"test()\n",
2136+
};
2137+
//@formatter:on
2138+
2139+
runConformTest(sources);
2140+
}
2141+
20842142
@Test
20852143
public void testTypeChecked9984() {
20862144
//@formatter:off

base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java

+59-27
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,7 @@ protected void inferDiamondType(final ConstructorCallExpression cce, final Class
12141214
// check if constructor call expression makes use of the diamond operator
12151215
if (cceType.getGenericsTypes() != null && cceType.getGenericsTypes().length == 0) {
12161216
ArgumentListExpression argumentListExpression = InvocationWriter.makeArgumentList(cce.getArguments());
1217-
/* GRECLIPSE edit -- GROOVY-9948
1217+
/* GRECLIPSE edit -- GROOVY-9948, GROOVY-9983
12181218
if (argumentListExpression.getExpressions().isEmpty()) {
12191219
adjustGenerics(lType, cceType);
12201220
} else {
@@ -1232,7 +1232,7 @@ protected void inferDiamondType(final ConstructorCallExpression cce, final Class
12321232
type = inferReturnTypeGenerics(type, constructor, argumentListExpression);
12331233
if (type.isUsingGenerics()) {
12341234
// GROOVY-6232, GROOVY-9956: if cce not assignment compatible, process target as additional type witness
1235-
if (checkCompatibleAssignmentTypes(lType, type, cce) && !GenericsUtils.buildWildcardType(lType).isCompatibleWith(type)) {
1235+
if (GenericsUtils.hasUnresolvedGenerics(type) || checkCompatibleAssignmentTypes(lType, type, cce) && !GenericsUtils.buildWildcardType(lType).isCompatibleWith(type)) {
12361236
// allow covariance of each type parameter, but maintain semantics for nested generics
12371237

12381238
ClassNode pType = GenericsUtils.parameterizeType(lType, type);
@@ -2264,7 +2264,7 @@ public void visitField(final FieldNode node) {
22642264
}
22652265
}
22662266

2267-
// GRECLIPSE add
2267+
// GRECLIPSE add -- GROOVY-9977, GROOVY-9983, GROOVY-9995
22682268
private void visitInitialExpression(final Expression value, final Expression target, final ASTNode position) {
22692269
if (value != null) {
22702270
ClassNode lType = target.getType();
@@ -2273,21 +2273,25 @@ private void visitInitialExpression(final Expression value, final Expression tar
22732273
} else if (isClosureWithType(lType) && value instanceof ClosureExpression) {
22742274
storeInferredReturnType(value, getCombinedBoundType(lType.getGenericsTypes()[0]));
22752275
}
2276+
2277+
typeCheckingContext.pushEnclosingBinaryExpression(assignX(target, value, position));
2278+
22762279
value.visit(this);
22772280
ClassNode rType = getType(value);
22782281
if (value instanceof ConstructorCallExpression) {
22792282
inferDiamondType((ConstructorCallExpression) value, lType);
22802283
}
22812284

2282-
BinaryExpression bexp = binX(
2283-
target,
2284-
Token.newSymbol("=", position.getLineNumber(), position.getColumnNumber()),
2285-
value
2286-
);
2287-
bexp.setSourcePosition(position);
2285+
BinaryExpression bexp = typeCheckingContext.popEnclosingBinaryExpression();
22882286
typeCheckAssignment(bexp, target, lType, value, getResultType(lType, ASSIGN, rType, bexp));
22892287
}
22902288
}
2289+
2290+
private static BinaryExpression assignX(final Expression lhs, final Expression rhs, final ASTNode pos) {
2291+
BinaryExpression exp = (BinaryExpression) GeneralUtils.assignX(lhs, rhs);
2292+
exp.setSourcePosition(pos);
2293+
return exp;
2294+
}
22912295
// GRECLIPSE end
22922296

22932297
@Override
@@ -4742,9 +4746,9 @@ public void visitTernaryExpression(final TernaryExpression expression) {
47424746
falseExpression.visit(this);
47434747
ClassNode resultType;
47444748
ClassNode typeOfFalse = getType(falseExpression);
4745-
// GRECLIPSE edit
4746-
//ClassNode typeOfTrue = getType(trueExpression);
4747-
// GRECLIPSE end
4749+
/* GRECLIPSE edit
4750+
ClassNode typeOfTrue = getType(trueExpression);
4751+
*/
47484752
// handle instanceof cases
47494753
if (hasInferredReturnType(falseExpression)) {
47504754
typeOfFalse = falseExpression.getNodeMetaData(StaticTypesMarker.INFERRED_RETURN_TYPE);
@@ -4772,7 +4776,7 @@ public void visitTernaryExpression(final TernaryExpression expression) {
47724776
}
47734777
*/
47744778
if (isNullConstant(trueExpression) && isNullConstant(falseExpression)) { // GROOVY-5523
4775-
resultType = checkForTargetType(expression, UNKNOWN_PARAMETER_TYPE);
4779+
resultType = checkForTargetType(trueExpression, UNKNOWN_PARAMETER_TYPE);
47764780
} else if (isNullConstant(trueExpression) || (isEmptyCollection(trueExpression)
47774781
&& isOrImplements(typeOfTrue, typeOfFalse))) { // [] : List/Collection/Iterable
47784782
resultType = wrapTypeIfNecessary(checkForTargetType(falseExpression, typeOfFalse));
@@ -4790,7 +4794,7 @@ && isOrImplements(typeOfFalse, typeOfTrue))) { // List/Collection/Iterable : []
47904794
}
47914795

47924796
private ClassNode checkForTargetType(final Expression expr, final ClassNode type) {
4793-
/* GRECLIPSE edit -- GROOVY-9972
4797+
/* GRECLIPSE edit -- GROOVY-9972, GROOVY-9983
47944798
BinaryExpression enclosingBinaryExpression = typeCheckingContext.getEnclosingBinaryExpression();
47954799
if (enclosingBinaryExpression instanceof DeclarationExpression
47964800
&& isEmptyCollection(expr) && isAssignment(enclosingBinaryExpression.getOperation().getType())) {
@@ -4812,23 +4816,19 @@ && isEmptyCollection(expr) && isAssignment(enclosingBinaryExpression.getOperatio
48124816
ClassNode sourceType = type, targetType = null;
48134817
MethodNode enclosingMethod = typeCheckingContext.getEnclosingMethod();
48144818
BinaryExpression enclosingBinaryExpression = typeCheckingContext.getEnclosingBinaryExpression();
4815-
if (enclosingBinaryExpression instanceof DeclarationExpression
4819+
if (enclosingBinaryExpression != null
48164820
&& isAssignment(enclosingBinaryExpression.getOperation().getType())
48174821
&& isTypeSource(expr, enclosingBinaryExpression.getRightExpression())) {
4818-
targetType = enclosingBinaryExpression.getLeftExpression().getType();
4819-
} else if (currentField != null
4820-
&& isTypeSource(expr, currentField.getInitialExpression())) {
4821-
targetType = currentField.getType();
4822-
} else if (currentProperty != null
4823-
&& isTypeSource(expr, currentProperty.getInitialExpression())) {
4824-
targetType = currentProperty.getType();
4825-
} else if (enclosingMethod != null) {
4826-
// TODO: try enclosingMethod's code with isTypeSource(expr, ...)
4827-
targetType = enclosingMethod.getReturnType();
4828-
} // TODO: closure parameter default expression
4822+
targetType = getDeclaredOrInferredType(enclosingBinaryExpression.getLeftExpression());
4823+
} else if (enclosingMethod != null
4824+
&& !enclosingMethod.isVoidMethod()
4825+
&& isTypeSource(expr, enclosingMethod)) {
4826+
targetType = enclosingMethod.getReturnType();
4827+
}
4828+
4829+
if (targetType == null) return sourceType;
48294830

48304831
if (expr instanceof ConstructorCallExpression) {
4831-
if (targetType == null) targetType = sourceType;
48324832
inferDiamondType((ConstructorCallExpression) expr, targetType);
48334833
} else if (targetType != null && !isPrimitiveType(getUnwrapper(targetType))
48344834
&& !targetType.equals(OBJECT_TYPE) && missesGenericsTypes(sourceType)) {
@@ -4860,6 +4860,32 @@ private static boolean isTypeSource(final Expression expr, final Expression righ
48604860
}
48614861
return expr == right;
48624862
}
4863+
4864+
private static boolean isTypeSource(final Expression expr, final MethodNode mNode) {
4865+
boolean[] returned = new boolean[1];
4866+
4867+
mNode.getCode().visit(new org.codehaus.groovy.ast.CodeVisitorSupport() {
4868+
@Override
4869+
public void visitReturnStatement(final ReturnStatement returnStatement) {
4870+
if (isTypeSource(expr, returnStatement.getExpression())) {
4871+
returned[0] = true;
4872+
}
4873+
}
4874+
@Override
4875+
public void visitClosureExpression(final ClosureExpression expression) {
4876+
}
4877+
});
4878+
4879+
if (!returned[0]) {
4880+
new ReturnAdder(returnStatement -> {
4881+
if (isTypeSource(expr, returnStatement.getExpression())) {
4882+
returned[0] = true;
4883+
}
4884+
}).visitMethod(mNode);
4885+
}
4886+
4887+
return returned[0];
4888+
}
48634889
// GRECLIPSE end
48644890

48654891
private static boolean isEmptyCollection(Expression expr) {
@@ -6176,6 +6202,12 @@ private void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals,
61766202
actuals[i] = getLiteralResultType(pt, at, LINKEDHASHMAP_CLASSNODE);
61776203
} else if (a instanceof ConstructorCallExpression) {
61786204
inferDiamondType((ConstructorCallExpression) a, pt); // GROOVY-10086
6205+
} else if (a instanceof TernaryExpression && at.isUsingGenerics() && at.getGenericsTypes().length == 0) {
6206+
// GROOVY-9983: double diamond scenario -- "m(flag ? new Type<>(...) : new Type<>(...))"
6207+
typeCheckingContext.pushEnclosingBinaryExpression(assignX(varX(p), a, a));
6208+
a.visit(this); // re-visit with target type witness
6209+
typeCheckingContext.popEnclosingBinaryExpression();
6210+
actuals[i] = getType(a);
61796211
}
61806212

61816213
// check for method call with known target

base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java

+51-20
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,7 @@ protected void inferDiamondType(final ConstructorCallExpression cce, final Class
10991099
ClassNode cceType = cce.getType(), inferredType = lType;
11001100
// check if constructor call expression makes use of the diamond operator
11011101
if (cceType.getGenericsTypes() != null && cceType.getGenericsTypes().length == 0) {
1102-
/* GRECLIPSE edit -- GROOVY-9948
1102+
/* GRECLIPSE edit -- GROOVY-9948, GROOVY-9983
11031103
ArgumentListExpression argumentListExpression = InvocationWriter.makeArgumentList(cce.getArguments());
11041104
if (argumentListExpression.getExpressions().isEmpty()) {
11051105
adjustGenerics(lType, cceType);
@@ -1119,7 +1119,7 @@ protected void inferDiamondType(final ConstructorCallExpression cce, final Class
11191119
type = inferReturnTypeGenerics(type, constructor, argumentList);
11201120
if (type.isUsingGenerics()) {
11211121
// GROOVY-6232, GROOVY-9956: if cce not assignment compatible, process target as additional type witness
1122-
if (checkCompatibleAssignmentTypes(lType, type, cce) && !GenericsUtils.buildWildcardType(lType).isCompatibleWith(type)) {
1122+
if (GenericsUtils.hasUnresolvedGenerics(type) || checkCompatibleAssignmentTypes(lType, type, cce) && !GenericsUtils.buildWildcardType(lType).isCompatibleWith(type)) {
11231123
// allow covariance of each type parameter, but maintain semantics for nested generics
11241124

11251125
ClassNode pType = GenericsUtils.parameterizeType(lType, type);
@@ -2083,22 +2083,25 @@ public void visitField(final FieldNode node) {
20832083
}
20842084
}
20852085

2086-
// GRECLIPSE add
2086+
// GRECLIPSE add -- GROOVY-9977, GROOVY-9983, GROOVY-9995
20872087
private void visitInitialExpression(final Expression value, final Expression target, final ASTNode position) {
20882088
if (value != null) {
20892089
ClassNode lType = target.getType();
2090-
if (isFunctionalInterface(lType)) { // GROOVY-9977
2090+
if (isFunctionalInterface(lType)) {
20912091
processFunctionalInterfaceAssignment(lType, value);
20922092
} else if (isClosureWithType(lType) && value instanceof ClosureExpression) {
20932093
storeInferredReturnType(value, getCombinedBoundType(lType.getGenericsTypes()[0]));
20942094
}
2095+
2096+
typeCheckingContext.pushEnclosingBinaryExpression(assignX(target, value, position));
2097+
20952098
value.visit(this);
20962099
ClassNode rType = getType(value);
20972100
if (value instanceof ConstructorCallExpression) {
20982101
inferDiamondType((ConstructorCallExpression) value, lType);
20992102
}
21002103

2101-
BinaryExpression bexp = assignX(target, value, position);
2104+
BinaryExpression bexp = typeCheckingContext.popEnclosingBinaryExpression();
21022105
typeCheckAssignment(bexp, target, lType, value, getResultType(lType, ASSIGN, rType, bexp));
21032106
}
21042107
}
@@ -4541,7 +4544,7 @@ public void visitTernaryExpression(final TernaryExpression expression) {
45414544
}
45424545
*/
45434546
if (isNullConstant(trueExpression) && isNullConstant(falseExpression)) { // GROOVY-5523
4544-
resultType = checkForTargetType(expression, UNKNOWN_PARAMETER_TYPE);
4547+
resultType = checkForTargetType(trueExpression, UNKNOWN_PARAMETER_TYPE);
45454548
} else if (isNullConstant(trueExpression) || (isEmptyCollection(trueExpression)
45464549
&& isOrImplements(typeOfTrue, typeOfFalse))) { // [] : List/Collection/Iterable
45474550
resultType = wrapTypeIfNecessary(checkForTargetType(falseExpression, typeOfFalse));
@@ -4559,7 +4562,7 @@ && isOrImplements(typeOfFalse, typeOfTrue))) { // List/Collection/Iterable : []
45594562
}
45604563

45614564
private ClassNode checkForTargetType(final Expression expr, final ClassNode type) {
4562-
/* GRECLIPSE edit -- GROOVY-9972
4565+
/* GRECLIPSE edit -- GROOVY-9972, GROOVY-9983
45634566
BinaryExpression enclosingBinaryExpression = typeCheckingContext.getEnclosingBinaryExpression();
45644567
if (enclosingBinaryExpression instanceof DeclarationExpression
45654568
&& isEmptyCollection(expr) && isAssignment(enclosingBinaryExpression.getOperation().getType())) {
@@ -4581,23 +4584,19 @@ && isEmptyCollection(expr) && isAssignment(enclosingBinaryExpression.getOperatio
45814584
ClassNode sourceType = type, targetType = null;
45824585
MethodNode enclosingMethod = typeCheckingContext.getEnclosingMethod();
45834586
BinaryExpression enclosingBinaryExpression = typeCheckingContext.getEnclosingBinaryExpression();
4584-
if (enclosingBinaryExpression instanceof DeclarationExpression
4587+
if (enclosingBinaryExpression != null
45854588
&& isAssignment(enclosingBinaryExpression.getOperation().getType())
45864589
&& isTypeSource(expr, enclosingBinaryExpression.getRightExpression())) {
4587-
targetType = enclosingBinaryExpression.getLeftExpression().getType();
4588-
} else if (currentField != null
4589-
&& isTypeSource(expr, currentField.getInitialExpression())) {
4590-
targetType = currentField.getType();
4591-
} else if (currentProperty != null
4592-
&& isTypeSource(expr, currentProperty.getInitialExpression())) {
4593-
targetType = currentProperty.getType();
4594-
} else if (enclosingMethod != null) {
4595-
// TODO: try enclosingMethod's code with isTypeSource(expr, ...)
4596-
targetType = enclosingMethod.getReturnType();
4597-
} // TODO: closure parameter default expression
4590+
targetType = getDeclaredOrInferredType(enclosingBinaryExpression.getLeftExpression());
4591+
} else if (enclosingMethod != null
4592+
&& !enclosingMethod.isVoidMethod()
4593+
&& isTypeSource(expr, enclosingMethod)) {
4594+
targetType = enclosingMethod.getReturnType();
4595+
}
4596+
4597+
if (targetType == null) return sourceType;
45984598

45994599
if (expr instanceof ConstructorCallExpression) {
4600-
if (targetType == null) targetType = sourceType;
46014600
inferDiamondType((ConstructorCallExpression) expr, targetType);
46024601
} else if (targetType != null && !isPrimitiveType(getUnwrapper(targetType))
46034602
&& !targetType.equals(OBJECT_TYPE) && missesGenericsTypes(sourceType)) {
@@ -4629,6 +4628,32 @@ private static boolean isTypeSource(final Expression expr, final Expression righ
46294628
}
46304629
return expr == right;
46314630
}
4631+
4632+
private static boolean isTypeSource(final Expression expr, final MethodNode mNode) {
4633+
boolean[] returned = new boolean[1];
4634+
4635+
mNode.getCode().visit(new org.codehaus.groovy.ast.CodeVisitorSupport() {
4636+
@Override
4637+
public void visitReturnStatement(final ReturnStatement returnStatement) {
4638+
if (isTypeSource(expr, returnStatement.getExpression())) {
4639+
returned[0] = true;
4640+
}
4641+
}
4642+
@Override
4643+
public void visitClosureExpression(final ClosureExpression expression) {
4644+
}
4645+
});
4646+
4647+
if (!returned[0]) {
4648+
new ReturnAdder(returnStatement -> {
4649+
if (isTypeSource(expr, returnStatement.getExpression())) {
4650+
returned[0] = true;
4651+
}
4652+
}).visitMethod(mNode);
4653+
}
4654+
4655+
return returned[0];
4656+
}
46324657
// GRECLIPSE end
46334658

46344659
private static boolean isEmptyCollection(final Expression expr) {
@@ -5895,6 +5920,12 @@ private void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals,
58955920
actuals[i] = getLiteralResultType(pt, at, LINKEDHASHMAP_CLASSNODE);
58965921
} else if (a instanceof ConstructorCallExpression) {
58975922
inferDiamondType((ConstructorCallExpression) a, pt); // GROOVY-10086
5923+
} else if (a instanceof TernaryExpression && at.isUsingGenerics() && at.getGenericsTypes().length == 0) {
5924+
// GROOVY-9983: double diamond scenario -- "m(flag ? new Type<>(...) : new Type<>(...))"
5925+
typeCheckingContext.pushEnclosingBinaryExpression(assignX(varX(p), a, a));
5926+
a.visit(this); // re-visit with target type witness
5927+
typeCheckingContext.popEnclosingBinaryExpression();
5928+
actuals[i] = getType(a);
58985929
}
58995930

59005931
// check for method call with known target

0 commit comments

Comments
 (0)