[IR] User-friendly message about unexpected unlinked symbols

^KT-53649
This commit is contained in:
Dmitriy Dolovov
2022-09-08 17:59:40 +02:00
committed by Space
parent 6c4dafc23c
commit e4556ecc0d
27 changed files with 231 additions and 143 deletions
@@ -51,7 +51,7 @@ abstract class KotlinIrLinker(
private lateinit var linkerExtensions: Collection<IrDeserializer.IrLinkerExtension>
protected open val unlinkedDeclarationsSupport: UnlinkedDeclarationsSupport get() = UnlinkedDeclarationsSupport.DISABLED
open val unlinkedDeclarationsSupport: UnlinkedDeclarationsSupport get() = UnlinkedDeclarationsSupport.DISABLED
protected open val userVisibleIrModulesSupport: UserVisibleIrModulesSupport get() = UserVisibleIrModulesSupport.DEFAULT
fun deserializeOrReturnUnboundIrSymbolIfPartialLinkageEnabled(
@@ -75,7 +75,7 @@ abstract class KotlinIrLinker(
if (unlinkedDeclarationsSupport.allowUnboundSymbols)
referenceDeserializedSymbol(symbolTable, null, symbolKind, idSignature)
else
throw SignatureIdNotFoundInModuleWithDependencies(
SignatureIdNotFoundInModuleWithDependencies(
idSignature = idSignature,
problemModuleDeserializer = moduleDeserializer,
allModuleDeserializers = deserializersForModules.values,
@@ -86,7 +86,7 @@ abstract class KotlinIrLinker(
fun resolveModuleDeserializer(module: ModuleDescriptor, idSignature: IdSignature?): IrModuleDeserializer {
return deserializersForModules[module.name.asString()]
?: throw NoDeserializerForModule(module.name, idSignature).raiseIssue(messageLogger)
?: NoDeserializerForModule(module.name, idSignature).raiseIssue(messageLogger)
}
protected abstract fun createModuleDeserializer(
@@ -165,7 +165,7 @@ abstract class KotlinIrLinker(
?: return null
} catch (e: IrSymbolTypeMismatchException) {
if (!unlinkedDeclarationsSupport.allowUnboundSymbols) {
throw SymbolTypeMismatch(e, deserializersForModules.values, userVisibleIrModulesSupport).raiseIssue(messageLogger)
SymbolTypeMismatch(e, deserializersForModules.values, userVisibleIrModulesSupport).raiseIssue(messageLogger)
}
}
}
@@ -7,8 +7,6 @@ package org.jetbrains.kotlin.backend.common.serialization.linkerissues
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
sealed class IrDeserializationException(message: String) : Exception(message) {
override val message: String get() = super.message!!
@@ -22,20 +20,3 @@ class IrSymbolTypeMismatchException(
class IrDisallowedErrorNode(
clazz: Class<out IrAnnotationContainer>
) : IrDeserializationException("${clazz::class.java.simpleName} found but error nodes are not allowed.")
@OptIn(ExperimentalContracts::class)
internal inline fun <reified T : IrSymbol> checkSymbolType(symbol: IrSymbol): T {
contract {
returns() implies (symbol is T)
}
if (symbol !is T) throw IrSymbolTypeMismatchException(T::class.java, symbol) else return symbol
}
@OptIn(ExperimentalContracts::class)
internal inline fun <reified T : IrAnnotationContainer> checkErrorNodesAllowed(errorNodesAllowed: Boolean) {
contract {
returns() implies errorNodesAllowed
}
if (!errorNodesAllowed) throw IrDisallowedErrorNode(T::class.java)
}
@@ -5,11 +5,13 @@
package org.jetbrains.kotlin.backend.common.serialization.linkerissues
import org.jetbrains.kotlin.analyzer.CompilationErrorException
import org.jetbrains.kotlin.backend.common.serialization.IrModuleDeserializer
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.PotentialConflictKind.*
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.PotentialConflictKind.Companion.mostSignificantConflictKind
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.PotentialConflictReason.Companion.mostSignificantConflictReasons
import org.jetbrains.kotlin.ir.linkage.KotlinIrLinkerInternalException
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.util.IdSignature
import org.jetbrains.kotlin.ir.util.IrMessageLogger
import org.jetbrains.kotlin.name.Name
@@ -18,12 +20,50 @@ import org.jetbrains.kotlin.utils.ResolvedDependencyId
import org.jetbrains.kotlin.utils.ResolvedDependencyVersion
import kotlin.Comparator
abstract class KotlinIrLinkerIssue {
/**
* TODO: Currently, [KotlinIrLinkerInternalException] is only needed to show the stacktrace to the user.
* If stacktrace is not needed it's enough to throw [CompilationErrorException] to interrupt the compilation process.
* But, probably, we don't even need to show the stacktrace, so we could probably get rid of [KotlinIrLinkerInternalException] at all.
*/
abstract class KotlinIrLinkerIssue(private val needStacktrace: Boolean) {
protected abstract val errorMessage: String
fun raiseIssue(messageLogger: IrMessageLogger): KotlinIrLinkerInternalException {
fun raiseIssue(messageLogger: IrMessageLogger): Nothing {
messageLogger.report(IrMessageLogger.Severity.ERROR, errorMessage, null)
throw KotlinIrLinkerInternalException()
throw if (needStacktrace) KotlinIrLinkerInternalException() else CompilationErrorException()
}
}
class UnexpectedUnboundIrSymbols(unboundSymbols: Set<IrSymbol>, whenDetected: String) : KotlinIrLinkerIssue(needStacktrace = false) {
override val errorMessage = buildString {
// cause:
append("There ").append(
when (val count = unboundSymbols.size) {
1 -> "is still an unbound symbol"
else -> "are still $count unbound symbols"
}
).append(" ").append(whenDetected).append(":\n")
unboundSymbols.joinTo(this, separator = "\n")
// explanation:
append("\n\nThis could happen if there are two libraries, where one library was compiled against the different version")
append(" of the other library than the one currently used in the project.")
// action items:
append(" Please check that the project configuration is correct and has consistent versions of dependencies.")
if (unboundSymbols.any { looksLikeEnumEntries(it.signature) }) {
append("\n\nAnother possible reason is that some parts of the project are compiled with EnumEntries language feature enabled,")
append(" but other parts or used libraries are compiled with EnumEntries language feature disabled.")
}
}
companion object {
fun looksLikeEnumEntries(signature: IdSignature?) : Boolean = when (signature) {
is IdSignature.AccessorSignature -> looksLikeEnumEntries(signature.propertySignature)
is IdSignature.CompositeSignature -> looksLikeEnumEntries(signature.inner)
is IdSignature.CommonSignature -> signature.shortName == "entries"
else -> false
}
}
}
@@ -32,7 +72,7 @@ class SignatureIdNotFoundInModuleWithDependencies(
private val problemModuleDeserializer: IrModuleDeserializer,
private val allModuleDeserializers: Collection<IrModuleDeserializer>,
private val userVisibleIrModulesSupport: UserVisibleIrModulesSupport
) : KotlinIrLinkerIssue() {
) : KotlinIrLinkerIssue(needStacktrace = false) {
override val errorMessage = try {
computeErrorMessage()
} catch (e: Throwable) {
@@ -89,7 +129,7 @@ class SignatureIdNotFoundInModuleWithDependencies(
}
}
class NoDeserializerForModule(moduleName: Name, idSignature: IdSignature?) : KotlinIrLinkerIssue() {
class NoDeserializerForModule(moduleName: Name, idSignature: IdSignature?) : KotlinIrLinkerIssue(needStacktrace = false) {
override val errorMessage = buildString {
append("Could not load module ${moduleName.asString()}")
if (idSignature != null) append(" in an attempt to find deserializer for symbol ${idSignature.render()}.")
@@ -100,7 +140,7 @@ class SymbolTypeMismatch(
private val cause: IrSymbolTypeMismatchException,
private val allModuleDeserializers: Collection<IrModuleDeserializer>,
private val userVisibleIrModulesSupport: UserVisibleIrModulesSupport
) : KotlinIrLinkerIssue() {
) : KotlinIrLinkerIssue(needStacktrace = true) {
override val errorMessage = try {
computeErrorMessage()
} catch (e: Throwable) {
@@ -0,0 +1,54 @@
/*
* 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.linkerissues
import org.jetbrains.kotlin.backend.common.serialization.KotlinIrLinker
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.util.IrMessageLogger
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.allUnbound
import org.jetbrains.kotlin.ir.util.irMessageLogger
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@OptIn(ExperimentalContracts::class)
internal inline fun <reified T : IrSymbol> checkSymbolType(symbol: IrSymbol): T {
contract {
returns() implies (symbol is T)
}
if (symbol !is T) throw IrSymbolTypeMismatchException(T::class.java, symbol) else return symbol
}
@OptIn(ExperimentalContracts::class)
internal inline fun <reified T : IrAnnotationContainer> checkErrorNodesAllowed(errorNodesAllowed: Boolean) {
contract {
returns() implies errorNodesAllowed
}
if (!errorNodesAllowed) throw IrDisallowedErrorNode(T::class.java)
}
// N.B. Checks for absence of unbound symbols only when unbound symbols are not allowed.
fun KotlinIrLinker.checkNoUnboundSymbols(symbolTable: SymbolTable, whenDetected: String) {
if (!unlinkedDeclarationsSupport.allowUnboundSymbols)
messageLogger.checkNoUnboundSymbols(symbolTable, whenDetected)
}
// N.B. Always checks for absence of unbound symbols. The condition whether this check should be applied is controlled outside.
fun IrMessageLogger.checkNoUnboundSymbols(symbolTable: SymbolTable, whenDetected: String) {
val unboundSymbols = symbolTable.allUnbound
if (unboundSymbols.isNotEmpty())
UnexpectedUnboundIrSymbols(unboundSymbols, whenDetected).raiseIssue(this)
}
// N.B. Always checks for absence of unbound symbols. The condition whether this check should be applied is controlled outside.
fun CompilerConfiguration.checkNoUnboundSymbols(symbolTable: SymbolTable, whenDetected: String) {
val unboundSymbols = symbolTable.allUnbound
if (unboundSymbols.isNotEmpty())
UnexpectedUnboundIrSymbols(unboundSymbols, whenDetected).raiseIssue(irMessageLogger)
}