[ObjCExport] Restructure ObjC export symbol queue to replicate order of K1
The previous implementation used a two-stage process: 1) Processing of declared symbols 2) Analysis and processing of referenced dependency symbols However, to replicate the exact same order as in K1 those two steps need to be done together. ^KT-64953 Fixed
This commit is contained in:
committed by
Space Team
parent
a2d76d739c
commit
d0e67ff336
+1
-1
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.analysis.api.symbols.KtFileSymbol
|
||||
* returns `sequenceOf(A, B, C)`
|
||||
*/
|
||||
context(KtAnalysisSession)
|
||||
internal fun KtFileSymbol.getAllClassOrObjectSymbols(): List<KtClassifierSymbol> {
|
||||
internal fun KtFileSymbol.getAllClassOrObjectSymbols(): List<KtClassOrObjectSymbol> {
|
||||
return getFileScope().getClassifierSymbols()
|
||||
.filterIsInstance<KtClassOrObjectSymbol>()
|
||||
.flatMap { classSymbol -> listOf(classSymbol) + classSymbol.getAllClassOrObjectSymbols() }
|
||||
|
||||
+12
-3
@@ -1,9 +1,8 @@
|
||||
package org.jetbrains.kotlin.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.*
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCClass
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
@@ -14,3 +13,13 @@ internal fun KtCallableSymbol.translateToObjCExportStub(): ObjCExportStub? {
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
internal fun KtClassOrObjectSymbol.translateToObjCExportStub(): ObjCClass? = when (classKind) {
|
||||
KtClassKind.INTERFACE -> translateToObjCProtocol()
|
||||
KtClassKind.CLASS -> translateToObjCClass()
|
||||
KtClassKind.OBJECT -> translateToObjCObject()
|
||||
KtClassKind.ENUM_CLASS -> translateToObjCClass()
|
||||
KtClassKind.COMPANION_OBJECT -> translateToObjCObject()
|
||||
else -> null
|
||||
}
|
||||
+155
-103
@@ -6,136 +6,188 @@
|
||||
package org.jetbrains.kotlin.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFileSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.objcexport.KtObjCExportHeaderGenerator.QueueElement
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.*
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
fun translateToObjCHeader(files: List<KtFile>): ObjCHeader {
|
||||
val stubs = mutableListOf<ObjCExportStub>()
|
||||
val protocolForwardDeclarations = mutableSetOf<String>()
|
||||
val classForwardDeclarations = mutableSetOf<ObjCClassForwardDeclaration>()
|
||||
val generator = KtObjCExportHeaderGenerator()
|
||||
generator.translateAll(files.sortedWith(StableFileOrder).map { QueueElement.File(it) })
|
||||
return generator.buildObjCHeader()
|
||||
}
|
||||
|
||||
val symbolTranslationQueue = mutableListOf<KtSymbol>()
|
||||
val translatedClassifiers = mutableMapOf<ClassId, ObjCClass>()
|
||||
/**
|
||||
* Encapsulates the 'dynamic' nature of the ObjCExport where only during the export phase decisions about
|
||||
* 1) Which symbols are to be exported
|
||||
* 2) In which order symbols are to be exported
|
||||
*
|
||||
* can be made.
|
||||
*
|
||||
* Functions inside this class will have side effects such as mutating the [symbolDeque] or adding results to the [objCStubs]
|
||||
*/
|
||||
private class KtObjCExportHeaderGenerator {
|
||||
/**
|
||||
* Represents all elements that still have to be processed and translated.
|
||||
* So far this only includes references to top level entities (such as files or classes):
|
||||
* Note: Top level functions and properties will be translated as part of the file.
|
||||
* See [translateToObjCTopLevelInterfaceFileFacade]
|
||||
*/
|
||||
private val symbolDeque = ArrayDeque<QueueElement>()
|
||||
|
||||
fun process(
|
||||
symbol: KtSymbol,
|
||||
forwardProtocolsAndClasses: Boolean = false,
|
||||
): List<ObjCExportStub> {
|
||||
val result = mutableListOf<ObjCExportStub>()
|
||||
when (symbol) {
|
||||
/* In this case: Go and translate all classes/objects/interfaces inside the file as well */
|
||||
is KtFileSymbol -> {
|
||||
val translatedTopLevelFileFacade = symbol.translateToObjCTopLevelInterfaceFileFacade()
|
||||
result.addIfNotNull(translatedTopLevelFileFacade)
|
||||
/**
|
||||
* The mutable aggregate of the already translated elements
|
||||
*/
|
||||
private val objCStubs = mutableListOf<ObjCTopLevel>()
|
||||
|
||||
symbolTranslationQueue.addAll(
|
||||
symbol.getAllClassOrObjectSymbols().sortedWith(StableClassifierOrder)
|
||||
)
|
||||
}
|
||||
/**
|
||||
* An index of all already translated classes. All classes here are also present in [objCStubs]
|
||||
*/
|
||||
private val objCStubsByClassId = hashMapOf<ClassId, ObjCClass?>()
|
||||
|
||||
/* Translate the Class/Interface, but ensure that all supertypes will be translated also */
|
||||
is KtClassOrObjectSymbol -> {
|
||||
val classId = symbol.classIdIfNonLocal ?: return result
|
||||
/**
|
||||
* The mutable aggregate of all entities that shall later be rendered as forward declarations
|
||||
*/
|
||||
private val objCForwardDeclarations = mutableSetOf<ClassId>()
|
||||
|
||||
/* Add the classId to already processed classIds and do not redo if already processed */
|
||||
val stub = translatedClassifiers.getOrPut(classId) {
|
||||
val translatedObjCClassOrProtocol = when (symbol.classKind) {
|
||||
KtClassKind.INTERFACE -> symbol.translateToObjCProtocol()
|
||||
KtClassKind.CLASS -> symbol.translateToObjCClass()
|
||||
KtClassKind.OBJECT -> symbol.translateToObjCObject()
|
||||
KtClassKind.ENUM_CLASS -> symbol.translateToObjCClass()
|
||||
KtClassKind.COMPANION_OBJECT -> symbol.translateToObjCObject()
|
||||
else -> return result
|
||||
} ?: return result
|
||||
|
||||
symbol.getDeclaredSuperInterfaceSymbols().forEach { superInterfaceSymbol ->
|
||||
result.addAll(process(superInterfaceSymbol, true))
|
||||
}
|
||||
|
||||
symbol.getSuperClassSymbolNotAny()?.let { superClassSymbol ->
|
||||
result.addAll(process(superClassSymbol, true))
|
||||
}
|
||||
|
||||
result.add(translatedObjCClassOrProtocol)
|
||||
translatedObjCClassOrProtocol
|
||||
}
|
||||
|
||||
if (forwardProtocolsAndClasses) {
|
||||
when (stub) {
|
||||
is ObjCInterface -> classForwardDeclarations.add(ObjCClassForwardDeclaration(stub.name, stub.generics))
|
||||
is ObjCProtocol -> protocolForwardDeclarations.add(stub.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
/**
|
||||
* See [symbolDeque]:
|
||||
* All top level 'to do' elements will be represented as [QueueElement] and later handled by the [translateAll] function.
|
||||
*/
|
||||
sealed class QueueElement {
|
||||
class File(val psi: KtFile) : QueueElement()
|
||||
class Class(val classId: ClassId) : QueueElement()
|
||||
}
|
||||
|
||||
fun processDeclaredSymbols(symbols: List<KtSymbol>): List<ObjCExportStub> {
|
||||
val result = mutableListOf<ObjCExportStub>()
|
||||
symbolTranslationQueue.addAll(symbols)
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
fun translateAll(symbolProviders: List<QueueElement>) {
|
||||
symbolDeque.addAll(symbolProviders)
|
||||
|
||||
while (true) {
|
||||
val next = symbolTranslationQueue.removeFirstOrNull() ?: break
|
||||
result.addAll(process(next))
|
||||
val symbolProvider = symbolDeque.removeFirstOrNull() ?: break
|
||||
translateElement(symbolProvider)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun processDependencySymbols(stubs: List<ObjCExportStub>): List<ObjCExportStub> {
|
||||
val dependencyClassSymbols = stubs.closureSequence()
|
||||
.mapNotNull { stub ->
|
||||
when (stub) {
|
||||
is ObjCMethod -> stub.returnType
|
||||
is ObjCParameter -> stub.type
|
||||
is ObjCProperty -> stub.type
|
||||
is ObjCTopLevel -> null
|
||||
}
|
||||
}
|
||||
.flatMap { type ->
|
||||
if (type is ObjCClassType) type.typeArguments + type
|
||||
else listOf(type)
|
||||
}
|
||||
.mapNotNull { if (it is ObjCReferenceType) it.classId else null }
|
||||
.mapNotNull { classId -> getClassOrObjectSymbolByClassId(classId) }
|
||||
.toList()
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
private fun translateElement(element: QueueElement) = when (element) {
|
||||
is QueueElement.Class -> translateClassElement(element)
|
||||
is QueueElement.File -> translateFileElement(element)
|
||||
}
|
||||
|
||||
symbolTranslationQueue.addAll(dependencyClassSymbols)
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
private fun translateClassElement(element: QueueElement.Class) {
|
||||
val classOrObjectSymbol = getClassOrObjectSymbolByClassId(element.classId) ?: return
|
||||
translateClassOrObjectSymbol(classOrObjectSymbol)
|
||||
}
|
||||
|
||||
val result = dependencyClassSymbols.flatMap { symbol ->
|
||||
process(symbol, forwardProtocolsAndClasses = true)
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
private fun translateFileElement(element: QueueElement.File) {
|
||||
val fileSymbol = element.psi.getFileSymbol()
|
||||
translateFileSymbol(fileSymbol)
|
||||
fileSymbol.getAllClassOrObjectSymbols().sortedWith(StableClassifierOrder).forEach { classOrObjectSymbol ->
|
||||
translateClassOrObjectSymbol(classOrObjectSymbol)
|
||||
}
|
||||
}
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
private fun translateFileSymbol(symbol: KtFileSymbol) {
|
||||
val objCInterface = symbol.translateToObjCTopLevelInterfaceFileFacade() ?: return
|
||||
objCStubs += objCInterface
|
||||
enqueueDependencyClasses(objCInterface)
|
||||
}
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
private fun translateClassOrObjectSymbol(symbol: KtClassOrObjectSymbol) {
|
||||
/* No classId, no stubs ¯\_(ツ)_/¯ */
|
||||
val classId = symbol.classIdIfNonLocal ?: return
|
||||
|
||||
/* Already processed this class, therefore nothing to do! */
|
||||
if (classId in objCStubsByClassId) return
|
||||
|
||||
/**
|
||||
* Translate: Note: Even if the result was 'null', the classId will still be marked as 'handled' by adding it
|
||||
* to the [objCStubsByClassId] index.
|
||||
*/
|
||||
val objCClass = symbol.translateToObjCExportStub()
|
||||
objCStubsByClassId[classId] = objCClass
|
||||
objCClass ?: return
|
||||
|
||||
/*
|
||||
To replicate the translation (and result stub order) of the K1 implementation:
|
||||
1) Super interface / superclass symbols have to be translated right away
|
||||
2) Super interface / superclass symbol export stubs (result of translation) have to be present in the stubs list before the
|
||||
original stub
|
||||
*/
|
||||
val superInterfaceOrClassSymbols = buildList {
|
||||
addAll(symbol.getDeclaredSuperInterfaceSymbols())
|
||||
addIfNotNull(symbol.getSuperClassSymbolNotAny())
|
||||
}
|
||||
|
||||
return if (result.isNotEmpty()) result + processDependencySymbols(result)
|
||||
else result
|
||||
superInterfaceOrClassSymbols.forEach { superInterfaceOrClassSymbol ->
|
||||
translateClassOrObjectSymbol(superInterfaceOrClassSymbol)
|
||||
}
|
||||
|
||||
/* Note: It is important to add *this* stub to the result list only after translating/processing the superclass symbols */
|
||||
objCStubs += objCClass
|
||||
objCForwardDeclarations += superInterfaceOrClassSymbols.mapNotNull { it.classIdIfNonLocal }
|
||||
enqueueDependencyClasses(objCClass)
|
||||
}
|
||||
|
||||
val fileSymbols = files.sortedWith(StableFileOrder).map { it.getFileSymbol() }
|
||||
val declaredStubs = processDeclaredSymbols(fileSymbols)
|
||||
val dependencyStubs = processDependencySymbols(declaredStubs)
|
||||
|
||||
stubs.addAll(declaredStubs + dependencyStubs)
|
||||
|
||||
if (stubs.hasErrorTypes()) {
|
||||
stubs.add(errorInterface)
|
||||
classForwardDeclarations.add(errorForwardClass)
|
||||
/**
|
||||
* Will introspect the given [stub] to collect all used 'dependency' types/classes.
|
||||
* Example: Usage of Kotlin Stdlib Type (Array):
|
||||
*
|
||||
* ```
|
||||
* class Foo {
|
||||
* fun createArray(): Array<String> = error("stub")
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The given symbol "Foo" will reference `Array`. Therefore, the `Array` class has to be translated as well (later)
|
||||
* and `Array` has to be registered as forward declaration.
|
||||
*/
|
||||
private fun enqueueDependencyClasses(stub: ObjCExportStub) {
|
||||
symbolDeque += stub.closureSequence().mapNotNull { child ->
|
||||
when (child) {
|
||||
is ObjCMethod -> child.returnType
|
||||
is ObjCParameter -> child.type
|
||||
is ObjCProperty -> child.type
|
||||
is ObjCTopLevel -> null
|
||||
}
|
||||
}.flatMap { type ->
|
||||
if (type is ObjCClassType) type.typeArguments + type
|
||||
else listOf(type)
|
||||
}.mapNotNull { if (it is ObjCReferenceType) it.classId else null }.onEach { objCForwardDeclarations += it }
|
||||
.map { QueueElement.Class(it) }.toList()
|
||||
}
|
||||
|
||||
if (configuration.generateBaseDeclarationStubs) {
|
||||
stubs.addAll(0, objCBaseDeclarations())
|
||||
}
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
fun buildObjCHeader(): ObjCHeader {
|
||||
val hasErrorTypes = objCStubs.hasErrorTypes()
|
||||
|
||||
return ObjCHeader(
|
||||
stubs = stubs,
|
||||
classForwardDeclarations = classForwardDeclarations,
|
||||
protocolForwardDeclarations = protocolForwardDeclarations,
|
||||
additionalImports = emptyList()
|
||||
)
|
||||
}
|
||||
val resolvedObjCForwardDeclarations = objCForwardDeclarations.mapNotNull { classId -> objCStubsByClassId[classId] }.asSequence()
|
||||
|
||||
val protocolForwardDeclarations = resolvedObjCForwardDeclarations.filterIsInstance<ObjCProtocol>().map { it.name }.toSet()
|
||||
|
||||
val classForwardDeclarations = resolvedObjCForwardDeclarations.filterIsInstance<ObjCInterface>()
|
||||
.map { stub -> ObjCClassForwardDeclaration(stub.name, stub.generics) }
|
||||
.plus(listOfNotNull(errorForwardClass.takeIf { hasErrorTypes })).toSet()
|
||||
|
||||
val stubs = (if (configuration.generateBaseDeclarationStubs) objCBaseDeclarations() else emptyList()).plus(objCStubs)
|
||||
.plus(listOfNotNull(errorInterface.takeIf { hasErrorTypes }))
|
||||
|
||||
return ObjCHeader(
|
||||
stubs = stubs,
|
||||
classForwardDeclarations = classForwardDeclarations,
|
||||
protocolForwardDeclarations = protocolForwardDeclarations,
|
||||
additionalImports = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user