diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt index b6966c76644..6560d4aa09c 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt @@ -72,7 +72,8 @@ class LocalDeclarationsLowering( val suggestUniqueNames: Boolean = true, // When `true` appends a `$#index` suffix to lifted declaration names val compatibilityModeForInlinedLocalDelegatedPropertyAccessors: Boolean = false, // Keep old names because of KT-49030 val forceFieldsForInlineCaptures: Boolean = false, // See `LocalClassContext` - private val postLocalDeclarationLoweringCallback: ((IntermediateDatastructures) -> Unit)? = null + private val postLocalDeclarationLoweringCallback: ((IntermediateDatastructures) -> Unit)? = null, + private val getConstructorsThatCouldCaptureParamsWithoutFieldCreating: IrClass.() -> Iterable = { listOfNotNull(primaryConstructor) } ) : BodyLoweringPass { @@ -338,6 +339,16 @@ class LocalDeclarationsLowering( visitMember(declaration) ?: super.visitFunction(declaration) } + override fun visitAnonymousInitializer(declaration: IrAnonymousInitializer): IrStatement = + visitWithTheSingleConstructorContext(declaration) + ?: visitMember(declaration) + ?: super.visitAnonymousInitializer(declaration) + + override fun visitField(declaration: IrField): IrStatement = + visitWithTheSingleConstructorContext(declaration) + ?: visitMember(declaration) + ?: super.visitField(declaration) + private fun visitMember(declaration: IrDeclaration): IrStatement? = if (localContext is LocalClassContext && declaration.parent == localContext.declaration) { val classMemberLocalContext = LocalClassMemberContext(declaration, localContext) @@ -346,6 +357,15 @@ class LocalDeclarationsLowering( null } + private fun visitWithTheSingleConstructorContext(declaration: IrDeclaration): IrStatement? { + return if (localContext is LocalClassContext && declaration.parent == localContext.declaration) { + val constructorContext = localContext.constructorContext ?: return null + declaration.apply { transformChildrenVoid(FunctionBodiesRewriter(constructorContext)) } + } else { + null + } + } + override fun visitConstructor(declaration: IrConstructor): IrStatement { // Body is transformed separately. See loop over constructors in rewriteDeclarations(). @@ -1057,8 +1077,10 @@ class LocalDeclarationsLowering( // TODO: this should ideally run after initializers are added to constructors, but that'd place // other restrictions on IR (e.g. after the initializers are moved you can no longer create fields // with initializers) which makes that hard to implement. - val constructorContext = declaration.constructors.mapNotNull { localClassConstructors[it] } - .singleOrNull { it.declaration.delegationKind(context.irBuiltIns) == ConstructorDelegationKind.CALLS_SUPER } + val constructorContext = declaration.getConstructorsThatCouldCaptureParamsWithoutFieldCreating() + .mapNotNull { localClassConstructors[it] } + .singleOrNull() + localClasses[declaration] = LocalClassContext(declaration, data.inInlineFunctionScope, constructorContext) } diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLoweringPhases.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLoweringPhases.kt index 6b635633f6e..c00e2df7f8c 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLoweringPhases.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLoweringPhases.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.ir.util.resolveFakeOverride import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities import org.jetbrains.kotlin.name.NameUtils +import org.jetbrains.kotlin.utils.filterIsInstanceAnd private var patchParentPhases = 0 @@ -154,6 +155,9 @@ internal val localDeclarationsPhase = makeIrFilePhase( JvmVisibilityPolicy(), compatibilityModeForInlinedLocalDelegatedPropertyAccessors = true, forceFieldsForInlineCaptures = true, + getConstructorsThatCouldCaptureParamsWithoutFieldCreating = { + declarations.filterIsInstanceAnd { it.delegationKind(context.irBuiltIns) == ConstructorDelegationKind.CALLS_SUPER } + }, postLocalDeclarationLoweringCallback = context.localDeclarationsLoweringData?.let { { data -> data.localFunctions.forEach { (localFunction, localContext) -> diff --git a/compiler/testData/codegen/box/classes/nestedInitBlocksWithLambda.kt b/compiler/testData/codegen/box/classes/nestedInitBlocksWithLambda.kt index 5a5d53f7b51..14ca4b7a20e 100644 --- a/compiler/testData/codegen/box/classes/nestedInitBlocksWithLambda.kt +++ b/compiler/testData/codegen/box/classes/nestedInitBlocksWithLambda.kt @@ -1,11 +1,12 @@ // KT-61929 // WITH_SDTLIB +// IGNORE_BACKEND: JVM // EXPECTED_REACHABLE_NODES: 1301 package foo fun doSomething(lambda: () -> Unit) { lambda() } -class CompilerBug(result: String) { +class CompilerBug1(result: String) { var result: String = "Failed" init { @@ -23,6 +24,62 @@ class CompilerBug(result: String) { } } +class CompilerBug2(result: String) { + var result: String = "Fail" + + init { + run { + class Foo { + init { + doSomething { completed(result) } + } + + constructor() {} + } + + Foo() + } + } + + fun completed(value: String) { + this.result = value + } +} + +class CompilerBug3(result: String) { + var result: String = "OK" + + init { + run { + class Foo { + init { + doSomething { completed(result) } + } + + constructor() {} + constructor(test: String) {} + } + + if (this.result == "OK") { + this.result = "Failed with the empty constructor" + Foo() + } + if (this.result == "OK") { + this.result = "Failed with one parameter constructor" + Foo("Test") + } + } + } + + fun completed(value: String) { + this.result = value + } +} + fun box(): String { - return CompilerBug("OK").result + CompilerBug1("OK").result.also { if (it != "OK") return "CompilerBug1: $it" } + CompilerBug2("OK").result.also { if (it != "OK") return "CompilerBug2: $it" } + CompilerBug3("OK").result.also { if (it != "OK") return "CompilerBug3: $it" } + + return "OK" } \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeListing/inline/enclosingInfo/transformedConstructor.txt b/compiler/testData/codegen/bytecodeListing/inline/enclosingInfo/transformedConstructor.txt index 1f30ae0a4af..e6de9b75d8d 100644 --- a/compiler/testData/codegen/bytecodeListing/inline/enclosingInfo/transformedConstructor.txt +++ b/compiler/testData/codegen/bytecodeListing/inline/enclosingInfo/transformedConstructor.txt @@ -40,7 +40,6 @@ public interface test/Z { public final class test/_1Kt$test$1 { // source: '1.kt' enclosing method test/_1Kt.test(Lkotlin/jvm/functions/Function0;)Ltest/Z; - synthetic final field $z: kotlin.jvm.functions.Function0 private final field p: java.lang.String inner (anonymous) class test/_1Kt$test$1 public method (p0: kotlin.jvm.functions.Function0): void diff --git a/compiler/testData/codegen/bytecodeListing/inline/enclosingInfo/transformedConstructorWithNestedInline.txt b/compiler/testData/codegen/bytecodeListing/inline/enclosingInfo/transformedConstructorWithNestedInline.txt index 75a914d0e7a..6216e65498b 100644 --- a/compiler/testData/codegen/bytecodeListing/inline/enclosingInfo/transformedConstructorWithNestedInline.txt +++ b/compiler/testData/codegen/bytecodeListing/inline/enclosingInfo/transformedConstructorWithNestedInline.txt @@ -40,7 +40,6 @@ public interface test/Z { public final class test/_1Kt$test$1 { // source: '1.kt' enclosing method test/_1Kt.test(Lkotlin/jvm/functions/Function0;)Ltest/Z; - synthetic final field $z: kotlin.jvm.functions.Function0 private final field p: java.lang.String inner (anonymous) class test/_1Kt$test$1 public method (p0: kotlin.jvm.functions.Function0): void