[klib] Fix exception for clashing signatures from different modules

If we encounter a declaration in the current module whose signature
is the same as that of a declaration in another module which we happen
to also reference from the current module, don't report any errors,
just like we don't do it in Kotlin/JVM. This leaves the user in the KLIB
hell situation, but this is intentional, because otherwise a legitimate
change like moving a declaration to another module and marking
the original one as `@Deprecated("", level = DeprecationLevel.HIDDEN)`
would lead to a error, and we don't want that.

Also, don't try to show the diagnostics on a declaration that doesn't
have an IrFile.

^KT-65063 Fixed
This commit is contained in:
Sergej Jaskiewicz
2024-01-18 15:58:50 +01:00
committed by Space Team
parent a5c8ecae0b
commit 6900e20096
8 changed files with 62 additions and 14 deletions
@@ -9,7 +9,8 @@ import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory1
import org.jetbrains.kotlin.ir.IrDiagnosticReporter
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.fileOrNull
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.util.sourceElement
import org.jetbrains.kotlin.utils.SmartSet
@@ -53,17 +54,34 @@ abstract class SignatureClashDetector<Signature : Any, Declaration : IrDeclarati
}
}
protected inline fun <Data : Any, ConflictingDeclaration : IrDeclaration> reportSignatureClashTo(
protected fun <Data : Any, ConflictingDeclaration : IrDeclaration> reportSignatureClashTo(
diagnosticReporter: IrDiagnosticReporter,
diagnosticFactory: KtDiagnosticFactory1<Data>,
declarations: Collection<ConflictingDeclaration>,
data: Data,
reportOnIfSynthetic: (ConflictingDeclaration) -> IrElement,
reportOnIfSynthetic: (ConflictingDeclaration) -> IrElement?,
) {
declarations.mapTo(LinkedHashSet()) { declaration ->
val reportOn = declaration.takeUnless { it.startOffset < 0 } ?: reportOnIfSynthetic(declaration)
diagnosticReporter.at(reportOn.sourceElement(), reportOn, declaration.file)
}.forEach {
val diagnostics = declarations.mapNotNullTo(LinkedHashSet()) { declaration ->
// Declarations that come from other modules may not have a file, so we don't show diagnostics on them.
val containingFile = declaration.fileOrNull ?: return@mapNotNullTo null
val reportOn = declaration.takeUnless { it.startOffset < 0 } ?: reportOnIfSynthetic(declaration) ?: return@mapNotNullTo null
diagnosticReporter.at(reportOn.sourceElement(), reportOn, containingFile)
}
assert(diagnostics.isEmpty() == declarations.isEmpty()) {
buildString {
append("Different declarations with the same signatures were detected, but no diagnostics will be reported ")
append("because none of those declarations have any source location info associated with them. ")
append("This could happen because the declarations came from an external module, or were generated by a compiler plugin. ")
appendLine("The following declarations have conflicting signatures:")
for (declaration in declarations) {
append(" - ")
appendLine(declaration.render())
}
}
}
diagnostics.forEach {
it.report(diagnosticFactory, data)
}
}
@@ -23,7 +23,7 @@ class IdSignatureClashDetector : SignatureClashDetector<IdSignature, IrDeclarati
SerializationErrors.CONFLICTING_KLIB_SIGNATURES_ERROR,
declarations,
ConflictingKlibSignaturesData(signature, declarations),
reportOnIfSynthetic = { it.parentClassOrNull ?: it.file },
reportOnIfSynthetic = { it.parentClassOrNull },
)
}
}
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrSymbolOwner
import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyDeclarationBase
import org.jetbrains.kotlin.ir.util.IdSignature
import org.jetbrains.kotlin.ir.util.KotlinMangler
import org.jetbrains.kotlin.ir.util.render
@@ -25,14 +26,14 @@ abstract class GlobalDeclarationTable(private val mangler: KotlinMangler.IrMangl
protected fun loadKnownBuiltins(builtIns: IrBuiltIns) {
builtIns.knownBuiltins.forEach {
val symbol = (it as IrSymbolOwner).symbol
table[it] = symbol.signature!!.also { id -> clashDetector.trackDeclaration(it, id) }
table[it] = symbol.signature!!.also { id -> clashDetector.trackDeclarationIfInCurrentModule(it, id) }
}
}
open fun computeSignatureByDeclaration(declaration: IrDeclaration, compatibleMode: Boolean): IdSignature {
return table.getOrPut(declaration) {
publicIdSignatureComputer.composePublicIdSignature(declaration, compatibleMode).also {
clashDetector.trackDeclaration(declaration, it)
clashDetector.trackDeclarationIfInCurrentModule(declaration, it)
}
}
}
@@ -40,6 +41,17 @@ abstract class GlobalDeclarationTable(private val mangler: KotlinMangler.IrMangl
fun isExportedDeclaration(declaration: IrDeclaration, compatibleMode: Boolean): Boolean = with(mangler) { declaration.isExported(compatibleMode) }
}
private fun IdSignatureClashDetector.trackDeclarationIfInCurrentModule(declaration: IrDeclaration, signature: IdSignature) {
// Only count signature clashes on the declarations declared in the module being serialized.
// If there is a declaration in an external module with the same signature as some declaration in the current module,
// so be it (for now).
// Note that when we are serializing a module into a KLIB, the declarations from other modules always come in the form of Lazy IR,
// so this check is enough to ensure that this declaration is declared in the module currently being serialized.
if (declaration !is IrLazyDeclarationBase) {
trackDeclaration(declaration, signature)
}
}
open class DeclarationTable(globalTable: GlobalDeclarationTable) {
protected val table = hashMapOf<IrDeclaration, IdSignature>()
protected open val globalDeclarationTable: GlobalDeclarationTable = globalTable
@@ -1,14 +1,14 @@
/foo.kt:7:1: error: Platform declaration clash: The following declarations have the same KLIB signature (com.example.klib.serialization.diagnostics/foo|foo(){}[0]):
/foo.kt:13:1: error: Platform declaration clash: The following declarations have the same KLIB signature (com.example.klib.serialization.diagnostics/foo|foo(){}[0]):
fun foo(): kotlin.Long defined in com.example.klib.serialization.diagnostics
fun foo(): kotlin.String defined in com.example.klib.serialization.diagnostics
fun foo(): kotlin.Int defined in com.example.klib.serialization.diagnostics
/main.kt:13:1: error: Platform declaration clash: The following declarations have the same KLIB signature (com.example.klib.serialization.diagnostics/foo|foo(){}[0]):
/main.kt:19:1: error: Platform declaration clash: The following declarations have the same KLIB signature (com.example.klib.serialization.diagnostics/foo|foo(){}[0]):
fun foo(): kotlin.Long defined in com.example.klib.serialization.diagnostics
fun foo(): kotlin.String defined in com.example.klib.serialization.diagnostics
fun foo(): kotlin.Int defined in com.example.klib.serialization.diagnostics
/main.kt:16:1: error: Platform declaration clash: The following declarations have the same KLIB signature (com.example.klib.serialization.diagnostics/foo|foo(){}[0]):
/main.kt:22:1: error: Platform declaration clash: The following declarations have the same KLIB signature (com.example.klib.serialization.diagnostics/foo|foo(){}[0]):
fun foo(): kotlin.Long defined in com.example.klib.serialization.diagnostics
fun foo(): kotlin.String defined in com.example.klib.serialization.diagnostics
fun foo(): kotlin.Int defined in com.example.klib.serialization.diagnostics
@@ -1,6 +1,12 @@
// FIR_IDENTICAL
// RENDER_ALL_DIAGNOSTICS_FULL_TEXT
// MODULE: lib
package com.example.klib.serialization.diagnostics
fun movedToLib() {}
// MODULE: main(lib)
// FILE: foo.kt
package com.example.klib.serialization.diagnostics
@@ -15,6 +21,10 @@ fun foo(): String = ""<!>
<!CONFLICTING_KLIB_SIGNATURES_ERROR!>fun foo(): Int = 0<!>
@Deprecated("This function moved to the 'lib' module", level = DeprecationLevel.HIDDEN)
fun movedToLib() {}
fun main() {
foo()
movedToLib()
}
@@ -0,0 +1,3 @@
package com.example.klib.serialization.diagnostics
fun movedToLib() {}
@@ -7,6 +7,10 @@ class A {
fun foo(): Int = 0
}
@Deprecated("This function moved to the 'lib' module", level = DeprecationLevel.HIDDEN)
fun movedToLib() {}
fun main() {
println(A().foo())
movedToLib()
}
@@ -163,7 +163,8 @@ class FirCompilerOutputTest : CompilerOutputTestBase() {
// TODO: use the Compiler Core test infrastructure for testing these diagnostics (KT-64393)
val rootDir = File("native/native.tests/testData/compilerOutput/SignatureClashDiagnostics")
val settings = testRunSettings
val compilationResult = compileLibrary(settings, rootDir.resolve("main.kt"))
val lib = compileLibrary(settings, rootDir.resolve("lib.kt")).assertSuccess().resultingArtifact
val compilationResult = compileLibrary(settings, rootDir.resolve("main.kt"), dependencies = listOf(lib))
val goldenData = rootDir.resolve("output.txt")
KotlinTestUtils.assertEqualsToFile(goldenData, compilationResult.toOutput())