All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.jet.lang.resolve.calls.InlineCallResolverExtension Maven / Gradle / Ivy

/*
 * Copyright 2010-2013 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.jet.lang.resolve.calls;

import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.diagnostics.Errors;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext;
import org.jetbrains.jet.lang.resolve.calls.model.DefaultValueArgument;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument;
import org.jetbrains.jet.lang.resolve.calls.model.VarargValueArgument;
import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver;
import org.jetbrains.jet.lang.resolve.scopes.receivers.ExtensionReceiver;
import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.lang.InlineUtil;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import org.jetbrains.jet.lexer.JetToken;
import org.jetbrains.jet.lexer.JetTokens;

import java.util.HashSet;
import java.util.Set;

public class InlineCallResolverExtension implements CallResolverExtension {

    private final SimpleFunctionDescriptor descriptor;

    private final Set inlinableParameters = new HashSet();

    private final boolean isEffectivelyPublicApiFunction;

    public InlineCallResolverExtension(@NotNull SimpleFunctionDescriptor descriptor) {
        assert descriptor.getInlineStrategy().isInline() : "This extension should be created only for inline functions but not " + descriptor;
        this.descriptor = descriptor;
        this.isEffectivelyPublicApiFunction = isEffectivelyPublicApi(descriptor);

        for (ValueParameterDescriptor param : descriptor.getValueParameters()) {
            if (isInlinableParameter(param)) {
                inlinableParameters.add(param);
            }
        }

        //add extension receiver as inlinable
        ReceiverParameterDescriptor receiverParameter = descriptor.getReceiverParameter();
        if (receiverParameter != null) {
            if (isInlinableParameter(receiverParameter)) {
                inlinableParameters.add(receiverParameter);
            }
        }
    }

    @Override
    public  void run(@NotNull ResolvedCall resolvedCall, @NotNull BasicCallResolutionContext context) {
        JetExpression expression = context.call.getCalleeExpression();
        if (expression == null) {
            return;
        }

        //checking that only invoke or inlinable extension called on function parameter
        CallableDescriptor targetDescriptor = resolvedCall.getResultingDescriptor();
        checkCallWithReceiver(context, targetDescriptor, resolvedCall.getThisObject(), expression);
        checkCallWithReceiver(context, targetDescriptor, resolvedCall.getReceiverArgument(), expression);

        if (inlinableParameters.contains(targetDescriptor)) {
            if (!couldAccessVariable(expression)) {
                context.trace.report(Errors.USAGE_IS_NOT_INLINABLE.on(expression, expression, descriptor));
            }
        }

        for (ResolvedValueArgument value : resolvedCall.getValueArguments().values()) {
            if (!(value instanceof DefaultValueArgument)) {
                for (ValueArgument argument : value.getArguments()) {
                    checkValueParameter(context, targetDescriptor, argument, value instanceof VarargValueArgument);
                }
            }
        }

        checkVisibility(targetDescriptor, expression, context);
        checkRecursion(targetDescriptor, expression, context);
    }

    private static boolean couldAccessVariable(JetExpression expression) {
        PsiElement parent = expression.getParent();
        while (parent != null) {
            if (parent instanceof JetValueArgument ||
                parent instanceof JetBinaryExpression ||
                parent instanceof JetUnaryExpression ||
                parent instanceof JetLabeledExpression ||
                parent instanceof JetDotQualifiedExpression ||
                parent instanceof JetCallExpression ||
                parent instanceof JetArrayAccessExpression ||
                parent instanceof JetMultiDeclaration) {

                if (parent instanceof JetLabeledExpression) {
                    parent = parent.getParent();
                    continue;
                }
                else if (parent instanceof JetBinaryExpression) {
                    JetToken token = JetPsiUtil.getOperationToken((JetOperationExpression) parent);
                    if (token == JetTokens.EQ || token == JetTokens.ANDAND || token == JetTokens.OROR) {
                        //assignment
                        return false;
                    }
                }

                //check that it's in inlineable call would be in resolve call of parent
                return true;
            }
            else if (parent instanceof JetParenthesizedExpression || parent instanceof JetBinaryExpressionWithTypeRHS) {
                parent = parent.getParent();
            }
            else {
                return false;
            }
        }
        return false;
    }

    private void checkValueParameter(BasicCallResolutionContext context, CallableDescriptor targetDescriptor, ValueArgument argument, boolean isVararg) {
        JetExpression jetExpression = argument.getArgumentExpression();
        if (jetExpression == null) {
            return;
        }
        CallableDescriptor varDescriptor = getDescriptor(context, jetExpression);

        if (varDescriptor != null && inlinableParameters.contains(varDescriptor)) {
            checkFunctionCall(context, targetDescriptor, jetExpression, isVararg);
        }
    }

    private void checkCallWithReceiver(
            @NotNull BasicCallResolutionContext context,
            @NotNull CallableDescriptor targetDescriptor,
            @NotNull ReceiverValue receiver,
            @Nullable JetExpression expression
    ) {
        if (!receiver.exists()) return;

        CallableDescriptor varDescriptor = null;
        JetExpression receiverExpression = null;
        if (receiver instanceof ExpressionReceiver) {
            receiverExpression = ((ExpressionReceiver) receiver).getExpression();
            varDescriptor = getDescriptor(context, receiverExpression);
        }
        else if (receiver instanceof ExtensionReceiver) {
            ExtensionReceiver extensionReceiver = (ExtensionReceiver) receiver;
            CallableDescriptor extension = extensionReceiver.getDeclarationDescriptor();

            varDescriptor = extension.getReceiverParameter();
            assert varDescriptor != null : "Extension should have receiverParameterDescriptor: " + extension;

            receiverExpression = expression;
        }

        if (inlinableParameters.contains(varDescriptor)) {
            //check that it's invoke or inlinable extension
            checkFunctionCall(context, targetDescriptor, receiverExpression, false);
        }
    }

    @Nullable
    private static CallableDescriptor getDescriptor(
            @NotNull BasicCallResolutionContext context,
            @NotNull JetExpression expression
    ) {
        ResolvedCall thisCall = context.trace.get(BindingContext.RESOLVED_CALL, expression);
        return thisCall != null ? thisCall.getResultingDescriptor() : null;
    }

    private void checkFunctionCall(
            BasicCallResolutionContext context,
            CallableDescriptor targetDescriptor,
            JetExpression receiverExpresssion,
            boolean isVararg
    ) {
        boolean inlinableCall = isInvokeOrInlineExtension(targetDescriptor);
        if (!inlinableCall || isVararg) {
            context.trace.report(Errors.USAGE_IS_NOT_INLINABLE.on(receiverExpresssion, receiverExpresssion, descriptor));
        }
    }

    public void checkRecursion(
            @NotNull CallableDescriptor targetDescriptor,
            @NotNull JetElement expression,
            @NotNull BasicCallResolutionContext context
    ) {
        if (targetDescriptor.getOriginal() == descriptor) {
            context.trace.report(Errors.RECURSION_IN_INLINE.on(expression, expression, descriptor));
        }
    }

    private static boolean isInlinableParameter(@NotNull CallableDescriptor descriptor) {
        JetType type = descriptor.getReturnType();
        return type != null &&
               KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(type) &&
               !type.isNullable() &&
               !InlineUtil.hasNoinlineAnnotation(descriptor);
    }

    private static boolean isInvokeOrInlineExtension(@NotNull CallableDescriptor descriptor) {
        if (!(descriptor instanceof SimpleFunctionDescriptor)) {
            return false;
        }

        DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
        boolean isInvoke = descriptor.getName().asString().equals("invoke") &&
                           containingDeclaration instanceof ClassDescriptor &&
                           KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(((ClassDescriptor) containingDeclaration).getDefaultType());

        return isInvoke ||
               //or inline extension
               ((SimpleFunctionDescriptor) descriptor).getInlineStrategy().isInline();
    }

    private void checkVisibility(@NotNull CallableDescriptor declarationDescriptor, @NotNull JetElement expression, @NotNull BasicCallResolutionContext context){
        if (isEffectivelyPublicApiFunction && !isEffectivelyPublicApi(declarationDescriptor) && declarationDescriptor.getVisibility() != Visibilities.LOCAL) {
            context.trace.report(Errors.INVISIBLE_MEMBER_FROM_INLINE.on(expression, declarationDescriptor, descriptor));
        }
    }

    private static boolean isEffectivelyPublicApi(DeclarationDescriptorWithVisibility descriptor) {
        DeclarationDescriptorWithVisibility parent = descriptor;
        while (parent != null) {
            if (!parent.getVisibility().isPublicAPI()) {
                return false;
            }
            parent = DescriptorUtils.getParentOfType(parent, DeclarationDescriptorWithVisibility.class);
        }
        return true;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy