From 36a25353e9684ba48c16a193fe7c9617ecc53d3f Mon Sep 17 00:00:00 2001 From: pyos Date: Fri, 7 May 2021 10:44:00 +0200 Subject: [PATCH] JVM_IR: permit `@NoArg` if super constructor is already zero-argument #KT-46537 Fixed --- .../common/lower/InitializersLowering.kt | 6 +- .../src/NoArgIrGenerationExtension.kt | 71 +++---------------- .../BlackBoxCodegenTestForNoArgGenerated.java | 5 ++ ...rBlackBoxCodegenTestForNoArgGenerated.java | 5 ++ .../noarg/noarg-cli/testData/box/subclass.kt | 22 ++++++ 5 files changed, 46 insertions(+), 63 deletions(-) create mode 100644 plugins/noarg/noarg-cli/testData/box/subclass.kt diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InitializersLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InitializersLowering.kt index d9e7b6b8e62..cb4ac075bd8 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InitializersLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InitializersLowering.kt @@ -35,7 +35,8 @@ class InitializersLowering(context: CommonBackendContext) : InitializersLowering val irClass = container.constructedClass val instanceInitializerStatements = extractInitializers(irClass) { - (it is IrField && !it.isStatic) || (it is IrAnonymousInitializer && !it.isStatic) + (it is IrField && !it.isStatic && (container.isPrimary || !it.primaryConstructorParameter)) || + (it is IrAnonymousInitializer && !it.isStatic) } val block = IrBlockImpl(irClass.startOffset, irClass.endOffset, context.irBuiltIns.unitType, null, instanceInitializerStatements) // Check that the initializers contain no local classes. Deep-copying them is a disaster for code size, and liable to break randomly. @@ -54,6 +55,9 @@ class InitializersLowering(context: CommonBackendContext) : InitializersLowering } } +private val IrField.primaryConstructorParameter: Boolean + get() = (initializer?.expression as? IrGetValue)?.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER + abstract class InitializersLoweringBase(open val context: CommonBackendContext) { protected fun extractInitializers(irClass: IrClass, filter: (IrDeclaration) -> Boolean) = // TODO What about fields that were added by lowerings? e.g. captured outer class or locals? diff --git a/plugins/noarg/noarg-cli/src/NoArgIrGenerationExtension.kt b/plugins/noarg/noarg-cli/src/NoArgIrGenerationExtension.kt index 52749df33b1..325874ebc49 100644 --- a/plugins/noarg/noarg-cli/src/NoArgIrGenerationExtension.kt +++ b/plugins/noarg/noarg-cli/src/NoArgIrGenerationExtension.kt @@ -7,29 +7,19 @@ package org.jetbrains.kotlin.noarg import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext -import org.jetbrains.kotlin.backend.common.lower.SYNTHESIZED_INIT_BLOCK import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.extensions.AnnotationBasedExtension import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor -import org.jetbrains.kotlin.ir.expressions.IrBlock -import org.jetbrains.kotlin.ir.expressions.IrGetValue -import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin -import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl +import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.getClass import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_OVERLOADS_FQ_NAME -import org.jetbrains.kotlin.utils.addToStdlib.safeAs internal class NoArgIrGenerationExtension( private val annotations: List, @@ -56,7 +46,7 @@ private class NoArgIrTransformer( if (declaration.kind == ClassKind.CLASS && declaration.isAnnotatedWithNoarg() && - declaration.constructors.none(::isZeroParameterConstructor) + declaration.constructors.none { it.isZeroParameterConstructor() } ) { declaration.declarations.add(getOrGenerateNoArgConstructor(declaration)) } @@ -72,7 +62,7 @@ private class NoArgIrTransformer( val superConstructor = if (superClass.isAnnotatedWithNoarg()) getOrGenerateNoArgConstructor(superClass) - else superClass.constructors.singleOrNull { it.valueParameters.isEmpty() } + else superClass.constructors.singleOrNull { it.isZeroParameterConstructor() } ?: error("No noarg super constructor for ${klass.render()}:\n" + superClass.constructors.joinToString("\n") { it.render() }) context.irFactory.buildConstructor { @@ -86,9 +76,9 @@ private class NoArgIrTransformer( ctor.startOffset, ctor.endOffset, context.irBuiltIns.unitType, superConstructor.symbol, 0, superConstructor.valueParameters.size ), - if (invokeInitializers) - NoArgInitializersLowering(context.irBuiltIns).createInitializersBlock(ctor) - else null + IrInstanceInitializerCallImpl( + ctor.startOffset, ctor.endOffset, klass.symbol, context.irBuiltIns.unitType + ).takeIf { invokeInitializers } ) ) } @@ -97,50 +87,7 @@ private class NoArgIrTransformer( private fun IrClass.isAnnotatedWithNoarg(): Boolean = toIrBasedDescriptor().hasSpecialAnnotation(null) - private fun isZeroParameterConstructor(constructor: IrConstructor): Boolean { - val parameters = constructor.valueParameters - return parameters.isEmpty() || - (parameters.all { it.defaultValue != null } && (constructor.isPrimary || constructor.hasAnnotation(JVM_OVERLOADS_FQ_NAME))) - } -} - -/** Main parts copied from [org.jetbrains.kotlin.backend.common.lower.InitializersLowering]. */ -private class NoArgInitializersLowering(private val builtIns: IrBuiltIns) { - fun createInitializersBlock(ctor: IrConstructor): IrBlock { - val irClass = ctor.constructedClass - return IrBlockImpl(irClass.startOffset, irClass.endOffset, builtIns.unitType, null, extractInitializers(irClass)) - .deepCopyWithSymbols(ctor) - } - - private fun extractInitializers(irClass: IrClass): List { - val result = mutableListOf() - for (declaration in irClass.declarations) { - when (declaration) { - is IrAnonymousInitializer -> if (!declaration.isStatic) { - result.add(with(declaration) { - IrBlockImpl(startOffset, endOffset, builtIns.unitType, SYNTHESIZED_INIT_BLOCK, body.statements) - }) - } - is IrProperty -> declaration.backingField.let { field -> - if (field != null && !field.isStatic) { - val initializer = field.initializer - // Take all field initializers except those for properties in the primary constructor, for which we have no values - // in the noarg constructor (and thus those properties will be uninitialized). - if (initializer != null && initializer.expression.safeAs()?.origin != - IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER - ) { - result.add(with(initializer) { - IrSetFieldImpl( - startOffset, endOffset, field.symbol, - IrGetValueImpl(startOffset, endOffset, irClass.thisReceiver!!.type, irClass.thisReceiver!!.symbol), - expression, builtIns.unitType, IrStatementOrigin.INITIALIZE_FIELD - ) - }) - } - } - } - } - } - return result - } + // Returns true if this constructor is callable with no arguments by JVM rules, i.e. will have descriptor `()V`. + private fun IrConstructor.isZeroParameterConstructor(): Boolean = + valueParameters.all { it.defaultValue != null } && (valueParameters.isEmpty() || isPrimary || hasAnnotation(JVM_OVERLOADS_FQ_NAME)) } diff --git a/plugins/noarg/noarg-cli/test/org/jetbrains/kotlin/noarg/BlackBoxCodegenTestForNoArgGenerated.java b/plugins/noarg/noarg-cli/test/org/jetbrains/kotlin/noarg/BlackBoxCodegenTestForNoArgGenerated.java index 055d81cd212..abeb0910d77 100644 --- a/plugins/noarg/noarg-cli/test/org/jetbrains/kotlin/noarg/BlackBoxCodegenTestForNoArgGenerated.java +++ b/plugins/noarg/noarg-cli/test/org/jetbrains/kotlin/noarg/BlackBoxCodegenTestForNoArgGenerated.java @@ -69,4 +69,9 @@ public class BlackBoxCodegenTestForNoArgGenerated extends AbstractBlackBoxCodege public void testSimple() throws Exception { runTest("plugins/noarg/noarg-cli/testData/box/simple.kt"); } + + @TestMetadata("subclass.kt") + public void testSubclass() throws Exception { + runTest("plugins/noarg/noarg-cli/testData/box/subclass.kt"); + } } diff --git a/plugins/noarg/noarg-cli/test/org/jetbrains/kotlin/noarg/IrBlackBoxCodegenTestForNoArgGenerated.java b/plugins/noarg/noarg-cli/test/org/jetbrains/kotlin/noarg/IrBlackBoxCodegenTestForNoArgGenerated.java index 6710f7b6026..28c905de0d3 100644 --- a/plugins/noarg/noarg-cli/test/org/jetbrains/kotlin/noarg/IrBlackBoxCodegenTestForNoArgGenerated.java +++ b/plugins/noarg/noarg-cli/test/org/jetbrains/kotlin/noarg/IrBlackBoxCodegenTestForNoArgGenerated.java @@ -69,4 +69,9 @@ public class IrBlackBoxCodegenTestForNoArgGenerated extends AbstractIrBlackBoxCo public void testSimple() throws Exception { runTest("plugins/noarg/noarg-cli/testData/box/simple.kt"); } + + @TestMetadata("subclass.kt") + public void testSubclass() throws Exception { + runTest("plugins/noarg/noarg-cli/testData/box/subclass.kt"); + } } diff --git a/plugins/noarg/noarg-cli/testData/box/subclass.kt b/plugins/noarg/noarg-cli/testData/box/subclass.kt new file mode 100644 index 00000000000..37855fa97e1 --- /dev/null +++ b/plugins/noarg/noarg-cli/testData/box/subclass.kt @@ -0,0 +1,22 @@ +// WITH_RUNTIME + +annotation class NoArg + +open class Base1 +open class Base2(val a: Int = 1) +open class Base3(val a: Int) { + @JvmOverloads constructor(a: Long = 1L) : this(a.toInt()) +} + +@NoArg class Test1(val b: String) : Base1() +@NoArg class Test2(val b: String) : Base2() +@NoArg class Test3(val b: String) : Base3() + +fun box(): String { + val test1 = Test1::class.java.newInstance() + val test2 = Test2::class.java.newInstance() + if (test2.a != 1) return "fail@test2: ${test2.a}" + val test3 = Test3::class.java.newInstance() + if (test3.a != 1) return "fail@test3: ${test3.a}" + return "OK" +}