JVM IR: do not generate hidden constructor for inline classes more than once
SyntheticAccessorLowering was initially implemented under the assumption that any access to an invisible declaration will cause an accessor to be generated _in the same file_. Moreover, it's declared in the group of phases that are performed by file. But this assumption is incorrect for constructors which need to be hidden (those which take parameters of inline class types), since such constructor is public and can be called from anywhere. In this case, SyntheticAccessorLowering actually generated a new accessor for the hidden constructor for each (!) source file where that constructor is called, which led to ClassFormatError because of the class file having multiple methods with the same signature. The internal `functionMap` cache didn't help because it's not shared among phase instances for different files (well, it helped to generate not more than one accessor per usage-file). In this change, we use the global cache, stored in JvmBackendContext, for accessors to hidden constructors. Note that after this change, calls to hidden constructors are always transformed to the corresponding accessor in SyntheticAccessorLowering right away, but that accessor might be orphaned for a while (not declared in any parent's declarations). Only when SyntheticAccessorLowering encounters the original constructor which needs to be hidden, it adds the accessor beside it. The test is sensitive to the file order, so both variants are added.
This commit is contained in:
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||
import org.jetbrains.kotlin.ir.builders.irBlock
|
||||
import org.jetbrains.kotlin.ir.builders.irNull
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl
|
||||
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
|
||||
@@ -92,6 +93,8 @@ class JvmBackendContext(
|
||||
internal val multifileFacadeForPart = mutableMapOf<IrClass, JvmClassName>()
|
||||
internal val multifileFacadeMemberToPartMember = mutableMapOf<IrFunction, IrFunction>()
|
||||
|
||||
internal val hiddenConstructors = mutableMapOf<IrConstructor, IrConstructorImpl>()
|
||||
|
||||
override var inVerbosePhase: Boolean = false
|
||||
|
||||
override val configuration get() = state.configuration
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.backend.jvm.JvmSymbols
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineOnly
|
||||
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
|
||||
import org.jetbrains.kotlin.backend.jvm.descriptors.JvmDeclarationFactory
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.hasMangledParameters
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
@@ -141,10 +140,6 @@ fun IrType.getArrayElementType(irBuiltIns: IrBuiltIns): IrType =
|
||||
val IrStatementOrigin?.isLambda: Boolean
|
||||
get() = this == IrStatementOrigin.LAMBDA || this == IrStatementOrigin.ANONYMOUS_FUNCTION
|
||||
|
||||
val IrConstructor.shouldBeHidden: Boolean
|
||||
get() = !Visibilities.isPrivate(visibility) && !constructedClass.isInline && hasMangledParameters &&
|
||||
origin != IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER
|
||||
|
||||
// An IR builder with a reference to the JvmBackendContext
|
||||
class JvmIrBuilder(
|
||||
val backendContext: JvmBackendContext,
|
||||
|
||||
+54
-45
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.backend.jvm.intrinsics.receiverAndArgs
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.IrInlineReferenceLocator
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.hasJvmDefault
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.isLambda
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.shouldBeHidden
|
||||
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.hasMangledParameters
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
@@ -57,20 +57,30 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
|
||||
return super.visitFunctionAccess(expression)
|
||||
}
|
||||
|
||||
val callee = expression.symbol.owner
|
||||
val withSuper = (expression as? IrCall)?.superQualifierSymbol != null
|
||||
val thisSymbol = (expression as? IrCall)?.dispatchReceiver?.type?.classifierOrNull as? IrClassSymbol
|
||||
if (expression.symbol.isAccessible(withSuper, thisSymbol)) {
|
||||
return super.visitFunctionAccess(expression)
|
||||
}
|
||||
return super.visitExpression(
|
||||
modifyFunctionAccessExpression(expression, functionMap.getOrPut(expression.symbol) {
|
||||
when (val symbol = expression.symbol) {
|
||||
is IrConstructorSymbol -> symbol.owner.makeConstructorAccessor().symbol
|
||||
is IrSimpleFunctionSymbol -> symbol.owner.makeSimpleFunctionAccessor(expression as IrCall).symbol
|
||||
else -> error("Unknown subclass of IrFunctionSymbol")
|
||||
|
||||
val accessor = when {
|
||||
callee is IrConstructor && callee.isOrShouldBeHidden ->
|
||||
handleHiddenConstructor(callee).symbol
|
||||
|
||||
!expression.symbol.isAccessible(withSuper, thisSymbol) ->
|
||||
functionMap.getOrPut(expression.symbol) {
|
||||
when (val symbol = expression.symbol) {
|
||||
is IrConstructorSymbol -> symbol.owner.makeConstructorAccessor().also { accessor ->
|
||||
pendingTransformations.add {
|
||||
(accessor.parent as IrDeclarationContainer).declarations.add(accessor)
|
||||
}
|
||||
}.symbol
|
||||
is IrSimpleFunctionSymbol -> symbol.owner.makeSimpleFunctionAccessor(expression as IrCall).symbol
|
||||
else -> error("Unknown subclass of IrFunctionSymbol")
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
else -> return super.visitFunctionAccess(expression)
|
||||
}
|
||||
return super.visitExpression(modifyFunctionAccessExpression(expression, accessor))
|
||||
}
|
||||
|
||||
override fun visitGetField(expression: IrGetField) = super.visitExpression(
|
||||
@@ -90,25 +100,26 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
|
||||
)
|
||||
|
||||
override fun visitConstructor(declaration: IrConstructor): IrStatement {
|
||||
handleHiddenConstructor(declaration)
|
||||
return super.visitConstructor(declaration)
|
||||
}
|
||||
if (declaration.isOrShouldBeHidden) {
|
||||
pendingTransformations.add {
|
||||
declaration.parentAsClass.declarations.add(handleHiddenConstructor(declaration))
|
||||
}
|
||||
declaration.visibility = Visibilities.PRIVATE
|
||||
}
|
||||
|
||||
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
|
||||
handleHiddenConstructor(expression.symbol.owner)
|
||||
return super.visitConstructorCall(expression)
|
||||
return super.visitConstructor(declaration)
|
||||
}
|
||||
|
||||
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
|
||||
val function = expression.symbol.owner
|
||||
|
||||
if (!expression.origin.isLambda && function is IrConstructor) {
|
||||
handleHiddenConstructor(function)?.let { accessor ->
|
||||
if (!expression.origin.isLambda && function is IrConstructor && function.isOrShouldBeHidden) {
|
||||
handleHiddenConstructor(function).let { accessor ->
|
||||
expression.transformChildrenVoid()
|
||||
return IrFunctionReferenceImpl(
|
||||
expression.startOffset, expression.endOffset, expression.type,
|
||||
accessor, accessor.owner.typeParameters.size,
|
||||
accessor.owner.valueParameters.size, expression.origin
|
||||
accessor.symbol, accessor.typeParameters.size,
|
||||
accessor.valueParameters.size, expression.origin
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -116,28 +127,28 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
|
||||
return super.visitFunctionReference(expression)
|
||||
}
|
||||
|
||||
private fun handleHiddenConstructor(declaration: IrConstructor): IrConstructorSymbol? {
|
||||
functionMap[declaration.symbol]?.let { return it as IrConstructorSymbol }
|
||||
private val IrConstructor.isOrShouldBeHidden: Boolean
|
||||
get() = this in context.hiddenConstructors || (
|
||||
!Visibilities.isPrivate(visibility) && !constructedClass.isInline && hasMangledParameters &&
|
||||
origin != IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER &&
|
||||
origin != JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR)
|
||||
|
||||
if (!declaration.shouldBeHidden)
|
||||
return null
|
||||
|
||||
declaration.visibility = Visibilities.PRIVATE
|
||||
|
||||
return declaration.makeConstructorAccessor().also { accessor ->
|
||||
functionMap[declaration.symbol] = accessor.symbol
|
||||
|
||||
// There's a special case in the JVM backend for serializing the metadata of hidden
|
||||
// constructors - we serialize the descriptor of the original constructor, but the
|
||||
// signature of the bridge. We implement this special case in the JVM IR backend by
|
||||
// attaching the metadata directly to the bridge. We also have to move all annotations
|
||||
// to the bridge method. Parameter annotations are already moved by the copyTo method.
|
||||
accessor.metadata = declaration.metadata
|
||||
declaration.safeAs<IrConstructorImpl>()?.metadata = null
|
||||
accessor.annotations += declaration.annotations
|
||||
declaration.annotations.clear()
|
||||
declaration.valueParameters.forEach { it.annotations.clear() }
|
||||
}.symbol
|
||||
private fun handleHiddenConstructor(declaration: IrConstructor): IrConstructorImpl {
|
||||
require(declaration.isOrShouldBeHidden, declaration::render)
|
||||
return context.hiddenConstructors.getOrPut(declaration) {
|
||||
declaration.makeConstructorAccessor().also { accessor ->
|
||||
// There's a special case in the JVM backend for serializing the metadata of hidden
|
||||
// constructors - we serialize the descriptor of the original constructor, but the
|
||||
// signature of the accessor. We implement this special case in the JVM IR backend by
|
||||
// attaching the metadata directly to the accessor. We also have to move all annotations
|
||||
// to the accessor. Parameter annotations are already moved by the copyTo method.
|
||||
accessor.metadata = declaration.metadata
|
||||
declaration.safeAs<IrConstructorImpl>()?.metadata = null
|
||||
accessor.annotations += declaration.annotations
|
||||
declaration.annotations.clear()
|
||||
declaration.valueParameters.forEach { it.annotations.clear() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In case of Java `protected static`, access could be done from a public inline function in the same package,
|
||||
@@ -155,10 +166,8 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
|
||||
origin = JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR
|
||||
name = source.name
|
||||
visibility = Visibilities.PUBLIC
|
||||
|
||||
}.also { accessor ->
|
||||
accessor.parent = source.parent
|
||||
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
|
||||
|
||||
accessor.copyTypeParametersFrom(source, JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR)
|
||||
accessor.copyValueParametersToStatic(source, JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR)
|
||||
|
||||
compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
fun box(): String = X(Z("OK")).z.result
|
||||
|
||||
// FILE: 2.kt
|
||||
|
||||
inline class Z(val result: String)
|
||||
|
||||
class X(val z: Z)
|
||||
compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// FILE: 2.kt
|
||||
|
||||
fun box(): String = X(Z("OK")).z.result
|
||||
|
||||
// FILE: 1.kt
|
||||
|
||||
inline class Z(val result: String)
|
||||
|
||||
class X(val z: Z)
|
||||
+10
@@ -13723,6 +13723,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile1.kt")
|
||||
public void testConstructorReferencedFromOtherFile1() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile2.kt")
|
||||
public void testConstructorReferencedFromOtherFile2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorWithDefaultParameters.kt")
|
||||
public void testConstructorWithDefaultParameters() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
|
||||
|
||||
+10
@@ -13723,6 +13723,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile1.kt")
|
||||
public void testConstructorReferencedFromOtherFile1() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile2.kt")
|
||||
public void testConstructorReferencedFromOtherFile2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorWithDefaultParameters.kt")
|
||||
public void testConstructorWithDefaultParameters() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
|
||||
|
||||
+10
@@ -12573,6 +12573,16 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile1.kt")
|
||||
public void testConstructorReferencedFromOtherFile1() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile2.kt")
|
||||
public void testConstructorReferencedFromOtherFile2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorWithDefaultParameters.kt")
|
||||
public void testConstructorWithDefaultParameters() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
|
||||
|
||||
+10
@@ -12573,6 +12573,16 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile1.kt")
|
||||
public void testConstructorReferencedFromOtherFile1() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile2.kt")
|
||||
public void testConstructorReferencedFromOtherFile2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorWithDefaultParameters.kt")
|
||||
public void testConstructorWithDefaultParameters() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
|
||||
|
||||
Generated
+10
@@ -10888,6 +10888,16 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile1.kt")
|
||||
public void testConstructorReferencedFromOtherFile1() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile2.kt")
|
||||
public void testConstructorReferencedFromOtherFile2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorWithDefaultParameters.kt")
|
||||
public void testConstructorWithDefaultParameters() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
|
||||
|
||||
+10
@@ -12028,6 +12028,16 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile1.kt")
|
||||
public void testConstructorReferencedFromOtherFile1() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorReferencedFromOtherFile2.kt")
|
||||
public void testConstructorReferencedFromOtherFile2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constructorWithDefaultParameters.kt")
|
||||
public void testConstructorWithDefaultParameters() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
|
||||
|
||||
Reference in New Issue
Block a user