[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)`
|
* returns `sequenceOf(A, B, C)`
|
||||||
*/
|
*/
|
||||||
context(KtAnalysisSession)
|
context(KtAnalysisSession)
|
||||||
internal fun KtFileSymbol.getAllClassOrObjectSymbols(): List<KtClassifierSymbol> {
|
internal fun KtFileSymbol.getAllClassOrObjectSymbols(): List<KtClassOrObjectSymbol> {
|
||||||
return getFileScope().getClassifierSymbols()
|
return getFileScope().getClassifierSymbols()
|
||||||
.filterIsInstance<KtClassOrObjectSymbol>()
|
.filterIsInstance<KtClassOrObjectSymbol>()
|
||||||
.flatMap { classSymbol -> listOf(classSymbol) + classSymbol.getAllClassOrObjectSymbols() }
|
.flatMap { classSymbol -> listOf(classSymbol) + classSymbol.getAllClassOrObjectSymbols() }
|
||||||
|
|||||||
+12
-3
@@ -1,9 +1,8 @@
|
|||||||
package org.jetbrains.kotlin.objcexport
|
package org.jetbrains.kotlin.objcexport
|
||||||
|
|
||||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
|
import org.jetbrains.kotlin.analysis.api.symbols.*
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
|
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCClass
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
|
|
||||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub
|
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub
|
||||||
|
|
||||||
context(KtAnalysisSession, KtObjCExportSession)
|
context(KtAnalysisSession, KtObjCExportSession)
|
||||||
@@ -14,3 +13,13 @@ internal fun KtCallableSymbol.translateToObjCExportStub(): ObjCExportStub? {
|
|||||||
else -> null
|
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
|
package org.jetbrains.kotlin.objcexport
|
||||||
|
|
||||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
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.KtClassOrObjectSymbol
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFileSymbol
|
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.backend.konan.objcexport.*
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.objcexport.KtObjCExportHeaderGenerator.QueueElement
|
||||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.*
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.*
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
|
|
||||||
|
|
||||||
context(KtAnalysisSession, KtObjCExportSession)
|
context(KtAnalysisSession, KtObjCExportSession)
|
||||||
fun translateToObjCHeader(files: List<KtFile>): ObjCHeader {
|
fun translateToObjCHeader(files: List<KtFile>): ObjCHeader {
|
||||||
val stubs = mutableListOf<ObjCExportStub>()
|
val generator = KtObjCExportHeaderGenerator()
|
||||||
val protocolForwardDeclarations = mutableSetOf<String>()
|
generator.translateAll(files.sortedWith(StableFileOrder).map { QueueElement.File(it) })
|
||||||
val classForwardDeclarations = mutableSetOf<ObjCClassForwardDeclaration>()
|
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,
|
* The mutable aggregate of the already translated elements
|
||||||
forwardProtocolsAndClasses: Boolean = false,
|
*/
|
||||||
): List<ObjCExportStub> {
|
private val objCStubs = mutableListOf<ObjCTopLevel>()
|
||||||
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)
|
|
||||||
|
|
||||||
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 -> {
|
* The mutable aggregate of all entities that shall later be rendered as forward declarations
|
||||||
val classId = symbol.classIdIfNonLocal ?: return result
|
*/
|
||||||
|
private val objCForwardDeclarations = mutableSetOf<ClassId>()
|
||||||
|
|
||||||
/* Add the classId to already processed classIds and do not redo if already processed */
|
/**
|
||||||
val stub = translatedClassifiers.getOrPut(classId) {
|
* See [symbolDeque]:
|
||||||
val translatedObjCClassOrProtocol = when (symbol.classKind) {
|
* All top level 'to do' elements will be represented as [QueueElement] and later handled by the [translateAll] function.
|
||||||
KtClassKind.INTERFACE -> symbol.translateToObjCProtocol()
|
*/
|
||||||
KtClassKind.CLASS -> symbol.translateToObjCClass()
|
sealed class QueueElement {
|
||||||
KtClassKind.OBJECT -> symbol.translateToObjCObject()
|
class File(val psi: KtFile) : QueueElement()
|
||||||
KtClassKind.ENUM_CLASS -> symbol.translateToObjCClass()
|
class Class(val classId: ClassId) : QueueElement()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processDeclaredSymbols(symbols: List<KtSymbol>): List<ObjCExportStub> {
|
context(KtAnalysisSession, KtObjCExportSession)
|
||||||
val result = mutableListOf<ObjCExportStub>()
|
fun translateAll(symbolProviders: List<QueueElement>) {
|
||||||
symbolTranslationQueue.addAll(symbols)
|
symbolDeque.addAll(symbolProviders)
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
val next = symbolTranslationQueue.removeFirstOrNull() ?: break
|
val symbolProvider = symbolDeque.removeFirstOrNull() ?: break
|
||||||
result.addAll(process(next))
|
translateElement(symbolProvider)
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processDependencySymbols(stubs: List<ObjCExportStub>): List<ObjCExportStub> {
|
context(KtAnalysisSession, KtObjCExportSession)
|
||||||
val dependencyClassSymbols = stubs.closureSequence()
|
private fun translateElement(element: QueueElement) = when (element) {
|
||||||
.mapNotNull { stub ->
|
is QueueElement.Class -> translateClassElement(element)
|
||||||
when (stub) {
|
is QueueElement.File -> translateFileElement(element)
|
||||||
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()
|
|
||||||
|
|
||||||
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 ->
|
context(KtAnalysisSession, KtObjCExportSession)
|
||||||
process(symbol, forwardProtocolsAndClasses = true)
|
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)
|
superInterfaceOrClassSymbols.forEach { superInterfaceOrClassSymbol ->
|
||||||
else result
|
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)
|
* Will introspect the given [stub] to collect all used 'dependency' types/classes.
|
||||||
val dependencyStubs = processDependencySymbols(declaredStubs)
|
* Example: Usage of Kotlin Stdlib Type (Array):
|
||||||
|
*
|
||||||
stubs.addAll(declaredStubs + dependencyStubs)
|
* ```
|
||||||
|
* class Foo {
|
||||||
if (stubs.hasErrorTypes()) {
|
* fun createArray(): Array<String> = error("stub")
|
||||||
stubs.add(errorInterface)
|
* }
|
||||||
classForwardDeclarations.add(errorForwardClass)
|
* ```
|
||||||
|
*
|
||||||
|
* 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) {
|
context(KtAnalysisSession, KtObjCExportSession)
|
||||||
stubs.addAll(0, objCBaseDeclarations())
|
fun buildObjCHeader(): ObjCHeader {
|
||||||
}
|
val hasErrorTypes = objCStubs.hasErrorTypes()
|
||||||
|
|
||||||
return ObjCHeader(
|
val resolvedObjCForwardDeclarations = objCForwardDeclarations.mapNotNull { classId -> objCStubsByClassId[classId] }.asSequence()
|
||||||
stubs = stubs,
|
|
||||||
classForwardDeclarations = classForwardDeclarations,
|
val protocolForwardDeclarations = resolvedObjCForwardDeclarations.filterIsInstance<ObjCProtocol>().map { it.name }.toSet()
|
||||||
protocolForwardDeclarations = protocolForwardDeclarations,
|
|
||||||
additionalImports = emptyList()
|
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