Skip to content

Commit 0adbe30

Browse files
committed
GROOVY-10049, GROOVY-10436, GROOVY-10648
1 parent 489ecf8 commit 0adbe30

File tree

3 files changed

+60
-177
lines changed

3 files changed

+60
-177
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -507,11 +507,11 @@ public void testTypeChecked21() {
507507
"Main.groovy",
508508
"def <T> T m(java.util.function.Consumer<? super T> c) {\n" +
509509
" c.accept(null)\n" +
510-
" null\n" +
510+
" return null\n" +
511511
"}\n" +
512512
"@groovy.transform.TypeChecked\n" +
513513
"void test() {\n" +
514-
" this." + (isAtLeastGroovy(40) ? "<Number>" : "" ) + "m { Number n ->\n" + // TODO: GROOVY-10436
514+
" m { Number n ->\n" + // GROOVY-10436
515515
" n?.toBigInteger()\n" +
516516
" }\n" +
517517
"}\n" +

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

+49-174
Original file line numberDiff line numberDiff line change
@@ -1679,7 +1679,7 @@ protected boolean existsProperty(final PropertyExpression pexp, final boolean re
16791679
ClassNode receiverType = receiver.getType();
16801680

16811681
if (receiverType.isArray() && "length".equals(propertyName)) {
1682-
// GRECLIPSE edit -- GROOVY-5450
1682+
// GRECLIPSE add -- GROOVY-5450
16831683
pexp.putNodeMetaData(READONLY_PROPERTY, Boolean.TRUE);
16841684
// GRECLIPSE end
16851685
storeType(pexp, int_TYPE);
@@ -3222,35 +3222,34 @@ private void processNamedParam(final AnnotationNode namedParam, final Map<Object
32223222
}
32233223

32243224
/**
3225-
* This method is responsible for performing type inference on closure argument types whenever code like this is
3226-
* found: <code>foo.collect { it.toUpperCase() }</code>.
3227-
* In this case, the type checker tries to find if the <code>collect</code> method has its {@link Closure} argument
3228-
* annotated with {@link groovy.transform.stc.ClosureParams}. If yes, then additional type inference can be performed
3229-
* and the type of <code>it</code> may be inferred.
3225+
* Performs type inference on closure argument types whenever code like this
3226+
* is found: <code>foo.collect { it.toUpperCase() }</code>.
3227+
* <p>
3228+
* In this case the type checker tries to find if the {@code collect} method
3229+
* has its {@link Closure} argument annotated with {@link ClosureParams}. If
3230+
* so, then additional type inference can be performed and the type of
3231+
* {@code it} may be inferred.
32303232
*
32313233
* @param receiver
32323234
* @param arguments
3233-
* @param expression a closure expression for which the argument types should be inferred
3234-
* @param param the parameter where to look for a {@link groovy.transform.stc.ClosureParams} annotation.
3235-
* @param selectedMethod the method accepting a closure
3235+
* @param expression closure or lambda expression for which the argument types should be inferred
3236+
* @param target parameter which may provide {@link ClosureParams} annotation or SAM type
3237+
* @param method method that declares {@code target}
32363238
*/
3237-
protected void inferClosureParameterTypes(final ClassNode receiver, final Expression arguments, final ClosureExpression expression, final Parameter param, final MethodNode selectedMethod) {
3238-
List<AnnotationNode> annotations = param.getAnnotations(CLOSUREPARAMS_CLASSNODE);
3239+
protected void inferClosureParameterTypes(final ClassNode receiver, final Expression arguments, final ClosureExpression expression, final Parameter target, final MethodNode method) {
3240+
List<AnnotationNode> annotations = target.getAnnotations(CLOSUREPARAMS_CLASSNODE);
32393241
if (annotations != null && !annotations.isEmpty()) {
32403242
for (AnnotationNode annotation : annotations) {
32413243
Expression hintClass = annotation.getMember("value");
3242-
Expression options = annotation.getMember("options");
3243-
Expression resolverClass = annotation.getMember("conflictResolutionStrategy");
32443244
if (hintClass instanceof ClassExpression) {
3245-
doInferClosureParameterTypes(receiver, arguments, expression, selectedMethod, hintClass, resolverClass, options);
3245+
Expression options = annotation.getMember("options");
3246+
Expression resolverClass = annotation.getMember("conflictResolutionStrategy");
3247+
doInferClosureParameterTypes(receiver, arguments, expression, method, hintClass, resolverClass, options);
32463248
}
32473249
}
3248-
} else if (isSAMType(param.getOriginType())) {
3249-
/* GRECLIPSE edit -- GROOVY-8917, GROOVY-10047, GROOVY-10049
3250-
inferSAMType(param, receiver, selectedMethod, InvocationWriter.makeArgumentList(arguments), expression);
3251-
*/
3252-
Map<GenericsTypeName, GenericsType> context = selectedMethod.isStatic() ? new HashMap<>() : extractPlaceHolders(null, receiver, getDeclaringClass(selectedMethod, arguments));
3253-
GenericsType[] typeParameters = selectedMethod instanceof ConstructorNode ? selectedMethod.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, selectedMethod.getGenericsTypes());
3250+
} else if (isSAMType(target.getOriginType())) { // SAM-type coercion
3251+
Map<GenericsTypeName, GenericsType> context = method.isStatic() ? new HashMap<>() : extractPlaceHolders(null, receiver, getDeclaringClass(method, arguments));
3252+
GenericsType[] typeParameters = method instanceof ConstructorNode ? method.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, method.getGenericsTypes());
32543253

32553254
if (typeParameters != null) {
32563255
boolean typeParametersResolved = false;
@@ -3273,13 +3272,21 @@ protected void inferClosureParameterTypes(final ClassNode receiver, final Expres
32733272
}
32743273
if (!typeParametersResolved) {
32753274
// check for implicit type arguments
3276-
int i = -1; Parameter[] p = selectedMethod.getParameters();
3275+
int i = -1; Parameter[] p = method.getParameters();
32773276
for (Expression argument : (ArgumentListExpression) arguments) { i += 1;
3278-
if (argument instanceof ClosureExpression || isNullConstant(argument)) continue;
3277+
if (isNullConstant(argument)) continue;
32793278

32803279
ClassNode pType = p[Math.min(i, p.length - 1)].getType();
32813280
Map<GenericsTypeName, GenericsType> gc = new HashMap<>();
32823281
extractGenericsConnections(gc, wrapTypeIfNecessary(getType(argument)), pType);
3282+
// GROOVY-10436: extract generics connections from closure parameter declaration(s)
3283+
if (argument == expression || (argument instanceof ClosureExpression && isSAMType(pType))) {
3284+
Parameter[] q = getParametersSafe((ClosureExpression) argument);
3285+
ClassNode[] r = extractTypesFromParameters(q); // maybe typed
3286+
ClassNode[] s = GenericsUtils.parameterizeSAM(pType).getV1();
3287+
for (int j = 0; j < r.length && j < s.length; j += 1)
3288+
if (!q[j].isDynamicTyped()) extractGenericsConnections(gc, r[j], s[j]);
3289+
}
32833290

32843291
gc.forEach((key, gt) -> {
32853292
for (GenericsType tp : typeParameters) {
@@ -3297,7 +3304,7 @@ protected void inferClosureParameterTypes(final ClassNode receiver, final Expres
32973304
}
32983305
}
32993306

3300-
ClassNode[] samParamTypes = GenericsUtils.parameterizeSAM(applyGenericsContext(context, param.getType())).getV1();
3307+
ClassNode[] samParamTypes = GenericsUtils.parameterizeSAM(applyGenericsContext(context, target.getType())).getV1();
33013308

33023309
ClassNode[] paramTypes = expression.getNodeMetaData(CLOSURE_ARGUMENTS);
33033310
if (paramTypes == null) {
@@ -3308,142 +3315,14 @@ protected void inferClosureParameterTypes(final ClassNode receiver, final Expres
33083315
} else if ((n = p.length) == 0) {
33093316
// implicit parameter(s)
33103317
paramTypes = samParamTypes;
3311-
} else {
3312-
paramTypes = new ClassNode[n];
3313-
for (int i = 0; i < n && i < samParamTypes.length; i += 1) {
3314-
if (p[i].isDynamicTyped()) {
3315-
paramTypes[i] = samParamTypes[i];
3316-
} else {
3317-
ClassNode declared = p[i].getOriginType(), inferred = samParamTypes[i];
3318-
if (isPrimitiveType(inferred) && getWrapper(inferred).equals(declared))
3319-
paramTypes[i] = inferred; // GROOVY-9790
3320-
else
3321-
paramTypes[i] = declared;
3322-
}
3323-
}
3318+
} else { // TODO: error for length mismatch
3319+
paramTypes = Arrays.copyOf(samParamTypes, n);
33243320
}
33253321
expression.putNodeMetaData(CLOSURE_ARGUMENTS, paramTypes);
33263322
}
3327-
// GRECLIPSE end
33283323
}
33293324
}
33303325

3331-
/**
3332-
* In a method call with SAM coercion the inference is to be understood as a
3333-
* two phase process. We have the normal method call to the target method
3334-
* with the closure argument and we have the SAM method that will be called
3335-
* inside the normal target method. To infer correctly we have to "simulate"
3336-
* this process. We know the call to the closure will be done through the SAM
3337-
* type, so the SAM type generics deliver information about the Closure. At
3338-
* the same time the SAM class is used in the target method parameter,
3339-
* providing a connection from the SAM type and the target method's class.
3340-
*/
3341-
/* GRECLIPSE edit
3342-
private void inferSAMType(final Parameter param, final ClassNode receiver, final MethodNode methodWithSAMParameter, final ArgumentListExpression originalMethodCallArguments, final ClosureExpression openBlock) {
3343-
// first we try to get as much information about the declaration class through the receiver
3344-
Map<GenericsTypeName, GenericsType> targetMethodConnections = new HashMap<>();
3345-
for (ClassNode face : receiver.getAllInterfaces()) {
3346-
extractGenericsConnections(targetMethodConnections, getCorrectedClassNode(receiver, face, true), face.redirect());
3347-
}
3348-
if (!receiver.isInterface()) {
3349-
extractGenericsConnections(targetMethodConnections, receiver, receiver.redirect());
3350-
}
3351-
3352-
// then we use the method with the SAM-type parameter to get more information about the declaration
3353-
Parameter[] parametersOfMethodContainingSAM = methodWithSAMParameter.getParameters();
3354-
for (int i = 0, n = parametersOfMethodContainingSAM.length; i < n; i += 1) {
3355-
ClassNode parameterType = parametersOfMethodContainingSAM[i].getType();
3356-
// potentially skip empty varargs
3357-
if (i == (n - 1) && i == originalMethodCallArguments.getExpressions().size() && parameterType.isArray()) {
3358-
continue;
3359-
}
3360-
Expression callArg = originalMethodCallArguments.getExpression(i);
3361-
// we look at the closure later in detail, so skip it here
3362-
if (callArg == openBlock) {
3363-
continue;
3364-
}
3365-
extractGenericsConnections(targetMethodConnections, getType(callArg), parameterType);
3366-
}
3367-
3368-
// To make a connection to the SAM class we use that new information
3369-
// to replace the generics in the SAM type parameter of the target
3370-
// method and than that to make the connections to the SAM type generics
3371-
ClassNode paramTypeWithReceiverInformation = applyGenericsContext(targetMethodConnections, param.getOriginType());
3372-
Map<GenericsTypeName, GenericsType> samTypeConnections = new HashMap<>();
3373-
ClassNode samTypeRedirect = paramTypeWithReceiverInformation.redirect();
3374-
extractGenericsConnections(samTypeConnections, paramTypeWithReceiverInformation, samTypeRedirect);
3375-
3376-
// should the open block provide final information we apply that
3377-
// to the corresponding parameters of the SAM type method
3378-
MethodNode abstractMethod = findSAM(samTypeRedirect);
3379-
ClassNode[] abstractMethodParamTypes = extractTypesFromParameters(abstractMethod.getParameters());
3380-
ClassNode[] blockParamTypes = openBlock.getNodeMetaData(CLOSURE_ARGUMENTS);
3381-
if (blockParamTypes == null) {
3382-
Parameter[] p = openBlock.getParameters();
3383-
if (p == null) {
3384-
// zero parameter closure e.g. { -> println 'no args' }
3385-
blockParamTypes = ClassNode.EMPTY_ARRAY;
3386-
} else if (p.length == 0 && abstractMethodParamTypes.length != 0) {
3387-
// implicit it
3388-
blockParamTypes = abstractMethodParamTypes;
3389-
} else {
3390-
blockParamTypes = new ClassNode[p.length];
3391-
for (int i = 0, n = p.length; i < n; i += 1) {
3392-
if (p[i] != null && !p[i].isDynamicTyped()) {
3393-
blockParamTypes[i] = p[i].getType();
3394-
} else {
3395-
blockParamTypes[i] = typeOrNull(abstractMethodParamTypes, i);
3396-
}
3397-
}
3398-
}
3399-
}
3400-
for (int i = 0, n = blockParamTypes.length; i < n; i += 1) {
3401-
extractGenericsConnections(samTypeConnections, blockParamTypes[i], typeOrNull(abstractMethodParamTypes, i));
3402-
}
3403-
3404-
// finally apply the generics information to the parameters and
3405-
// store the type of parameter and block type as meta information
3406-
for (int i = 0, n = blockParamTypes.length; i < n; i += 1) {
3407-
blockParamTypes[i] = applyGenericsContext(samTypeConnections, typeOrNull(abstractMethodParamTypes, i));
3408-
}
3409-
3410-
tryToInferUnresolvedBlockParameterType(paramTypeWithReceiverInformation, abstractMethod, blockParamTypes);
3411-
3412-
openBlock.putNodeMetaData(CLOSURE_ARGUMENTS, blockParamTypes);
3413-
}
3414-
3415-
private void tryToInferUnresolvedBlockParameterType(final ClassNode paramTypeWithReceiverInformation, final MethodNode methodForSAM, final ClassNode[] blockParameterTypes) {
3416-
List<Integer> indexList = new LinkedList<>();
3417-
for (int i = 0, n = blockParameterTypes.length; i < n; i += 1) {
3418-
ClassNode blockParameterType = blockParameterTypes[i];
3419-
if (blockParameterType != null && blockParameterType.isGenericsPlaceHolder()) {
3420-
indexList.add(i);
3421-
}
3422-
}
3423-
3424-
if (!indexList.isEmpty()) {
3425-
// If the parameter type failed to resolve, try to find the parameter type through the class hierarchy
3426-
Map<GenericsType, GenericsType> genericsTypeMap = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(methodForSAM.getDeclaringClass(), paramTypeWithReceiverInformation);
3427-
3428-
for (Integer index : indexList) {
3429-
for (Map.Entry<GenericsType, GenericsType> entry : genericsTypeMap.entrySet()) {
3430-
if (entry.getKey().getName().equals(blockParameterTypes[index].getUnresolvedName())) {
3431-
ClassNode type = entry.getValue().getType();
3432-
if (type != null && !type.isGenericsPlaceHolder()) {
3433-
blockParameterTypes[index] = type;
3434-
}
3435-
break;
3436-
}
3437-
}
3438-
}
3439-
}
3440-
}
3441-
3442-
private static ClassNode typeOrNull(final ClassNode[] parameterTypesForSAM, final int i) {
3443-
return i < parameterTypesForSAM.length ? parameterTypesForSAM[i] : null;
3444-
}
3445-
*/
3446-
34473326
private List<ClassNode[]> getSignaturesFromHint(final ClosureExpression expression, final MethodNode selectedMethod, final Expression hintClass, final Expression options) {
34483327
// initialize hints
34493328
List<ClassNode[]> closureSignatures;
@@ -3507,25 +3386,24 @@ private void doInferClosureParameterTypes(final ClassNode receiver, final Expres
35073386
}
35083387
}
35093388
if (candidates.size() > 1) {
3510-
Iterator<ClassNode[]> candIt = candidates.iterator();
3511-
while (candIt.hasNext()) {
3389+
for (Iterator<ClassNode[]> candIt = candidates.iterator(); candIt.hasNext(); ) {
35123390
ClassNode[] inferred = candIt.next();
35133391
for (int i = 0, n = closureParams.length; i < n; i += 1) {
35143392
Parameter closureParam = closureParams[i];
3515-
ClassNode originType = closureParam.getOriginType();
3393+
ClassNode declaredType = closureParam.getOriginType();
35163394
ClassNode inferredType;
3517-
if (i < inferred.length - 1 || inferred.length == closureParams.length) {
3395+
if (i < inferred.length - 1 || inferred.length == n) {
35183396
inferredType = inferred[i];
3519-
} else { // vargs?
3520-
ClassNode lastArgInferred = inferred[inferred.length - 1];
3521-
if (lastArgInferred.isArray()) {
3522-
inferredType = lastArgInferred.getComponentType();
3397+
} else {
3398+
ClassNode lastInferred = inferred[inferred.length - 1];
3399+
if (lastInferred.isArray()) {
3400+
inferredType = lastInferred.getComponentType();
35233401
} else {
35243402
candIt.remove();
35253403
continue;
35263404
}
35273405
}
3528-
if (!typeCheckMethodArgumentWithGenerics(originType, inferredType, i == (n - 1))) {
3406+
if (!typeCheckMethodArgumentWithGenerics(declaredType, inferredType, i == (n - 1))) {
35293407
candIt.remove();
35303408
}
35313409
}
@@ -3544,24 +3422,21 @@ private void doInferClosureParameterTypes(final ClassNode receiver, final Expres
35443422
} else {
35453423
for (int i = 0, n = closureParams.length; i < n; i += 1) {
35463424
Parameter closureParam = closureParams[i];
3547-
ClassNode originType = closureParam.getOriginType();
3425+
ClassNode declaredType = closureParam.getOriginType();
35483426
ClassNode inferredType = OBJECT_TYPE;
3549-
if (i < inferred.length - 1 || inferred.length == closureParams.length) {
3427+
if (i < inferred.length - 1 || inferred.length == n) {
35503428
inferredType = inferred[i];
3551-
} else { // vargs?
3552-
ClassNode lastArgInferred = inferred[inferred.length - 1];
3553-
if (lastArgInferred.isArray()) {
3554-
inferredType = lastArgInferred.getComponentType();
3429+
} else {
3430+
ClassNode lastInferred = inferred[inferred.length - 1];
3431+
if (lastInferred.isArray()) {
3432+
inferredType = lastInferred.getComponentType();
35553433
} else {
3556-
addError("Incorrect number of parameters. Expected " + inferred.length + " but found " + closureParams.length, expression);
3434+
addError("Incorrect number of parameters. Expected " + inferred.length + " but found " + n, expression);
35573435
}
35583436
}
3559-
boolean lastArg = i == (n - 1);
3560-
3561-
if (!typeCheckMethodArgumentWithGenerics(originType, inferredType, lastArg)) {
3562-
addError("Expected parameter of type " + inferredType.toString(false) + " but got " + originType.toString(false), closureParam.getType());
3437+
if (!typeCheckMethodArgumentWithGenerics(declaredType, inferredType, i == n-1)) {
3438+
addError("Expected parameter of type " + prettyPrintType(inferredType) + " but got " + prettyPrintType(declaredType), closureParam.getType());
35633439
}
3564-
35653440
typeCheckingContext.controlStructureVariables.put(closureParam, inferredType);
35663441
}
35673442
}

0 commit comments

Comments
 (0)