[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:
Marco Pennekamp
2023-01-13 19:46:52 +01:00
committed by Space Team
parent 8b1e508740
commit c9461a3827
4 changed files with 345 additions and 145 deletions
@@ -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)
}
}
}
@@ -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()