[IR] Partial linkage: report the exact IR element where unlinked symbols are used
This commit is contained in:
+110
-147
@@ -6,6 +6,8 @@
|
|||||||
package org.jetbrains.kotlin.backend.common.serialization.unlinked
|
package org.jetbrains.kotlin.backend.common.serialization.unlinked
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.serialization.unlinked.PartialLinkageSupport.UnlinkedMarkerTypeHandler
|
import org.jetbrains.kotlin.backend.common.serialization.unlinked.PartialLinkageSupport.UnlinkedMarkerTypeHandler
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.unlinked.UnlinkedIrElementRenderer.appendDeclaration
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.unlinked.UnlinkedIrElementRenderer.renderError
|
||||||
import org.jetbrains.kotlin.backend.common.serialization.unlinked.UsedClassifierSymbolStatus.Companion.isUnlinked
|
import org.jetbrains.kotlin.backend.common.serialization.unlinked.UsedClassifierSymbolStatus.Companion.isUnlinked
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.ir.IrBuiltIns
|
import org.jetbrains.kotlin.ir.IrBuiltIns
|
||||||
@@ -22,9 +24,10 @@ import org.jetbrains.kotlin.ir.types.IrSimpleType
|
|||||||
import org.jetbrains.kotlin.ir.types.IrType
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
import org.jetbrains.kotlin.ir.types.IrTypeProjection
|
import org.jetbrains.kotlin.ir.types.IrTypeProjection
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
|
import org.jetbrains.kotlin.ir.util.IrMessageLogger.Location
|
||||||
|
import org.jetbrains.kotlin.ir.util.IrMessageLogger.Severity
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
import org.jetbrains.kotlin.name.Name
|
|
||||||
|
|
||||||
internal class UnlinkedDeclarationsProcessor(
|
internal class UnlinkedDeclarationsProcessor(
|
||||||
private val builtIns: IrBuiltIns,
|
private val builtIns: IrBuiltIns,
|
||||||
@@ -32,14 +35,9 @@ internal class UnlinkedDeclarationsProcessor(
|
|||||||
private val unlinkedMarkerTypeHandler: UnlinkedMarkerTypeHandler,
|
private val unlinkedMarkerTypeHandler: UnlinkedMarkerTypeHandler,
|
||||||
private val messageLogger: IrMessageLogger
|
private val messageLogger: IrMessageLogger
|
||||||
) {
|
) {
|
||||||
companion object {
|
|
||||||
private val ERROR_ORIGIN = object : IrStatementOriginImpl("LINKAGE ERROR") {}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addLinkageErrorIntoUnlinkedClasses() {
|
fun addLinkageErrorIntoUnlinkedClasses() {
|
||||||
usedClassifierSymbols.forEachClassSymbolToPatch { unlinkedSymbol ->
|
usedClassifierSymbols.forEachClassSymbolToPatch { unlinkedSymbol ->
|
||||||
val clazz = unlinkedSymbol.owner
|
val clazz = unlinkedSymbol.owner
|
||||||
clazz.reportUnlinkedSymbolsWarning("Class", clazz.fqNameForIrSerialization)
|
|
||||||
|
|
||||||
val anonInitializer = clazz.declarations.firstNotNullOfOrNull { it as? IrAnonymousInitializer }
|
val anonInitializer = clazz.declarations.firstNotNullOfOrNull { it as? IrAnonymousInitializer }
|
||||||
?: builtIns.irFactory.createAnonymousInitializer(
|
?: builtIns.irFactory.createAnonymousInitializer(
|
||||||
@@ -53,38 +51,12 @@ internal class UnlinkedDeclarationsProcessor(
|
|||||||
clazz.declarations.add(it)
|
clazz.declarations.add(it)
|
||||||
}
|
}
|
||||||
anonInitializer.body.statements.clear()
|
anonInitializer.body.statements.clear()
|
||||||
anonInitializer.body.statements.add(clazz.throwLinkageError(clazz.symbol))
|
anonInitializer.body.statements += clazz.throwLinkageError() // TODO: which exactly classifiers are unlinked?
|
||||||
|
|
||||||
clazz.superTypes = clazz.superTypes.filter { !it.isUnlinked() }
|
clazz.superTypes = clazz.superTypes.filter { !it.isUnlinked() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrDeclaration.location(): IrMessageLogger.Location? = fileOrNull?.location(startOffset)
|
|
||||||
|
|
||||||
private fun IrFile.location(offset: Int): IrMessageLogger.Location {
|
|
||||||
val module = module.name
|
|
||||||
val fileEntry = fileEntry
|
|
||||||
val fileName = fileEntry.name
|
|
||||||
val lineNumber = fileEntry.getLineNumber(offset) + 1 // since humans count from 1, not 0
|
|
||||||
val columnNumber = fileEntry.getColumnNumber(offset) + 1
|
|
||||||
// unsure whether should module name be added here
|
|
||||||
return IrMessageLogger.Location("$module @ $fileName", lineNumber, columnNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun IrDeclaration.reportUnlinkedSymbolsWarning(kind: String, fqn: FqName, localName: Name? = null) =
|
|
||||||
reportWarning(
|
|
||||||
buildString {
|
|
||||||
append(kind).append(" declaration ")
|
|
||||||
if (localName != null) append(localName.asString()).append(" at ")
|
|
||||||
append(fqn.asString()).append(" contains unlinked symbols")
|
|
||||||
},
|
|
||||||
location()
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun reportWarning(message: String, location: IrMessageLogger.Location?) {
|
|
||||||
messageLogger.report(IrMessageLogger.Severity.WARNING, message, location)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun signatureTransformer(): IrElementTransformerVoid = SignatureTransformer()
|
fun signatureTransformer(): IrElementTransformerVoid = SignatureTransformer()
|
||||||
|
|
||||||
private inner class SignatureTransformer : IrElementTransformerVoid() {
|
private inner class SignatureTransformer : IrElementTransformerVoid() {
|
||||||
@@ -117,10 +89,8 @@ internal class UnlinkedDeclarationsProcessor(
|
|||||||
|
|
||||||
override fun visitField(declaration: IrField): IrStatement {
|
override fun visitField(declaration: IrField): IrStatement {
|
||||||
if (declaration.type.isUnlinked()) {
|
if (declaration.type.isUnlinked()) {
|
||||||
val fqn = declaration.correspondingPropertySymbol?.owner?.fqNameWhenAvailable ?: declaration.fqNameForIrSerialization
|
// TODO: it would be more precise to use the set of unlinked symbols than a collection of unlinked types here.
|
||||||
val kind = if (declaration.correspondingPropertySymbol != null) "Property" else "Field"
|
declaration.logLinkageError(listOfNotNull((declaration.type as? IrSimpleType)?.classifier))
|
||||||
declaration.reportUnlinkedSymbolsWarning(kind, fqn)
|
|
||||||
|
|
||||||
declaration.type = unlinkedMarkerTypeHandler.unlinkedMarkerType
|
declaration.type = unlinkedMarkerTypeHandler.unlinkedMarkerType
|
||||||
declaration.initializer = null
|
declaration.initializer = null
|
||||||
} else {
|
} else {
|
||||||
@@ -131,11 +101,8 @@ internal class UnlinkedDeclarationsProcessor(
|
|||||||
|
|
||||||
override fun visitVariable(declaration: IrVariable): IrStatement {
|
override fun visitVariable(declaration: IrVariable): IrStatement {
|
||||||
if (declaration.type.isUnlinked()) {
|
if (declaration.type.isUnlinked()) {
|
||||||
val fqn = declaration.parent.fqNameForIrSerialization
|
// TODO: it would be more precise to use the set of unlinked symbols than a collection of unlinked types here.
|
||||||
val localName = declaration.name
|
declaration.logLinkageError(listOfNotNull((declaration.type as? IrSimpleType)?.classifier))
|
||||||
val kind = if (declaration.isVar) "var" else "val"
|
|
||||||
declaration.reportUnlinkedSymbolsWarning(kind, fqn, localName)
|
|
||||||
|
|
||||||
declaration.type = unlinkedMarkerTypeHandler.unlinkedMarkerType
|
declaration.type = unlinkedMarkerTypeHandler.unlinkedMarkerType
|
||||||
declaration.initializer = null
|
declaration.initializer = null
|
||||||
} else {
|
} else {
|
||||||
@@ -199,37 +166,23 @@ internal class UnlinkedDeclarationsProcessor(
|
|||||||
if (!isImplementedFakeOverride && removedUnlinkedTypes.isEmpty()) {
|
if (!isImplementedFakeOverride && removedUnlinkedTypes.isEmpty()) {
|
||||||
transformChildrenVoid()
|
transformChildrenVoid()
|
||||||
} else {
|
} else {
|
||||||
val functionKind = when {
|
|
||||||
this@transformBodyIfNecessary is IrSimpleFunction && correspondingPropertySymbol != null -> "property accessor"
|
|
||||||
this@transformBodyIfNecessary is IrConstructor -> "constructor"
|
|
||||||
else -> "function"
|
|
||||||
}
|
|
||||||
|
|
||||||
val errorMessages = listOfNotNull(
|
val errorMessages = listOfNotNull(
|
||||||
if (isImplementedFakeOverride)
|
if (isImplementedFakeOverride)
|
||||||
buildString {
|
buildString {
|
||||||
append("Abstract ").append(functionKind).append(" ")
|
append("Abstract ").appendDeclaration(this@transformBodyIfNecessary)
|
||||||
append(fqNameForIrSerialization.asString())
|
append(" is not implemented in non-abstract ").appendDeclaration(parentAsClass)
|
||||||
append(" is not implemented in non-abstract class ")
|
|
||||||
append(parent.fqNameForIrSerialization.asString())
|
|
||||||
}
|
}
|
||||||
else null,
|
else null,
|
||||||
if (removedUnlinkedTypes.isNotEmpty())
|
if (removedUnlinkedTypes.isNotEmpty()) {
|
||||||
buildString {
|
// TODO: it would be more precise to use the set of unlinked symbols than a collection of unlinked types here.
|
||||||
append("The signature of ").append(functionKind).append(" ")
|
composeUnlinkedSymbolsErrorMessage(removedUnlinkedTypes.mapNotNull { (it as? IrSimpleType)?.classifier })
|
||||||
append(fqNameForIrSerialization.asString())
|
} else null
|
||||||
append(" contains unlinked symbols: ")
|
|
||||||
removedUnlinkedTypes.joinTo(this) { it.render() }
|
|
||||||
}
|
|
||||||
else null
|
|
||||||
)
|
)
|
||||||
|
|
||||||
errorMessages.forEach { reportWarning(it, location()) }
|
|
||||||
|
|
||||||
body?.let { body ->
|
body?.let { body ->
|
||||||
val bb = body as IrBlockBody
|
val bb = body as IrBlockBody
|
||||||
bb.statements.clear()
|
bb.statements.clear()
|
||||||
bb.statements.add(throwLinkageError(unlinkedSymbol = null, errorMessages.joinToString(separator = "; ")))
|
bb.statements += throwLinkageError(errorMessages, location())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +193,7 @@ internal class UnlinkedDeclarationsProcessor(
|
|||||||
private fun IrSymbol.isUnlinked(): Boolean {
|
private fun IrSymbol.isUnlinked(): Boolean {
|
||||||
if (!isBound) return true
|
if (!isBound) return true
|
||||||
when (this) {
|
when (this) {
|
||||||
is IrClassifierSymbol -> this.isUnlinked()
|
is IrClassifierSymbol -> isUnlinked()
|
||||||
is IrPropertySymbol -> {
|
is IrPropertySymbol -> {
|
||||||
owner.getter?.let { if (it.symbol.isUnlinked()) return true }
|
owner.getter?.let { if (it.symbol.isUnlinked()) return true }
|
||||||
owner.setter?.let { if (it.symbol.isUnlinked()) return true }
|
owner.setter?.let { if (it.symbol.isUnlinked()) return true }
|
||||||
@@ -280,14 +233,36 @@ internal class UnlinkedDeclarationsProcessor(
|
|||||||
return with(unlinkedMarkerTypeHandler) { isUnlinkedMarkerType() }
|
return with(unlinkedMarkerTypeHandler) { isUnlinkedMarkerType() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrElement.throwLinkageError(unlinkedSymbol: IrSymbol?, message: String = "Unlinked IR symbol"): IrCall {
|
private fun IrElement.composeUnlinkedSymbolsErrorMessage(unlinkedSymbols: Collection<IrSymbol>) =
|
||||||
val messageLiteral = message + unlinkedSymbol?.signature?.render()?.let { " $it" }.orEmpty()
|
renderError(this@composeUnlinkedSymbolsErrorMessage, unlinkedSymbols)
|
||||||
|
|
||||||
|
private fun IrDeclaration.throwLinkageError(unlinkedSymbols: Collection<IrSymbol> = emptyList()): IrCall =
|
||||||
|
throwLinkageError(
|
||||||
|
messages = listOf(composeUnlinkedSymbolsErrorMessage(unlinkedSymbols)),
|
||||||
|
location = location()
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun IrElement.throwLinkageError(messages: List<String>, location: Location?): IrCall {
|
||||||
|
check(messages.isNotEmpty())
|
||||||
|
|
||||||
|
messages.forEach { logLinkageError(it, location) }
|
||||||
|
|
||||||
val irCall = IrCallImpl(startOffset, endOffset, builtIns.nothingType, builtIns.linkageErrorSymbol, 0, 1, ERROR_ORIGIN)
|
val irCall = IrCallImpl(startOffset, endOffset, builtIns.nothingType, builtIns.linkageErrorSymbol, 0, 1, ERROR_ORIGIN)
|
||||||
irCall.putValueArgument(0, IrConstImpl.string(startOffset, endOffset, builtIns.stringType, messageLiteral))
|
irCall.putValueArgument(0, IrConstImpl.string(startOffset, endOffset, builtIns.stringType, messages.joinToString("\n")))
|
||||||
return irCall
|
return irCall
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun IrDeclaration.logLinkageError(unlinkedSymbols: Collection<IrSymbol>) {
|
||||||
|
logLinkageError(
|
||||||
|
composeUnlinkedSymbolsErrorMessage(unlinkedSymbols),
|
||||||
|
location()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logLinkageError(message: String, location: Location?) {
|
||||||
|
messageLogger.report(Severity.WARNING, message, location) // It's OK. We log it as a warning.
|
||||||
|
}
|
||||||
|
|
||||||
fun usageTransformer(): IrElementTransformerVoid = UsageTransformer()
|
fun usageTransformer(): IrElementTransformerVoid = UsageTransformer()
|
||||||
|
|
||||||
private inner class UsageTransformer : IrElementTransformerVoid() {
|
private inner class UsageTransformer : IrElementTransformerVoid() {
|
||||||
@@ -302,118 +277,106 @@ internal class UnlinkedDeclarationsProcessor(
|
|||||||
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
|
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
|
||||||
expression.transformChildrenVoid()
|
expression.transformChildrenVoid()
|
||||||
|
|
||||||
val checkType = expression.typeOperandClassifier
|
val classifierSymbol = expression.typeOperandClassifier
|
||||||
|
return if (classifierSymbol.isUnlinked())
|
||||||
if (!checkType.isUnlinked()) return expression
|
IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType).apply {
|
||||||
|
statements += expression.argument
|
||||||
reportWarning(
|
statements += expression.throwLinkageError(classifierSymbol)
|
||||||
"TypeOperator contains unlinked symbol ${checkType.signature?.render() ?: ""}",
|
}
|
||||||
currentFile?.location(expression.startOffset)
|
else
|
||||||
)
|
expression
|
||||||
|
|
||||||
val composite = IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType)
|
|
||||||
|
|
||||||
composite.statements.add(expression.argument)
|
|
||||||
composite.statements.add(expression.throwLinkageError(expression.typeOperandClassifier))
|
|
||||||
|
|
||||||
return composite
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitExpression(expression: IrExpression): IrExpression {
|
override fun visitExpression(expression: IrExpression): IrExpression {
|
||||||
if (expression.type.isUnlinked() || expression.type.isUnlinkedMarkerType()) {
|
return if (expression.type.isUnlinked() || expression.type.isUnlinkedMarkerType())
|
||||||
reportWarning("Expression type contains unlinked symbol", currentFile?.location(expression.startOffset))
|
expression.throwLinkageError() // TODO: which exactly classifiers are unlinked?
|
||||||
return expression.throwLinkageError(unlinkedSymbol = null, "Unlinked type of IR expression")
|
else
|
||||||
}
|
super.visitExpression(expression)
|
||||||
return super.visitExpression(expression)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitMemberAccess(expression: IrMemberAccessExpression<*>): IrExpression {
|
override fun visitMemberAccess(expression: IrMemberAccessExpression<*>): IrExpression {
|
||||||
expression.transformChildrenVoid()
|
expression.transformChildrenVoid()
|
||||||
val symbol = expression.symbol
|
|
||||||
|
|
||||||
if (!symbol.isUnlinked() && !expression.type.isUnlinked()) return expression
|
return if (!expression.symbol.isUnlinked() && !expression.type.isUnlinked())
|
||||||
|
expression
|
||||||
|
else
|
||||||
|
IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType, expression.origin).apply {
|
||||||
|
statements.addIfNotNull(expression.dispatchReceiver)
|
||||||
|
statements.addIfNotNull(expression.extensionReceiver)
|
||||||
|
|
||||||
reportWarning(
|
for (i in 0 until expression.valueArgumentsCount) {
|
||||||
"Accessing declaration with unlinked symbol ${symbol.signature?.render() ?: ""}",
|
statements.addIfNotNull(expression.getValueArgument(i))
|
||||||
currentFile?.location(expression.startOffset)
|
}
|
||||||
)
|
|
||||||
|
|
||||||
val composite = IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType, expression.origin)
|
statements += expression.throwLinkageError() // TODO: which exactly classifiers are unlinked?
|
||||||
|
}
|
||||||
expression.dispatchReceiver?.let { composite.statements.add(it) }
|
|
||||||
expression.extensionReceiver?.let { composite.statements.add(it) }
|
|
||||||
|
|
||||||
for (i in 0 until expression.valueArgumentsCount) {
|
|
||||||
val arg = expression.getValueArgument(i)
|
|
||||||
arg?.let { composite.statements.add(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
composite.statements.add(
|
|
||||||
if (!symbol.isBound)
|
|
||||||
expression.throwLinkageError(symbol)
|
|
||||||
else
|
|
||||||
expression.throwLinkageError(symbol, "Unlinked type in signature of IR symbol")
|
|
||||||
)
|
|
||||||
|
|
||||||
return composite
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitFieldAccess(expression: IrFieldAccessExpression): IrExpression {
|
override fun visitFieldAccess(expression: IrFieldAccessExpression): IrExpression {
|
||||||
expression.transformChildrenVoid()
|
expression.transformChildrenVoid()
|
||||||
val symbol = expression.symbol
|
|
||||||
if (!symbol.isUnlinked()) {
|
|
||||||
return expression
|
|
||||||
}
|
|
||||||
|
|
||||||
reportWarning(
|
return if (!expression.symbol.isUnlinked())
|
||||||
"Accessing field contains unlinked symbol ${symbol.signature?.render()}",
|
expression
|
||||||
currentFile?.location(expression.startOffset)
|
else
|
||||||
)
|
IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType, expression.origin).apply {
|
||||||
|
statements.addIfNotNull(expression.receiver)
|
||||||
val composite = IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType, expression.origin)
|
if (expression is IrSetField)
|
||||||
expression.receiver?.let { composite.statements.add(it) }
|
statements += expression.value
|
||||||
if (expression is IrSetField) {
|
statements += expression.throwLinkageError(expression.symbol)
|
||||||
composite.statements.add(expression.value)
|
}
|
||||||
}
|
|
||||||
composite.statements.add(
|
|
||||||
if (!symbol.isBound)
|
|
||||||
expression.throwLinkageError(symbol)
|
|
||||||
else
|
|
||||||
expression.throwLinkageError(symbol, "Field type is unlinked in IR symbol")
|
|
||||||
)
|
|
||||||
return composite
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitClassReference(expression: IrClassReference): IrExpression {
|
override fun visitClassReference(expression: IrClassReference): IrExpression {
|
||||||
if (expression.symbol.isUnlinked()) {
|
return if (expression.symbol.isUnlinked())
|
||||||
reportWarning(
|
expression.throwLinkageError(expression.symbol)
|
||||||
"Accessing class contains unlinked symbol ${expression.symbol.signature?.render()}",
|
else
|
||||||
currentFile?.location(expression.startOffset)
|
expression
|
||||||
)
|
|
||||||
return expression.throwLinkageError(expression.symbol)
|
|
||||||
}
|
|
||||||
return expression
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitClass(declaration: IrClass): IrStatement {
|
override fun visitClass(declaration: IrClass): IrStatement {
|
||||||
declaration.transformChildrenVoid()
|
declaration.transformChildrenVoid()
|
||||||
|
|
||||||
fun <S : IrSymbol> IrOverridableDeclaration<S>.filterOverriddens() {
|
fun <S : IrSymbol> IrOverridableDeclaration<S>.filterOverriddenSymbols() {
|
||||||
overriddenSymbols = overriddenSymbols.filter { it.isBound }
|
overriddenSymbols = overriddenSymbols.filter { it.isBound }
|
||||||
}
|
}
|
||||||
|
|
||||||
for (member in declaration.declarations) {
|
for (member in declaration.declarations) {
|
||||||
when (member) {
|
when (member) {
|
||||||
is IrSimpleFunction -> member.filterOverriddens()
|
is IrSimpleFunction -> member.filterOverriddenSymbols()
|
||||||
is IrProperty -> {
|
is IrProperty -> {
|
||||||
member.filterOverriddens()
|
member.filterOverriddenSymbols()
|
||||||
member.getter?.filterOverriddens()
|
member.getter?.filterOverriddenSymbols()
|
||||||
member.setter?.filterOverriddens()
|
member.setter?.filterOverriddenSymbols()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return declaration
|
return declaration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.throwLinkageError(unlinkedSymbol: IrSymbol? = null): IrCall =
|
||||||
|
throwLinkageError(
|
||||||
|
messages = listOf(composeUnlinkedSymbolsErrorMessage(listOfNotNull(unlinkedSymbol))),
|
||||||
|
location = locationIn(currentFile)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
companion object {
|
||||||
|
private val ERROR_ORIGIN = object : IrStatementOriginImpl("LINKAGE ERROR") {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrDeclaration.location(): Location? = locationIn(fileOrNull)
|
||||||
|
|
||||||
|
private fun IrElement.locationIn(currentFile: IrFile?): Location? {
|
||||||
|
if (currentFile == null) return null
|
||||||
|
|
||||||
|
val moduleName = currentFile.module.name
|
||||||
|
val fileEntry = currentFile.fileEntry
|
||||||
|
val fileName = fileEntry.name
|
||||||
|
val lineNumber = fileEntry.getLineNumber(startOffset) + 1 // since humans count from 1, not 0
|
||||||
|
val columnNumber = fileEntry.getColumnNumber(startOffset) + 1
|
||||||
|
|
||||||
|
// unsure whether should module name be added here
|
||||||
|
return Location("$moduleName @ $fileName", lineNumber, columnNumber)
|
||||||
|
}
|
||||||
|
|||||||
+221
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.serialization.unlinked
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.unlinked.DeclarationKind.*
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.unlinked.ExpressionKind.*
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.*
|
||||||
|
import org.jetbrains.kotlin.ir.util.IdSignature
|
||||||
|
import org.jetbrains.kotlin.ir.util.IdSignature.*
|
||||||
|
import org.jetbrains.kotlin.ir.util.isAnonymousObject
|
||||||
|
import org.jetbrains.kotlin.ir.util.nameForIrSerialization
|
||||||
|
|
||||||
|
// TODO: Consider getting rid of this class when new self-descriptive signatures are implemented.
|
||||||
|
internal object UnlinkedIrElementRenderer {
|
||||||
|
fun StringBuilder.appendDeclaration(declaration: IrDeclaration): StringBuilder = appendDeclaration(
|
||||||
|
declarationKind = declaration.declarationKind,
|
||||||
|
declarationName = declaration.symbol.guessName()
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun StringBuilder.appendDeclaration(declarationKind: DeclarationKind, declarationName: String?): StringBuilder {
|
||||||
|
append(declarationKind)
|
||||||
|
|
||||||
|
if (declarationKind != ANONYMOUS_OBJECT) {
|
||||||
|
// This is a declaration NOT under a property.
|
||||||
|
appendWithWhitespaceBefore(declarationName ?: UNKNOWN_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun renderError(element: IrElement, unlinkedSymbols: Collection<IrSymbol>): String = buildString {
|
||||||
|
when (element) {
|
||||||
|
is IrDeclaration -> appendDeclaration(element)
|
||||||
|
is IrExpression -> {
|
||||||
|
val (expressionKind, referencedDeclarationKind) = element.expression
|
||||||
|
appendWithWhitespaceAfter(expressionKind.displayName)
|
||||||
|
|
||||||
|
if (referencedDeclarationKind != null) {
|
||||||
|
val referencedDeclarationSymbol = when (element) {
|
||||||
|
is IrDeclarationReference -> element.symbol
|
||||||
|
is IrInstanceInitializerCall -> element.classSymbol
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
val referencedDeclaration = referencedDeclarationSymbol?.boundOwnerDeclarationOrNull
|
||||||
|
if (referencedDeclaration == null) {
|
||||||
|
// If it's impossible to obtain referenced declaration because the symbol of this declaration is unbound,
|
||||||
|
// but the declaration itself is supposed to be, then do best effort and try to output the short name of
|
||||||
|
// the declaration at least.
|
||||||
|
appendDeclaration(referencedDeclarationKind, referencedDeclarationSymbol?.guessName())
|
||||||
|
} else {
|
||||||
|
appendDeclaration(referencedDeclaration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append(" can not be ").append(expressionKind.verb3rdForm).append(" because it")
|
||||||
|
}
|
||||||
|
else -> error("Unexpected type of IR element: ${this::class.java}, $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
append(" uses unlinked symbols")
|
||||||
|
if (unlinkedSymbols.isNotEmpty()) {
|
||||||
|
unlinkedSymbols.joinTo(this, prefix = ": ") { it.anySignature?.render() ?: UNKNOWN_SYMBOL }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class DeclarationKind(private val displayName: String) {
|
||||||
|
CLASS("class"),
|
||||||
|
INTERFACE("interface"),
|
||||||
|
ENUM_CLASS("enum class"),
|
||||||
|
ENUM_ENTRY("enum entry"),
|
||||||
|
ANNOTATION_CLASS("annotation class"),
|
||||||
|
OBJECT("object"),
|
||||||
|
ANONYMOUS_OBJECT("anonymous object"),
|
||||||
|
COMPANION_OBJECT("companion object"),
|
||||||
|
MUTABLE_VARIABLE("var"),
|
||||||
|
IMMUTABLE_VARIABLE("val"),
|
||||||
|
VALUE_PARAMETER("value parameter"),
|
||||||
|
FIELD("field"),
|
||||||
|
FIELD_OF_PROPERTY("backing field of property"),
|
||||||
|
PROPERTY("property"),
|
||||||
|
PROPERTY_ACCESSOR("property accessor"),
|
||||||
|
FUNCTION("function"),
|
||||||
|
CONSTRUCTOR("constructor"),
|
||||||
|
OTHER_DECLARATION("declaration");
|
||||||
|
|
||||||
|
override fun toString() = displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
private val IrDeclaration.declarationKind: DeclarationKind
|
||||||
|
get() = when (this) {
|
||||||
|
is IrClass -> when (kind) {
|
||||||
|
ClassKind.CLASS -> if (isAnonymousObject) ANONYMOUS_OBJECT else CLASS
|
||||||
|
ClassKind.INTERFACE -> INTERFACE
|
||||||
|
ClassKind.ENUM_CLASS -> ENUM_CLASS
|
||||||
|
ClassKind.ENUM_ENTRY -> ENUM_ENTRY
|
||||||
|
ClassKind.ANNOTATION_CLASS -> ANNOTATION_CLASS
|
||||||
|
ClassKind.OBJECT -> if (isCompanion) COMPANION_OBJECT else OBJECT
|
||||||
|
}
|
||||||
|
is IrVariable -> if (isVar) MUTABLE_VARIABLE else IMMUTABLE_VARIABLE
|
||||||
|
is IrValueParameter -> VALUE_PARAMETER
|
||||||
|
is IrField -> if (correspondingPropertySymbol != null) FIELD_OF_PROPERTY else FIELD
|
||||||
|
is IrProperty -> PROPERTY
|
||||||
|
is IrSimpleFunction -> if (correspondingPropertySymbol != null) PROPERTY_ACCESSOR else FUNCTION
|
||||||
|
is IrConstructor -> CONSTRUCTOR
|
||||||
|
else -> OTHER_DECLARATION
|
||||||
|
}
|
||||||
|
|
||||||
|
private val IrFunctionSymbol.functionDeclarationKind: DeclarationKind
|
||||||
|
get() = when (this) {
|
||||||
|
is IrConstructorSymbol -> CONSTRUCTOR
|
||||||
|
is IrSimpleFunctionSymbol -> boundOwnerDeclarationOrNull?.declarationKind
|
||||||
|
?: if (anySignature is AccessorSignature) PROPERTY_ACCESSOR else FUNCTION
|
||||||
|
else -> OTHER_DECLARATION // Something unexpected.
|
||||||
|
}
|
||||||
|
|
||||||
|
private val IrFieldSymbol.fieldDeclarationKind: DeclarationKind
|
||||||
|
get() = boundOwnerDeclarationOrNull?.declarationKind
|
||||||
|
?: FIELD // Fallback to simple field as it's impossible to make a guess by signature.
|
||||||
|
|
||||||
|
private val IrValueSymbol.valueDeclarationKind: DeclarationKind
|
||||||
|
get() = when (this) {
|
||||||
|
is IrValueParameterSymbol -> VALUE_PARAMETER
|
||||||
|
is IrVariableSymbol -> boundOwnerDeclarationOrNull?.declarationKind
|
||||||
|
?: IMMUTABLE_VARIABLE // Fallback to immutable variable as it's impossible to make a guess by signature.
|
||||||
|
else -> OTHER_DECLARATION // Something unexpected.
|
||||||
|
}
|
||||||
|
|
||||||
|
private val IrSymbol.classDeclarationKind: DeclarationKind
|
||||||
|
get() = when (this) {
|
||||||
|
is IrClassSymbol -> boundOwnerDeclarationOrNull?.declarationKind
|
||||||
|
?: CLASS // Fallback to class as it's impossible to make a guess by signature.
|
||||||
|
is IrEnumEntrySymbol -> ENUM_ENTRY
|
||||||
|
else -> OTHER_DECLARATION // Something unexpected.
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Expression(val kind: ExpressionKind, val referencedDeclarationKind: DeclarationKind?)
|
||||||
|
|
||||||
|
private enum class ExpressionKind(val displayName: String?, val verb3rdForm: String) {
|
||||||
|
REFERENCE("reference to", "evaluated"),
|
||||||
|
CALLING(null, "called"),
|
||||||
|
CALLING_INSTANCE_INITIALIZER("instance initializer of", "called"),
|
||||||
|
READING(null, "read"),
|
||||||
|
WRITING(null, "written"),
|
||||||
|
GETTING_INSTANCE(null, "gotten"),
|
||||||
|
OTHER_EXPRESSION("expression", "evaluated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// More can be added for verbosity in the future.
|
||||||
|
private val IrExpression.expression: Expression
|
||||||
|
get() = when (this) {
|
||||||
|
is IrDeclarationReference -> when (this) {
|
||||||
|
is IrFunctionReference -> Expression(REFERENCE, symbol.functionDeclarationKind)
|
||||||
|
is IrPropertyReference,
|
||||||
|
is IrLocalDelegatedPropertyReference -> Expression(REFERENCE, PROPERTY)
|
||||||
|
is IrCall -> Expression(CALLING, symbol.functionDeclarationKind)
|
||||||
|
is IrConstructorCall,
|
||||||
|
is IrEnumConstructorCall,
|
||||||
|
is IrDelegatingConstructorCall -> Expression(CALLING, CONSTRUCTOR)
|
||||||
|
is IrClassReference -> Expression(REFERENCE, symbol.classDeclarationKind)
|
||||||
|
is IrGetField -> Expression(READING, symbol.fieldDeclarationKind)
|
||||||
|
is IrSetField -> Expression(WRITING, symbol.fieldDeclarationKind)
|
||||||
|
is IrGetValue -> Expression(READING, symbol.valueDeclarationKind)
|
||||||
|
is IrSetValue -> Expression(WRITING, symbol.valueDeclarationKind)
|
||||||
|
is IrGetSingletonValue -> Expression(GETTING_INSTANCE, symbol.classDeclarationKind)
|
||||||
|
else -> Expression(REFERENCE, OTHER_DECLARATION)
|
||||||
|
}
|
||||||
|
is IrInstanceInitializerCall -> Expression(CALLING_INSTANCE_INITIALIZER, classSymbol.classDeclarationKind)
|
||||||
|
else -> Expression(OTHER_EXPRESSION, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val IrSymbol.boundOwnerDeclarationOrNull: IrDeclaration?
|
||||||
|
get() = if (isBound) owner as? IrDeclaration else null
|
||||||
|
|
||||||
|
private fun IrSymbol.guessName(): String? {
|
||||||
|
fun IdSignature.guessNameBySignature(nameSegmentsToPickUp: Int): String? = when (this) {
|
||||||
|
is CommonSignature -> nameSegments.takeLast(nameSegmentsToPickUp).joinToString(".")
|
||||||
|
is CompositeSignature -> inner.guessNameBySignature(nameSegmentsToPickUp)
|
||||||
|
is AccessorSignature -> accessorSignature.guessNameBySignature(nameSegmentsToPickUp)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
return anySignature
|
||||||
|
?.let { effectiveSignature ->
|
||||||
|
val nameSegmentsToPickUp = when {
|
||||||
|
effectiveSignature is AccessorSignature -> 2 // property_name.accessor_name
|
||||||
|
this is IrConstructorSymbol -> 2 // class_name.<init>
|
||||||
|
else -> 1
|
||||||
|
}
|
||||||
|
effectiveSignature.guessNameBySignature(nameSegmentsToPickUp)
|
||||||
|
}
|
||||||
|
?: boundOwnerDeclarationOrNull?.nameForIrSerialization?.asString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val IrSymbol.anySignature: IdSignature?
|
||||||
|
get() = signature ?: privateSignature
|
||||||
|
|
||||||
|
private const val UNKNOWN_SYMBOL = "<unknown symbol>"
|
||||||
|
private const val UNKNOWN_NAME = "<unknown name>"
|
||||||
|
|
||||||
|
private fun StringBuilder.appendWithWhitespaceBefore(text: String): StringBuilder {
|
||||||
|
if (text.isNotEmpty()) appendWhitespaceIfNotEmpty().append(text)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.appendWithWhitespaceAfter(text: String?): StringBuilder {
|
||||||
|
if (!text.isNullOrEmpty()) append(text).append(" ")
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.appendWhitespaceIfNotEmpty(): StringBuilder {
|
||||||
|
if (isNotEmpty()) append(" ")
|
||||||
|
return this
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user