Forbid val field initialization inside EXACLTY_ONCE lambda
unless the lambda is inline. This way, final field will remain final.
This commit is contained in:
@@ -422,14 +422,6 @@ 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);
|
||||
|
||||
+5
@@ -3816,6 +3816,11 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirOldFronte
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldAsClassDelegate.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fieldInitialization.kt")
|
||||
public void testFieldInitialization() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("infiniteLoops.kt")
|
||||
public void testInfiniteLoops() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt");
|
||||
|
||||
Generated
-5
@@ -5097,11 +5097,6 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
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");
|
||||
|
||||
@@ -179,7 +179,6 @@ 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();
|
||||
|
||||
+17
-5
@@ -13,10 +13,10 @@ import org.jetbrains.kotlin.descriptors.*
|
||||
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.diagnostics.Errors.CAPTURED_VAL_INITIALIZATION
|
||||
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
|
||||
@@ -35,6 +35,7 @@ class CapturingInClosureChecker : CallChecker {
|
||||
val variableDescriptor = variableResolvedCall.resultingDescriptor as? VariableDescriptor
|
||||
if (variableDescriptor != null) {
|
||||
checkCapturingInClosure(variableDescriptor, context.trace, context.scope)
|
||||
checkFieldInExactlyOnceLambdaInitialization(variableDescriptor, context.trace, context.scope.ownerDescriptor, reportOn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,12 +48,23 @@ class CapturingInClosureChecker : CallChecker {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Check whether a field is captured in EXACTLY_ONCE contract
|
||||
if (variable !is PropertyDescriptor || scopeContainer !is AnonymousFunctionDescriptor) return
|
||||
}
|
||||
|
||||
private fun checkFieldInExactlyOnceLambdaInitialization(
|
||||
variable: VariableDescriptor,
|
||||
trace: BindingTrace,
|
||||
scopeContainer: DeclarationDescriptor,
|
||||
reportOn: PsiElement
|
||||
) {
|
||||
if (variable !is PropertyDescriptor || scopeContainer !is AnonymousFunctionDescriptor || variable.isVar) 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)
|
||||
if (!isExactlyOnceContract(trace.bindingContext, scopeDeclaration)) return
|
||||
if (trace.bindingContext[CAPTURED_IN_CLOSURE, variable] == CaptureKind.NOT_INLINE) return
|
||||
val (callee, param) = getCalleeDescriptorAndParameter(trace.bindingContext, scopeDeclaration) ?: return
|
||||
if (callee !is FunctionDescriptor) return
|
||||
if (!callee.isInline || (param.isCrossinline || !InlineUtil.isInlineParameter(param))) {
|
||||
trace.report(CAPTURED_VAL_INITIALIZATION.on(reportOn as KtExpression, variable))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// !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
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||
// !USE_EXPERIMENTAL: kotlin.internal.ContractsDsl
|
||||
|
||||
import kotlin.contracts.*
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts
|
||||
inline fun inlineMe(block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts
|
||||
inline fun crossinlineMe(crossinline block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@kotlin.contracts.ExperimentalContracts
|
||||
inline fun noinlineMe(noinline block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts
|
||||
fun notinline(block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts
|
||||
class Test {
|
||||
val a: String
|
||||
val b: String
|
||||
val c: String
|
||||
val d: String
|
||||
|
||||
init {
|
||||
inlineMe {
|
||||
a = "allowed"
|
||||
}
|
||||
crossinlineMe {
|
||||
b = "not allowed"
|
||||
}
|
||||
noinlineMe {
|
||||
c = "not allowed"
|
||||
}
|
||||
notinline {
|
||||
d = "not allowed"
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||
// !USE_EXPERIMENTAL: kotlin.internal.ContractsDsl
|
||||
|
||||
import kotlin.contracts.*
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts
|
||||
inline fun inlineMe(block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts
|
||||
inline fun crossinlineMe(crossinline block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@kotlin.contracts.ExperimentalContracts
|
||||
inline fun noinlineMe(noinline block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts
|
||||
fun notinline(block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts
|
||||
class Test {
|
||||
val a: String
|
||||
val b: String
|
||||
val c: String
|
||||
val d: String
|
||||
|
||||
init {
|
||||
inlineMe {
|
||||
a = "allowed"
|
||||
}
|
||||
crossinlineMe {
|
||||
<!CAPTURED_VAL_INITIALIZATION!>b<!> = "not allowed"
|
||||
}
|
||||
noinlineMe {
|
||||
<!CAPTURED_VAL_INITIALIZATION!>c<!> = "not allowed"
|
||||
}
|
||||
notinline {
|
||||
<!CAPTURED_VAL_INITIALIZATION!>d<!> = "not allowed"
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts public inline fun crossinlineMe(/*0*/ crossinline block: () -> kotlin.Unit): kotlin.Unit
|
||||
CallsInPlace(block, EXACTLY_ONCE)
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts public inline fun inlineMe(/*0*/ block: () -> kotlin.Unit): kotlin.Unit
|
||||
CallsInPlace(block, EXACTLY_ONCE)
|
||||
|
||||
@kotlin.Suppress(names = {"NOTHING_TO_INLINE"}) @kotlin.contracts.ExperimentalContracts public inline fun noinlineMe(/*0*/ noinline block: () -> kotlin.Unit): kotlin.Unit
|
||||
CallsInPlace(block, EXACTLY_ONCE)
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts public fun notinline(/*0*/ block: () -> kotlin.Unit): kotlin.Unit
|
||||
CallsInPlace(block, EXACTLY_ONCE)
|
||||
|
||||
@kotlin.contracts.ExperimentalContracts public final class Test {
|
||||
public constructor Test()
|
||||
public final val a: kotlin.String
|
||||
public final val b: kotlin.String
|
||||
public final val c: kotlin.String
|
||||
public final val d: kotlin.String
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
+1
-1
@@ -86,7 +86,7 @@ class DefiniteInitializationInInitSection {
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val y: Int<!>
|
||||
|
||||
init {
|
||||
myRun { x = 42 }
|
||||
myRun { <!CAPTURED_VAL_INITIALIZATION!>x<!> = 42 }
|
||||
unknownRun { <!CAPTURED_MEMBER_VAL_INITIALIZATION!>y<!> = 239 }
|
||||
}
|
||||
}
|
||||
@@ -3823,6 +3823,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTestWithFirVali
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldAsClassDelegate.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fieldInitialization.kt")
|
||||
public void testFieldInitialization() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("infiniteLoops.kt")
|
||||
public void testInfiniteLoops() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt");
|
||||
|
||||
Generated
+5
@@ -3818,6 +3818,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldAsClassDelegate.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fieldInitialization.kt")
|
||||
public void testFieldInitialization() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("infiniteLoops.kt")
|
||||
public void testInfiniteLoops() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt");
|
||||
|
||||
-5
@@ -5127,11 +5127,6 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
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");
|
||||
|
||||
-5
@@ -5127,11 +5127,6 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
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");
|
||||
|
||||
-5
@@ -5097,11 +5097,6 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
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");
|
||||
|
||||
Generated
-5
@@ -4127,11 +4127,6 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes
|
||||
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");
|
||||
|
||||
Generated
-5
@@ -4137,11 +4137,6 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
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");
|
||||
|
||||
-5
@@ -4137,11 +4137,6 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user