[JVM IR] KTIJ-24206 Add an option to stub orphaned expect symbols
- The cause for KTIJ-24206 is that the `expect` function's parent is an `IrFile` instead of an `IrClass`. This is because `ExpectDeclarationsRemoveLowering` removes `expect` declarations before `FileClassLowering` can replace `IrFile` parents. - That behavior is normally okay, but breaks down when an `expect` declaration has no associated `actual` declaration. In such cases, `ExpectDeclarationsRemoveLowering` doesn't replace `expect` symbols in expressions with their corresponding `actual` symbols, as it normally would. - The solution fills in `ExpectDeclarationsRemoveLowering`'s behavior by replacing `expect` symbols for which no `actual` symbols exist with stubs. See `stubOrphanedExpectSymbols`. - To not mess with the lowerings, `stubOrphanedExpectSymbols` is invoked during IR generation. It uses the same `ExpectSymbolTransformer` as `ExpectDeclarationRemover`. ^KTIJ-24206 fixed
This commit is contained in:
committed by
Space Team
parent
8b1e508740
commit
c9461a3827
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.common.ir
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.util.copyTypeAndValueArgumentsFrom
|
||||
import org.jetbrains.kotlin.ir.util.irCall
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
|
||||
/**
|
||||
* [ExpectSymbolTransformer] replaces `expect` symbols in expressions with `actual` symbols. An `actual` symbol must be provided by
|
||||
* overriding [getActualClass], [getActualProperty], [getActualConstructor], and [getActualFunction].
|
||||
*/
|
||||
@OptIn(ObsoleteDescriptorBasedAPI::class)
|
||||
abstract class ExpectSymbolTransformer : IrElementTransformerVoid() {
|
||||
|
||||
protected abstract fun getActualClass(descriptor: ClassDescriptor): IrClassSymbol?
|
||||
|
||||
protected data class ActualPropertyResult(
|
||||
val propertySymbol: IrPropertySymbol,
|
||||
val getterSymbol: IrSimpleFunctionSymbol?,
|
||||
val setterSymbol: IrSimpleFunctionSymbol?,
|
||||
)
|
||||
|
||||
protected abstract fun getActualProperty(descriptor: PropertyDescriptor): ActualPropertyResult?
|
||||
|
||||
protected abstract fun getActualConstructor(descriptor: ClassConstructorDescriptor): IrConstructorSymbol?
|
||||
|
||||
protected abstract fun getActualFunction(descriptor: FunctionDescriptor): IrSimpleFunctionSymbol?
|
||||
|
||||
override fun visitElement(element: IrElement): IrElement {
|
||||
element.transformChildrenVoid()
|
||||
return element
|
||||
}
|
||||
|
||||
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
|
||||
val nExpression = super.visitConstructorCall(expression) as IrConstructorCall
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newCallee = getActualConstructor(nExpression.symbol.descriptor) ?: return nExpression
|
||||
with(nExpression) {
|
||||
return IrConstructorCallImpl(
|
||||
startOffset, endOffset, type, newCallee, typeArgumentsCount, constructorTypeArgumentsCount, valueArgumentsCount, origin
|
||||
).also {
|
||||
it.attributeOwnerId = attributeOwnerId
|
||||
it.copyTypeAndValueArgumentsFrom(nExpression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
|
||||
val nExpression = super.visitDelegatingConstructorCall(expression) as IrDelegatingConstructorCall
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newCallee = getActualConstructor(nExpression.symbol.descriptor) ?: return nExpression
|
||||
with(nExpression) {
|
||||
return IrDelegatingConstructorCallImpl(
|
||||
startOffset, endOffset, type, newCallee, typeArgumentsCount, valueArgumentsCount
|
||||
).also {
|
||||
it.attributeOwnerId = attributeOwnerId
|
||||
it.copyTypeAndValueArgumentsFrom(nExpression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitEnumConstructorCall(expression: IrEnumConstructorCall): IrExpression {
|
||||
val nExpression = super.visitEnumConstructorCall(expression) as IrEnumConstructorCall
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newCallee = getActualConstructor(nExpression.symbol.descriptor) ?: return nExpression
|
||||
with(nExpression) {
|
||||
return IrEnumConstructorCallImpl(
|
||||
startOffset, endOffset, type, newCallee, typeArgumentsCount, valueArgumentsCount
|
||||
).also {
|
||||
it.attributeOwnerId = attributeOwnerId
|
||||
it.copyTypeAndValueArgumentsFrom(nExpression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
val nExpression = super.visitCall(expression) as IrCall
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newCallee = getActualFunction(nExpression.symbol.descriptor) ?: return nExpression
|
||||
return irCall(nExpression, newCallee).also {
|
||||
it.attributeOwnerId = nExpression.attributeOwnerId
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitPropertyReference(expression: IrPropertyReference): IrExpression {
|
||||
val nExpression = super.visitPropertyReference(expression) as IrPropertyReference
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val (newSymbol, newGetter, newSetter) = getActualProperty(nExpression.symbol.descriptor) ?: return nExpression
|
||||
with(nExpression) {
|
||||
return IrPropertyReferenceImpl(
|
||||
startOffset, endOffset, type,
|
||||
newSymbol, typeArgumentsCount,
|
||||
field, newGetter, newSetter,
|
||||
origin
|
||||
).also {
|
||||
it.attributeOwnerId = attributeOwnerId
|
||||
copyTypeArgumentsFrom(nExpression)
|
||||
it.dispatchReceiver = dispatchReceiver
|
||||
it.extensionReceiver = extensionReceiver
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
|
||||
val nExpression = super.visitFunctionReference(expression) as IrFunctionReference
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newCallee = getActualFunction(nExpression.symbol.descriptor) ?: return nExpression
|
||||
with(nExpression) {
|
||||
return IrFunctionReferenceImpl(
|
||||
startOffset, endOffset, type, newCallee, typeArgumentsCount, valueArgumentsCount, reflectionTarget, origin
|
||||
).also {
|
||||
it.attributeOwnerId = attributeOwnerId
|
||||
it.copyTypeArgumentsFrom(nExpression)
|
||||
it.dispatchReceiver = dispatchReceiver
|
||||
it.extensionReceiver = extensionReceiver
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitClassReference(expression: IrClassReference): IrExpression {
|
||||
val nExpression = super.visitClassReference(expression) as IrClassReference
|
||||
val oldSymbol = nExpression.symbol as? IrClassSymbol ?: return nExpression
|
||||
if (!oldSymbol.owner.isExpect) return nExpression
|
||||
|
||||
val newSymbol = getActualClass(oldSymbol.descriptor) ?: return nExpression
|
||||
with(nExpression) {
|
||||
return IrClassReferenceImpl(startOffset, endOffset, type, newSymbol, classType)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+45
-142
@@ -7,26 +7,28 @@ package org.jetbrains.kotlin.backend.common.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.BackendContext
|
||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.ir.ExpectSymbolTransformer
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrGetValue
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.*
|
||||
import org.jetbrains.kotlin.ir.types.extractTypeParameters
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.*
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.OptionalAnnotationUtil
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.findCompatibleActualsForExpected
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.findCompatibleExpectsForActual
|
||||
import kotlin.collections.set
|
||||
|
||||
// `doRemove` means should expect-declaration be removed from IR
|
||||
@OptIn(ObsoleteDescriptorBasedAPI::class)
|
||||
class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private val doRemove: Boolean)
|
||||
: IrElementTransformerVoid(), FileLoweringPass {
|
||||
open class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private val doRemove: Boolean)
|
||||
: ExpectSymbolTransformer(), FileLoweringPass {
|
||||
|
||||
constructor(context: BackendContext) : this(context.ir.symbols.externalSymbolTable, true)
|
||||
|
||||
@@ -36,14 +38,9 @@ class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private va
|
||||
visitFile(irFile)
|
||||
}
|
||||
|
||||
override fun visitElement(element: IrElement): IrElement {
|
||||
element.transformChildrenVoid()
|
||||
return element
|
||||
}
|
||||
|
||||
override fun visitFile(declaration: IrFile): IrFile {
|
||||
declaration.declarations.removeAll {
|
||||
shouldRemoveTopLevelDeclaration(it)
|
||||
if (doRemove) {
|
||||
declaration.declarations.removeAll { shouldRemoveTopLevelDeclaration(it) }
|
||||
}
|
||||
return super.visitFile(declaration)
|
||||
}
|
||||
@@ -53,127 +50,7 @@ class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private va
|
||||
return super.visitValueParameter(declaration)
|
||||
}
|
||||
|
||||
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
|
||||
val nExpression = super.visitConstructorCall(expression) as IrConstructorCall
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newCallee = symbolTable.referenceConstructor(
|
||||
nExpression.symbol.descriptor.findActualForExpect() as? ClassConstructorDescriptor ?: return nExpression
|
||||
)
|
||||
with(nExpression) {
|
||||
return IrConstructorCallImpl(
|
||||
startOffset, endOffset, type, newCallee, typeArgumentsCount, constructorTypeArgumentsCount, valueArgumentsCount, origin
|
||||
).also {
|
||||
it.attributeOwnerId = attributeOwnerId
|
||||
it.copyTypeAndValueArgumentsFrom(nExpression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
|
||||
val nExpression = super.visitDelegatingConstructorCall(expression) as IrDelegatingConstructorCall
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newCallee = symbolTable.referenceConstructor(
|
||||
nExpression.symbol.descriptor.findActualForExpect() as? ClassConstructorDescriptor ?: return nExpression
|
||||
)
|
||||
with(nExpression) {
|
||||
return IrDelegatingConstructorCallImpl(
|
||||
startOffset, endOffset, type, newCallee, typeArgumentsCount, valueArgumentsCount
|
||||
).also {
|
||||
it.attributeOwnerId = attributeOwnerId
|
||||
it.copyTypeAndValueArgumentsFrom(nExpression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitEnumConstructorCall(expression: IrEnumConstructorCall): IrExpression {
|
||||
val nExpression = super.visitEnumConstructorCall(expression) as IrEnumConstructorCall
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newCallee = symbolTable.referenceConstructor(
|
||||
nExpression.symbol.descriptor.findActualForExpect() as? ClassConstructorDescriptor ?: return nExpression
|
||||
)
|
||||
with(nExpression) {
|
||||
return IrEnumConstructorCallImpl(
|
||||
startOffset, endOffset, type, newCallee, typeArgumentsCount, valueArgumentsCount
|
||||
).also {
|
||||
it.attributeOwnerId = attributeOwnerId
|
||||
it.copyTypeAndValueArgumentsFrom(nExpression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
val nExpression = super.visitCall(expression) as IrCall
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newCallee = symbolTable.referenceSimpleFunction(
|
||||
nExpression.symbol.descriptor.findActualForExpect() as? FunctionDescriptor ?: return nExpression
|
||||
)
|
||||
return irCall(nExpression, newCallee).also {
|
||||
it.attributeOwnerId = nExpression.attributeOwnerId
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitPropertyReference(expression: IrPropertyReference): IrExpression {
|
||||
val nExpression = super.visitPropertyReference(expression) as IrPropertyReference
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newSymbol = symbolTable.referenceProperty(
|
||||
nExpression.symbol.descriptor.findActualForExpect() as? PropertyDescriptor ?: return nExpression
|
||||
)
|
||||
val newGetter = newSymbol.descriptor.getter?.let { symbolTable.referenceSimpleFunction(it) }
|
||||
val newSetter = newSymbol.descriptor.setter?.let { symbolTable.referenceSimpleFunction(it) }
|
||||
with(nExpression) {
|
||||
return IrPropertyReferenceImpl(
|
||||
startOffset, endOffset, type,
|
||||
newSymbol, typeArgumentsCount,
|
||||
field, newGetter, newSetter,
|
||||
origin
|
||||
).also {
|
||||
it.attributeOwnerId = attributeOwnerId
|
||||
copyTypeArgumentsFrom(nExpression)
|
||||
it.dispatchReceiver = dispatchReceiver
|
||||
it.extensionReceiver = extensionReceiver
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
|
||||
val nExpression = super.visitFunctionReference(expression) as IrFunctionReference
|
||||
if (!nExpression.symbol.owner.isExpect) return nExpression
|
||||
|
||||
val newCallee = symbolTable.referenceSimpleFunction(
|
||||
nExpression.symbol.descriptor.findActualForExpect() as? FunctionDescriptor ?: return nExpression
|
||||
)
|
||||
with(nExpression) {
|
||||
return IrFunctionReferenceImpl(
|
||||
startOffset, endOffset, type, newCallee, typeArgumentsCount, valueArgumentsCount, reflectionTarget, origin
|
||||
).also {
|
||||
it.attributeOwnerId = attributeOwnerId
|
||||
it.copyTypeArgumentsFrom(nExpression)
|
||||
it.dispatchReceiver = dispatchReceiver
|
||||
it.extensionReceiver = extensionReceiver
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitClassReference(expression: IrClassReference): IrExpression {
|
||||
val nExpression = super.visitClassReference(expression) as IrClassReference
|
||||
val oldSymbol = nExpression.symbol as? IrClassSymbol ?: return nExpression
|
||||
if (!oldSymbol.owner.isExpect) return nExpression
|
||||
|
||||
val newSymbol = symbolTable.referenceClass(
|
||||
oldSymbol.descriptor.findActualForExpect() as? ClassDescriptor ?: return nExpression
|
||||
)
|
||||
with(nExpression) {
|
||||
return IrClassReferenceImpl(startOffset, endOffset, type, newSymbol, classType)
|
||||
}
|
||||
}
|
||||
|
||||
fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
|
||||
|
||||
if (declaration.isTopLevelDeclaration && shouldRemoveTopLevelDeclaration(declaration)) {
|
||||
return emptyList()
|
||||
}
|
||||
@@ -185,6 +62,38 @@ class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private va
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getActualClass(descriptor: ClassDescriptor): IrClassSymbol? {
|
||||
return symbolTable.referenceClass(
|
||||
descriptor.findActualForExpect() as? ClassDescriptor ?: return null
|
||||
)
|
||||
}
|
||||
|
||||
override fun getActualProperty(descriptor: PropertyDescriptor): ActualPropertyResult? {
|
||||
val newSymbol = symbolTable.referenceProperty(
|
||||
descriptor.findActualForExpect() as? PropertyDescriptor ?: return null
|
||||
)
|
||||
val newGetter = newSymbol.descriptor.getter?.let { symbolTable.referenceSimpleFunction(it) }
|
||||
val newSetter = newSymbol.descriptor.setter?.let { symbolTable.referenceSimpleFunction(it) }
|
||||
return ActualPropertyResult(newSymbol, newGetter, newSetter)
|
||||
}
|
||||
|
||||
override fun getActualConstructor(descriptor: ClassConstructorDescriptor): IrConstructorSymbol? {
|
||||
return symbolTable.referenceConstructor(
|
||||
descriptor.findActualForExpect() as? ClassConstructorDescriptor ?: return null
|
||||
)
|
||||
}
|
||||
|
||||
override fun getActualFunction(descriptor: FunctionDescriptor): IrSimpleFunctionSymbol? {
|
||||
return symbolTable.referenceSimpleFunction(
|
||||
descriptor.findActualForExpect() as? FunctionDescriptor ?: return null
|
||||
)
|
||||
}
|
||||
|
||||
private fun MemberDescriptor.findActualForExpect(): MemberDescriptor? {
|
||||
if (!isExpect) error(this)
|
||||
return findCompatibleActualsForExpected(module).singleOrNull()
|
||||
}
|
||||
|
||||
private fun shouldRemoveTopLevelDeclaration(declaration: IrDeclaration): Boolean {
|
||||
return doRemove && when (declaration) {
|
||||
is IrClass -> declaration.isExpect
|
||||
@@ -257,12 +166,6 @@ class ExpectDeclarationRemover(val symbolTable: ReferenceSymbolTable, private va
|
||||
}
|
||||
}
|
||||
|
||||
private fun MemberDescriptor.findActualForExpect(): MemberDescriptor? {
|
||||
if (!isExpect) error(this)
|
||||
return findCompatibleActualsForExpected(module).singleOrNull()
|
||||
|
||||
}
|
||||
|
||||
private fun MemberDescriptor.findExpectForActual(): MemberDescriptor? {
|
||||
if (!isActual) error(this)
|
||||
return findCompatibleExpectsForActual().singleOrNull()
|
||||
|
||||
+15
-3
@@ -60,7 +60,7 @@ open class JvmIrCodegenFactory(
|
||||
private val externalSymbolTable: SymbolTable? = null,
|
||||
private val jvmGeneratorExtensions: JvmGeneratorExtensionsImpl = JvmGeneratorExtensionsImpl(configuration),
|
||||
private val evaluatorFragmentInfoForPsi2Ir: EvaluatorFragmentInfo? = null,
|
||||
private val shouldStubAndNotLinkUnboundSymbols: Boolean = false,
|
||||
private val stubSettings: StubSettings = StubSettings(),
|
||||
) : CodegenFactory {
|
||||
|
||||
@IDEAPluginsCompatibilityAPI(IDEAPlatforms._221, message = "Please migrate to the other constructor", plugins = "Android Studio")
|
||||
@@ -81,7 +81,15 @@ open class JvmIrCodegenFactory(
|
||||
externalSymbolTable,
|
||||
jvmGeneratorExtensions,
|
||||
evaluatorFragmentInfoForPsi2Ir,
|
||||
shouldStubAndNotLinkUnboundSymbols
|
||||
StubSettings(shouldStubAndNotLinkUnboundSymbols = shouldStubAndNotLinkUnboundSymbols),
|
||||
)
|
||||
|
||||
/**
|
||||
* @param shouldStubOrphanedExpectSymbols See [stubOrphanedExpectSymbols].
|
||||
*/
|
||||
data class StubSettings(
|
||||
val shouldStubAndNotLinkUnboundSymbols: Boolean = false,
|
||||
val shouldStubOrphanedExpectSymbols: Boolean = false,
|
||||
)
|
||||
|
||||
data class JvmIrBackendInput(
|
||||
@@ -208,7 +216,7 @@ open class JvmIrCodegenFactory(
|
||||
}
|
||||
|
||||
|
||||
val irProviders = if (shouldStubAndNotLinkUnboundSymbols) {
|
||||
val irProviders = if (stubSettings.shouldStubAndNotLinkUnboundSymbols) {
|
||||
listOf(stubGenerator)
|
||||
} else {
|
||||
val stubGeneratorForMissingClasses = DeclarationStubGeneratorForNotFoundClasses(stubGenerator)
|
||||
@@ -232,6 +240,10 @@ open class JvmIrCodegenFactory(
|
||||
// We need to compile all files we reference in Klibs
|
||||
irModuleFragment.files.addAll(dependencies.flatMap { it.files })
|
||||
|
||||
if (stubSettings.shouldStubOrphanedExpectSymbols) {
|
||||
irModuleFragment.stubOrphanedExpectSymbols(stubGenerator)
|
||||
}
|
||||
|
||||
if (!input.configuration.getBoolean(JVMConfigurationKeys.DO_NOT_CLEAR_BINDING_CONTEXT)) {
|
||||
val originalBindingContext = input.bindingContext as? CleanableBindingContext
|
||||
?: error("BindingContext should be cleanable in JVM IR to avoid leaking memory: ${input.bindingContext}")
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.jvm
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.ir.ExpectSymbolTransformer
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.util.DeclarationStubGenerator
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.findCompatibleActualsForExpected
|
||||
|
||||
/**
|
||||
* Replaces `expect` symbols for which no `actual` counterpart exists with an `actual` stub in all files of the [IrModuleFragment]. The
|
||||
* implementation keeps track of generated stubs and only generates a single stub for each unique `expect` symbol.
|
||||
*
|
||||
* [stubOrphanedExpectSymbols] is used by the IDE bytecode tool window to allow compiling source files with `expect` declarations for which
|
||||
* the compiled module has no `actual` declaration. (The `actual` declaration would be defined in a module dependent on the compiled
|
||||
* module, but choosing this module is non-trivial due to possibly multiple implementations of the same `expect` symbol. In addition, when
|
||||
* generating bytecode for a single source file, the number of source files to compile should be kept low. Stubbing helps with that.)
|
||||
*/
|
||||
internal fun IrModuleFragment.stubOrphanedExpectSymbols(stubGenerator: DeclarationStubGenerator) {
|
||||
val transformer = StubOrphanedExpectSymbolTransformer(stubGenerator)
|
||||
files.forEach(transformer::visitFile)
|
||||
}
|
||||
|
||||
private class StubOrphanedExpectSymbolTransformer(val stubGenerator: DeclarationStubGenerator) : ExpectSymbolTransformer() {
|
||||
|
||||
private val stubbedClasses = mutableMapOf<ClassDescriptor, IrClassSymbol>()
|
||||
private val stubbedProperties = mutableMapOf<PropertyDescriptor, ActualPropertyResult>()
|
||||
private val stubbedConstructors = mutableMapOf<ClassConstructorDescriptor, IrConstructorSymbol>()
|
||||
private val stubbedFunctions = mutableMapOf<FunctionDescriptor, IrSimpleFunctionSymbol>()
|
||||
|
||||
override fun getActualClass(descriptor: ClassDescriptor): IrClassSymbol? {
|
||||
if (!descriptor.isOrphanedExpect()) return null
|
||||
|
||||
return stubbedClasses.getOrPut(descriptor) {
|
||||
stubGenerator.generateClassStub(FakeActualClassDescriptor(descriptor)).symbol
|
||||
}
|
||||
}
|
||||
|
||||
override fun getActualProperty(descriptor: PropertyDescriptor): ActualPropertyResult? {
|
||||
if (!descriptor.isOrphanedExpect()) return null
|
||||
|
||||
return stubbedProperties.getOrPut(descriptor) {
|
||||
val irProperty =
|
||||
stubGenerator.generatePropertyStub(FakeActualPropertyDescriptor(descriptor)).apply { ensureClassParent(descriptor) }
|
||||
val irGetter = descriptor.getter?.let(::getActualFunction)
|
||||
val irSetter = descriptor.setter?.let(::getActualFunction)
|
||||
ActualPropertyResult(irProperty.symbol, irGetter, irSetter)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getActualConstructor(descriptor: ClassConstructorDescriptor): IrConstructorSymbol? {
|
||||
if (!descriptor.isOrphanedExpect()) return null
|
||||
|
||||
return stubbedConstructors.getOrPut(descriptor) {
|
||||
stubGenerator.generateConstructorStub(FakeActualClassConstructorDescriptor(descriptor)).symbol
|
||||
}
|
||||
}
|
||||
|
||||
override fun getActualFunction(descriptor: FunctionDescriptor): IrSimpleFunctionSymbol? {
|
||||
if (!descriptor.isOrphanedExpect()) return null
|
||||
|
||||
return stubbedFunctions.getOrPut(descriptor) {
|
||||
stubGenerator
|
||||
.generateFunctionStub(FakeActualFunctionDescriptor(descriptor), createPropertyIfNeeded = false)
|
||||
.apply { ensureClassParent(descriptor) }
|
||||
.symbol
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If an `actual` symbol exists, we shouldn't stub the `expect` symbol. This will be performed by
|
||||
* [org.jetbrains.kotlin.backend.common.lower.ExpectDeclarationsRemoveLowering] during lowering.
|
||||
*/
|
||||
private fun MemberDescriptor.isOrphanedExpect(): Boolean = findCompatibleActualsForExpected(module).isEmpty()
|
||||
|
||||
/**
|
||||
* [descriptor] should be the original descriptor, because the copied `actual` descriptor has no source.
|
||||
*/
|
||||
private fun IrDeclaration.ensureClassParent(descriptor: MemberDescriptor) {
|
||||
if (parent !is IrClass) {
|
||||
parent = stubGenerator.generateOrGetFacadeClass(descriptor) ?: return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class FakeActualClassDescriptor(original: ClassDescriptor) : ClassDescriptor by original {
|
||||
override fun isActual(): Boolean = true
|
||||
override fun isExpect(): Boolean = false
|
||||
|
||||
override fun getSource(): SourceElement = SourceElement.NO_SOURCE
|
||||
override fun getOriginal(): ClassDescriptor = this
|
||||
}
|
||||
|
||||
private class FakeActualPropertyDescriptor(original: PropertyDescriptor) : PropertyDescriptor by original {
|
||||
override fun isActual(): Boolean = true
|
||||
override fun isExpect(): Boolean = false
|
||||
|
||||
override fun getSource(): SourceElement = SourceElement.NO_SOURCE
|
||||
override fun getOriginal(): PropertyDescriptor = this
|
||||
}
|
||||
|
||||
private class FakeActualClassConstructorDescriptor(original: ClassConstructorDescriptor) : ClassConstructorDescriptor by original {
|
||||
override fun isActual(): Boolean = true
|
||||
override fun isExpect(): Boolean = false
|
||||
|
||||
override fun getSource(): SourceElement = SourceElement.NO_SOURCE
|
||||
override fun getOriginal(): ClassConstructorDescriptor = this
|
||||
}
|
||||
|
||||
private class FakeActualFunctionDescriptor(original: FunctionDescriptor) : FunctionDescriptor by original {
|
||||
override fun isActual(): Boolean = true
|
||||
override fun isExpect(): Boolean = false
|
||||
|
||||
// `actual` functions are stubbed without providing a body. Hence, they may not be inlined, even if the `expect` function is marked as
|
||||
// `inline`. Given that inlining requires meaningful bodies (assuming the generated bytecode is of interest), it does not suffice to
|
||||
// just supply an empty body stub.
|
||||
override fun isInline(): Boolean = false
|
||||
|
||||
override fun getSource(): SourceElement = SourceElement.NO_SOURCE
|
||||
override fun getOriginal(): FunctionDescriptor = this
|
||||
}
|
||||
Reference in New Issue
Block a user