[ObjCExport] Implement initial sorting and ordering for ObjC translation

^KT-65237
This commit is contained in:
Sebastian Sellmair
2024-02-01 14:29:58 +01:00
committed by Space Team
parent 27fa3ebfb3
commit 32da1b70f5
14 changed files with 307 additions and 13 deletions
@@ -27,7 +27,11 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
val comment: ObjCComment? = annotationsList.translateToObjCComment()
val origin: ObjCExportStubOrigin = getObjCExportStubOrigin()
val superProtocols: List<String> = superProtocols()
val members: List<ObjCExportStub> = getAllMembers().flatMap { it.translateToObjCExportStubs() }
val members: List<ObjCExportStub> = getAllMembers()
.sortedWith(StableSymbolOrder)
.flatMap { it.translateToObjCExportStubs() }
val categoryName: String? = null
val generics: List<ObjCGenericTypeDeclaration> = emptyList()
val superClassGenerics: List<ObjCNonNullReferenceType> = emptyList()
@@ -17,7 +17,11 @@ import org.jetbrains.kotlin.psi.KtFile
context(KtAnalysisSession, KtObjCExportSession)
fun translateToObjCHeader(files: List<KtFile>): ObjCHeader {
val declarations = files.flatMap { ktFile -> ktFile.translateToObjCExportStubs() }.toMutableList()
val declarations = files
.sortedWith(StableFileOrder)
.flatMap { file -> file.getFileSymbol().translateToObjCExportStubs() }
.toMutableList()
val classForwardDeclarations = getClassForwardDeclarations(declarations).toMutableSet()
val protocolForwardDeclarations = getProtocolForwardDeclarations(declarations)
@@ -66,14 +70,11 @@ private fun getProtocolForwardDeclarations(declarations: List<ObjCExportStub>) =
.flatMap { it.superProtocols }
.toSet()
context(KtAnalysisSession, KtObjCExportSession)
fun KtFile.translateToObjCExportStubs(): List<ObjCExportStub> {
return this.getFileSymbol().translateToObjCExportStubs()
}
context(KtAnalysisSession, KtObjCExportSession)
fun KtFileSymbol.translateToObjCExportStubs(): List<ObjCExportStub> {
return listOfNotNull(translateToObjCTopLevelInterfaceFileFacade()) + getFileScope().getClassifierSymbols()
.sortedWith(StableClassifierOrder)
.flatMap { classifierSymbol -> classifierSymbol.translateToObjCExportStubs() }
}
@@ -26,7 +26,9 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? {
// TODO: Check error type!
val name = getObjCClassOrProtocolName()
val members = getDeclaredMembers().flatMap { it.translateToObjCExportStubs() }
val members = getDeclaredMembers()
.sortedWith(StableSymbolOrder)
.flatMap { it.translateToObjCExportStubs() }
val comment: ObjCComment? = annotationsList.translateToObjCComment()
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrPr
context(KtAnalysisSession, KtObjCExportSession)
fun KtFileSymbol.translateToObjCTopLevelInterfaceFileFacade(): ObjCInterface? {
val topLevelCallableStubs = getFileScope().getCallableSymbols()
.sortedWith(StableCallableOrder)
.flatMap { callableSymbol -> callableSymbol.translateToObjCExportStubs() }
.toList()
/* If there are no top level functions or properties, we do not need to export a file facade */
@@ -0,0 +1,60 @@
/*
* Copyright 2010-2024 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.objcexport
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.psi.KtFile
internal val StableFileOrder: Comparator<KtFile>
get() = compareBy<KtFile> { file -> file.packageFqName.asString() }
.thenComparing { file -> file.name }
internal val StablePropertyOrder: Comparator<KtPropertySymbol> = compareBy { it.name }
internal val StableFunctionOrder: Comparator<KtFunctionSymbol>
get() = compareBy(
{ it.isConstructor },
{ it.name },
{ it.valueParameters.size },
// TODO NOW! { KonanManglerDesc.run { it.signatureString(false) } }
)
internal val StableClassifierOrder: Comparator<KtClassifierSymbol> =
compareBy<KtClassifierSymbol> { classifier ->
if (classifier !is KtClassOrObjectSymbol) return@compareBy 0
else 2
}.thenComparing { classifier ->
if (classifier is KtClassLikeSymbol) classifier.classIdIfNonLocal?.toString().orEmpty()
else ""
}
internal val StableCallableOrder: Comparator<KtCallableSymbol> = compareBy<KtCallableSymbol> {
when (it) {
is KtConstructorSymbol -> 0
is KtFunctionSymbol -> 1
is KtPropertySymbol -> 2
else -> 3
}
}
.thenComparing(StablePropertyOrder)
.thenComparing(StableFunctionOrder)
internal val StableSymbolOrder: Comparator<KtSymbol> = compareBy<KtSymbol> { symbol ->
when (symbol) {
is KtFileSymbol -> 0
is KtClassifierSymbol -> 1
is KtCallableSymbol -> 2
else -> Int.MAX_VALUE
}
}
.thenComparing(StableClassifierOrder)
.thenComparing(StableCallableOrder)
private inline fun <T, reified R> Comparator<T>.thenComparing(comparator: Comparator<R>): Comparator<T> where R : T {
return thenComparing { a, b ->
if (a is R && b is R) comparator.compare(a, b) else 0
}
}
@@ -36,7 +36,16 @@ import java.nio.file.Files
* ```
*/
interface InlineSourceCodeAnalysis {
fun createKtFile(@Language("kotlin") sourceCode: String): KtFile
fun createKtFiles(
builder: KtModuleBuilder.() -> Unit,
): Map</* File Name */ String, KtFile>
interface KtModuleBuilder {
fun sourceFile(fileName: String, @Language("kotlin") sourceCode: String)
}
}
/**
@@ -68,8 +77,25 @@ class InlineSourceCodeAnalysisExtension : ParameterResolver, AfterEachCallback {
*/
private class InlineSourceCodeAnalysisImpl(private val tempDir: File) : InlineSourceCodeAnalysis {
override fun createKtFile(@Language("kotlin") sourceCode: String): KtFile {
return createStandaloneAnalysisApiSession(tempDir, listOf(sourceCode))
return createStandaloneAnalysisApiSession(tempDir, mapOf("TestSources.kt" to sourceCode))
.modulesWithFiles.entries.single()
.value.single() as KtFile
}
override fun createKtFiles(builder: InlineSourceCodeAnalysis.KtModuleBuilder.() -> Unit): Map<String, KtFile> {
val sources = KtModuleBuilderImpl().also(builder).sources.toMap()
return createStandaloneAnalysisApiSession(tempDir, sources)
.modulesWithFiles.entries.single()
.value.map { it as KtFile }
.associateBy { it.name }
}
private class KtModuleBuilderImpl : InlineSourceCodeAnalysis.KtModuleBuilder {
val sources = mutableMapOf<String, String>()
override fun sourceFile(fileName: String, sourceCode: String) {
check(fileName !in sourceCode)
sources[fileName] = sourceCode
}
}
}
@@ -23,14 +23,14 @@ import kotlin.io.path.Path
*/
fun createStandaloneAnalysisApiSession(
tempDir: File,
kotlinSources: List<String>,
kotlinSources: Map</* File Name */ String, /* Source Code */ String>,
): StandaloneAnalysisAPISession {
val testModuleRoot = tempDir.resolve("testModule")
testModuleRoot.mkdirs()
kotlinSources.forEachIndexed { index, kotlinSource ->
testModuleRoot.resolve("TestSources$index.kt").apply {
writeText(kotlinSource)
kotlinSources.forEach { (fileName, sourceCode) ->
testModuleRoot.resolve(fileName).apply {
writeText(sourceCode)
}
}
return createStandaloneAnalysisApiSession(listOf(testModuleRoot))
@@ -0,0 +1,47 @@
/*
* Copyright 2010-2024 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.objcexport.tests
import org.jetbrains.kotlin.objcexport.StableFileOrder
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
class TranslationOrderTest(
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
) {
@Test
fun `test - StableFileOrder - sort by packageName`() {
val files = inlineSourceCodeAnalysis.createKtFiles {
sourceFile("A.kt", "package com.a")
sourceFile("B.kt", "package com.b")
sourceFile("C.kt", "package com.c")
}
val unsorted = listOf(files.getValue("C.kt"), files.getValue("B.kt"), files.getValue("A.kt"))
val sorted = listOf(files.getValue("A.kt"), files.getValue("B.kt"), files.getValue("C.kt"))
assertNotEquals(sorted, unsorted)
assertEquals(sorted, unsorted.sortedWith(StableFileOrder))
}
@Test
fun `test - StableFileOrder - sort by packageName then FileName`() {
val files = inlineSourceCodeAnalysis.createKtFiles {
sourceFile("A.kt", "package com.a")
sourceFile("B1.kt", "package com.b")
sourceFile("B2.kt", "package com.b")
}
val unsorted = listOf(files.getValue("B2.kt"), files.getValue("A.kt"), files.getValue("B1.kt"))
val sorted = listOf(files.getValue("A.kt"), files.getValue("B1.kt"), files.getValue("B2.kt"))
assertNotEquals(sorted, unsorted)
assertEquals(sorted, unsorted.sortedWith(StableFileOrder))
}
}