JVM IR: improve ABI of properties in multifile facades

When copying top level declarations from multifile parts to facades,
also copy corresponding properties. This allows to keep their
annotations, which are later used in codegen (for example, to generate
ACC_DEPRECATED on property accessors), and allows to get rid of the hack
where the JVM name of the property accessor was computed prematurely via
methodSignatureMapper.

Also, don't copy `$annotations` methods for non-const properties to
facades because the old backend only generates them in parts (which
might be a separate problem, see KT-27644).

 #KT-40262 Fixed
This commit is contained in:
Alexander Udalov
2020-11-05 13:08:03 +01:00
parent 55974b4eda
commit 791be7c2dc
8 changed files with 115 additions and 21 deletions
@@ -31,5 +31,7 @@ sealed class FirMetadataSource : MetadataSource {
class Function(override val fir: FirFunction<*>) : FirMetadataSource(), MetadataSource.Function
class Property(override val fir: FirProperty) : FirMetadataSource(), MetadataSource.Property
}
class Property(override val fir: FirProperty) : FirMetadataSource(), MetadataSource.Property {
override val isConst: Boolean get() = fir.isConst
}
}
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.backend.common.phaser.makeCustomPhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.fileParent
import org.jetbrains.kotlin.backend.jvm.codegen.isSyntheticMethodForProperty
import org.jetbrains.kotlin.config.JvmAnalysisFlags
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
@@ -27,6 +28,7 @@ import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
import org.jetbrains.kotlin.ir.builders.declarations.buildClass
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.declarations.buildProperty
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
import org.jetbrains.kotlin.ir.expressions.*
@@ -39,7 +41,6 @@ import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.inline.INLINE_ONLY_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
@@ -140,19 +141,23 @@ private fun generateMultifileFacades(
context.multifileFacadeForPart[partClass.attributeOwnerId as IrClass] = jvmClassName
context.multifileFacadeClassForPart[partClass.attributeOwnerId as IrClass] = facadeClass
moveFieldsOfConstProperties(partClass, facadeClass)
val correspondingProperties = CorrespondingPropertyCache(context, facadeClass)
for (member in partClass.declarations) {
if (member !is IrSimpleFunction) continue
val correspondingProperty = member.correspondingPropertySymbol?.owner
if (member.hasAnnotation(INLINE_ONLY_ANNOTATION_FQ_NAME) ||
member.correspondingPropertySymbol?.owner?.hasAnnotation(INLINE_ONLY_ANNOTATION_FQ_NAME) == true
correspondingProperty?.hasAnnotation(INLINE_ONLY_ANNOTATION_FQ_NAME) == true
) continue
val newMember = member.createMultifileDelegateIfNeeded(context, facadeClass, shouldGeneratePartHierarchy)
val newMember =
member.createMultifileDelegateIfNeeded(context, facadeClass, correspondingProperties, shouldGeneratePartHierarchy)
if (newMember != null) {
functionDelegates[member] = newMember
}
}
moveFieldsOfConstProperties(partClass, facadeClass, correspondingProperties)
}
file
@@ -181,11 +186,16 @@ private fun modifyMultifilePartsForHierarchy(context: JvmBackendContext, parts:
return parts.last()
}
private fun moveFieldsOfConstProperties(partClass: IrClass, facadeClass: IrClass) {
private fun moveFieldsOfConstProperties(partClass: IrClass, facadeClass: IrClass, correspondingProperties: CorrespondingPropertyCache) {
partClass.declarations.transformFlat { member ->
if (member is IrField && member.shouldMoveToFacade()) {
member.patchDeclarationParents(facadeClass)
facadeClass.declarations.add(member)
member.correspondingPropertySymbol?.let { oldPropertySymbol ->
val newProperty = correspondingProperties.getOrCopyProperty(oldPropertySymbol.owner)
member.correspondingPropertySymbol = newProperty.symbol
newProperty.backingField = member
}
emptyList()
} else null
}
@@ -199,25 +209,31 @@ private fun IrField.shouldMoveToFacade(): Boolean {
private fun IrSimpleFunction.createMultifileDelegateIfNeeded(
context: JvmBackendContext,
facadeClass: IrClass,
correspondingProperties: CorrespondingPropertyCache,
shouldGeneratePartHierarchy: Boolean
): IrSimpleFunction? {
val target = this
if (DescriptorVisibilities.isPrivate(visibility) ||
name == StaticInitializersLowering.clinitName ||
origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR
origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR ||
// $annotations methods in the facade are only needed for const properties.
(isSyntheticMethodForProperty && (metadata as? MetadataSource.Property)?.isConst != true)
) return null
val function = context.irFactory.buildFun {
updateFrom(target)
isFakeOverride = shouldGeneratePartHierarchy
name = if (shouldGeneratePartHierarchy) {
target.name
} else {
// Generating a bridge. If the bridge is targeting a property getter
// we need to take care to get the name right. The bridge is not a
// property getter itself.
Name.identifier(context.methodSignatureMapper.mapFunctionName(target))
name = target.name
}
val targetProperty = correspondingPropertySymbol?.owner
if (targetProperty != null) {
val newProperty = correspondingProperties.getOrCopyProperty(targetProperty)
function.correspondingPropertySymbol = newProperty.symbol
when (target.valueParameters.size) {
0 -> newProperty.getter = function
1 -> newProperty.setter = function
}
}
@@ -250,6 +266,23 @@ private fun IrSimpleFunction.createMultifileDelegateIfNeeded(
return function
}
private class CorrespondingPropertyCache(private val context: JvmBackendContext, private val facadeClass: IrClass) {
private var cache: MutableMap<IrProperty, IrProperty>? = null
fun getOrCopyProperty(from: IrProperty): IrProperty {
val cache = cache ?: mutableMapOf<IrProperty, IrProperty>().also { cache = it }
return cache.getOrPut(from) {
context.irFactory.buildProperty {
updateFrom(from)
name = from.name
}.apply {
parent = facadeClass
copyAnnotationsFrom(from)
}
}
}
}
private class UpdateFunctionCallSites(
private val functionDelegates: MutableMap<IrSimpleFunction, IrSimpleFunction>
) : FileLoweringPass, IrElementTransformer<IrFunction?> {
@@ -14,7 +14,9 @@ interface MetadataSource {
interface File : MetadataSource
interface Class : MetadataSource
interface Function : MetadataSource
interface Property : MetadataSource
interface Property : MetadataSource {
val isConst: Boolean
}
}
sealed class DescriptorMetadataSource : MetadataSource {
@@ -30,8 +32,12 @@ sealed class DescriptorMetadataSource : MetadataSource {
class Function(override val descriptor: FunctionDescriptor) : DescriptorMetadataSource(), MetadataSource.Function
class Property(override val descriptor: PropertyDescriptor) : DescriptorMetadataSource(), MetadataSource.Property
class Property(override val descriptor: PropertyDescriptor) : DescriptorMetadataSource(), MetadataSource.Property {
override val isConst: Boolean get() = descriptor.isConst
}
class LocalDelegatedProperty(override val descriptor: VariableDescriptorWithAccessors) : DescriptorMetadataSource(),
MetadataSource.Property
MetadataSource.Property {
override val isConst: Boolean get() = descriptor.isConst
}
}
@@ -5,8 +5,8 @@ public final class A {
public final static method internal(@org.jetbrains.annotations.NotNull p0: java.lang.String): void
public synthetic static method internalInline$default(p0: java.lang.String, p1: int, p2: int, p3: java.lang.Object): void
public final static method internalInline(@org.jetbrains.annotations.NotNull p0: java.lang.String, p1: int): void
synthetic static method private$A__TestKt$default(p0: java.lang.String, p1: int, p2: java.lang.Object): void
synthetic static method privateInline$A__TestKt$default(p0: java.lang.String, p1: int, p2: int, p3: java.lang.Object): void
synthetic static method private$default(p0: java.lang.String, p1: int, p2: java.lang.Object): void
synthetic static method privateInline$default(p0: java.lang.String, p1: int, p2: int, p3: java.lang.Object): void
public synthetic static method public$default(p0: java.lang.String, p1: int, p2: java.lang.Object): void
public final static method public(@org.jetbrains.annotations.NotNull p0: java.lang.String): void
public synthetic static method publicInline$default(p0: java.lang.String, p1: int, p2: int, p3: java.lang.Object): void
@@ -0,0 +1,22 @@
// WITH_RUNTIME
// FILE: part.kt
@file:JvmMultifileClass
@file:JvmName("A")
package test
@Deprecated("")
val str: String
get() = ""
@Deprecated("")
fun f() {}
@Deprecated("")
val Int.ext: Int get() = this
var Int.extA: Int
get() = this
@Deprecated("")
set(value) {}
@@ -0,0 +1,21 @@
@kotlin.Metadata
public final class test/A {
// source: 'part.kt'
public deprecated final static @kotlin.Deprecated method f(): void
public deprecated final static method getExt(p0: int): int
public final static method getExtA(p0: int): int
public deprecated final static @org.jetbrains.annotations.NotNull method getStr(): java.lang.String
public deprecated final static @kotlin.Deprecated method setExtA(p0: int, p1: int): void
}
@kotlin.Metadata
synthetic final class test/A__PartKt {
// source: 'part.kt'
public deprecated final static @kotlin.Deprecated method f(): void
public synthetic deprecated static @kotlin.Deprecated method getExt$annotations(p0: int): void
public deprecated final static method getExt(p0: int): int
public final static method getExtA(p0: int): int
public synthetic deprecated static @kotlin.Deprecated method getStr$annotations(): void
public deprecated final static @org.jetbrains.annotations.NotNull method getStr(): java.lang.String
public deprecated final static @kotlin.Deprecated method setExtA(p0: int, p1: int): void
}
@@ -738,6 +738,11 @@ public class BytecodeListingTestGenerated extends AbstractBytecodeListingTest {
runTest("compiler/testData/codegen/bytecodeListing/deprecated/deprecatedEnumEntryFields.kt");
}
@TestMetadata("deprecatedInMultifileClass.kt")
public void testDeprecatedInMultifileClass() throws Exception {
runTest("compiler/testData/codegen/bytecodeListing/deprecated/deprecatedInMultifileClass.kt");
}
@TestMetadata("deprecatedLateinitVar.kt")
public void testDeprecatedLateinitVar() throws Exception {
runTest("compiler/testData/codegen/bytecodeListing/deprecated/deprecatedLateinitVar.kt");
@@ -708,6 +708,11 @@ public class IrBytecodeListingTestGenerated extends AbstractIrBytecodeListingTes
runTest("compiler/testData/codegen/bytecodeListing/deprecated/deprecatedEnumEntryFields.kt");
}
@TestMetadata("deprecatedInMultifileClass.kt")
public void testDeprecatedInMultifileClass() throws Exception {
runTest("compiler/testData/codegen/bytecodeListing/deprecated/deprecatedInMultifileClass.kt");
}
@TestMetadata("deprecatedLateinitVar.kt")
public void testDeprecatedLateinitVar() throws Exception {
runTest("compiler/testData/codegen/bytecodeListing/deprecated/deprecatedLateinitVar.kt");