public void visitMethodCallExpression()

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);
        }
    }