Fix bugs with capturing rhs into EXACTLY_ONCE lambda

There are multiple ways to declare a named variable-like entity in
Kotlin:
1. val/var variable declaration
2. destructuring declaration
3. parameter of a function
4. parameter of a lambda
5. destructured lambda parameter
6. for-loop's variable declaration
7. catch block exception declaration
8. val in when
9. field declaration

Out of them, only variable and field can be assignable, in other words,
they can be on the left hand side of an assignment.
Val/var variable declarations were already supported.
So, we needed to just support field initialization and tell the backend
that other ways are prohibited. Function and lambda parameters were
already been supported. So, the only thing to explain to the backend are
remaining ways.
 #KT-39113 Fixed
 #KT-34048 Fixed
This commit is contained in:
Ilmir Usmanov
2020-05-27 01:04:30 +02:00
parent 0fc43b1f57
commit 5f3e296f19
16 changed files with 506 additions and 5 deletions
@@ -422,6 +422,14 @@ public class PropertyCodegen {
}
modifiers |= getVisibilityForBackingField(propertyDescriptor, isDelegate);
// If val is initialized in EXACTLY_ONCE closure, other class from the same package initializes it
// so, its visibility should be package private and not final
if (!propertyDescriptor.isVar() &&
bindingContext.get(BindingContext.FIELD_CAPTURED_IN_EXACLY_ONCE_CLOSURE, propertyDescriptor) != null
) {
modifiers &= ~(ACC_PRIVATE | ACC_FINAL);
}
if (AsmUtil.isPropertyWithBackingFieldCopyInOuterClass(propertyDescriptor)) {
ImplementationBodyCodegen parentBodyCodegen = (ImplementationBodyCodegen) memberCodegen.getParentCodegen();
parentBodyCodegen.addCompanionObjectPropertyToCopy(propertyDescriptor, defaultValue);
@@ -5082,10 +5082,45 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("destructuredVariable.kt")
public void testDestructuredVariable() throws Exception {
runTest("compiler/testData/codegen/box/contracts/destructuredVariable.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
}
@TestMetadata("exception.kt")
public void testException() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exception.kt");
}
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt")
public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
}
@TestMetadata("functionParameter.kt")
public void testFunctionParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/functionParameter.kt");
}
@TestMetadata("lambdaParameter.kt")
public void testLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/lambdaParameter.kt");
}
@TestMetadata("valInWhen.kt")
public void testValInWhen() throws Exception {
runTest("compiler/testData/codegen/box/contracts/valInWhen.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/controlStructures")
@@ -179,6 +179,7 @@ public interface BindingContext {
WritableSlice<KtElement, Boolean> USED_AS_RESULT_OF_LAMBDA = Slices.createSimpleSetSlice();
WritableSlice<VariableDescriptor, CaptureKind> CAPTURED_IN_CLOSURE = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<PropertyDescriptor, Boolean> FIELD_CAPTURED_IN_EXACLY_ONCE_CLOSURE = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<KtDeclaration, PreliminaryDeclarationVisitor> PRELIMINARY_VISITOR = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<Box<DeferredType>, Boolean> DEFERRED_TYPE = Slices.createCollectiveSetSlice();
@@ -10,10 +10,13 @@ import org.jetbrains.kotlin.contracts.description.CallsEffectDeclaration
import org.jetbrains.kotlin.contracts.description.ContractProviderKey
import org.jetbrains.kotlin.contracts.description.InvocationKind
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContext.CAPTURED_IN_CLOSURE
import org.jetbrains.kotlin.resolve.BindingContext.FIELD_CAPTURED_IN_EXACLY_ONCE_CLOSURE
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
@@ -23,6 +26,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.types.expressions.CaptureKind
class CapturingInClosureChecker : CallChecker {
@@ -40,8 +44,16 @@ class CapturingInClosureChecker : CallChecker {
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, variable))
return
}
}
// Check whether a field is captured in EXACTLY_ONCE contract
if (variable !is PropertyDescriptor || scopeContainer !is AnonymousFunctionDescriptor) return
val scopeDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(scopeContainer) as? KtFunction ?: return
if (scopeContainer.containingDeclaration !is ConstructorDescriptor) return
if (isExactlyOnceContract(trace.bindingContext, scopeDeclaration)) {
trace.record(FIELD_CAPTURED_IN_EXACLY_ONCE_CLOSURE, variable)
}
}
private fun isCapturedVariable(variableParent: DeclarationDescriptor, scopeContainer: DeclarationDescriptor): Boolean {
@@ -75,10 +87,52 @@ class CapturingInClosureChecker : CallChecker {
) CaptureKind.INLINE_ONLY else CaptureKind.NOT_INLINE
}
val exactlyOnceContract = isExactlyOnceContract(context, scopeDeclaration)
// We cannot box arguments.
val isArgument = variable is ValueParameterDescriptor && variableParent is CallableDescriptor
if (!exactlyOnceContract) return CaptureKind.NOT_INLINE
// We cannot box function/lambda arguments, destructured lambda arguments, for-loop index variables,
// exceptions inside catch blocks ans vals in when
return if (isArgument(variable, variableParent) ||
isDestructuredVariable(variable, variableParent) ||
isForLoopParameter(variable) ||
isCatchBlockParameter(variable) ||
isValInWhen(variable)
) {
CaptureKind.NOT_INLINE
} else {
CaptureKind.EXACTLY_ONCE_EFFECT
}
}
private fun isArgument(variable: VariableDescriptor, variableParent: DeclarationDescriptor): Boolean =
variable is ValueParameterDescriptor && variableParent is CallableDescriptor
&& variableParent.valueParameters.contains(variable)
return if (exactlyOnceContract && !isArgument) CaptureKind.EXACTLY_ONCE_EFFECT else CaptureKind.NOT_INLINE
private fun isDestructuredVariable(variable: VariableDescriptor, variableParent: DeclarationDescriptor): Boolean =
variable is LocalVariableDescriptor && variableParent is AnonymousFunctionDescriptor &&
variableParent.valueParameters.any {
it is ValueParameterDescriptorImpl.WithDestructuringDeclaration && it.destructuringVariables.contains(variable)
}
private fun isValInWhen(variable: VariableDescriptor): Boolean {
val psi = ((variable as? LocalVariableDescriptor)?.source as? KotlinSourceElement)?.psi ?: return false
return (psi.parent as? KtWhenExpression)?.let { it.subjectVariable == psi } == true
}
private fun isCatchBlockParameter(variable: VariableDescriptor): Boolean {
val psi = ((variable as? LocalVariableDescriptor)?.source as? KotlinSourceElement)?.psi ?: return false
return psi.parent.parent.let { it is KtCatchClause && it.parameterList?.parameters?.contains(psi) == true }
}
private fun isForLoopParameter(variable: VariableDescriptor): Boolean {
val psi = ((variable as? LocalVariableDescriptor)?.source as? KotlinSourceElement)?.psi ?: return false
if (psi.parent is KtForExpression) {
val forExpression = psi.parent as KtForExpression
return forExpression.loopParameter == psi
} else if (psi.parent is KtDestructuringDeclaration) {
val parameter = psi.parent.parent as? KtParameter ?: return false
val forExpression = parameter.parent as? KtForExpression ?: return false
return forExpression.loopParameter == parameter
}
return false
}
private fun isExactlyOnceParameter(function: DeclarationDescriptor, parameter: VariableDescriptor): Boolean {
@@ -0,0 +1,26 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: NATIVE, JS
// WITH_RUNTIME
// KJS_WITH_FULL_RUNTIME
import kotlin.contracts.*
fun runOnce(action: () -> Unit) {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
action()
}
fun ok(): String {
val res: String
val (o, _) = "OK" to "FAIL"
runOnce {
res = o
}
return res
}
fun box(): String {
return ok()
}
+28
View File
@@ -0,0 +1,28 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: NATIVE
// WITH_RUNTIME
import kotlin.contracts.*
fun runOnce(action: () -> Unit) {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
action()
}
fun foo(): String {
var res = "FAIL"
try {
error("OK")
} catch(e: Exception) {
runOnce {
res = e.message!!
}
}
return res
}
fun box(): String {
return foo()
}
+26
View File
@@ -0,0 +1,26 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: NATIVE
// WITH_RUNTIME
// IGNORE_LIGHT_ANALYSIS
import kotlin.contracts.*
fun runOnce(action: () -> Unit) {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
action()
}
class Foo {
val res: String
init {
runOnce {
res = "OK"
}
}
}
fun box(): String {
return Foo().res
}
+61
View File
@@ -0,0 +1,61 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: NATIVE
// WITH_RUNTIME
// KJS_WITH_FULL_RUNTIME
import kotlin.contracts.*
fun runOnce(action: () -> Unit) {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
action()
}
fun test1(): String {
var res = ""
for (s in listOf("OK")) {
runOnce {
res += s
}
}
return res
}
fun test2(): String {
var res = ""
for (s: String in listOf("OK")) {
runOnce {
res += s
}
}
return res
}
fun test3(): String {
var res = ""
for ((s, _) in listOf("OK" to "FAIL")) {
runOnce {
res += s
}
}
return res
}
fun test4(): String {
var res = ""
for ((s: String, _) in listOf("OK" to "FAIL")) {
runOnce {
res += s
}
}
return res
}
fun box(): String {
test1().let { if (it != "OK") return "FAIL 1: $it" }
test2().let { if (it != "OK") return "FAIL 2: $it" }
test3().let { if (it != "OK") return "FAIL 3: $it" }
test4().let { if (it != "OK") return "FAIL 4: $it" }
return "OK"
}
@@ -0,0 +1,24 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: NATIVE
import kotlin.contracts.*
fun runOnce(action: () -> Unit) {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
action()
}
fun foo(b: Boolean): String {
val res: String
runOnce {
b
res = "OK"
}
return res
}
fun box(): String {
return foo(true)
}
@@ -0,0 +1,37 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: NATIVE
// WITH_RUNTIME
// KJS_WITH_FULL_RUNTIME
import kotlin.contracts.*
fun runOnce(action: () -> Unit) {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
action()
}
fun o(): String {
var res = "FAIL1 "
("O" to "").let { (a, _) ->
runOnce {
res = a
}
}
return res
}
fun k(): String {
val res: String
"K".let { b ->
runOnce {
res = b
}
}
return res
}
fun box(): String {
return o() + k()
}
+26
View File
@@ -0,0 +1,26 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: NATIVE
// WITH_RUNTIME
import kotlin.contracts.*
fun runOnce(action: () -> Unit) {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
action()
}
fun ok(): String {
val res: String
when (val o = "OK") {
else -> runOnce {
res = o
}
}
return res
}
fun box(): String {
return ok()
}
@@ -5112,10 +5112,45 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("destructuredVariable.kt")
public void testDestructuredVariable() throws Exception {
runTest("compiler/testData/codegen/box/contracts/destructuredVariable.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
}
@TestMetadata("exception.kt")
public void testException() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exception.kt");
}
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt")
public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
}
@TestMetadata("functionParameter.kt")
public void testFunctionParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/functionParameter.kt");
}
@TestMetadata("lambdaParameter.kt")
public void testLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/lambdaParameter.kt");
}
@TestMetadata("valInWhen.kt")
public void testValInWhen() throws Exception {
runTest("compiler/testData/codegen/box/contracts/valInWhen.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/controlStructures")
@@ -5112,10 +5112,45 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("destructuredVariable.kt")
public void testDestructuredVariable() throws Exception {
runTest("compiler/testData/codegen/box/contracts/destructuredVariable.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
}
@TestMetadata("exception.kt")
public void testException() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exception.kt");
}
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt")
public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
}
@TestMetadata("functionParameter.kt")
public void testFunctionParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/functionParameter.kt");
}
@TestMetadata("lambdaParameter.kt")
public void testLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/lambdaParameter.kt");
}
@TestMetadata("valInWhen.kt")
public void testValInWhen() throws Exception {
runTest("compiler/testData/codegen/box/contracts/valInWhen.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/controlStructures")
@@ -5082,10 +5082,45 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("destructuredVariable.kt")
public void testDestructuredVariable() throws Exception {
runTest("compiler/testData/codegen/box/contracts/destructuredVariable.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
}
@TestMetadata("exception.kt")
public void testException() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exception.kt");
}
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt")
public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
}
@TestMetadata("functionParameter.kt")
public void testFunctionParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/functionParameter.kt");
}
@TestMetadata("lambdaParameter.kt")
public void testLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/lambdaParameter.kt");
}
@TestMetadata("valInWhen.kt")
public void testValInWhen() throws Exception {
runTest("compiler/testData/codegen/box/contracts/valInWhen.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/controlStructures")
@@ -4122,10 +4122,45 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("destructuredVariable.kt")
public void testDestructuredVariable() throws Exception {
runTest("compiler/testData/codegen/box/contracts/destructuredVariable.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
}
@TestMetadata("exception.kt")
public void testException() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exception.kt");
}
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt")
public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
}
@TestMetadata("functionParameter.kt")
public void testFunctionParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/functionParameter.kt");
}
@TestMetadata("lambdaParameter.kt")
public void testLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/lambdaParameter.kt");
}
@TestMetadata("valInWhen.kt")
public void testValInWhen() throws Exception {
runTest("compiler/testData/codegen/box/contracts/valInWhen.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/controlStructures")
@@ -4122,10 +4122,45 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/contracts/constructorArgument.kt");
}
@TestMetadata("destructuredVariable.kt")
public void testDestructuredVariable() throws Exception {
runTest("compiler/testData/codegen/box/contracts/destructuredVariable.kt");
}
@TestMetadata("exactlyOnceNotInline.kt")
public void testExactlyOnceNotInline() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exactlyOnceNotInline.kt");
}
@TestMetadata("exception.kt")
public void testException() throws Exception {
runTest("compiler/testData/codegen/box/contracts/exception.kt");
}
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt")
public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
}
@TestMetadata("functionParameter.kt")
public void testFunctionParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/functionParameter.kt");
}
@TestMetadata("lambdaParameter.kt")
public void testLambdaParameter() throws Exception {
runTest("compiler/testData/codegen/box/contracts/lambdaParameter.kt");
}
@TestMetadata("valInWhen.kt")
public void testValInWhen() throws Exception {
runTest("compiler/testData/codegen/box/contracts/valInWhen.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/controlStructures")