diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index faa14e8957f..2805aa1e67c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -927,6 +927,7 @@ public interface Errors { DiagnosticFactory0 NON_INTERNAL_PUBLISHED_API = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 PROTECTED_CALL_FROM_PUBLIC_INLINE = DiagnosticFactory1.create(WARNING); DiagnosticFactory2 INVALID_DEFAULT_FUNCTIONAL_PARAMETER_FOR_INLINE = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 NOT_SUPPORTED_USAGE_OF_INLINE_PARAMETER_IN_DEFAULT_INLINE_ONE = DiagnosticFactory2.create(ERROR); DiagnosticFactory0 NON_LOCAL_SUSPENSION_POINT = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 ILLEGAL_SUSPEND_FUNCTION_CALL = DiagnosticFactory1.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 2173c55b142..440fc9d83e0 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -837,6 +837,7 @@ public class DefaultErrorMessages { MAP.put(NON_INTERNAL_PUBLISHED_API, "@PublishedApi annotation is only applicable for internal declaration"); MAP.put(PROTECTED_CALL_FROM_PUBLIC_INLINE, "Protected function call from public-API inline function is deprecated", NAME); MAP.put(INVALID_DEFAULT_FUNCTIONAL_PARAMETER_FOR_INLINE, "Invalid default value for inline parameter: ''{0}''. Only lambdas, anonymous functions, and callable references are supported", ELEMENT_TEXT, SHORT_NAMES_IN_TYPES); + MAP.put(NOT_SUPPORTED_USAGE_OF_INLINE_PARAMETER_IN_DEFAULT_INLINE_ONE, "Usage of inline parameter ''{0}'' in default value for another inline parameter is not supported", ELEMENT_TEXT, SHORT_NAMES_IN_TYPES); //Inline non locals MAP.put(NON_LOCAL_RETURN_NOT_ALLOWED, "Can''t inline ''{0}'' here: it may contain non-local returns. Add ''crossinline'' modifier to parameter declaration ''{0}''", ELEMENT_TEXT); MAP.put(INLINE_CALL_CYCLE, "The ''{0}'' invocation is a part of inline cycle", NAME); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/InlineChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/InlineChecker.kt index 6390df0f92c..2031ee923bc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/InlineChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/InlineChecker.kt @@ -17,48 +17,49 @@ package org.jetbrains.kotlin.resolve.calls.checkers import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.builtins.* +import org.jetbrains.kotlin.builtins.isFunctionType +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.calls.callUtil.* +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall -import org.jetbrains.kotlin.resolve.descriptorUtil.* +import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPrivateApi +import org.jetbrains.kotlin.resolve.descriptorUtil.isInsidePrivateClass import org.jetbrains.kotlin.resolve.inline.InlineUtil +import org.jetbrains.kotlin.resolve.inline.InlineUtil.allowsNonLocalReturns +import org.jetbrains.kotlin.resolve.inline.InlineUtil.checkNonLocalReturnUsage 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.resolve.source.KotlinSourceElement import org.jetbrains.kotlin.util.OperatorNameConventions - -import java.util.LinkedHashSet - -import org.jetbrains.kotlin.diagnostics.Errors.NON_LOCAL_RETURN_NOT_ALLOWED -import org.jetbrains.kotlin.diagnostics.Errors.USAGE_IS_NOT_INLINABLE -import org.jetbrains.kotlin.resolve.inline.InlineUtil.allowsNonLocalReturns -import org.jetbrains.kotlin.resolve.inline.InlineUtil.checkNonLocalReturnUsage +import kotlin.properties.Delegates internal class InlineChecker(private val descriptor: FunctionDescriptor) : CallChecker { - private val inlinableParameters = LinkedHashSet() - private val inlineFunEffectiveVisibility: EffectiveVisibility - private val isEffectivelyPrivateApiFunction: Boolean - init { - assert(InlineUtil.isInline(descriptor)) { "This extension should be created only for inline functions: " + descriptor } - this.inlineFunEffectiveVisibility = descriptor.effectiveVisibility(descriptor.visibility, true) - this.isEffectivelyPrivateApiFunction = descriptor.isEffectivelyPrivateApi - for (param in descriptor.valueParameters) { - if (isInlinableParameter(param)) { - inlinableParameters.add(param) - } - } + assert(InlineUtil.isInline(descriptor)) { "This extension should be created only for inline functions: $descriptor" } } + private val inlineFunEffectiveVisibility = descriptor.effectiveVisibility(descriptor.visibility, true) + + private val isEffectivelyPrivateApiFunction = descriptor.isEffectivelyPrivateApi + + private val inlinableParameters = descriptor.valueParameters.filter { isInlinableParameter(it) } + + private val inlinableKtParameters = inlinableParameters.mapNotNull { (it.source as? KotlinSourceElement)?.psi } + + private var supportDefaultValueInline by Delegates.notNull() + override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { val expression = resolvedCall.call.calleeExpression ?: return + supportDefaultValueInline = context.languageVersionSettings.supportsFeature(LanguageFeature.InlineDefaultFunctionalParameters) //checking that only invoke or inlinable extension called on function parameter val targetDescriptor = resolvedCall.resultingDescriptor @@ -66,8 +67,9 @@ internal class InlineChecker(private val descriptor: FunctionDescriptor) : CallC checkCallWithReceiver(context, targetDescriptor, resolvedCall.extensionReceiver, expression) if (inlinableParameters.contains(targetDescriptor)) { - if (!isInsideCall(expression)) { - context.trace.report(USAGE_IS_NOT_INLINABLE.on(expression, expression, descriptor)) + when { + !checkNotInDefaultParameter(context, targetDescriptor, expression) -> { /*error*/ } + !isInsideCall(expression) -> context.trace.report(USAGE_IS_NOT_INLINABLE.on(expression, expression, descriptor)) } } @@ -83,6 +85,15 @@ internal class InlineChecker(private val descriptor: FunctionDescriptor) : CallC checkRecursion(context, targetDescriptor, expression) } + private fun checkNotInDefaultParameter( context: CallCheckerContext , targetDescriptor: CallableDescriptor, expression: KtExpression) = + !supportDefaultValueInline || expression.getParentOfType(true)?.let { + val allow = it !in inlinableKtParameters + if (!allow) { + context.trace.report(NOT_SUPPORTED_USAGE_OF_INLINE_PARAMETER_IN_DEFAULT_INLINE_ONE.on(expression, expression, descriptor)) + } + allow + } ?: true + private fun isInsideCall(expression: KtExpression): Boolean { val parent = KtPsiUtil.getParentCallIfPresent(expression) if (parent is KtBinaryExpression) { @@ -118,16 +129,18 @@ internal class InlineChecker(private val descriptor: FunctionDescriptor) : CallC val argumentCallee = getCalleeDescriptor(context, argumentExpression, false) if (argumentCallee != null && inlinableParameters.contains(argumentCallee)) { - if (InlineUtil.isInline(targetDescriptor) && isInlinableParameter(targetParameterDescriptor)) { - if (allowsNonLocalReturns(argumentCallee) && !allowsNonLocalReturns(targetParameterDescriptor)) { - context.trace.report(NON_LOCAL_RETURN_NOT_ALLOWED.on(argumentExpression, argumentExpression)) - } - else { - checkNonLocalReturn(context, argumentCallee, argumentExpression) - } - } - else { - context.trace.report(USAGE_IS_NOT_INLINABLE.on(argumentExpression, argumentExpression, descriptor)) + when { + !checkNotInDefaultParameter(context, argumentCallee, argumentExpression) -> { /*error*/ } + + InlineUtil.isInline(targetDescriptor) && isInlinableParameter(targetParameterDescriptor) -> + if (allowsNonLocalReturns(argumentCallee) && !allowsNonLocalReturns(targetParameterDescriptor)) { + context.trace.report(NON_LOCAL_RETURN_NOT_ALLOWED.on(argumentExpression, argumentExpression)) + } + else { + checkNonLocalReturn(context, argumentCallee, argumentExpression) + } + + else -> context.trace.report(USAGE_IS_NOT_INLINABLE.on(argumentExpression, argumentExpression, descriptor)) } } } diff --git a/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameter.kt b/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameter.kt new file mode 100644 index 00000000000..f8e53c2ea26 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameter.kt @@ -0,0 +1,58 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE +// !LANGUAGE: +InlineDefaultFunctionalParameters + +inline fun inlineFun(lambda: () -> String) = lambda() + +inline fun noInlineFun(lambda: () -> String) = lambda() + + +inline fun default0_1(lambda: () -> String, dlambda: () -> String = { lambda; "OK" }) { + lambda() + dlambda() +} + +inline fun default0_2(lambda: () -> String, dlambda: () -> String = { noInlineFun (lambda) }) { + lambda() + dlambda() +} + + + +inline fun default0(lambda: () -> String, dlambda: () -> String = { noInlineFun (lambda) }) { + lambda() + dlambda() +} + +inline fun default1_0(lambda: () -> String, dlambda: () -> String = { lambda() }) { + lambda() + dlambda() +} + +inline fun default1_1(lambda: () -> String, noinline dlambda: () -> String = { lambda() }) { + lambda() + dlambda() +} + +inline fun default1_1crossinline(crossinline lambda: () -> String, noinline dlambda: () -> String = { lambda() }) { + lambda() + dlambda() +} + +inline fun default1_2(noinline lambda: () -> String, dlambda: () -> String = { lambda() }) { + lambda() + dlambda() +} + +inline fun default1_3(noinline lambda: () -> String, noinline dlambda: () -> String = { lambda() }) { + lambda() + dlambda() +} + + +inline fun default2_1(lambda: () -> String, noinline dlambda: () -> String = { inlineFun(lambda) }) { + lambda() + dlambda() +} + +inline fun default2_1crossinline(crossinline lambda: () -> String, noinline dlambda: () -> String = { inlineFun(lambda) }) { + lambda() + dlambda() +} + +inline fun default2_2(noinline lambda: () -> String, dlambda: () -> String = { inlineFun(lambda) }) { + lambda() + dlambda() +} + +inline fun default2_3(noinline lambda: () -> String, noinline dlambda: () -> String = { inlineFun(lambda) }) { + lambda() + dlambda() +} diff --git a/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameter.txt b/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameter.txt new file mode 100644 index 00000000000..1363590b8ca --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameter.txt @@ -0,0 +1,16 @@ +package + +public inline fun default0(/*0*/ lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default0_1(/*0*/ lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default0_2(/*0*/ lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default1_0(/*0*/ lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default1_1(/*0*/ lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default1_1crossinline(/*0*/ crossinline lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default1_2(/*0*/ noinline lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default1_3(/*0*/ noinline lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default2_1(/*0*/ lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default2_1crossinline(/*0*/ crossinline lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default2_2(/*0*/ noinline lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default2_3(/*0*/ noinline lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun inlineFun(/*0*/ lambda: () -> kotlin.String): kotlin.String +public inline fun noInlineFun(/*0*/ lambda: () -> kotlin.String): kotlin.String diff --git a/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameterDisabled.kt b/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameterDisabled.kt new file mode 100644 index 00000000000..32016f9118a --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameterDisabled.kt @@ -0,0 +1,60 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE +// !LANGUAGE: -InlineDefaultFunctionalParameters + +inline fun inlineFun(lambda: () -> String) = lambda() + +inline fun noInlineFun(lambda: () -> String) = lambda() + + +inline fun default0_1(lambda: () -> String, dlambda: () -> String = { lambda }) { + lambda() + dlambda() +} + +inline fun default0_2(lambda: () -> String, dlambda: () -> String = { noInlineFun (lambda) }) { + lambda() + dlambda() +} + + + +inline fun default1_0(lambda: () -> String, dlambda: () -> String = { lambda() }) { + lambda() + dlambda() +} + +inline fun default1_1(lambda: () -> String, noinline dlambda: () -> String = { lambda() }) { + lambda() + dlambda() +} + +inline fun default1_1crossinline(crossinline lambda: () -> String, noinline dlambda: () -> String = { lambda() }) { + lambda() + dlambda() +} + +inline fun default1_2(noinline lambda: () -> String, dlambda: () -> String = { lambda() }) { + lambda() + dlambda() +} + +inline fun default1_3(noinline lambda: () -> String, noinline dlambda: () -> String = { lambda() }) { + lambda() + dlambda() +} + + + + +inline fun default2_0(lambda: () -> String, dlambda: () -> String = { inlineFun(lambda) }) { + lambda() + dlambda() +} + +inline fun default2_1(lambda: () -> String, noinline dlambda: () -> String = { inlineFun(lambda) }) { + lambda() + dlambda() +} + +inline fun default2_1crossinline(crossinline lambda: () -> String, noinline dlambda: () -> String = { inlineFun(lambda) }) { + lambda() + dlambda() +} + +inline fun default2_2(noinline lambda: () -> String, dlambda: () -> String = { inlineFun(lambda) }) { + lambda() + dlambda() +} + +inline fun default2_3(noinline lambda: () -> String, noinline dlambda: () -> String = { inlineFun(lambda) }) { + lambda() + dlambda() +} diff --git a/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameterDisabled.txt b/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameterDisabled.txt new file mode 100644 index 00000000000..c3ac89f084e --- /dev/null +++ b/compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameterDisabled.txt @@ -0,0 +1,16 @@ +package + +public inline fun default0_1(/*0*/ lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default0_2(/*0*/ lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default1_0(/*0*/ lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default1_1(/*0*/ lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default1_1crossinline(/*0*/ crossinline lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default1_2(/*0*/ noinline lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default1_3(/*0*/ noinline lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default2_0(/*0*/ lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default2_1(/*0*/ lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default2_1crossinline(/*0*/ crossinline lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default2_2(/*0*/ noinline lambda: () -> kotlin.String, /*1*/ dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun default2_3(/*0*/ noinline lambda: () -> kotlin.String, /*1*/ noinline dlambda: () -> kotlin.String = ...): kotlin.Unit +public inline fun inlineFun(/*0*/ lambda: () -> kotlin.String): kotlin.String +public inline fun noInlineFun(/*0*/ lambda: () -> kotlin.String): kotlin.String diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index e79c1f77214..9129591cef7 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -11089,6 +11089,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("inlineLambdaInDefaultInlineParameter.kt") + public void testInlineLambdaInDefaultInlineParameter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameter.kt"); + doTest(fileName); + } + + @TestMetadata("inlineLambdaInDefaultInlineParameterDisabled.kt") + public void testInlineLambdaInDefaultInlineParameterDisabled() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inline/inlineLambdaInDefaultInlineParameterDisabled.kt"); + doTest(fileName); + } + @TestMetadata("inlineReified.kt") public void testInlineReified() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/inline/inlineReified.kt");