public void writeMethodReferenceExpression()

in src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java [83:205]


    public void writeMethodReferenceExpression(final MethodReferenceExpression methodReferenceExpression) {
        // functional interface target is required for native method reference generation
        ClassNode  functionalType = methodReferenceExpression.getNodeMetaData(StaticTypesMarker.PARAMETER_TYPE);
        MethodNode abstractMethod = ClassHelper.findSAM(functionalType);
        if (abstractMethod == null || !functionalType.isInterface()) {
            // generate the default bytecode -- most likely a method closure
            super.writeMethodReferenceExpression(methodReferenceExpression);
            return;
        }

        ClassNode classNode = controller.getClassNode();
        Expression typeOrTargetRef = methodReferenceExpression.getExpression();
        boolean isClassExpression = (typeOrTargetRef instanceof ClassExpression);
        boolean targetIsArgument = false; // implied argument for expr::staticMethod?
        ClassNode typeOrTargetRefType = isClassExpression ? typeOrTargetRef.getType()
                : controller.getTypeChooser().resolveType(typeOrTargetRef, classNode);

        if (ClassHelper.isPrimitiveType(typeOrTargetRefType)) // GROOVY-11353
            typeOrTargetRefType = ClassHelper.getWrapper(typeOrTargetRefType);

        ClassNode[] methodReferenceParamTypes = methodReferenceExpression.getNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS);
        Parameter[] parametersWithExactType = createParametersWithExactType(abstractMethod, methodReferenceParamTypes);
        String methodRefName = methodReferenceExpression.getMethodName().getText();
        boolean isConstructorReference = isConstructorReference(methodRefName);

        MethodNode methodRefMethod;
        if (isConstructorReference) {
            methodRefName = controller.getContext().getNextConstructorReferenceSyntheticMethodName(controller.getMethodNode());
            methodRefMethod = addSyntheticMethodForConstructorReference(methodRefName, typeOrTargetRefType, parametersWithExactType);
        } else {
            // TODO: move the findMethodRefMethod and checking to StaticTypeCheckingVisitor
            methodRefMethod = findMethodRefMethod(methodRefName, parametersWithExactType, typeOrTargetRef, typeOrTargetRefType);
            if (methodReferenceExpression.getNodeMetaData(StaticTypesMarker.PV_METHODS_ACCESS) != null) { // GROOVY-11301, GROOVY-11365: access bridge indicated
                Map<MethodNode,MethodNode> bridgeMethods = typeOrTargetRefType.redirect().getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_BRIDGE_METHODS);
                if (bridgeMethods != null) methodRefMethod = bridgeMethods.getOrDefault(methodRefMethod, methodRefMethod); // bridge may not have been generated
            }
        }

        validate(methodReferenceExpression, typeOrTargetRefType, methodRefName, methodRefMethod, parametersWithExactType,
                resolveClassNodeGenerics(extractPlaceholders(functionalType), null, abstractMethod.getReturnType()));

        if (isBridgeMethod(methodRefMethod)) {
            targetIsArgument = true; // GROOVY-11301, GROOVY-11365
            if (isClassExpression) { // method expects an instance argument
                methodRefMethod = addSyntheticMethodForDGSM(methodRefMethod);
            }
        } else if (isExtensionMethod(methodRefMethod)) {
            ExtensionMethodNode extensionMethodNode = (ExtensionMethodNode) methodRefMethod;
            methodRefMethod  = extensionMethodNode.getExtensionMethodNode();
            boolean isStatic = extensionMethodNode.isStaticExtension();
            if (isStatic) { // create adapter method to pass extra argument
                methodRefMethod = addSyntheticMethodForDGSM(methodRefMethod);
            }
            if (isStatic || isClassExpression) {
                // replace expression with declaring type
                typeOrTargetRefType = methodRefMethod.getDeclaringClass();
                typeOrTargetRef = makeClassTarget(typeOrTargetRefType, typeOrTargetRef);
            } else { // GROOVY-10653
                targetIsArgument = true; // ex: "string"::size
            }
        } else if (isVargs(methodRefMethod.getParameters())) {
            int mParameters = abstractMethod.getParameters().length;
            int nParameters = methodRefMethod.getParameters().length;
            if (isTypeReferringInstanceMethod(typeOrTargetRef, methodRefMethod)) nParameters += 1;
            if (mParameters > nParameters || mParameters == nParameters-1 || (mParameters == nParameters
                    && !isAssignableTo(last(parametersWithExactType).getType(), last(methodRefMethod.getParameters()).getType()))) {
                // GROOVY-9813: reference to variadic method which needs adapter method to match runtime arguments to its parameters
                if (!isClassExpression && !methodRefMethod.isStatic() && !methodRefMethod.getDeclaringClass().equals(classNode)) {
                    targetIsArgument = true; // GROOVY-10653: create static adapter in source class with target as first parameter
                    mParameters += 1;
                }
                methodRefMethod = addSyntheticMethodForVariadicReference(methodRefMethod, mParameters, isClassExpression || targetIsArgument);
                if (methodRefMethod.isStatic() && !targetIsArgument) {
                    // replace expression with declaring type
                    typeOrTargetRefType = methodRefMethod.getDeclaringClass();
                    typeOrTargetRef = makeClassTarget(typeOrTargetRefType, typeOrTargetRef);
                }
            }
        }

        if (!isClassExpression) {
            if (isConstructorReference) { // TODO: move this check to the parser
                addFatalError("Constructor reference must be TypeName::new", methodReferenceExpression);
            } else if (methodRefMethod.isStatic() && !targetIsArgument) {
                // "string"::valueOf refers to static method, so instance is superfluous
                typeOrTargetRef = makeClassTarget(typeOrTargetRefType, typeOrTargetRef);
                isClassExpression = true;
            } else {
                typeOrTargetRef.visit(controller.getAcg());
                controller.getOperandStack().box(); // GROOVY-11353
            }
        }

        int referenceKind;
        if (isConstructorReference || methodRefMethod.isStatic()) {
            referenceKind = Opcodes.H_INVOKESTATIC;
        } else if (methodRefMethod.getDeclaringClass().isInterface()) {
            referenceKind = Opcodes.H_INVOKEINTERFACE; // GROOVY-9853
        } else {
            referenceKind = Opcodes.H_INVOKEVIRTUAL;
        }

        String methodName = abstractMethod.getName();
        String methodDesc = BytecodeHelper.getMethodDescriptor(functionalType.redirect(),
                isClassExpression ? Parameter.EMPTY_ARRAY : new Parameter[]{new Parameter(typeOrTargetRefType, "__METHODREF_EXPR_INSTANCE")});

        Handle bootstrapMethod = createBootstrapMethod(classNode.isInterface(), false);
        Object[] bootstrapArgs = createBootstrapMethodArguments(
                createMethodDescriptor(abstractMethod),
                referenceKind,
                methodRefMethod.getDeclaringClass(),
                methodRefMethod,
                parametersWithExactType,
                false
        );
        controller.getMethodVisitor().visitInvokeDynamicInsn(methodName, methodDesc, bootstrapMethod, bootstrapArgs);

        if (isClassExpression) {
            controller.getOperandStack().push(functionalType);
        } else {
            controller.getOperandStack().replace(functionalType, 1);
        }
    }