[ObjCExport] Implement initial processing queue (stable order, dependencies, forward declarations)
This commit will introduce the first processing queue which will take care of properly ordering the 'to translate' symbols as well as taking care of 'dependency' symbols (aka symbols mentioned in signatures or as supertypes). - There are several changes like properly translating types as ObjCProtocolType instead of ObjCClassType (if origin was an interface) - Type translation of generics defined on interfaces will also emit id type. - Add initial nested classes collection to the queue - Add extension function test, add tickets references ^KT-65237 Verification Pending ^KT-65329 Verification Pending
This commit is contained in:
committed by
Space Team
parent
32da1b70f5
commit
797284dada
+1
-1
@@ -44,5 +44,5 @@ internal val errorInterface
|
||||
superClassGenerics = emptyList()
|
||||
)
|
||||
|
||||
internal val objCErrorType = ObjCClassType(errorClassName)
|
||||
internal val objCErrorType = ObjCClassType(errorClassName, classId = null)
|
||||
internal val errorForwardClass = ObjCClassForwardDeclaration(errorClassName)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package org.jetbrains.kotlin.objcexport.analysisApiUtils
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassifierSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFileSymbol
|
||||
|
||||
|
||||
/**
|
||||
* Returns all classes or objects (including transitively nested ones) contained within a file
|
||||
*
|
||||
* Example:
|
||||
*```kotlin
|
||||
* class A {
|
||||
* class B {
|
||||
* class C
|
||||
* }
|
||||
* }
|
||||
*```
|
||||
* returns `sequenceOf(A, B, C)`
|
||||
*/
|
||||
context(KtAnalysisSession)
|
||||
internal fun KtFileSymbol.getAllClassOrObjectSymbols(): List<KtClassifierSymbol> {
|
||||
return getFileScope().getClassifierSymbols()
|
||||
.filterIsInstance<KtClassOrObjectSymbol>()
|
||||
.flatMap { classSymbol -> listOf(classSymbol) + classSymbol.getAllClassOrObjectSymbols() }
|
||||
.toList()
|
||||
}
|
||||
|
||||
context(KtAnalysisSession)
|
||||
private fun KtClassOrObjectSymbol.getAllClassOrObjectSymbols(): Sequence<KtClassOrObjectSymbol> {
|
||||
return sequence {
|
||||
val nestedClasses = getMemberScope().getClassifierSymbols().filterIsInstance<KtClassOrObjectSymbol>()
|
||||
yieldAll(nestedClasses)
|
||||
|
||||
nestedClasses.forEach { nestedClass ->
|
||||
yieldAll(nestedClass.getAllClassOrObjectSymbols())
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.analysisApiUtils
|
||||
|
||||
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.types.KtClassType
|
||||
import org.jetbrains.kotlin.analysis.api.types.KtClassTypeQualifier
|
||||
|
||||
/**
|
||||
* @return The **declared** super interfaces (**not the transitive closure**)
|
||||
*/
|
||||
context(KtAnalysisSession)
|
||||
internal fun KtClassOrObjectSymbol.getDeclaredSuperInterfaceSymbols(): List<KtClassOrObjectSymbol> {
|
||||
return superTypes
|
||||
.asSequence()
|
||||
.mapNotNull { type -> type as? KtClassType }
|
||||
.flatMap { type -> type.qualifiers }
|
||||
.mapNotNull { qualifier -> qualifier as? KtClassTypeQualifier.KtResolvedClassTypeQualifier }
|
||||
.mapNotNull { it.symbol as? KtClassOrObjectSymbol }
|
||||
.filter { !it.isCloneable } // TODO: Write unit test for this
|
||||
.filter { superInterface -> superInterface.classKind == KtClassKind.INTERFACE }
|
||||
.toList()
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package org.jetbrains.kotlin.objcexport.analysisApiUtils
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
|
||||
internal val KtClassOrObjectSymbol.isCloneable: Boolean
|
||||
get() {
|
||||
return classIdIfNonLocal?.isCloneable ?: false
|
||||
}
|
||||
|
||||
internal val ClassId.isCloneable: Boolean
|
||||
get() {
|
||||
return asSingleFqName() == StandardNames.FqNames.cloneable.toSafe()
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDeclaredSuperInterfaceSymbols
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
internal fun KtClassOrObjectSymbol.translateSuperInterfaces(): List<ObjCProtocol> {
|
||||
return getDeclaredSuperInterfaceSymbols().mapNotNull { superInterfaceSymbol ->
|
||||
superInterfaceSymbol.translateToObjCProtocol()
|
||||
}
|
||||
}
|
||||
+12
-5
@@ -6,7 +6,6 @@ import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getAllMembers
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrProtocolName
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getSuperClassSymbolNotAny
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
||||
@@ -18,7 +17,7 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
|
||||
|
||||
val superClass = getSuperClassSymbolNotAny()
|
||||
val kotlinAnyName = getDefaultSuperClassOrProtocolName()
|
||||
val superName = if (superClass == null) kotlinAnyName else throw RuntimeException("Super class translation isn't implemented yet")
|
||||
val superName = if (superClass == null) kotlinAnyName else getSuperClassName()
|
||||
val enumKind = this.classKind == KtClassKind.ENUM_CLASS
|
||||
val final = if (this is KtSymbolWithModality) this.modality == Modality.FINAL else false
|
||||
val attributes = if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList()
|
||||
@@ -28,9 +27,10 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
|
||||
val origin: ObjCExportStubOrigin = getObjCExportStubOrigin()
|
||||
val superProtocols: List<String> = superProtocols()
|
||||
|
||||
val members: List<ObjCExportStub> = getAllMembers()
|
||||
.sortedWith(StableSymbolOrder)
|
||||
val members: List<ObjCExportStub> = getMemberScope().getCallableSymbols().plus(getMemberScope().getConstructors())
|
||||
.sortedWith(StableCallableOrder)
|
||||
.flatMap { it.translateToObjCExportStubs() }
|
||||
.toList()
|
||||
|
||||
val categoryName: String? = null
|
||||
val generics: List<ObjCGenericTypeDeclaration> = emptyList()
|
||||
@@ -57,6 +57,13 @@ private fun abbreviate(name: String): String {
|
||||
|
||||
val uppers = normalizedName.filterIndexed { index, character -> index == 0 || character.isUpperCase() }
|
||||
if (uppers.length >= 3) return uppers
|
||||
|
||||
return normalizedName
|
||||
}
|
||||
|
||||
/**
|
||||
* See issue KT-65384
|
||||
* And K1 implementation [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.translateClass]
|
||||
*/
|
||||
private fun KtClassOrObjectSymbol.getSuperClassName(): ObjCExportClassOrProtocolName {
|
||||
return ObjCExportClassOrProtocolName("UnimplementedSwiftName", "UnimplementedObjCName")
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
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.KtConstructorSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
internal fun KtCallableSymbol.translateToObjCExportStubs(): List<ObjCExportStub> {
|
||||
return when (this) {
|
||||
is KtConstructorSymbol -> translateToObjCConstructors()
|
||||
is KtPropertySymbol -> listOfNotNull(translateToObjCProperty())
|
||||
is KtFunctionSymbol -> listOfNotNull(translateToObjCMethod())
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
+114
-72
@@ -1,94 +1,136 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.*
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind.*
|
||||
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.objcexport.analysisApiUtils.errorForwardClass
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.errorInterface
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.hasErrorTypes
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
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 declarations = files
|
||||
.sortedWith(StableFileOrder)
|
||||
.flatMap { file -> file.getFileSymbol().translateToObjCExportStubs() }
|
||||
.toMutableList()
|
||||
val stubs = mutableListOf<ObjCExportStub>()
|
||||
val protocolForwardDeclarations = mutableSetOf<String>()
|
||||
val classForwardDeclarations = mutableSetOf<ObjCClassForwardDeclaration>()
|
||||
|
||||
val classForwardDeclarations = getClassForwardDeclarations(declarations).toMutableSet()
|
||||
val protocolForwardDeclarations = getProtocolForwardDeclarations(declarations)
|
||||
val symbolTranslationQueue = mutableListOf<KtSymbol>()
|
||||
val translatedClassifiers = mutableMapOf<ClassId, ObjCClass>()
|
||||
|
||||
if (declarations.hasErrorTypes()) {
|
||||
declarations.add(errorInterface)
|
||||
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)
|
||||
|
||||
symbolTranslationQueue.addAll(
|
||||
symbol.getAllClassOrObjectSymbols().sortedWith(StableClassifierOrder)
|
||||
)
|
||||
}
|
||||
|
||||
/* Translate the Class/Interface, but ensure that all supertypes will be translated also */
|
||||
is KtClassOrObjectSymbol -> {
|
||||
val classId = symbol.classIdIfNonLocal ?: return result
|
||||
|
||||
/* 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()
|
||||
else -> return result
|
||||
} ?: return result
|
||||
|
||||
symbol.getDeclaredSuperInterfaceSymbols().forEach { superInterfaceSymbol ->
|
||||
result.addAll(process(superInterfaceSymbol))
|
||||
}
|
||||
|
||||
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> {
|
||||
val result = mutableListOf<ObjCExportStub>()
|
||||
symbolTranslationQueue.addAll(symbols)
|
||||
while (true) {
|
||||
val next = symbolTranslationQueue.removeFirstOrNull() ?: break
|
||||
result.addAll(process(next))
|
||||
}
|
||||
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()
|
||||
|
||||
symbolTranslationQueue.addAll(dependencyClassSymbols)
|
||||
|
||||
val result = dependencyClassSymbols.flatMap { symbol ->
|
||||
process(symbol, forwardProtocolsAndClasses = true)
|
||||
}
|
||||
|
||||
return if (result.isNotEmpty()) result + processDependencySymbols(result)
|
||||
else result
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
protocolForwardDeclarations += stubs
|
||||
.filterIsInstance<ObjCClass>()
|
||||
.flatMap { it.superProtocols }
|
||||
|
||||
return ObjCHeader(
|
||||
stubs = declarations,
|
||||
stubs = stubs,
|
||||
classForwardDeclarations = classForwardDeclarations,
|
||||
protocolForwardDeclarations = protocolForwardDeclarations,
|
||||
additionalImports = emptyList(),
|
||||
additionalImports = emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Class which have static property must have forward declaration
|
||||
*
|
||||
* ```
|
||||
* @class Foo;
|
||||
*
|
||||
* @interface Foo
|
||||
* @property (class) Foo
|
||||
* @end
|
||||
* ```
|
||||
*/
|
||||
private fun getClassForwardDeclarations(declarations: List<ObjCExportStub>): Set<ObjCClassForwardDeclaration> {
|
||||
return declarations
|
||||
.filterIsInstance<ObjCClass>()
|
||||
.filter { clazz ->
|
||||
clazz.members
|
||||
.filterIsInstance<ObjCProperty>()
|
||||
.any { property ->
|
||||
val className = (property.type as? ObjCClassType)?.className == clazz.name
|
||||
val static = property.propertyAttributes.contains("class")
|
||||
className && static
|
||||
}
|
||||
}.map { clazz ->
|
||||
ObjCClassForwardDeclaration(clazz.name)
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
private fun getProtocolForwardDeclarations(declarations: List<ObjCExportStub>) = declarations
|
||||
.filterIsInstance<ObjCClass>()
|
||||
.flatMap { it.superProtocols }
|
||||
.toSet()
|
||||
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
fun KtFileSymbol.translateToObjCExportStubs(): List<ObjCExportStub> {
|
||||
return listOfNotNull(translateToObjCTopLevelInterfaceFileFacade()) + getFileScope().getClassifierSymbols()
|
||||
.sortedWith(StableClassifierOrder)
|
||||
.flatMap { classifierSymbol -> classifierSymbol.translateToObjCExportStubs() }
|
||||
}
|
||||
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
internal fun KtSymbol.translateToObjCExportStubs(): List<ObjCExportStub> {
|
||||
return when {
|
||||
this is KtFileSymbol -> translateToObjCExportStubs()
|
||||
this is KtClassOrObjectSymbol && classKind == INTERFACE -> listOfNotNull(translateToObjCProtocol())
|
||||
this is KtClassOrObjectSymbol && classKind == CLASS -> listOfNotNull(translateToObjCClass())
|
||||
this is KtClassOrObjectSymbol && classKind == OBJECT -> listOfNotNull(translateToObjCObject())
|
||||
this is KtConstructorSymbol -> translateToObjCConstructors()
|
||||
this is KtPropertySymbol -> listOfNotNull(translateToObjCProperty())
|
||||
this is KtFunctionSymbol -> listOfNotNull(translateToObjCMethod())
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -6,7 +6,6 @@ import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getAllMembers
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrProtocolName
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getSuperClassSymbolNotAny
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
||||
@@ -32,9 +31,10 @@ fun KtClassOrObjectSymbol.translateToObjCObject(): ObjCClass? {
|
||||
val superClassGenerics: List<ObjCNonNullReferenceType> = emptyList()
|
||||
val objectMembers = getDefaultMembers()
|
||||
|
||||
getAllMembers().flatMap { it.translateToObjCExportStubs() }.forEach {
|
||||
objectMembers.add(it)
|
||||
}
|
||||
getMemberScope().getCallableSymbols()
|
||||
.sortedWith(StableCallableOrder)
|
||||
.flatMap { it.translateToObjCExportStubs() }
|
||||
.forEach { objectMembers.add(it) }
|
||||
|
||||
return ObjCInterfaceImpl(
|
||||
name.objCName,
|
||||
@@ -97,7 +97,8 @@ private fun KtClassOrObjectSymbol.getDefaultMembers(): MutableList<ObjCExportStu
|
||||
*/
|
||||
private fun KtClassOrObjectSymbol.toPropertyType() = ObjCClassType(
|
||||
this.classIdIfNonLocal!!.shortClassName.asString(),
|
||||
emptyList()
|
||||
emptyList(),
|
||||
classIdIfNonLocal!!
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
+7
-13
@@ -8,13 +8,12 @@ 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.types.KtClassType
|
||||
import org.jetbrains.kotlin.analysis.api.types.KtClassTypeQualifier
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCComment
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocolImpl
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.toNameAttributes
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDeclaredMembers
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDeclaredSuperInterfaceSymbols
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isCloneable
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
@@ -26,9 +25,10 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? {
|
||||
// TODO: Check error type!
|
||||
val name = getObjCClassOrProtocolName()
|
||||
|
||||
val members = getDeclaredMembers()
|
||||
.sortedWith(StableSymbolOrder)
|
||||
val members = getMemberScope().getCallableSymbols()
|
||||
.sortedWith(StableCallableOrder)
|
||||
.flatMap { it.translateToObjCExportStubs() }
|
||||
.toList()
|
||||
|
||||
val comment: ObjCComment? = annotationsList.translateToObjCComment()
|
||||
|
||||
@@ -44,14 +44,8 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? {
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
internal fun KtClassOrObjectSymbol.superProtocols(): List<String> {
|
||||
return superTypes
|
||||
.asSequence()
|
||||
.filter { type -> !type.isAny }
|
||||
.mapNotNull { type -> type as? KtClassType }
|
||||
.flatMap { type -> type.qualifiers }
|
||||
.mapNotNull { qualifier -> qualifier as? KtClassTypeQualifier.KtResolvedClassTypeQualifier }
|
||||
.mapNotNull { it.symbol as? KtClassOrObjectSymbol }
|
||||
.filter { superInterface -> superInterface.classKind == KtClassKind.INTERFACE }
|
||||
return getDeclaredSuperInterfaceSymbols()
|
||||
.filter { superInterface -> !superInterface.isCloneable }
|
||||
.map { superInterface -> superInterface.getObjCClassOrProtocolName().objCName }
|
||||
.toList()
|
||||
}
|
||||
+22
-5
@@ -4,6 +4,8 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.KtStarTypeProjection
|
||||
import org.jetbrains.kotlin.analysis.api.KtTypeArgumentWithVariance
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.types.KtErrorType
|
||||
import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType
|
||||
@@ -79,9 +81,9 @@ private fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReference
|
||||
}
|
||||
|
||||
/* Check if inline type represents 'regular' inline class */
|
||||
val classSymbol: KtClassOrObjectSymbol? = if (classId != null) getClassOrObjectSymbolByClassId(classId) else null
|
||||
run check@{
|
||||
if (classId == null) return@check
|
||||
val classSymbol = getClassOrObjectSymbolByClassId(classId) ?: return@check
|
||||
if (classSymbol !is KtNamedClassOrObjectSymbol) return@check
|
||||
if (classSymbol.isInline) return ObjCIdType
|
||||
}
|
||||
@@ -112,15 +114,30 @@ private fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReference
|
||||
}
|
||||
|
||||
if (fullyExpandedType is KtNonErrorClassType) {
|
||||
val typeName = typesMap[classId]
|
||||
?: fullyExpandedType.classId.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName
|
||||
typesMap[classId]?.let { typeName ->
|
||||
val typeArguments = translateToObjCTypeArguments()
|
||||
return ObjCClassType(typeName, typeArguments, classId = null) // todo: not clear for reader why null is important here!
|
||||
}
|
||||
|
||||
val typeName = fullyExpandedType.classId.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName
|
||||
val typeArguments = translateToObjCTypeArguments()
|
||||
return ObjCClassType(typeName, typeArguments)
|
||||
|
||||
// TODO NOW: create type translation test
|
||||
if (classSymbol?.classKind == KtClassKind.INTERFACE) {
|
||||
return ObjCProtocolType(typeName, classId)
|
||||
}
|
||||
|
||||
return ObjCClassType(typeName, typeArguments, classId)
|
||||
}
|
||||
|
||||
if (fullyExpandedType is KtTypeParameterType) {
|
||||
if (fullyExpandedType.symbol.getContainingSymbol() is KtCallableSymbol) {
|
||||
val definingSymbol = fullyExpandedType.symbol.getContainingSymbol()
|
||||
|
||||
if (definingSymbol is KtCallableSymbol) {
|
||||
return ObjCIdType
|
||||
}
|
||||
|
||||
if (definingSymbol is KtClassOrObjectSymbol && definingSymbol.classKind == KtClassKind.INTERFACE) {
|
||||
return ObjCIdType
|
||||
}
|
||||
/*
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package org.jetbrains.kotlin.objcexport.tests
|
||||
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.jetbrains.kotlin.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCHeader
|
||||
import org.jetbrains.kotlin.objcexport.KtObjCExportConfiguration
|
||||
import org.jetbrains.kotlin.objcexport.KtObjCExportSession
|
||||
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
|
||||
import org.jetbrains.kotlin.objcexport.translateToObjCHeader
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* Tests logic of filtering protocols and classes which must be forwarded or excluded when dependency is used
|
||||
* For full tests of dependencies see [ObjCDependenciesTypesTest]
|
||||
*/
|
||||
class ForwardedClassesAndProtocolsDependenciesTest(
|
||||
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
|
||||
) {
|
||||
@Test
|
||||
fun `test - iterator`() {
|
||||
doTest(
|
||||
code = """
|
||||
val i: Iterator<Int>
|
||||
""",
|
||||
protocols = setOf("Iterator"),
|
||||
classes = emptySet()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - array`() {
|
||||
doTest(
|
||||
code = """
|
||||
val i: Array<Int>
|
||||
""",
|
||||
protocols = setOf("Iterator"),
|
||||
classes = setOf("Array")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - string builder`() {
|
||||
doTest(
|
||||
code = """
|
||||
val i: StringBuilder
|
||||
""",
|
||||
protocols = setOf("CharSequence", "Appendable", "Iterator"),
|
||||
classes = setOf("StringBuilder", "CharArray", "CharIterator")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - declared symbols`() {
|
||||
doTest(
|
||||
code = """
|
||||
open class ClassA
|
||||
class ClassB: ClassA()
|
||||
interface InterfaceA
|
||||
interface InterfaceB : InterfaceA()
|
||||
fun getClass(): ClassB = error("error")
|
||||
fun getInterface(): InterfaceB = error("error")
|
||||
""",
|
||||
protocols = setOf("InterfaceB", "InterfaceA"),
|
||||
classes = setOf("ClassB")
|
||||
)
|
||||
}
|
||||
|
||||
private fun doTest(
|
||||
@Language("kotlin") code: String,
|
||||
protocols: Set<String>,
|
||||
classes: Set<String>,
|
||||
) {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(code.trimIndent())
|
||||
val classesAndProtocols = translateClassesAndProtocols(file)
|
||||
|
||||
assertEquals(protocols, classesAndProtocols.protocolForwardDeclarations.toSet(), "Invalid protocols set")
|
||||
assertEquals(classes, classesAndProtocols.classForwardDeclarations.map { it.className }.toSet(), "Invalid classes set")
|
||||
}
|
||||
|
||||
private fun translateClassesAndProtocols(file: KtFile): ObjCHeader {
|
||||
return analyze(file) {
|
||||
KtObjCExportSession(KtObjCExportConfiguration()) {
|
||||
translateToObjCHeader(listOf(file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getAllClassOrObjectSymbols
|
||||
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
|
||||
import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class GetAllClassOrObjectSymbolsTest(
|
||||
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
|
||||
) {
|
||||
|
||||
@Test
|
||||
fun `test - no classifiers in file`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile("val foo = 42")
|
||||
analyze(file) {
|
||||
assertEquals(emptyList(), file.getFileSymbol().getAllClassOrObjectSymbols())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - single class in file`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile("class Foo")
|
||||
analyze(file) {
|
||||
assertEquals(listOf(file.getClassOrFail("Foo")), file.getFileSymbol().getAllClassOrObjectSymbols())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - multiple nested classes in file`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
class A {
|
||||
class B {
|
||||
class C
|
||||
}
|
||||
}
|
||||
|
||||
class D {
|
||||
class E
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
analyze(file) {
|
||||
assertEquals(
|
||||
listOf(
|
||||
file.getClassOrFail("A"),
|
||||
file.getClassOrFail("A").getMemberScope().getClassOrFail("B"),
|
||||
file.getClassOrFail("A").getMemberScope().getClassOrFail("B").getMemberScope().getClassOrFail("C"),
|
||||
|
||||
file.getClassOrFail("D"),
|
||||
file.getClassOrFail("D").getMemberScope().getClassOrFail("E")
|
||||
),
|
||||
file.getFileSymbol().getAllClassOrObjectSymbols()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDeclaredSuperInterfaceSymbols
|
||||
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
|
||||
import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class GetSuperInterfacesTest(
|
||||
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
|
||||
) {
|
||||
@Test
|
||||
fun `test - transitive super interfaces`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
interface A
|
||||
interface B
|
||||
interface C
|
||||
|
||||
interface X: A, B
|
||||
interface Y : C
|
||||
|
||||
class Foo: X, Y
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
analyze(file) {
|
||||
val foo = file.getClassOrFail("Foo")
|
||||
|
||||
assertEquals(
|
||||
listOf(file.getClassOrFail("X"), file.getClassOrFail("Y")),
|
||||
foo.getDeclaredSuperInterfaceSymbols()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - super interface and super class`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
interface A
|
||||
interface B
|
||||
abstract class X
|
||||
class Foo: X(), A, B
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
analyze(file) {
|
||||
assertEquals(
|
||||
listOf(file.getClassOrFail("A"), file.getClassOrFail("B")),
|
||||
file.getClassOrFail("Foo").getDeclaredSuperInterfaceSymbols()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - subclassing Any explicitly`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
interface A
|
||||
interface B
|
||||
class Foo: Any(), A, B
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
analyze(file) {
|
||||
assertEquals(
|
||||
listOf(file.getClassOrFail("A"), file.getClassOrFail("B")),
|
||||
file.getClassOrFail("Foo").getDeclaredSuperInterfaceSymbols()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package org.jetbrains.kotlin.objcexport.tests
|
||||
|
||||
import junit.framework.TestCase.assertFalse
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isCloneable
|
||||
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
|
||||
import org.jetbrains.kotlin.objcexport.testUtils.getPropertyOrFail
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class IsCloneableTest(
|
||||
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
|
||||
) {
|
||||
|
||||
@Test
|
||||
fun `test - is cloneable`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile("val foo: Cloneable")
|
||||
analyze(file) {
|
||||
val foo = file.getPropertyOrFail("foo")
|
||||
assertTrue(foo.type.isCloneable)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - is iterator cloneable`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
class A
|
||||
val foo: A
|
||||
""".trimIndent()
|
||||
)
|
||||
analyze(file) {
|
||||
val foo = file.getPropertyOrFail("foo")
|
||||
assertFalse(foo.type.isCloneable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context(KtAnalysisSession)
|
||||
private val KtPropertySymbol.type: KtClassOrObjectSymbol
|
||||
get() {
|
||||
return getter?.returnType?.expandedClassSymbol!!
|
||||
}
|
||||
Reference in New Issue
Block a user