org.jetbrains.kotlin.resolve.calls.checkers.InlineChecker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* Copyright 2010-2016 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.kotlin.resolve.calls.checkers;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.FunctionTypesKt;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.lexer.KtToken;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument;
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver;
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
import org.jetbrains.kotlin.util.OperatorNameConventions;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import static org.jetbrains.kotlin.diagnostics.Errors.NON_LOCAL_RETURN_NOT_ALLOWED;
import static org.jetbrains.kotlin.diagnostics.Errors.USAGE_IS_NOT_INLINABLE;
import static org.jetbrains.kotlin.resolve.inline.InlineUtil.allowsNonLocalReturns;
import static org.jetbrains.kotlin.resolve.inline.InlineUtil.checkNonLocalReturnUsage;
class InlineChecker implements CallChecker {
private final FunctionDescriptor descriptor;
private final Set inlinableParameters = new LinkedHashSet();
private final EffectiveVisibility inlineFunEffectiveVisibility;
private final boolean isEffectivelyPrivateApiFunction;
public InlineChecker(@NotNull FunctionDescriptor descriptor) {
assert InlineUtil.isInline(descriptor) : "This extension should be created only for inline functions: " + descriptor;
this.descriptor = descriptor;
this.inlineFunEffectiveVisibility = EffectiveVisibilityKt.effectiveVisibility(descriptor, descriptor.getVisibility(), true);
this.isEffectivelyPrivateApiFunction = DescriptorUtilsKt.isEffectivelyPrivateApi(descriptor);
for (ValueParameterDescriptor param : descriptor.getValueParameters()) {
if (isInlinableParameter(param)) {
inlinableParameters.add(param);
}
}
}
@Override
public void check(@NotNull ResolvedCall> resolvedCall, @NotNull PsiElement reportOn, @NotNull CallCheckerContext context) {
KtExpression expression = resolvedCall.getCall().getCalleeExpression();
if (expression == null) {
return;
}
//checking that only invoke or inlinable extension called on function parameter
CallableDescriptor targetDescriptor = resolvedCall.getResultingDescriptor();
checkCallWithReceiver(context, targetDescriptor, resolvedCall.getDispatchReceiver(), expression);
checkCallWithReceiver(context, targetDescriptor, resolvedCall.getExtensionReceiver(), expression);
if (inlinableParameters.contains(targetDescriptor)) {
if (!isInsideCall(expression)) {
context.getTrace().report(USAGE_IS_NOT_INLINABLE.on(expression, expression, descriptor));
}
}
for (Map.Entry entry : resolvedCall.getValueArguments().entrySet()) {
ResolvedValueArgument value = entry.getValue();
ValueParameterDescriptor valueDescriptor = entry.getKey();
if (!(value instanceof DefaultValueArgument)) {
for (ValueArgument argument : value.getArguments()) {
checkValueParameter(context, targetDescriptor, argument, valueDescriptor);
}
}
}
checkVisibilityAndAccess(targetDescriptor, expression, context);
checkRecursion(context, targetDescriptor, expression);
}
private static boolean isInsideCall(KtExpression expression) {
KtElement parent = KtPsiUtil.getParentCallIfPresent(expression);
if (parent instanceof KtBinaryExpression) {
KtToken token = KtPsiUtil.getOperationToken((KtOperationExpression) parent);
if (token == KtTokens.EQ || token == KtTokens.ANDAND || token == KtTokens.OROR) {
//assignment
return false;
}
}
if (parent != null) {
//UGLY HACK
//check there is no casts
PsiElement current = expression;
while (current != parent) {
if (current instanceof KtBinaryExpressionWithTypeRHS) {
return false;
}
current = current.getParent();
}
}
return parent != null;
}
private void checkValueParameter(
@NotNull CallCheckerContext context,
@NotNull CallableDescriptor targetDescriptor,
@NotNull ValueArgument targetArgument,
@NotNull ValueParameterDescriptor targetParameterDescriptor
) {
KtExpression argumentExpression = targetArgument.getArgumentExpression();
if (argumentExpression == null) {
return;
}
CallableDescriptor argumentCallee = getCalleeDescriptor(context, argumentExpression, false);
if (argumentCallee != null && inlinableParameters.contains(argumentCallee)) {
if (InlineUtil.isInline(targetDescriptor) && isInlinableParameter(targetParameterDescriptor)) {
if (allowsNonLocalReturns(argumentCallee) && !allowsNonLocalReturns(targetParameterDescriptor)) {
context.getTrace().report(NON_LOCAL_RETURN_NOT_ALLOWED.on(argumentExpression, argumentExpression));
}
else {
checkNonLocalReturn(context, argumentCallee, argumentExpression);
}
}
else {
context.getTrace().report(USAGE_IS_NOT_INLINABLE.on(argumentExpression, argumentExpression, descriptor));
}
}
}
private void checkCallWithReceiver(
@NotNull CallCheckerContext context,
@NotNull CallableDescriptor targetDescriptor,
@Nullable ReceiverValue receiver,
@Nullable KtExpression expression
) {
if (receiver == null) return;
CallableDescriptor varDescriptor = null;
KtExpression receiverExpression = null;
if (receiver instanceof ExpressionReceiver) {
receiverExpression = ((ExpressionReceiver) receiver).getExpression();
varDescriptor = getCalleeDescriptor(context, receiverExpression, true);
}
else if (receiver instanceof ExtensionReceiver) {
ExtensionReceiver extensionReceiver = (ExtensionReceiver) receiver;
CallableDescriptor extension = extensionReceiver.getDeclarationDescriptor();
varDescriptor = extension.getExtensionReceiverParameter();
assert varDescriptor != null : "Extension should have receiverParameterDescriptor: " + extension;
receiverExpression = expression;
}
if (inlinableParameters.contains(varDescriptor)) {
//check that it's invoke or inlinable extension
checkLambdaInvokeOrExtensionCall(context, varDescriptor, targetDescriptor, receiverExpression);
}
}
@Nullable
private static CallableDescriptor getCalleeDescriptor(
@NotNull CallCheckerContext context,
@NotNull KtExpression expression,
boolean unwrapVariableAsFunction
) {
if (!(expression instanceof KtSimpleNameExpression || expression instanceof KtThisExpression)) return null;
ResolvedCall> thisCall = CallUtilKt.getResolvedCall(expression, context.getTrace().getBindingContext());
if (unwrapVariableAsFunction && thisCall instanceof VariableAsFunctionResolvedCall) {
return ((VariableAsFunctionResolvedCall) thisCall).getVariableCall().getResultingDescriptor();
}
return thisCall != null ? thisCall.getResultingDescriptor() : null;
}
private void checkLambdaInvokeOrExtensionCall(
@NotNull CallCheckerContext context,
@NotNull CallableDescriptor lambdaDescriptor,
@NotNull CallableDescriptor callDescriptor,
@NotNull KtExpression receiverExpression
) {
boolean inlinableCall = isInvokeOrInlineExtension(callDescriptor);
if (!inlinableCall) {
context.getTrace().report(USAGE_IS_NOT_INLINABLE.on(receiverExpression, receiverExpression, descriptor));
}
else {
checkNonLocalReturn(context, lambdaDescriptor, receiverExpression);
}
}
private void checkRecursion(
@NotNull CallCheckerContext context,
@NotNull CallableDescriptor targetDescriptor,
@NotNull KtElement expression
) {
if (targetDescriptor.getOriginal() == descriptor) {
context.getTrace().report(Errors.RECURSION_IN_INLINE.on(expression, expression, descriptor));
}
}
private static boolean isInlinableParameter(@NotNull ParameterDescriptor descriptor) {
return InlineUtil.isInlineLambdaParameter(descriptor) && !descriptor.getType().isMarkedNullable();
}
private static boolean isInvokeOrInlineExtension(@NotNull CallableDescriptor descriptor) {
if (!(descriptor instanceof SimpleFunctionDescriptor)) {
return false;
}
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
boolean isInvoke =
descriptor.getName().equals(OperatorNameConventions.INVOKE) &&
containingDeclaration instanceof ClassDescriptor &&
FunctionTypesKt.isFunctionType(((ClassDescriptor) containingDeclaration).getDefaultType());
return isInvoke || InlineUtil.isInline(descriptor);
}
private void checkVisibilityAndAccess(
@NotNull CallableDescriptor calledDescriptor,
@NotNull KtElement expression,
@NotNull CallCheckerContext context
) {
EffectiveVisibility calledFunEffectiveVisibility =
isDefinedInInlineFunction(calledDescriptor) ?
EffectiveVisibility.Public.INSTANCE :
EffectiveVisibilityKt.effectiveVisibility(calledDescriptor, calledDescriptor.getVisibility(), true);
boolean isCalledFunPublicOrPublishedApi = calledFunEffectiveVisibility.getPublicApi();
boolean isInlineFunPublicOrPublishedApi = inlineFunEffectiveVisibility.getPublicApi();
if (isInlineFunPublicOrPublishedApi &&
!isCalledFunPublicOrPublishedApi &&
calledDescriptor.getVisibility() != Visibilities.LOCAL) {
context.getTrace().report(Errors.NON_PUBLIC_CALL_FROM_PUBLIC_INLINE.on(expression, calledDescriptor, descriptor));
}
else {
checkPrivateClassMemberAccess(calledDescriptor, expression, context);
}
if (!(calledDescriptor instanceof ConstructorDescriptor) &&
isInlineFunPublicOrPublishedApi &&
inlineFunEffectiveVisibility.toVisibility() != Visibilities.PROTECTED &&
calledFunEffectiveVisibility.toVisibility() == Visibilities.PROTECTED) {
context.getTrace().report(Errors.PROTECTED_CALL_FROM_PUBLIC_INLINE.on(expression, calledDescriptor));
}
}
private void checkPrivateClassMemberAccess(
@NotNull DeclarationDescriptor declarationDescriptor,
@NotNull KtElement expression,
@NotNull CallCheckerContext context
) {
if (!isEffectivelyPrivateApiFunction) {
if (DescriptorUtilsKt.isInsidePrivateClass(declarationDescriptor)) {
context.getTrace().report(Errors.PRIVATE_CLASS_MEMBER_FROM_INLINE.on(expression, declarationDescriptor, descriptor));
}
}
}
private boolean isDefinedInInlineFunction(@NotNull DeclarationDescriptorWithVisibility startDescriptor) {
DeclarationDescriptorWithVisibility parent = startDescriptor;
while (parent != null) {
if (parent.getContainingDeclaration() == descriptor) return true;
parent = DescriptorUtils.getParentOfType(parent, DeclarationDescriptorWithVisibility.class);
}
return false;
}
private void checkNonLocalReturn(
@NotNull CallCheckerContext context,
@NotNull CallableDescriptor inlinableParameterDescriptor,
@NotNull KtExpression parameterUsage
) {
if (!allowsNonLocalReturns(inlinableParameterDescriptor)) return;
if (!checkNonLocalReturnUsage(descriptor, parameterUsage, context.getResolutionContext())) {
context.getTrace().report(NON_LOCAL_RETURN_NOT_ALLOWED.on(parameterUsage, parameterUsage));
}
}
}