in src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java [3634:3901]
public void visitMethodCallExpression(final MethodCallExpression call) {
String name = call.getMethodAsString();
if (name == null) {
addStaticTypeError("Cannot resolve dynamic method name at compile time", call.getMethod());
return;
}
if (extension.beforeMethodCall(call)) {
extension.afterMethodCall(call);
return;
}
typeCheckingContext.pushEnclosingMethodCall(call);
Expression objectExpression = call.getObjectExpression();
objectExpression.visit(this);
call.getMethod().visit(this);
ClassNode receiver = getType(objectExpression);
if (objectExpression instanceof ConstructorCallExpression) { // GROOVY-10228
inferDiamondType((ConstructorCallExpression) objectExpression, receiver.getPlainNodeReference());
}
if (call.isSpreadSafe()) {
ClassNode componentType = inferComponentType(receiver, null);
if (componentType == null) {
addStaticTypeError("Spread-dot operator can only be used on iterable types", objectExpression);
} else {
MethodCallExpression subcall = callX(varX("item", componentType), name, call.getArguments());
subcall.setLineNumber(call.getLineNumber()); subcall.setColumnNumber(call.getColumnNumber());
subcall.setImplicitThis(call.isImplicitThis());
visitMethodCallExpression(subcall);
// inferred type should be a list of what sub-call returns
storeType(call, extension.buildListType(getType(subcall)));
storeTargetMethod(call, subcall.getNodeMetaData(DIRECT_METHOD_CALL_TARGET));
}
typeCheckingContext.popEnclosingMethodCall();
return;
}
Expression callArguments = call.getArguments();
ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(callArguments);
// visit functional arguments *after* target method selection
visitMethodCallArguments(receiver, argumentList, false, null);
try {
boolean isThisObjectExpression = isThisExpression(objectExpression);
ClassNode[] args = getArgumentTypes(argumentList);
boolean functorsVisited = false;
if (!isThisObjectExpression && receiver.equals(CLOSURE_TYPE)
&& ("call".equals(name) || "doCall".equals(name))) {
if (objectExpression instanceof VariableExpression) {
Variable variable = findTargetVariable((VariableExpression) objectExpression);
if (variable instanceof ASTNode) {
Parameter[] parameters = ((ASTNode) variable).getNodeMetaData(CLOSURE_ARGUMENTS);
if (parameters != null) {
checkForbiddenSpreadArgument(argumentList, parameters);
typeCheckClosureCall(callArguments, args, parameters);
}
ClassNode type = getType((ASTNode) variable);
if (type.equals(CLOSURE_TYPE)) { // GROOVY-10098, et al.
GenericsType[] genericsTypes = type.getGenericsTypes();
if (genericsTypes != null && genericsTypes.length == 1
&& genericsTypes[0].getLowerBound() == null) {
type = genericsTypes[0].getType();
} else {
type = OBJECT_TYPE;
}
}
if (type != null) {
storeType(call, type);
}
}
} else if (objectExpression instanceof ClosureExpression) {
// we can get actual parameters directly
Parameter[] parameters = ((ClosureExpression) objectExpression).getParameters();
if (parameters != null) {
typeCheckClosureCall(callArguments, args, parameters);
}
ClassNode type = getInferredReturnType(objectExpression);
if (type != null) {
storeType(call, type);
}
}
List<Expression> list = argumentList.getExpressions();
int nArgs = list.stream().noneMatch(e -> e instanceof SpreadExpression) ? list.size() : Integer.MAX_VALUE;
storeTargetMethod(call, nArgs == 0 ? CLOSURE_CALL_NO_ARG : nArgs == 1 ? CLOSURE_CALL_ONE_ARG : CLOSURE_CALL_VARGS);
} else {
List<MethodNode> mn = null;
Receiver<String> chosenReceiver = null;
// GROOVY-11386:
if (isThisObjectExpression && call.isImplicitThis()
&& typeCheckingContext.getEnclosingClosure() != null
&& !"call".equals(name) && !"doCall".equals(name)) { // GROOVY-9662
mn = CLOSURE_TYPE.getMethods(name);
if (!mn.isEmpty()) {
receiver = CLOSURE_TYPE.getPlainNodeReference();
objectExpression.removeNodeMetaData(INFERRED_TYPE);
}
}
if (mn == null || mn.isEmpty()) {
// method call receivers are:
// - closure delegate(s) or owner(s) and trait self type(s)
// - the actual receiver as found in the method call expression
// - any of the potential receivers found in the instanceof temporary table
// in that order
List<Receiver<String>> receivers = new ArrayList<>();
addReceivers(receivers, makeOwnerList(objectExpression), call.isImplicitThis());
MethodNode first = null;
for (Receiver<String> currentReceiver : receivers) {
mn = findMethod(currentReceiver.getType().getPlainNodeReference(), name, args);
if (!mn.isEmpty()) {
first = mn.get(0); // capture for error reporting
chosenReceiver = currentReceiver;
// GROOVY-10819, GROOVY-10820, GROOVY-11393, et al.:
// for a static receiver, only static methods are compatible
mn = allowStaticAccessToMember(mn, !currentReceiver.isObject());
if (!mn.isEmpty()) {
break;
}
}
}
if (mn.isEmpty() && !"call".equals(name)) {
// GROOVY-5705, GROOVY-5881, GROOVY-6324, GROOVY-11366: closure property
var property = propX(objectExpression, call.getMethod(), call.isSafe());
property.setImplicitThis(call.isImplicitThis());
if (existsProperty(property, true)
&& getType(property).equals(CLOSURE_TYPE)) {
chosenReceiver = Receiver.make(getType(property));
call.putNodeMetaData("callable property", property);
List<Expression> list = argumentList.getExpressions();
int nArgs = list.stream().noneMatch(e -> e instanceof SpreadExpression) ? list.size() : -1;
mn = List.of(nArgs == 0 ? CLOSURE_CALL_NO_ARG : nArgs == 1 ? CLOSURE_CALL_ONE_ARG : CLOSURE_CALL_VARGS);
}
}
if (mn.isEmpty()) {
mn = extension.handleMissingMethod(receiver, name, argumentList, args, call);
if (mn == null || mn.isEmpty()) {
if (first != null) mn = List.of(first); // instance method
else addNoMatchingMethodError(receiver, name, args, call);
}
}
}
if (!mn.isEmpty()) {
if (areCategoryMethodCalls(mn, name, args)) addCategoryMethodCallError(call);
{
ClassNode obj = chosenReceiver != null ? chosenReceiver.getType() : null;
if (mn.size() > 1 && obj instanceof UnionTypeClassNode) { // GROOVY-8965: support duck-typing using dynamic resolution
ClassNode returnType = mn.stream().map(MethodNode::getReturnType).reduce(WideningCategories::lowestUpperBound).get();
call.putNodeMetaData(DYNAMIC_RESOLUTION, returnType);
MethodNode tmp = new MethodNode(name, 0, returnType, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
tmp.setDeclaringClass(obj); // for storeTargetMethod
mn = Collections.singletonList(tmp);
} else {
mn = disambiguateMethods(mn, obj, args, call);
}
}
out: if (mn.size() != 1) {
addAmbiguousErrorMessage(mn, name, args, call);
} else {
MethodNode targetMethod = mn.get(0);
// note second pass to differentiate from extension that sets type
boolean mergeType = (call.getNodeMetaData(INFERRED_TYPE) != null);
storeTargetMethod(call, targetMethod); // trigger onMethodSelection
if (call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET) != targetMethod) {
break out; // type-checking script intervention
}
ClassNode declaringClass = targetMethod.getDeclaringClass();
if (chosenReceiver == null) {
chosenReceiver = Receiver.make(declaringClass.getPlainNodeReference());
}
if (!targetMethod.isStatic() && !(isClassType(declaringClass) || isObjectType(declaringClass)) // GROOVY-10939: Class or Object
&& !chosenReceiver.isObject() && !Boolean.TRUE.equals(call.getNodeMetaData(DYNAMIC_RESOLUTION))) {
addStaticTypeError("Non-static method " + prettyPrintTypeName(declaringClass) + "#" + targetMethod.getName() + " cannot be called from static context", call);
} else if ((chosenReceiver.getType().isInterface() || targetMethod.isAbstract()) && isSuperExpression(objectExpression)) { // GROOVY-8299, GROOVY-10341
String target = toMethodParametersString(targetMethod.getName(), extractTypesFromParameters(targetMethod.getParameters()));
if (targetMethod.isDefault() || Traits.hasDefaultImplementation(targetMethod)) { // GROOVY-10494
if (objectExpression instanceof VariableExpression)
addStaticTypeError("Default method " + target + " requires qualified super", call);
} else {
addStaticTypeError("Abstract method " + target + " cannot be called directly", call);
}
}
visitMethodCallArguments(chosenReceiver.getType(), argumentList, true, targetMethod); functorsVisited = true;
ClassNode returnType = getType(targetMethod);
// GROOVY-5470, GROOVY-6091, GROOVY-9604, GROOVY-11399:
if (isThisObjectExpression && call.isImplicitThis() && typeCheckingContext.getEnclosingClosure() != null
&& (targetMethod == GET_DELEGATE || targetMethod == GET_OWNER || targetMethod == GET_THISOBJECT)) {
switch (name) {
case "getDelegate":
var dm = getDelegationMetadata(typeCheckingContext.getEnclosingClosure().getClosureExpression());
if (dm != null) {
returnType = dm.getType();
break;
}
// falls through
case "getOwner":
if (typeCheckingContext.getEnclosingClosureStack().size() > 1) {
returnType = CLOSURE_TYPE.getPlainNodeReference();
break;
}
// falls through
case "getThisObject":
returnType = makeThis();
}
} else if (isUsingGenericsOrIsArrayUsingGenerics(returnType)) {
ClassNode irtg = inferReturnTypeGenerics(chosenReceiver.getType(), targetMethod, callArguments, call.getGenericsTypes());
if (irtg != null && implementsInterfaceOrIsSubclassOf(irtg, returnType))
returnType = irtg;
}
// GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915, et al.
Parameter[] parameters = targetMethod.getParameters();
if (chosenReceiver.getType().getGenericsTypes() != null && !targetMethod.isStatic() && !(targetMethod instanceof ExtensionMethodNode)) {
Map<GenericsTypeName, GenericsType> context = extractPlaceHoldersVisibleToDeclaration(chosenReceiver.getType(), targetMethod, argumentList);
parameters = Arrays.stream(parameters).map(p -> new Parameter(applyGenericsContext(context, p.getType()), p.getName())).toArray(Parameter[]::new);
}
resolvePlaceholdersFromImplicitTypeHints(args, argumentList, parameters);
if (typeCheckMethodsWithGenericsOrFail(chosenReceiver.getType(), args, targetMethod, call)) {
String data = chosenReceiver.getData();
if (data != null) {
// the method which has been chosen is supposed to be a call on delegate or owner
// so we store the information so that the static compiler may reuse it
call.putNodeMetaData(IMPLICIT_RECEIVER, data);
}
receiver = chosenReceiver.getType();
if (mergeType || call.getNodeMetaData(INFERRED_TYPE) == null)
storeType(call, adjustWithTraits(targetMethod, receiver, args, returnType));
if (objectExpression instanceof VariableExpression && ((VariableExpression) objectExpression).isClosureSharedVariable()) {
// if the object expression is a closure shared variable, we will have to perform a second pass
typeCheckingContext.secondPassExpressions.add(new SecondPassExpression<>(call, args));
}
} else {
call.removeNodeMetaData(DIRECT_METHOD_CALL_TARGET);
}
}
}
}
// adjust typing for explicit math methods which have special handling; operator variants handled elsewhere
if (args.length == 1 && isNumberType(args[0]) && isNumberType(receiver) && NUMBER_OPS.containsKey(name)) {
ClassNode resultType = getMathResultType(NUMBER_OPS.get(name), receiver, args[0], name);
if (resultType != null) {
storeType(call, resultType);
}
}
// type-checking extension may have changed or cleared target method
MethodNode target = call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
if (!functorsVisited) {
visitMethodCallArguments(receiver, argumentList, true, target);
}
if (target != null) { Parameter[] params = target.getParameters();
checkClosureMetadata(argumentList.getExpressions(), params);
checkForbiddenSpreadArgument(argumentList, params);
} else {
checkForbiddenSpreadArgument(argumentList);
}
} finally {
typeCheckingContext.popEnclosingMethodCall();
extension.afterMethodCall(call);
}
}