[PL] Deep rework of the partial linkage

1. Leaving no unbound symbols in the IR tree, KT-54491:
   - All unbound symbols are bound to synthetic stub declarations
   - Improved detection of the root cause for every partially linked classifier
   - Improved error messages

2. Visibility valiation, KT-54469

3. Always check deserialized symbols:
   If the deserialized symbol mismatches the symbol kind at the call site in the deserializer then generate and reference another symbol with the same signature. In case PL is off, just throw IrSymbolTypeMismatchException.

4. Handle class inheritance violation:
   - Detect illegal inheritance (ex: inheriting from a final class)
   - Detect invalid constructor delegation (ex: delegating to another class than the direct superclass)
   - Simplification: Reduce the number of PartialLinkageCase subclasses
   - Reworked error message generation to have shorter and clearer messages

5. Handle class transformations and all known side-effects, examples:
   - nested <-> inner
   - class <-> enum/object
   - adding/removing subclasses of sealed class
   - adding/removing enum entries

6. Check direct instantiation of abstract class.
   Such instantiation could be possible if a class was non-abstract in the previous version of a library.

7. Handle unlinked annotations on declarations.
   Such annotations are removed from the IR. The appropriate compiler error message is produced for every individual case.

8. Handle value argument count mismatch at call sites

9. Handle calling suspend function from non-suspend context.
   This could happen if a suspen function was non-suspend in the previous version of a library.

10. Handle overriding inline callables.
    Only the leaf final callable can be marked with `inline`.

11. Detect illegal non-local returns from noinline/crossinline lambdas.
This commit is contained in:
Dmitriy Dolovov
2022-10-17 18:57:58 +02:00
committed by Space Team
parent 974ee3139c
commit 2a4d880037
69 changed files with 3526 additions and 1397 deletions
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageSupportForLowerings
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFileSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
@@ -82,6 +83,9 @@ interface CommonBackendContext : BackendContext, LoggingContext, ErrorReportingC
*/
val inlineClassesUtils: InlineClassesUtils
get() = DefaultInlineClassesUtils
val partialLinkageSupport: PartialLinkageSupportForLowerings
get() = PartialLinkageSupportForLowerings.DISABLED
}
/**
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.builtins.StandardNames.KOTLIN_REFLECT_FQ_NAME
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
@@ -537,13 +537,16 @@ class LocalDeclarationsLowering(
// NOTE: if running before InitializersLowering, we can instead look for constructors that have
// IrInstanceInitializerCall. However, Native runs these two lowerings in opposite order.
val constructorsCallingSuper = constructors
val constructorsByDelegationKinds: Map<ConstructorDelegationKind, List<LocalClassConstructorContext>> = constructors
.asSequence()
.map { localClassConstructors[it]!! }
.filter { it.declaration.callsSuper(context.irBuiltIns) }
.toList()
.groupBy { it.declaration.delegationKind(context.irBuiltIns) }
assert(constructorsCallingSuper.any()) { "Expected at least one constructor calling super; class: $irClass" }
val constructorsCallingSuper = constructorsByDelegationKinds[ConstructorDelegationKind.CALLS_SUPER].orEmpty()
assert(constructorsCallingSuper.isNotEmpty() || constructorsByDelegationKinds[ConstructorDelegationKind.PARTIAL_LINKAGE_ERROR] != null) {
"Expected at least one constructor calling super; class: $irClass"
}
val usedCaptureFields = createFieldsForCapturedValues(localClassContext)
irClass.declarations += usedCaptureFields
@@ -1049,7 +1052,7 @@ class LocalDeclarationsLowering(
// other restrictions on IR (e.g. after the initializers are moved you can no longer create fields
// with initializers) which makes that hard to implement.
val constructorContext = declaration.constructors.mapNotNull { localClassConstructors[it] }
.singleOrNull { it.declaration.callsSuper(context.irBuiltIns) }
.singleOrNull { it.declaration.delegationKind(context.irBuiltIns) == ConstructorDelegationKind.CALLS_SUPER }
localClasses[declaration] = LocalClassContext(declaration, data.inInlineFunctionScope, constructorContext)
}
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.linkage.partial.isPartialLinkageRuntimeError
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
@@ -168,14 +169,26 @@ open class IrBuildingTransformer(private val context: BackendContext) : IrElemen
}
}
fun IrConstructor.callsSuper(irBuiltIns: IrBuiltIns): Boolean {
enum class ConstructorDelegationKind {
/** Calls another constructor of the same class. */
CALLS_THIS,
/** Calls the constructor of the super class. */
CALLS_SUPER,
/** The actual delegation is not known. The constructor call was replaced by a partial linkage error. */
PARTIAL_LINKAGE_ERROR
}
fun IrConstructor.delegationKind(irBuiltIns: IrBuiltIns): ConstructorDelegationKind {
val constructedClass = parent as IrClass
val superClass = constructedClass.superTypes
.mapNotNull { it as? IrSimpleType }
.firstOrNull { (it.classifier.owner as IrClass).run { kind == ClassKind.CLASS || kind == ClassKind.ANNOTATION_CLASS || kind == ClassKind.ENUM_CLASS } }
?: irBuiltIns.anyType
var callsSuper = false
var numberOfCalls = 0
var numberOfDelegatingCalls = 0
var hasPartialLinkageError = false
acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
@@ -186,7 +199,7 @@ fun IrConstructor.callsSuper(irBuiltIns: IrBuiltIns): Boolean {
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall) {
assert(++numberOfCalls == 1) { "More than one delegating constructor call: ${symbol.owner}" }
numberOfDelegatingCalls++
val delegatingClass = expression.symbol.owner.parent as IrClass
// TODO: figure out why Lazy IR multiplies Declarations for descriptors and fix it
// It happens because of IrBuiltIns whose IrDeclarations are different for runtime and test
@@ -198,11 +211,31 @@ fun IrConstructor.callsSuper(irBuiltIns: IrBuiltIns): Boolean {
" call to super class constructor. But was: $delegatingClass with '${delegatingClass.name}' name"
)
}
override fun visitExpression(expression: IrExpression) {
hasPartialLinkageError = hasPartialLinkageError || expression.isPartialLinkageRuntimeError()
super.visitExpression(expression)
}
})
assert(numberOfCalls == 1) { "Expected exactly one delegating constructor call but none encountered: ${symbol.owner}" }
return callsSuper
val delegationKind: ConstructorDelegationKind? = when (numberOfDelegatingCalls) {
0 -> if (hasPartialLinkageError) ConstructorDelegationKind.PARTIAL_LINKAGE_ERROR else null
1 -> if (callsSuper) ConstructorDelegationKind.CALLS_SUPER else ConstructorDelegationKind.CALLS_THIS
else -> null
}
if (delegationKind != null)
return delegationKind
else
throw AssertionError("Expected exactly one delegating constructor call but $numberOfDelegatingCalls encountered: ${symbol.owner}")
}
@Deprecated(
"Replaced by delegationKind() that is aware of the possible partial linkage side effects",
ReplaceWith("delegationKind(irBuiltIns)")
)
fun IrConstructor.callsSuper(irBuiltIns: IrBuiltIns): Boolean = delegationKind(irBuiltIns) == ConstructorDelegationKind.CALLS_SUPER
fun ParameterDescriptor.copyAsValueParameter(newOwner: CallableDescriptor, index: Int, name: Name = this.name) = when (this) {
is ValueParameterDescriptor -> this.copy(newOwner, name, index)
is ReceiverParameterDescriptor -> ValueParameterDescriptorImpl(
@@ -11,13 +11,13 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.runOnFilePostfix
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageCase.SuspendableFunctionCallWithoutCoroutineContext
import org.jetbrains.kotlin.ir.util.irCall
import org.jetbrains.kotlin.ir.util.isSuspend
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageUtils.File as PLFile
/**
* Add continuation to suspend function calls.
@@ -25,21 +25,23 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
* Additionally materialize continuation for `getContinuation` intrinsic calls.
*/
abstract class AbstractAddContinuationToFunctionCallsLowering : BodyLoweringPass {
abstract val context: CommonBackendContext
protected abstract val context: CommonBackendContext
protected abstract fun IrSimpleFunction.isContinuationItself(): Boolean
override fun lower(irFile: IrFile) {
runOnFilePostfix(irFile, withLocalDeclarations = true)
}
abstract fun IrSimpleFunction.getContinuationParameter() : IrValueParameter
override fun lower(irBody: IrBody, container: IrDeclaration) {
val continuation: IrValueParameter by lazy {
val continuation: IrValueParameter? by lazy {
(container as IrSimpleFunction).getContinuationParameter()
}
val builder by lazy { context.createIrBuilder(container.symbol) }
fun getContinuation() = builder.irGet(continuation)
fun getContinuation(): IrGetValue? = continuation?.let(builder::irGet)
val plFile: PLFile by lazy { PLFile.determineFileFor(container) }
irBody.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitBody(body: IrBody): IrBody {
@@ -52,7 +54,7 @@ abstract class AbstractAddContinuationToFunctionCallsLowering : BodyLoweringPass
if (!expression.isSuspend) {
if (expression.symbol == context.ir.symbols.getContinuation)
return getContinuation()
return getContinuation() ?: expression.throwLinkageError(plFile)
return expression
}
@@ -65,11 +67,40 @@ abstract class AbstractAddContinuationToFunctionCallsLowering : BodyLoweringPass
newReturnType = newFun.returnType,
newSuperQualifierSymbol = expression.superQualifierSymbol
).also {
it.putValueArgument(it.valueArgumentsCount - 1, getContinuation())
it.putValueArgument(it.valueArgumentsCount - 1, getContinuation() ?: return expression.throwLinkageError(plFile))
}
}
})
}
// IMPORTANT: May return null only if partial linkage is turned on.
private fun IrSimpleFunction.getContinuationParameter(): IrValueParameter? {
if (isContinuationItself())
return dispatchReceiverParameter!!
else {
val isLoweredSuspendFunction = origin == IrDeclarationOrigin.LOWERED_SUSPEND_FUNCTION
if (!isLoweredSuspendFunction) {
return if (context.partialLinkageSupport.isEnabled)
null
else
throw IllegalArgumentException("Continuation parameter only exists in lowered suspend functions, but function origin is $origin")
}
val continuation = valueParameters.lastOrNull()
require(continuation != null && continuation.origin == IrDeclarationOrigin.CONTINUATION) {
"Continuation parameter is expected to be the last one"
}
return continuation
}
}
private fun IrCall.throwLinkageError(file: PLFile): IrCall =
context.partialLinkageSupport.throwLinkageError(
SuspendableFunctionCallWithoutCoroutineContext(this),
element = this,
file,
suppressWarningInCompilerOutput = false
)
}