Convert InlineChecker to Kotlin

This commit is contained in:
Mikhael Bogdanov
2017-05-17 13:54:01 +02:00
parent 4a28287c68
commit 0d1ede40c1
@@ -14,291 +14,275 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.resolve.calls.checkers; package org.jetbrains.kotlin.resolve.calls.checkers
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement
import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.builtins.*
import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.builtins.FunctionTypesKt; import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.diagnostics.Errors; import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.lexer.KtToken; import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.resolve.calls.callUtil.*
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument; import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument; import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall; import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.inline.InlineUtil; import org.jetbrains.kotlin.util.OperatorNameConventions
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.LinkedHashSet
import java.util.Map;
import java.util.Set;
import static org.jetbrains.kotlin.diagnostics.Errors.NON_LOCAL_RETURN_NOT_ALLOWED; import org.jetbrains.kotlin.diagnostics.Errors.NON_LOCAL_RETURN_NOT_ALLOWED
import static org.jetbrains.kotlin.diagnostics.Errors.USAGE_IS_NOT_INLINABLE; import org.jetbrains.kotlin.diagnostics.Errors.USAGE_IS_NOT_INLINABLE
import static org.jetbrains.kotlin.resolve.inline.InlineUtil.allowsNonLocalReturns; import org.jetbrains.kotlin.resolve.inline.InlineUtil.allowsNonLocalReturns
import static org.jetbrains.kotlin.resolve.inline.InlineUtil.checkNonLocalReturnUsage; import org.jetbrains.kotlin.resolve.inline.InlineUtil.checkNonLocalReturnUsage
class InlineChecker implements CallChecker { internal class InlineChecker(private val descriptor: FunctionDescriptor) : CallChecker {
private final FunctionDescriptor descriptor; private val inlinableParameters = LinkedHashSet<CallableDescriptor>()
private final Set<CallableDescriptor> inlinableParameters = new LinkedHashSet<>(); private val inlineFunEffectiveVisibility: EffectiveVisibility
private final EffectiveVisibility inlineFunEffectiveVisibility; private val isEffectivelyPrivateApiFunction: Boolean
private final boolean isEffectivelyPrivateApiFunction;
public InlineChecker(@NotNull FunctionDescriptor descriptor) { init {
assert InlineUtil.isInline(descriptor) : "This extension should be created only for inline functions: " + descriptor; assert(InlineUtil.isInline(descriptor)) { "This extension should be created only for inline functions: " + descriptor }
this.descriptor = descriptor; this.inlineFunEffectiveVisibility = descriptor.effectiveVisibility(descriptor.visibility, true)
this.inlineFunEffectiveVisibility = EffectiveVisibilityKt.effectiveVisibility(descriptor, descriptor.getVisibility(), true); this.isEffectivelyPrivateApiFunction = descriptor.isEffectivelyPrivateApi
this.isEffectivelyPrivateApiFunction = DescriptorUtilsKt.isEffectivelyPrivateApi(descriptor); for (param in descriptor.valueParameters) {
for (ValueParameterDescriptor param : descriptor.getValueParameters()) {
if (isInlinableParameter(param)) { if (isInlinableParameter(param)) {
inlinableParameters.add(param); inlinableParameters.add(param)
} }
} }
} }
@Override override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
public void check(@NotNull ResolvedCall<?> resolvedCall, @NotNull PsiElement reportOn, @NotNull CallCheckerContext context) { val expression = resolvedCall.call.calleeExpression ?: return
KtExpression expression = resolvedCall.getCall().getCalleeExpression();
if (expression == null) {
return;
}
//checking that only invoke or inlinable extension called on function parameter //checking that only invoke or inlinable extension called on function parameter
CallableDescriptor targetDescriptor = resolvedCall.getResultingDescriptor(); val targetDescriptor = resolvedCall.resultingDescriptor
checkCallWithReceiver(context, targetDescriptor, resolvedCall.getDispatchReceiver(), expression); checkCallWithReceiver(context, targetDescriptor, resolvedCall.dispatchReceiver, expression)
checkCallWithReceiver(context, targetDescriptor, resolvedCall.getExtensionReceiver(), expression); checkCallWithReceiver(context, targetDescriptor, resolvedCall.extensionReceiver, expression)
if (inlinableParameters.contains(targetDescriptor)) { if (inlinableParameters.contains(targetDescriptor)) {
if (!isInsideCall(expression)) { if (!isInsideCall(expression)) {
context.getTrace().report(USAGE_IS_NOT_INLINABLE.on(expression, expression, descriptor)); context.trace.report(USAGE_IS_NOT_INLINABLE.on(expression, expression, descriptor))
} }
} }
for (Map.Entry<ValueParameterDescriptor, ResolvedValueArgument> entry : resolvedCall.getValueArguments().entrySet()) { for ((valueDescriptor, value) in resolvedCall.valueArguments) {
ResolvedValueArgument value = entry.getValue(); if (value !is DefaultValueArgument) {
ValueParameterDescriptor valueDescriptor = entry.getKey(); for (argument in value.arguments) {
if (!(value instanceof DefaultValueArgument)) { checkValueParameter(context, targetDescriptor, argument, valueDescriptor)
for (ValueArgument argument : value.getArguments()) {
checkValueParameter(context, targetDescriptor, argument, valueDescriptor);
} }
} }
} }
checkVisibilityAndAccess(targetDescriptor, expression, context); checkVisibilityAndAccess(targetDescriptor, expression, context)
checkRecursion(context, targetDescriptor, expression); checkRecursion(context, targetDescriptor, expression)
} }
private static boolean isInsideCall(KtExpression expression) { private fun isInsideCall(expression: KtExpression): Boolean {
KtElement parent = KtPsiUtil.getParentCallIfPresent(expression); val parent = KtPsiUtil.getParentCallIfPresent(expression)
if (parent instanceof KtBinaryExpression) { if (parent is KtBinaryExpression) {
KtToken token = KtPsiUtil.getOperationToken((KtOperationExpression) parent); val token = KtPsiUtil.getOperationToken((parent as KtOperationExpression?)!!)
if (token == KtTokens.EQ || token == KtTokens.ANDAND || token == KtTokens.OROR) { if (token === KtTokens.EQ || token === KtTokens.ANDAND || token === KtTokens.OROR) {
//assignment //assignment
return false; return false
} }
} }
if (parent != null) { if (parent != null) {
//UGLY HACK //UGLY HACK
//check there is no casts //check there is no casts
PsiElement current = expression; var current: PsiElement = expression
while (current != parent) { while (current !== parent) {
if (current instanceof KtBinaryExpressionWithTypeRHS) { if (current is KtBinaryExpressionWithTypeRHS) {
return false; return false
} }
current = current.getParent(); current = current.parent
} }
} }
return parent != null; return parent != null
} }
private void checkValueParameter( private fun checkValueParameter(
@NotNull CallCheckerContext context, context: CallCheckerContext,
@NotNull CallableDescriptor targetDescriptor, targetDescriptor: CallableDescriptor,
@NotNull ValueArgument targetArgument, targetArgument: ValueArgument,
@NotNull ValueParameterDescriptor targetParameterDescriptor targetParameterDescriptor: ValueParameterDescriptor
) { ) {
KtExpression argumentExpression = targetArgument.getArgumentExpression(); val argumentExpression = targetArgument.getArgumentExpression() ?: return
if (argumentExpression == null) { val argumentCallee = getCalleeDescriptor(context, argumentExpression, false)
return;
}
CallableDescriptor argumentCallee = getCalleeDescriptor(context, argumentExpression, false);
if (argumentCallee != null && inlinableParameters.contains(argumentCallee)) { if (argumentCallee != null && inlinableParameters.contains(argumentCallee)) {
if (InlineUtil.isInline(targetDescriptor) && isInlinableParameter(targetParameterDescriptor)) { if (InlineUtil.isInline(targetDescriptor) && isInlinableParameter(targetParameterDescriptor)) {
if (allowsNonLocalReturns(argumentCallee) && !allowsNonLocalReturns(targetParameterDescriptor)) { if (allowsNonLocalReturns(argumentCallee) && !allowsNonLocalReturns(targetParameterDescriptor)) {
context.getTrace().report(NON_LOCAL_RETURN_NOT_ALLOWED.on(argumentExpression, argumentExpression)); context.trace.report(NON_LOCAL_RETURN_NOT_ALLOWED.on(argumentExpression, argumentExpression))
} }
else { else {
checkNonLocalReturn(context, argumentCallee, argumentExpression); checkNonLocalReturn(context, argumentCallee, argumentExpression)
} }
} }
else { else {
context.getTrace().report(USAGE_IS_NOT_INLINABLE.on(argumentExpression, argumentExpression, descriptor)); context.trace.report(USAGE_IS_NOT_INLINABLE.on(argumentExpression, argumentExpression, descriptor))
} }
} }
} }
private void checkCallWithReceiver( private fun checkCallWithReceiver(
@NotNull CallCheckerContext context, context: CallCheckerContext,
@NotNull CallableDescriptor targetDescriptor, targetDescriptor: CallableDescriptor,
@Nullable ReceiverValue receiver, receiver: ReceiverValue?,
@Nullable KtExpression expression expression: KtExpression?
) { ) {
if (receiver == null) return; if (receiver == null) return
CallableDescriptor varDescriptor = null; val varDescriptor: CallableDescriptor?
KtExpression receiverExpression = null; val receiverExpression: KtExpression?
if (receiver instanceof ExpressionReceiver) { if (receiver is ExpressionReceiver) {
receiverExpression = ((ExpressionReceiver) receiver).getExpression(); receiverExpression = receiver.expression
varDescriptor = getCalleeDescriptor(context, receiverExpression, true); varDescriptor = getCalleeDescriptor(context, receiverExpression, true)
} }
else if (receiver instanceof ExtensionReceiver) { else if (receiver is ExtensionReceiver) {
ExtensionReceiver extensionReceiver = (ExtensionReceiver) receiver; val extension = receiver.declarationDescriptor
CallableDescriptor extension = extensionReceiver.getDeclarationDescriptor();
varDescriptor = extension.getExtensionReceiverParameter(); varDescriptor = extension.extensionReceiverParameter
assert varDescriptor != null : "Extension should have receiverParameterDescriptor: " + extension; assert(varDescriptor != null) { "Extension should have receiverParameterDescriptor: " + extension }
receiverExpression = expression; receiverExpression = expression
}
else {
varDescriptor = null
receiverExpression = null
} }
if (inlinableParameters.contains(varDescriptor)) { if (inlinableParameters.contains(varDescriptor)) {
//check that it's invoke or inlinable extension //check that it's invoke or inlinable extension
checkLambdaInvokeOrExtensionCall(context, varDescriptor, targetDescriptor, receiverExpression); checkLambdaInvokeOrExtensionCall(context, varDescriptor!!, targetDescriptor, receiverExpression!!)
} }
} }
@Nullable private fun getCalleeDescriptor(
private static CallableDescriptor getCalleeDescriptor( context: CallCheckerContext,
@NotNull CallCheckerContext context, expression: KtExpression,
@NotNull KtExpression expression, unwrapVariableAsFunction: Boolean
boolean unwrapVariableAsFunction ): CallableDescriptor? {
) { if (!(expression is KtSimpleNameExpression || expression is KtThisExpression)) return null
if (!(expression instanceof KtSimpleNameExpression || expression instanceof KtThisExpression)) return null;
ResolvedCall<?> thisCall = CallUtilKt.getResolvedCall(expression, context.getTrace().getBindingContext()); val thisCall = expression.getResolvedCall(context.trace.bindingContext)
if (unwrapVariableAsFunction && thisCall instanceof VariableAsFunctionResolvedCall) { if (unwrapVariableAsFunction && thisCall is VariableAsFunctionResolvedCall) {
return ((VariableAsFunctionResolvedCall) thisCall).getVariableCall().getResultingDescriptor(); return (thisCall as VariableAsFunctionResolvedCall).variableCall.resultingDescriptor
} }
return thisCall != null ? thisCall.getResultingDescriptor() : null; return thisCall?.resultingDescriptor
} }
private void checkLambdaInvokeOrExtensionCall( private fun checkLambdaInvokeOrExtensionCall(
@NotNull CallCheckerContext context, context: CallCheckerContext,
@NotNull CallableDescriptor lambdaDescriptor, lambdaDescriptor: CallableDescriptor,
@NotNull CallableDescriptor callDescriptor, callDescriptor: CallableDescriptor,
@NotNull KtExpression receiverExpression receiverExpression: KtExpression
) { ) {
boolean inlinableCall = isInvokeOrInlineExtension(callDescriptor); val inlinableCall = isInvokeOrInlineExtension(callDescriptor)
if (!inlinableCall) { if (!inlinableCall) {
context.getTrace().report(USAGE_IS_NOT_INLINABLE.on(receiverExpression, receiverExpression, descriptor)); context.trace.report(USAGE_IS_NOT_INLINABLE.on(receiverExpression, receiverExpression, descriptor))
} }
else { else {
checkNonLocalReturn(context, lambdaDescriptor, receiverExpression); checkNonLocalReturn(context, lambdaDescriptor, receiverExpression)
} }
} }
private void checkRecursion( private fun checkRecursion(
@NotNull CallCheckerContext context, context: CallCheckerContext,
@NotNull CallableDescriptor targetDescriptor, targetDescriptor: CallableDescriptor,
@NotNull KtElement expression expression: KtElement
) { ) {
if (targetDescriptor.getOriginal() == descriptor) { if (targetDescriptor.original === descriptor) {
context.getTrace().report(Errors.RECURSION_IN_INLINE.on(expression, expression, descriptor)); context.trace.report(Errors.RECURSION_IN_INLINE.on(expression, expression, descriptor))
} }
} }
private static boolean isInlinableParameter(@NotNull ParameterDescriptor descriptor) { private fun isInlinableParameter(descriptor: ParameterDescriptor): Boolean {
return InlineUtil.isInlineLambdaParameter(descriptor) && !descriptor.getType().isMarkedNullable(); return InlineUtil.isInlineLambdaParameter(descriptor) && !descriptor.type.isMarkedNullable
} }
private static boolean isInvokeOrInlineExtension(@NotNull CallableDescriptor descriptor) { private fun isInvokeOrInlineExtension(descriptor: CallableDescriptor): Boolean {
if (!(descriptor instanceof SimpleFunctionDescriptor)) { if (descriptor !is SimpleFunctionDescriptor) {
return false; return false
} }
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration(); val containingDeclaration = descriptor.getContainingDeclaration()
boolean isInvoke = val isInvoke = descriptor.getName() == OperatorNameConventions.INVOKE &&
descriptor.getName().equals(OperatorNameConventions.INVOKE) && containingDeclaration is ClassDescriptor &&
containingDeclaration instanceof ClassDescriptor && containingDeclaration.defaultType.isFunctionType
FunctionTypesKt.isFunctionType(((ClassDescriptor) containingDeclaration).getDefaultType());
return isInvoke || InlineUtil.isInline(descriptor); return isInvoke || InlineUtil.isInline(descriptor)
} }
private void checkVisibilityAndAccess( private fun checkVisibilityAndAccess(
@NotNull CallableDescriptor calledDescriptor, calledDescriptor: CallableDescriptor,
@NotNull KtElement expression, expression: KtElement,
@NotNull CallCheckerContext context context: CallCheckerContext
) { ) {
EffectiveVisibility calledFunEffectiveVisibility = val calledFunEffectiveVisibility = if (isDefinedInInlineFunction(calledDescriptor))
isDefinedInInlineFunction(calledDescriptor) ? EffectiveVisibility.Public
EffectiveVisibility.Public.INSTANCE : else
EffectiveVisibilityKt.effectiveVisibility(calledDescriptor, calledDescriptor.getVisibility(), true); calledDescriptor.effectiveVisibility(calledDescriptor.visibility, true)
boolean isCalledFunPublicOrPublishedApi = calledFunEffectiveVisibility.getPublicApi(); val isCalledFunPublicOrPublishedApi = calledFunEffectiveVisibility.publicApi
boolean isInlineFunPublicOrPublishedApi = inlineFunEffectiveVisibility.getPublicApi(); val isInlineFunPublicOrPublishedApi = inlineFunEffectiveVisibility.publicApi
if (isInlineFunPublicOrPublishedApi && if (isInlineFunPublicOrPublishedApi &&
!isCalledFunPublicOrPublishedApi && !isCalledFunPublicOrPublishedApi &&
calledDescriptor.getVisibility() != Visibilities.LOCAL) { calledDescriptor.visibility !== Visibilities.LOCAL) {
context.getTrace().report(Errors.NON_PUBLIC_CALL_FROM_PUBLIC_INLINE.on(expression, calledDescriptor, descriptor)); context.trace.report(Errors.NON_PUBLIC_CALL_FROM_PUBLIC_INLINE.on(expression, calledDescriptor, descriptor))
} }
else { else {
checkPrivateClassMemberAccess(calledDescriptor, expression, context); checkPrivateClassMemberAccess(calledDescriptor, expression, context)
} }
if (!(calledDescriptor instanceof ConstructorDescriptor) && if (calledDescriptor !is ConstructorDescriptor &&
isInlineFunPublicOrPublishedApi && isInlineFunPublicOrPublishedApi &&
inlineFunEffectiveVisibility.toVisibility() != Visibilities.PROTECTED && inlineFunEffectiveVisibility.toVisibility() !== Visibilities.PROTECTED &&
calledFunEffectiveVisibility.toVisibility() == Visibilities.PROTECTED) { calledFunEffectiveVisibility.toVisibility() === Visibilities.PROTECTED) {
context.getTrace().report(Errors.PROTECTED_CALL_FROM_PUBLIC_INLINE.on(expression, calledDescriptor)); context.trace.report(Errors.PROTECTED_CALL_FROM_PUBLIC_INLINE.on(expression, calledDescriptor))
} }
} }
private void checkPrivateClassMemberAccess( private fun checkPrivateClassMemberAccess(
@NotNull DeclarationDescriptor declarationDescriptor, declarationDescriptor: DeclarationDescriptor,
@NotNull KtElement expression, expression: KtElement,
@NotNull CallCheckerContext context context: CallCheckerContext
) { ) {
if (!isEffectivelyPrivateApiFunction) { if (!isEffectivelyPrivateApiFunction) {
if (DescriptorUtilsKt.isInsidePrivateClass(declarationDescriptor)) { if (declarationDescriptor.isInsidePrivateClass) {
context.getTrace().report(Errors.PRIVATE_CLASS_MEMBER_FROM_INLINE.on(expression, declarationDescriptor, descriptor)); context.trace.report(Errors.PRIVATE_CLASS_MEMBER_FROM_INLINE.on(expression, declarationDescriptor, descriptor))
} }
} }
} }
private boolean isDefinedInInlineFunction(@NotNull DeclarationDescriptorWithVisibility startDescriptor) { private fun isDefinedInInlineFunction(startDescriptor: DeclarationDescriptorWithVisibility): Boolean {
DeclarationDescriptorWithVisibility parent = startDescriptor; var parent: DeclarationDescriptorWithVisibility? = startDescriptor
while (parent != null) { while (parent != null) {
if (parent.getContainingDeclaration() == descriptor) return true; if (parent.containingDeclaration === descriptor) return true
parent = DescriptorUtils.getParentOfType(parent, DeclarationDescriptorWithVisibility.class); parent = DescriptorUtils.getParentOfType(parent, DeclarationDescriptorWithVisibility::class.java)
} }
return false; return false
} }
private void checkNonLocalReturn( private fun checkNonLocalReturn(
@NotNull CallCheckerContext context, context: CallCheckerContext,
@NotNull CallableDescriptor inlinableParameterDescriptor, inlinableParameterDescriptor: CallableDescriptor,
@NotNull KtExpression parameterUsage parameterUsage: KtExpression
) { ) {
if (!allowsNonLocalReturns(inlinableParameterDescriptor)) return; if (!allowsNonLocalReturns(inlinableParameterDescriptor)) return
if (!checkNonLocalReturnUsage(descriptor, parameterUsage, context.getResolutionContext())) { if (!checkNonLocalReturnUsage(descriptor, parameterUsage, context.resolutionContext)) {
context.getTrace().report(NON_LOCAL_RETURN_NOT_ALLOWED.on(parameterUsage, parameterUsage)); context.trace.report(NON_LOCAL_RETURN_NOT_ALLOWED.on(parameterUsage, parameterUsage))
} }
} }
} }