Skip to content

Commit c1c4fef

Browse files
committed
GROOVY-8917, GROOVY-9347, GROOVY-10049
1 parent eadbd7b commit c1c4fef

File tree

4 files changed

+292
-28
lines changed

4 files changed

+292
-28
lines changed

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

+69-5
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,35 @@ public void testTypeChecked15() {
361361

362362
@Test
363363
public void testTypeChecked16() {
364+
//@formatter:off
365+
String[] sources = {
366+
"Main.groovy",
367+
"@groovy.transform.TypeChecked\n" +
368+
"void test() {\n" +
369+
" new Order<Pogo, Comparable>({Pogo p -> p.s})\n" + // No such property s for class Object
370+
"}\n" +
371+
"test()\n",
372+
373+
"Order.groovy",
374+
"class Order<T, U extends Comparable<? super U>> {\n" +
375+
" Order(java.util.function.Function<? super T, ? extends U> keyExtractor) {\n" +
376+
" }\n" +
377+
"}\n",
378+
379+
"Pogo.groovy",
380+
"@groovy.transform.Canonical\n" +
381+
"class Pogo {\n" +
382+
" Number n\n" +
383+
" String s\n" +
384+
"}\n",
385+
};
386+
//@formatter:on
387+
388+
runConformTest(sources);
389+
}
390+
391+
@Test
392+
public void testTypeChecked17() {
364393
//@formatter:off
365394
String[] sources = {
366395
"Main.groovy",
@@ -1079,6 +1108,44 @@ public void testTypeChecked8909a() {
10791108
"----------\n");
10801109
}
10811110

1111+
@Test
1112+
public void testTypeChecked8917() {
1113+
if (Float.parseFloat(System.getProperty("java.specification.version")) > 8)
1114+
vmArguments = new String[] {"--add-opens", "java.base/java.util.stream=ALL-UNNAMED"};
1115+
1116+
//@formatter:off
1117+
String[] sources = {
1118+
"Main.groovy",
1119+
"@groovy.transform.TypeChecked\n" +
1120+
"def test() {\n" +
1121+
" [1, 2, 3].stream().reduce(7) { r, e -> r + e }\n" +
1122+
"}\n" +
1123+
"print test()\n",
1124+
};
1125+
//@formatter:on
1126+
1127+
runConformTest(sources, "13");
1128+
}
1129+
1130+
@Test
1131+
public void testTypeChecked8917a() {
1132+
if (Float.parseFloat(System.getProperty("java.specification.version")) > 8)
1133+
vmArguments = new String[] {"--add-opens", "java.base/java.util.stream=ALL-UNNAMED"};
1134+
1135+
//@formatter:off
1136+
String[] sources = {
1137+
"Main.groovy",
1138+
"@groovy.transform.TypeChecked\n" +
1139+
"def test() {\n" +
1140+
" [1, 2, 3].stream().<String>map{i -> null}.limit(1).toList()\n" +
1141+
"}\n" +
1142+
"print test()\n",
1143+
};
1144+
//@formatter:on
1145+
1146+
runConformTest(sources, "[null]");
1147+
}
1148+
10821149
@Test
10831150
public void testTypeChecked8974() {
10841151
//@formatter:off
@@ -2643,12 +2710,9 @@ public void testTypeChecked10049() {
26432710
//@formatter:off
26442711
String[] sources = {
26452712
"Main.groovy",
2646-
"def <X /*extends Number*/> Set<X> generateNumbers(Class<X> type) {\n" +
2647-
" return Collections.singleton(type.newInstance(42))\n" +
2648-
"}\n" +
26492713
"@groovy.transform.TypeChecked\n" +
2650-
"def <Y extends Number> void printNumbers(Class<Y> numberType) {\n" +
2651-
" generateNumbers(numberType).stream()\n" +
2714+
"def <T extends Number> void printNumbers(Class<T> numberType) {\n" +
2715+
" Collections.singleton(numberType.newInstance(42)).stream()\n" +
26522716
" .filter { n -> n.intValue() > 0 }\n" +
26532717
" .forEach { n -> print n }\n" +
26542718
"}\n" +

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

+77-9
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@
246246
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments;
247247
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findSetters;
248248
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findTargetVariable;
249+
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolve;
249250
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolveType;
250251
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCombinedBoundType;
251252
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getGenericsWithoutArray;
@@ -3433,6 +3434,7 @@ private void processNamedParam(AnnotationConstantExpression value, Map<Object, E
34333434
* @param selectedMethod the method accepting a closure
34343435
*/
34353436
protected void inferClosureParameterTypes(final ClassNode receiver, final Expression arguments, final ClosureExpression expression, final Parameter param, final MethodNode selectedMethod) {
3437+
MethodNode abstractMethod;
34363438
List<AnnotationNode> annotations = param.getAnnotations(CLOSUREPARAMS_CLASSNODE);
34373439
if (annotations != null && !annotations.isEmpty()) {
34383440
for (AnnotationNode annotation : annotations) {
@@ -3443,12 +3445,85 @@ protected void inferClosureParameterTypes(final ClassNode receiver, final Expres
34433445
doInferClosureParameterTypes(receiver, arguments, expression, selectedMethod, hintClass, resolverClass, options);
34443446
}
34453447
}
3448+
/* GRECLIPSE edit -- GROOVY-8917, GROOVY-9347, GROOVY-10049
34463449
} else if (isSAMType(param.getOriginType())) {
3447-
// SAM coercion
34483450
inferSAMType(param, receiver, selectedMethod, InvocationWriter.makeArgumentList(arguments), expression);
34493451
}
3452+
*/
3453+
} else if ((abstractMethod = findSAM(param.getOriginType())) != null) {
3454+
Map<GenericsTypeName, GenericsType> context = selectedMethod.isStatic() ? new HashMap<>() : extractPlaceHolders(null, receiver, getDeclaringClass(selectedMethod, arguments));
3455+
GenericsType[] typeParameters = selectedMethod instanceof ConstructorNode ? selectedMethod.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, selectedMethod.getGenericsTypes());
3456+
3457+
if (typeParameters != null) {
3458+
boolean typeParametersResolved = false;
3459+
// first check for explicit type arguments
3460+
Expression emc = typeCheckingContext.getEnclosingMethodCall();
3461+
if (emc instanceof MethodCallExpression) {
3462+
MethodCallExpression mce = (MethodCallExpression) emc;
3463+
if (mce.getArguments() == arguments) {
3464+
GenericsType[] typeArguments = mce.getGenericsTypes();
3465+
if (typeArguments != null) {
3466+
int n = typeParameters.length;
3467+
if (n == typeArguments.length) {
3468+
typeParametersResolved = true;
3469+
for (int i = 0; i < n; i += 1) {
3470+
context.put(new GenericsTypeName(typeParameters[i].getName()), typeArguments[i]);
3471+
}
3472+
}
3473+
}
3474+
}
3475+
}
3476+
if (!typeParametersResolved) {
3477+
// check for implicit type arguments
3478+
int i = -1; Parameter[] p = selectedMethod.getParameters();
3479+
for (Expression argument : (ArgumentListExpression) arguments) { i += 1;
3480+
if (argument instanceof ClosureExpression || isNullConstant(argument)) continue;
3481+
3482+
ClassNode pType = p[Math.min(i, p.length - 1)].getType();
3483+
Map<GenericsTypeName, GenericsType> gc = new HashMap<>();
3484+
extractGenericsConnections(gc, wrapTypeIfNecessary(getType(argument)), pType);
3485+
3486+
gc.forEach((key, gt) -> {
3487+
for (GenericsType tp : typeParameters) {
3488+
if (tp.getName().equals(key.getName())) {
3489+
context.putIfAbsent(key, gt); // TODO: merge
3490+
break;
3491+
}
3492+
}
3493+
});
3494+
}
3495+
3496+
for (GenericsType tp : typeParameters) {
3497+
context.computeIfAbsent(new GenericsTypeName(tp.getName()), x -> fullyResolve(tp, context));
3498+
}
3499+
}
3500+
}
3501+
3502+
Map<GenericsType, GenericsType> samTypeGenerics = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(abstractMethod.getDeclaringClass(), applyGenericsContext(context, param.getType()));
3503+
ClassNode[] samParamTypes = Arrays.stream(abstractMethod.getParameters()).map(Parameter::getType).map(t -> t.isGenericsPlaceHolder() ? GenericsUtils.findActualTypeByGenericsPlaceholderName(t.getUnresolvedName(), samTypeGenerics) : t).toArray(ClassNode[]::new);
3504+
3505+
ClassNode[] paramTypes = expression.getNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS);
3506+
if (paramTypes == null) {
3507+
int n; Parameter[] p = expression.getParameters();
3508+
if (p == null) {
3509+
// zero parameters
3510+
paramTypes = ClassNode.EMPTY_ARRAY;
3511+
} else if ((n = p.length) == 0) {
3512+
// implicit parameter(s)
3513+
paramTypes = samParamTypes;
3514+
} else {
3515+
paramTypes = new ClassNode[n];
3516+
for (int i = 0; i < n; i += 1) {
3517+
paramTypes[i] = i < samParamTypes.length ? samParamTypes[i] : null;
3518+
}
3519+
}
3520+
expression.putNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS, paramTypes);
3521+
}
3522+
}
3523+
// GRECLIPSE end
34503524
}
34513525

3526+
/* GRECLIPSE edit
34523527
private void inferSAMType(Parameter param, ClassNode receiver, MethodNode methodWithSAMParameter, ArgumentListExpression originalMethodCallArguments, ClosureExpression openBlock) {
34533528
// In a method call with SAM coercion the inference is to be
34543529
// understood as a two phase process. We have the normal method call
@@ -3464,14 +3539,6 @@ private void inferSAMType(Parameter param, ClassNode receiver, MethodNode method
34643539
// First we try to get as much information about the declaration
34653540
// class through the receiver
34663541
Map<GenericsTypeName, GenericsType> targetMethodDeclarationClassConnections = new HashMap<GenericsTypeName, GenericsType>();
3467-
// GRECLIPSE add -- GROOVY-9347, GROOVY-10049
3468-
for (ClassNode face : receiver.getAllInterfaces()) {
3469-
if (face != receiver) {
3470-
ClassNode type = StaticTypeCheckingSupport.getCorrectedClassNode(receiver, face, true);
3471-
extractGenericsConnections(targetMethodDeclarationClassConnections, type, face.redirect());
3472-
}
3473-
}
3474-
// GRECLIPSE end
34753542
extractGenericsConnections(targetMethodDeclarationClassConnections, receiver, receiver.redirect());
34763543
// then we use the method with the SAM parameter to get more information about the declaration
34773544
Parameter[] parametersOfMethodContainingSAM = methodWithSAMParameter.getParameters();
@@ -3537,6 +3604,7 @@ private void inferSAMType(Parameter param, ClassNode receiver, MethodNode method
35373604
private ClassNode typeOrNull(ClassNode[] parameterTypesForSAM, int i) {
35383605
return i < parameterTypesForSAM.length ? parameterTypesForSAM[i] : null;
35393606
}
3607+
*/
35403608

35413609
private List<ClassNode[]> getSignaturesFromHint(final ClosureExpression expression, final MethodNode selectedMethod, final Expression hintClass, final Expression options) {
35423610
// initialize hints

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

+73-12
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,9 @@
254254
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsForClassNode;
255255
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findSetters;
256256
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findTargetVariable;
257+
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolve;
257258
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolveType;
258259
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCombinedBoundType;
259-
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCorrectedClassNode;
260260
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getGenericsWithoutArray;
261261
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getOperationName;
262262
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf;
@@ -3101,8 +3101,77 @@ protected void inferClosureParameterTypes(final ClassNode receiver, final Expres
31013101
}
31023102
}
31033103
} else if (isSAMType(param.getOriginType())) {
3104-
// SAM coercion
3104+
/* GRECLIPSE edit -- GROOVY-8917, GROOVY-10049
31053105
inferSAMType(param, receiver, selectedMethod, InvocationWriter.makeArgumentList(arguments), expression);
3106+
*/
3107+
Map<GenericsTypeName, GenericsType> context = selectedMethod.isStatic() ? new HashMap<>() : extractPlaceHolders(null, receiver, getDeclaringClass(selectedMethod, arguments));
3108+
GenericsType[] typeParameters = selectedMethod instanceof ConstructorNode ? selectedMethod.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, selectedMethod.getGenericsTypes());
3109+
3110+
if (typeParameters != null) {
3111+
boolean typeParametersResolved = false;
3112+
// first check for explicit type arguments
3113+
Expression emc = typeCheckingContext.getEnclosingMethodCall();
3114+
if (emc instanceof MethodCallExpression) {
3115+
MethodCallExpression mce = (MethodCallExpression) emc;
3116+
if (mce.getArguments() == arguments) {
3117+
GenericsType[] typeArguments = mce.getGenericsTypes();
3118+
if (typeArguments != null) {
3119+
int n = typeParameters.length;
3120+
if (n == typeArguments.length) {
3121+
typeParametersResolved = true;
3122+
for (int i = 0; i < n; i += 1) {
3123+
context.put(new GenericsTypeName(typeParameters[i].getName()), typeArguments[i]);
3124+
}
3125+
}
3126+
}
3127+
}
3128+
}
3129+
if (!typeParametersResolved) {
3130+
// check for implicit type arguments
3131+
int i = -1; Parameter[] p = selectedMethod.getParameters();
3132+
for (Expression argument : (ArgumentListExpression) arguments) { i += 1;
3133+
if (argument instanceof ClosureExpression || isNullConstant(argument)) continue;
3134+
3135+
ClassNode pType = p[Math.min(i, p.length - 1)].getType();
3136+
Map<GenericsTypeName, GenericsType> gc = new HashMap<>();
3137+
extractGenericsConnections(gc, wrapTypeIfNecessary(getType(argument)), pType);
3138+
3139+
gc.forEach((key, gt) -> {
3140+
for (GenericsType tp : typeParameters) {
3141+
if (tp.getName().equals(key.getName())) {
3142+
context.putIfAbsent(key, gt); // TODO: merge
3143+
break;
3144+
}
3145+
}
3146+
});
3147+
}
3148+
3149+
for (GenericsType tp : typeParameters) {
3150+
context.computeIfAbsent(new GenericsTypeName(tp.getName()), x -> fullyResolve(tp, context));
3151+
}
3152+
}
3153+
}
3154+
3155+
ClassNode[] samParamTypes = GenericsUtils.parameterizeSAM(applyGenericsContext(context, param.getType())).getV1();
3156+
3157+
ClassNode[] paramTypes = expression.getNodeMetaData(CLOSURE_ARGUMENTS);
3158+
if (paramTypes == null) {
3159+
int n; Parameter[] p = expression.getParameters();
3160+
if (p == null) {
3161+
// zero parameters
3162+
paramTypes = ClassNode.EMPTY_ARRAY;
3163+
} else if ((n = p.length) == 0) {
3164+
// implicit parameter(s)
3165+
paramTypes = samParamTypes;
3166+
} else {
3167+
paramTypes = new ClassNode[n];
3168+
for (int i = 0; i < n; i += 1) {
3169+
paramTypes[i] = i < samParamTypes.length ? samParamTypes[i] : null;
3170+
}
3171+
}
3172+
expression.putNodeMetaData(CLOSURE_ARGUMENTS, paramTypes);
3173+
}
3174+
// GRECLIPSE end
31063175
}
31073176
}
31083177

@@ -3116,25 +3185,16 @@ protected void inferClosureParameterTypes(final ClassNode receiver, final Expres
31163185
* the same time the SAM class is used in the target method parameter,
31173186
* providing a connection from the SAM type and the target method's class.
31183187
*/
3188+
/* GRECLIPSE edit
31193189
private void inferSAMType(final Parameter param, final ClassNode receiver, final MethodNode methodWithSAMParameter, final ArgumentListExpression originalMethodCallArguments, final ClosureExpression openBlock) {
31203190
// first we try to get as much information about the declaration class through the receiver
31213191
Map<GenericsTypeName, GenericsType> targetMethodConnections = new HashMap<>();
3122-
/* GRECLIPSE edit -- GROOVY-10049
31233192
for (ClassNode face : receiver.getAllInterfaces()) {
31243193
extractGenericsConnections(targetMethodConnections, getCorrectedClassNode(receiver, face, true), face.redirect());
31253194
}
31263195
if (!receiver.isInterface()) {
31273196
extractGenericsConnections(targetMethodConnections, receiver, receiver.redirect());
31283197
}
3129-
*/
3130-
for (ClassNode face : receiver.getAllInterfaces()) {
3131-
if (face != receiver) {
3132-
ClassNode type = getCorrectedClassNode(receiver, face, true);
3133-
extractGenericsConnections(targetMethodConnections, type, face.redirect());
3134-
}
3135-
}
3136-
extractGenericsConnections(targetMethodConnections, receiver, receiver.redirect());
3137-
// GRECLIPSE end
31383198
31393199
// then we use the method with the SAM-type parameter to get more information about the declaration
31403200
Parameter[] parametersOfMethodContainingSAM = methodWithSAMParameter.getParameters();
@@ -3229,6 +3289,7 @@ private void tryToInferUnresolvedBlockParameterType(final ClassNode paramTypeWit
32293289
private static ClassNode typeOrNull(final ClassNode[] parameterTypesForSAM, final int i) {
32303290
return i < parameterTypesForSAM.length ? parameterTypesForSAM[i] : null;
32313291
}
3292+
*/
32323293

32333294
private List<ClassNode[]> getSignaturesFromHint(final ClosureExpression expression, final MethodNode selectedMethod, final Expression hintClass, final Expression options) {
32343295
// initialize hints

0 commit comments

Comments
 (0)