Do not box function argument if it is used in EXACTLY_ONCE lambda

Since we cannot change type of parameter, we cannot replace it with
box type.
 #KT-29510 Fixed
 #KT-29614 Fixed
 #KT-29385 Fixed
This commit is contained in:
Ilmir Usmanov
2019-09-25 22:55:48 +03:00
parent c5ba19451f
commit 08794d17a0
15 changed files with 254 additions and 16 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -39,7 +39,7 @@ class CapturingInClosureChecker : CallChecker {
val scopeContainer = scope.ownerDescriptor
if (isCapturedVariable(variableParent, scopeContainer)) {
if (trace.get(CAPTURED_IN_CLOSURE, variable) != CaptureKind.NOT_INLINE) {
trace.record(CAPTURED_IN_CLOSURE, variable, getCaptureKind(trace.bindingContext, scopeContainer, variableParent))
trace.record(CAPTURED_IN_CLOSURE, variable, getCaptureKind(trace.bindingContext, scopeContainer, variableParent, variable))
}
}
}
@@ -57,33 +57,58 @@ class CapturingInClosureChecker : CallChecker {
}
private fun getCaptureKind(
context: BindingContext, scopeContainer: DeclarationDescriptor, variableParent: DeclarationDescriptor
context: BindingContext,
scopeContainer: DeclarationDescriptor,
variableParent: DeclarationDescriptor,
variable: VariableDescriptor
): CaptureKind {
val scopeDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(scopeContainer)
if (!InlineUtil.canBeInlineArgument(scopeDeclaration)) return CaptureKind.NOT_INLINE
val exactlyOnceContract = isExactlyOnceContract(context, scopeDeclaration as KtFunction)
if (InlineUtil.isInlinedArgument(scopeDeclaration, context, exactlyOnceContract)) {
if (InlineUtil.isInlinedArgument(scopeDeclaration as KtFunction, context, false) &&
!isCrossinlineParameter(context, scopeDeclaration)
) {
val scopeContainerParent = scopeContainer.containingDeclaration ?: error("parent is null for $scopeContainer")
return if (
!isCapturedVariable(variableParent, scopeContainerParent) ||
getCaptureKind(context, scopeContainerParent, variableParent) == CaptureKind.INLINE_ONLY
getCaptureKind(context, scopeContainerParent, variableParent, variable) == CaptureKind.INLINE_ONLY
) CaptureKind.INLINE_ONLY else CaptureKind.NOT_INLINE
}
if (exactlyOnceContract) return CaptureKind.EXACTLY_ONCE_EFFECT
return CaptureKind.NOT_INLINE
val exactlyOnceContract = isExactlyOnceContract(context, scopeDeclaration)
// We cannot box arguments.
val isArgument = variable is ValueParameterDescriptor && variableParent is CallableDescriptor
&& variableParent.valueParameters.contains(variable)
return if (exactlyOnceContract && !isArgument) CaptureKind.EXACTLY_ONCE_EFFECT else CaptureKind.NOT_INLINE
}
private fun isExactlyOnceContract(bindingContext: BindingContext, argument: KtFunction): Boolean {
val call = KtPsiUtil.getParentCallIfPresent(argument) ?: return false
val resolvedCall = call.getResolvedCall(bindingContext) ?: return false
val descriptor = resolvedCall.getResultingDescriptor()
val valueArgument = resolvedCall.call.getValueArgumentForExpression(argument) ?: return false
val mapping = resolvedCall.getArgumentMapping(valueArgument) as? ArgumentMatch ?: return false
val parameter = mapping.valueParameter
val contractDescription = descriptor.getUserData(ContractProviderKey)?.getContractDescription() ?: return false
private fun isExactlyOnceParameter(function: DeclarationDescriptor, parameter: VariableDescriptor): Boolean {
if (function !is CallableDescriptor) return false
if (parameter !is ValueParameterDescriptor) return false
val contractDescription = function.getUserData(ContractProviderKey)?.getContractDescription() ?: return false
val effect = contractDescription.effects.filterIsInstance<CallsEffectDeclaration>()
.find { it.variableReference.descriptor == parameter } ?: return false
return effect.kind == InvocationKind.EXACTLY_ONCE
}
private fun isExactlyOnceContract(bindingContext: BindingContext, argument: KtFunction): Boolean {
val (descriptor, parameter) = getCalleeDescriptorAndParameter(bindingContext, argument) ?: return false
return isExactlyOnceParameter(descriptor, parameter)
}
private fun getCalleeDescriptorAndParameter(
bindingContext: BindingContext,
argument: KtFunction
): Pair<CallableDescriptor, ValueParameterDescriptor>? {
val call = KtPsiUtil.getParentCallIfPresent(argument) ?: return null
val resolvedCall = call.getResolvedCall(bindingContext) ?: return null
val descriptor = resolvedCall.resultingDescriptor
val valueArgument = resolvedCall.call.getValueArgumentForExpression(argument) ?: return null
val mapping = resolvedCall.getArgumentMapping(valueArgument) as? ArgumentMatch ?: return null
val parameter = mapping.valueParameter
return descriptor to parameter
}
private fun isCrossinlineParameter(bindingContext: BindingContext, argument: KtFunction): Boolean {
return getCalleeDescriptorAndParameter(bindingContext, argument)?.second?.isCrossinline == true
}
}
@@ -0,0 +1,30 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: NATIVE
import kotlin.contracts.*
@ExperimentalContracts
fun runOnce(action: () -> Unit) {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
action()
}
@ExperimentalContracts
class Foo(foo: Boolean) {
var res = "FAIL"
init {
runOnce {
foo
res = "OK"
}
}
}
@ExperimentalContracts
fun box(): String {
val foo = Foo(true)
return foo.res
}
@@ -0,0 +1,54 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR, NATIVE
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt
package test
import kotlin.contracts.*
interface SomeOutputScreenCallbacks {
fun ontest()
}
@ExperimentalContracts
class OutputWorkScreenView(callbacks: SomeOutputScreenCallbacks) {
val root = vBox {
button(callbacks::ontest)
}
}
@ExperimentalContracts
inline fun vBox(
crossinline action: () -> Unit
) {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
return { action() }()
}
@ExperimentalContracts
inline fun button(
onAction: () -> Unit
) {
onAction()
}
// FILE: 2.kt
import test.*
import kotlin.contracts.*
@ExperimentalContracts
fun box(): String {
var res = "FAIL"
OutputWorkScreenView(object : SomeOutputScreenCallbacks {
override fun ontest() {
res = "OK"
}
})
return res
}
@@ -0,0 +1,44 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect +ReadDeserializedContracts
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: JVM_IR, NATIVE
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// FILE: 1.kt
package test
import kotlin.contracts.*
@ExperimentalContracts
class A {
var res = "FAIL"
fun foo() {
bar {
res = "OK"
}
}
inline fun bar(crossinline not_exactly_once: () -> Unit) {
baz {
not_exactly_once()
}
}
}
@ExperimentalContracts
inline fun baz(crossinline exactly_once: () -> Unit) {
contract {
callsInPlace(exactly_once, InvocationKind.EXACTLY_ONCE)
};
{ exactly_once() }()
}
// FILE: 2.kt
import test.*
fun box(): String {
val a = A()
a.foo()
return a.res
}
@@ -4655,6 +4655,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/contracts"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("constructorArgument.kt")
public void testConstructorArgument() throws Exception {
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
@@ -1084,6 +1084,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
runTest("compiler/testData/codegen/boxInline/contracts/complexInitializerWithStackTransformation.kt");
}
@TestMetadata("crossinlineCallableReference.kt")
public void testCrossinlineCallableReference() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/crossinlineCallableReference.kt");
}
@TestMetadata("definiteLongValInitialization.kt")
public void testDefiniteLongValInitialization() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/definiteLongValInitialization.kt");
@@ -1109,6 +1114,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline.kt");
}
@TestMetadata("exactlyOnceCrossinline2.kt")
public void testExactlyOnceCrossinline2() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline2.kt");
}
@TestMetadata("exactlyOnceNoinline.kt")
public void testExactlyOnceNoinline() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceNoinline.kt");
@@ -1084,6 +1084,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
runTest("compiler/testData/codegen/boxInline/contracts/complexInitializerWithStackTransformation.kt");
}
@TestMetadata("crossinlineCallableReference.kt")
public void testCrossinlineCallableReference() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/crossinlineCallableReference.kt");
}
@TestMetadata("definiteLongValInitialization.kt")
public void testDefiniteLongValInitialization() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/definiteLongValInitialization.kt");
@@ -1109,6 +1114,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline.kt");
}
@TestMetadata("exactlyOnceCrossinline2.kt")
public void testExactlyOnceCrossinline2() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline2.kt");
}
@TestMetadata("exactlyOnceNoinline.kt")
public void testExactlyOnceNoinline() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceNoinline.kt");
@@ -4655,6 +4655,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/contracts"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("constructorArgument.kt")
public void testConstructorArgument() throws Exception {
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
@@ -4625,6 +4625,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/contracts"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
}
@TestMetadata("constructorArgument.kt")
public void testConstructorArgument() throws Exception {
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
@@ -1084,6 +1084,11 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
runTest("compiler/testData/codegen/boxInline/contracts/complexInitializerWithStackTransformation.kt");
}
@TestMetadata("crossinlineCallableReference.kt")
public void testCrossinlineCallableReference() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/crossinlineCallableReference.kt");
}
@TestMetadata("definiteLongValInitialization.kt")
public void testDefiniteLongValInitialization() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/definiteLongValInitialization.kt");
@@ -1109,6 +1114,11 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline.kt");
}
@TestMetadata("exactlyOnceCrossinline2.kt")
public void testExactlyOnceCrossinline2() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline2.kt");
}
@TestMetadata("exactlyOnceNoinline.kt")
public void testExactlyOnceNoinline() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceNoinline.kt");
@@ -1084,6 +1084,11 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
runTest("compiler/testData/codegen/boxInline/contracts/complexInitializerWithStackTransformation.kt");
}
@TestMetadata("crossinlineCallableReference.kt")
public void testCrossinlineCallableReference() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/crossinlineCallableReference.kt");
}
@TestMetadata("definiteLongValInitialization.kt")
public void testDefiniteLongValInitialization() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/definiteLongValInitialization.kt");
@@ -1109,6 +1114,11 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline.kt");
}
@TestMetadata("exactlyOnceCrossinline2.kt")
public void testExactlyOnceCrossinline2() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline2.kt");
}
@TestMetadata("exactlyOnceNoinline.kt")
public void testExactlyOnceNoinline() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceNoinline.kt");
@@ -3780,6 +3780,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/contracts"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS_IR, true);
}
@TestMetadata("constructorArgument.kt")
public void testConstructorArgument() throws Exception {
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
@@ -44,6 +44,11 @@ public class IrJsInlineContractsTestsGenerated extends AbstractIrJsInlineContrac
runTest("compiler/testData/codegen/boxInline/contracts/complexInitializerWithStackTransformation.kt");
}
@TestMetadata("crossinlineCallableReference.kt")
public void testCrossinlineCallableReference() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/crossinlineCallableReference.kt");
}
@TestMetadata("definiteLongValInitialization.kt")
public void testDefiniteLongValInitialization() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/definiteLongValInitialization.kt");
@@ -69,6 +74,11 @@ public class IrJsInlineContractsTestsGenerated extends AbstractIrJsInlineContrac
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline.kt");
}
@TestMetadata("exactlyOnceCrossinline2.kt")
public void testExactlyOnceCrossinline2() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline2.kt");
}
@TestMetadata("exactlyOnceNoinline.kt")
public void testExactlyOnceNoinline() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceNoinline.kt");
@@ -3790,6 +3790,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/contracts"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
}
@TestMetadata("constructorArgument.kt")
public void testConstructorArgument() throws Exception {
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
@@ -44,6 +44,11 @@ public class JsInlineContractsTestsGenerated extends AbstractJsInlineContractsTe
runTest("compiler/testData/codegen/boxInline/contracts/complexInitializerWithStackTransformation.kt");
}
@TestMetadata("crossinlineCallableReference.kt")
public void testCrossinlineCallableReference() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/crossinlineCallableReference.kt");
}
@TestMetadata("definiteLongValInitialization.kt")
public void testDefiniteLongValInitialization() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/definiteLongValInitialization.kt");
@@ -69,6 +74,11 @@ public class JsInlineContractsTestsGenerated extends AbstractJsInlineContractsTe
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline.kt");
}
@TestMetadata("exactlyOnceCrossinline2.kt")
public void testExactlyOnceCrossinline2() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceCrossinline2.kt");
}
@TestMetadata("exactlyOnceNoinline.kt")
public void testExactlyOnceNoinline() throws Exception {
runTest("compiler/testData/codegen/boxInline/contracts/exactlyOnceNoinline.kt");