use of org.codehaus.groovy.transform.stc.StaticTypesMarker.TYPE in project groovy by apache.
the class StaticTypeCheckingVisitor method convertClosureTypeToSAMType.
/**
* Converts a Closure type to the appropriate SAM type, which can be used to
* infer return type generics.
*
* @param expression the argument expression
* @param closureType the inferred type of {@code expression}
* @param samType the target type for the argument expression
* @return SAM type augmented using information from the argument expression
*/
private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType) {
Map<GenericsTypeName, GenericsType> samTypeConnections = GenericsUtils.extractPlaceholders(samType);
samTypeConnections.replaceAll((// GROOVY-9762, GROOVY-9803: reduce "? super T" to "T"
xx, // GROOVY-9762, GROOVY-9803: reduce "? super T" to "T"
gt) -> Optional.ofNullable(gt.getLowerBound()).map(GenericsType::new).orElse(gt));
ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType();
Parameter[] parameters = sam.getParameters();
if (parameters.length > 0 && expression instanceof MethodPointerExpression && GenericsUtils.hasUnresolvedGenerics(closureReturnType)) {
// try to resolve referenced method type parameters in return type
MethodPointerExpression mp = (MethodPointerExpression) expression;
List<MethodNode> candidates = mp.getNodeMetaData(MethodNode.class);
if (candidates != null && !candidates.isEmpty()) {
ClassNode[] paramTypes = applyGenericsContext(samTypeConnections, extractTypesFromParameters(parameters));
ClassNode[] matchTypes = candidates.stream().map(candidate -> collateMethodReferenceParameterTypes(mp, candidate)).filter(candidate -> checkSignatureSuitability(candidate, paramTypes)).findFirst().orElse(// TODO: order signatures by param distance
null);
if (matchTypes != null) {
Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
for (int i = 0, n = parameters.length; i < n; i += 1) {
// SAM parameters should align with the referenced method's parameters
extractGenericsConnections(connections, paramTypes[i], matchTypes[i]);
}
// convert the method reference's generics into the SAM's generics domain
closureReturnType = applyGenericsContext(connections, closureReturnType);
// apply known generics connections to the SAM's placeholders in the return type
closureReturnType = applyGenericsContext(samTypeConnections, closureReturnType);
}
}
}
// the SAM's return type exactly corresponds to the inferred closure return type
extractGenericsConnections(samTypeConnections, closureReturnType, sam.getReturnType());
// repeat the same for each parameter given in the ClosureExpression
if (parameters.length > 0 && expression instanceof ClosureExpression) {
// TODO
return closureType;
}
return applyGenericsContext(samTypeConnections, samType.redirect());
}
use of org.codehaus.groovy.transform.stc.StaticTypesMarker.TYPE in project groovy by apache.
the class StaticTypeCheckingVisitor method resolvePlaceholdersFromImplicitTypeHints.
/**
* Given method call like "m(Collections.emptyList())", the type of the call
* argument is {@code List<T>} without explicit type arguments. Knowning the
* method target of "m", {@code T} could be resolved.
*/
private void resolvePlaceholdersFromImplicitTypeHints(final ClassNode[] actuals, final ArgumentListExpression argumentList, final Parameter[] parameterArray) {
int np = parameterArray.length;
for (int i = 0, n = actuals.length; np > 0 && i < n; i += 1) {
Expression a = argumentList.getExpression(i);
Parameter p = parameterArray[Math.min(i, np - 1)];
ClassNode at = actuals[i], pt = p.getOriginType();
if (!isUsingGenericsOrIsArrayUsingGenerics(pt))
continue;
if (i >= (np - 1) && pt.isArray() && !at.isArray())
pt = pt.getComponentType();
if (a instanceof ListExpression) {
actuals[i] = getLiteralResultType(pt, at, ArrayList_TYPE);
} else if (a instanceof MapExpression) {
actuals[i] = getLiteralResultType(pt, at, LinkedHashMap_TYPE);
} else if (a instanceof ConstructorCallExpression) {
// GROOVY-10086
inferDiamondType((ConstructorCallExpression) a, pt);
} else if (a instanceof TernaryExpression && at.getGenericsTypes() != null && at.getGenericsTypes().length == 0) {
// GROOVY-9983: double diamond scenario -- "m(flag ? new Type<>(...) : new Type<>(...))"
typeCheckingContext.pushEnclosingBinaryExpression(assignX(varX(p), a, a));
// re-visit with target type witness
a.visit(this);
typeCheckingContext.popEnclosingBinaryExpression();
actuals[i] = getType(a);
}
// check for method call with known target
if (!(a instanceof MethodCallExpression))
continue;
if (((MethodCallExpression) a).isUsingGenerics())
continue;
MethodNode aNode = a.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
if (aNode == null || aNode.getGenericsTypes() == null)
continue;
// and unknown generics
if (!GenericsUtils.hasUnresolvedGenerics(at))
continue;
while (!at.equals(pt) && !isObjectType(at) && !isGenericsPlaceHolderOrArrayOf(at)) {
at = applyGenericsContext(GenericsUtils.extractPlaceholders(at), getNextSuperClass(at, pt));
}
// try to resolve placeholder(s) in argument type using parameter type
Map<GenericsTypeName, GenericsType> linked = new HashMap<>();
Map<GenericsTypeName, GenericsType> source = GenericsUtils.extractPlaceholders(at);
Map<GenericsTypeName, GenericsType> target = GenericsUtils.extractPlaceholders(pt);
// connect E:T from source to E:Type from target
for (GenericsType placeholder : aNode.getGenericsTypes()) {
for (Map.Entry<GenericsTypeName, GenericsType> e : source.entrySet()) {
if (e.getValue().getNodeMetaData(GenericsType.class) == placeholder) {
Optional.ofNullable(target.get(e.getKey())).filter(gt -> isAssignableTo(gt.getType(), placeholder.getType())).ifPresent(gt -> linked.put(new GenericsTypeName(e.getValue().getName()), gt));
break;
}
}
}
actuals[i] = applyGenericsContext(linked, at);
}
}
use of org.codehaus.groovy.transform.stc.StaticTypesMarker.TYPE in project groovy by apache.
the class StaticTypeCheckingVisitor method ensureValidSetter.
/**
* Given a binary expression corresponding to an assignment, will check that
* the type of the RHS matches one of the possible setters and if not, throw
* a type checking error.
*
* @param expression the assignment expression
* @param leftExpression left expression of the assignment
* @param rightExpression right expression of the assignment
* @param setterInfo possible setters
* @return {@code false} if valid setter found or {@code true} if type checking error created
*/
private boolean ensureValidSetter(final Expression expression, final Expression leftExpression, final Expression rightExpression, final SetterInfo setterInfo) {
// for expressions like foo = { ... }
// we know that the RHS type is a closure
// but we must check if the binary expression is an assignment
// because we need to check if a setter uses @DelegatesTo
VariableExpression receiver = varX("%", setterInfo.receiverType);
// for "x op= y" expression, find type as if it was "x = x op y"
Expression valueExpression = rightExpression;
if (isCompoundAssignment(expression)) {
Token op = ((BinaryExpression) expression).getOperation();
if (op.getType() == ELVIS_EQUAL) {
// GROOVY-10419: "x ?= y"
valueExpression = elvisX(leftExpression, rightExpression);
} else {
op = Token.newSymbol(TokenUtil.removeAssignment(op.getType()), op.getStartLine(), op.getStartColumn());
valueExpression = binX(leftExpression, op, rightExpression);
}
}
Function<Expression, MethodNode> setterCall = right -> {
MethodCallExpression call = callX(receiver, setterInfo.name, right);
call.setImplicitThis(false);
visitMethodCallExpression(call);
return call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
};
Function<MethodNode, ClassNode> setterType = setter -> {
ClassNode type = setter.getParameters()[0].getOriginType();
if (!setter.isStatic() && !(setter instanceof ExtensionMethodNode) && GenericsUtils.hasUnresolvedGenerics(type)) {
type = applyGenericsContext(extractPlaceHolders(setterInfo.receiverType, setter.getDeclaringClass()), type);
}
return type;
};
MethodNode methodTarget = setterCall.apply(valueExpression);
if (methodTarget == null && !isCompoundAssignment(expression)) {
// if no direct match, try implicit conversion
for (MethodNode setter : setterInfo.setters) {
ClassNode lType = setterType.apply(setter);
ClassNode rType = getDeclaredOrInferredType(valueExpression);
if (checkCompatibleAssignmentTypes(lType, rType, valueExpression, false)) {
methodTarget = setterCall.apply(castX(lType, valueExpression));
if (methodTarget != null) {
break;
}
}
}
}
if (methodTarget != null) {
for (MethodNode setter : setterInfo.setters) {
if (setter == methodTarget) {
leftExpression.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, methodTarget);
// clear assumption
leftExpression.removeNodeMetaData(INFERRED_TYPE);
storeType(leftExpression, setterType.apply(methodTarget));
break;
}
}
return false;
} else {
ClassNode firstSetterType = setterType.apply(setterInfo.setters.get(0));
addAssignmentError(firstSetterType, getType(valueExpression), expression);
return true;
}
}
use of org.codehaus.groovy.transform.stc.StaticTypesMarker.TYPE in project groovy by apache.
the class StaticTypeCheckingVisitor method inferMethodReferenceType.
private void inferMethodReferenceType(final ClassNode receiver, final ArgumentListExpression argumentList, final MethodNode selectedMethod) {
if (receiver == null)
return;
if (argumentList == null)
return;
if (selectedMethod == null)
return;
List<Expression> argumentExpressions = argumentList.getExpressions();
if (argumentExpressions == null || argumentExpressions.stream().noneMatch(e -> e instanceof MethodReferenceExpression)) {
return;
}
Parameter[] parameters = selectedMethod.getParameters();
final int nthParameter = parameters.length - 1;
List<Integer> methodReferencePositions = new LinkedList<>();
List<Expression> newArgumentExpressions = new LinkedList<>();
for (int i = 0, n = argumentExpressions.size(); i < n; i += 1) {
Expression argumentExpression = argumentExpressions.get(i);
if (!(argumentExpression instanceof MethodReferenceExpression)) {
newArgumentExpressions.add(argumentExpression);
} else {
// GROOVY-10336
Parameter param = parameters[Math.min(i, nthParameter)];
ClassNode paramType = param.getType();
if (i >= nthParameter && paramType.isArray())
paramType = paramType.getComponentType();
if (!isFunctionalInterface(paramType.redirect())) {
addError("The argument is a method reference, but the parameter type is not a functional interface", argumentExpression);
newArgumentExpressions.add(argumentExpression);
} else {
methodReferencePositions.add(i);
newArgumentExpressions.add(constructLambdaExpressionForMethodReference(paramType));
}
}
}
// GROOVY-10269
if (methodReferencePositions.isEmpty())
return;
visitMethodCallArguments(receiver, args(newArgumentExpressions), true, selectedMethod);
for (int index : methodReferencePositions) {
Expression lambdaExpression = newArgumentExpressions.get(index);
Expression methodReferenceExpression = argumentExpressions.get(index);
methodReferenceExpression.putNodeMetaData(CLOSURE_ARGUMENTS, lambdaExpression.getNodeMetaData(CLOSURE_ARGUMENTS));
}
}
use of org.codehaus.groovy.transform.stc.StaticTypesMarker.TYPE in project groovy by apache.
the class StaticTypeCheckingVisitor method inferClosureParameterTypes.
/**
* Performs type inference on closure argument types whenever code like this
* is found: <code>foo.collect { it.toUpperCase() }</code>.
* <p>
* In this case the type checker tries to find if the {@code collect} method
* has its {@link Closure} argument annotated with {@link ClosureParams}. If
* so, then additional type inference can be performed and the type of
* {@code it} may be inferred.
*
* @param receiver
* @param arguments
* @param expression closure or lambda expression for which the argument types should be inferred
* @param target parameter which may provide {@link ClosureParams} annotation or SAM type
* @param method method that declares {@code target}
*/
protected void inferClosureParameterTypes(final ClassNode receiver, final Expression arguments, final ClosureExpression expression, final Parameter target, final MethodNode method) {
List<AnnotationNode> annotations = target.getAnnotations(CLOSUREPARAMS_CLASSNODE);
if (annotations != null && !annotations.isEmpty()) {
for (AnnotationNode annotation : annotations) {
Expression value = annotation.getMember("value");
Expression options = annotation.getMember("options");
Expression conflictResolver = annotation.getMember("conflictResolutionStrategy");
doInferClosureParameterTypes(receiver, arguments, expression, method, value, conflictResolver, options);
}
} else if (isSAMType(target.getOriginType())) {
// SAM-type coercion
Map<GenericsTypeName, GenericsType> context = extractPlaceHoldersVisibleToDeclaration(receiver, method, arguments);
GenericsType[] typeParameters = method instanceof ConstructorNode ? method.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, method.getGenericsTypes());
if (typeParameters != null) {
boolean typeParametersResolved = false;
// first check for explicit type arguments
Expression emc = typeCheckingContext.getEnclosingMethodCall();
if (emc instanceof MethodCallExpression) {
MethodCallExpression mce = (MethodCallExpression) emc;
if (mce.getArguments() == arguments) {
GenericsType[] typeArguments = mce.getGenericsTypes();
if (typeArguments != null) {
int n = typeParameters.length;
if (n == typeArguments.length) {
typeParametersResolved = true;
for (int i = 0; i < n; i += 1) {
context.put(new GenericsTypeName(typeParameters[i].getName()), typeArguments[i]);
}
}
}
}
}
if (!typeParametersResolved) {
// check for implicit type arguments
int i = -1;
Parameter[] p = method.getParameters();
for (Expression argument : (ArgumentListExpression) arguments) {
i += 1;
if (argument instanceof ClosureExpression || isNullConstant(argument))
continue;
ClassNode pType = p[Math.min(i, p.length - 1)].getType();
Map<GenericsTypeName, GenericsType> gc = new HashMap<>();
extractGenericsConnections(gc, wrapTypeIfNecessary(getType(argument)), pType);
gc.forEach((key, gt) -> {
for (GenericsType tp : typeParameters) {
if (tp.getName().equals(key.getName())) {
// TODO: merge
context.putIfAbsent(key, gt);
break;
}
}
});
}
for (GenericsType tp : typeParameters) {
context.computeIfAbsent(new GenericsTypeName(tp.getName()), x -> fullyResolve(tp, context));
}
}
}
ClassNode[] samParamTypes = GenericsUtils.parameterizeSAM(applyGenericsContext(context, target.getType())).getV1();
ClassNode[] paramTypes = expression.getNodeMetaData(CLOSURE_ARGUMENTS);
if (paramTypes == null) {
int n;
Parameter[] p = expression.getParameters();
if (p == null) {
// zero parameters
paramTypes = ClassNode.EMPTY_ARRAY;
} else if ((n = p.length) == 0) {
// implicit parameter(s)
paramTypes = samParamTypes;
} else {
// TODO: error for length mismatch
paramTypes = Arrays.copyOf(samParamTypes, n);
for (int i = 0; i < Math.min(n, samParamTypes.length); i += 1) {
checkParamType(p[i], paramTypes[i], i == n - 1, expression instanceof LambdaExpression);
}
}
expression.putNodeMetaData(CLOSURE_ARGUMENTS, paramTypes);
}
}
}
Aggregations