[klib] Implement diagnostics for clashing KLIB signatures

Now, we detect clashing signatures during serialization to KLIB and
report a compiler error if two or more declarations have the same
`IdSignature`

For example, for the following code:
```kotlin
@Deprecated("", level = DeprecationLevel.HIDDEN)
fun foo(): String = ""

fun foo(): Int = 0
```

the compiler will produce this diagnostic:
```
e: main.kt:1:1 Platform declaration clash: The following declarations
       have the same KLIB signature (/foo|foo(){}[0]):
    fun foo(): String defined in root package
    fun foo(): Int defined in root package
e: main.kt:4:1 Platform declaration clash: The following declarations
       have the same KLIB signature (/foo|foo(){}[0]):
    fun foo(): String defined in root package
    fun foo(): Int defined in root package
```

Note that we report this diagnostic during serialization and not earlier
(e.g., in fir2ir) for more robustness, so ensure that we check
exactly the signatures that will be written to a KLIB.
If we later introduce some annotation for customizing a declaration's
signature (e.g., for preserving binary compatibility), this
diagnostic will continue to work as expected.

^KT-63670 Fixed
This commit is contained in:
Sergej Jaskiewicz
2023-12-15 20:05:02 +01:00
committed by Space Team
parent e0cb145c6b
commit eda30ff704
27 changed files with 366 additions and 98 deletions
@@ -0,0 +1,12 @@
package com.example.klib.serialization.diagnostics
class A {
@Deprecated("", level = DeprecationLevel.HIDDEN)
fun foo(): String = ""
fun foo(): Int = 0
}
fun main() {
println(A().foo())
}
@@ -0,0 +1,11 @@
native/native.tests/testData/compilerOutput/SignatureClashDiagnostics/main.kt:4:5: error: platform declaration clash: The following declarations have the same KLIB signature (com.example.klib.serialization.diagnostics/A.foo|foo(){}[0]):
fun foo(): kotlin.String defined in com.example.klib.serialization.diagnostics.A
fun foo(): kotlin.Int defined in com.example.klib.serialization.diagnostics.A
@Deprecated("", level = DeprecationLevel.HIDDEN)
^
native/native.tests/testData/compilerOutput/SignatureClashDiagnostics/main.kt:7:5: error: platform declaration clash: The following declarations have the same KLIB signature (com.example.klib.serialization.diagnostics/A.foo|foo(){}[0]):
fun foo(): kotlin.String defined in com.example.klib.serialization.diagnostics.A
fun foo(): kotlin.Int defined in com.example.klib.serialization.diagnostics.A
fun foo(): Int = 0
^
COMPILATION_ERROR
@@ -109,7 +109,7 @@ abstract class CompilerOutputTestBase : AbstractNativeSimpleTest() {
KotlinTestUtils.assertEqualsToFile(goldenData, compilationResult.toOutput())
}
private fun compileLibrary(
internal fun compileLibrary(
settings: Settings,
source: File,
freeCompilerArgs: List<String> = emptyList(),
@@ -126,7 +126,7 @@ abstract class CompilerOutputTestBase : AbstractNativeSimpleTest() {
return compilation.result
}
private fun TestCompilationResult<*>.toOutput(): String {
internal fun TestCompilationResult<*>.toOutput(): String {
check(this is TestCompilationResult.ImmediateResult<*>) { this }
val loggedData = this.loggedData
check(loggedData is LoggedData.CompilationToolCall) { loggedData::class }
@@ -152,4 +152,16 @@ class ClassicCompilerOutputTest : CompilerOutputTestBase()
@Tag("frontend-fir")
@TestDataPath("\$PROJECT_ROOT")
@EnforcedProperty(ClassLevelProperty.COMPILER_OUTPUT_INTERCEPTOR, "NONE")
class FirCompilerOutputTest : CompilerOutputTestBase()
class FirCompilerOutputTest : CompilerOutputTestBase() {
@Test
fun testSignatureClashDiagnostics() {
// 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 goldenData = rootDir.resolve("output.txt")
KotlinTestUtils.assertEqualsToFile(goldenData, compilationResult.toOutput())
}
}