[ObjCExport] Add companion object translation

KT-64953: Required part for enum translation
This commit is contained in:
eugene.levenetc
2024-02-13 14:35:09 +01:00
committed by Space Team
parent acf2296590
commit 97f585dd25
12 changed files with 248 additions and 27 deletions
@@ -0,0 +1,8 @@
package org.jetbrains.kotlin.objcexport.analysisApiUtils
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
internal val KtClassOrObjectSymbol.isCompanion: Boolean
get() = classKind == KtClassKind.COMPANION_OBJECT
@@ -173,6 +173,6 @@ private fun KtClassOrObjectSymbol.isInlined(): Boolean {
}
private fun KtClassKind.isVisibleInObjC(): Boolean = when (this) {
CLASS, ENUM_CLASS, INTERFACE, OBJECT -> true
COMPANION_OBJECT, ANONYMOUS_OBJECT, ANNOTATION_CLASS -> false
CLASS, ENUM_CLASS, INTERFACE, OBJECT, COMPANION_OBJECT -> true
ANONYMOUS_OBJECT, ANNOTATION_CLASS -> false
}
@@ -0,0 +1,31 @@
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.ObjCClassType
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProperty
import org.jetbrains.kotlin.backend.konan.objcexport.swiftNameAttribute
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isCompanion
/**
* If object class has companion object it needs to have property which returns this companion.
* To check whether class has companion object see [needsCompanionProperty]
*/
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtClassOrObjectSymbol.buildCompanionProperty(): ObjCProperty {
val companion = this.getStaticMemberScope().getClassifierSymbols().toList()
.firstOrNull { (it as? KtClassOrObjectSymbol)?.isCompanion == true }
val typeName = (companion as KtClassOrObjectSymbol).getObjCClassOrProtocolName()
val propertyName = ObjCPropertyNames.companionObjectPropertyName
return ObjCProperty(
propertyName,
null,
null,
ObjCClassType(typeName.objCName, classId = companion.classIdIfNonLocal),
listOf("class", "readonly"),
getterName = propertyName,
declarationAttributes = listOf(swiftNameAttribute(propertyName))
)
}
@@ -0,0 +1,15 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isCompanion
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslator.needCompanionObjectProperty]
*/
context(KtAnalysisSession)
internal val KtClassOrObjectSymbol.needsCompanionProperty: Boolean
get() {
return this.getStaticMemberScope().getClassifierSymbols()
.any { (it as? KtClassOrObjectSymbol)?.isCompanion == true }
}
@@ -29,10 +29,12 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
val superProtocols: List<String> = superProtocols()
val constructors = getMemberScope().getConstructors().filter { !it.hasExportForCompilerAnnotation }
val members: List<ObjCExportStub> = getMemberScope().getCallableSymbols().plus(constructors)
val members = getMemberScope().getCallableSymbols().plus(constructors)
.sortedWith(StableCallableOrder)
.flatMap { it.translateToObjCExportStubs() }
.toList()
.toMutableList()
if (needsCompanionProperty) members.add(buildCompanionProperty())
val categoryName: String? = null
@@ -57,15 +59,7 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
)
}
private fun abbreviate(name: String): String {
val normalizedName = name
.replaceFirstChar(Char::uppercaseChar)
.replace("-|\\.".toRegex(), "_")
val uppers = normalizedName.filterIndexed { index, character -> index == 0 || character.isUpperCase() }
if (uppers.length >= 3) return uppers
return normalizedName
}
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtNonErrorClassType.getSuperClassName(): ObjCExportClassOrProtocolName? {
@@ -52,6 +52,7 @@ fun translateToObjCHeader(files: List<KtFile>): ObjCHeader {
KtClassKind.CLASS -> symbol.translateToObjCClass()
KtClassKind.OBJECT -> symbol.translateToObjCObject()
KtClassKind.ENUM_CLASS -> symbol.translateToObjCClass()
KtClassKind.COMPANION_OBJECT -> symbol.translateToObjCObject()
else -> return result
} ?: return result
@@ -6,24 +6,24 @@ 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.isCompanion
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
context(KtAnalysisSession, KtObjCExportSession)
fun KtClassOrObjectSymbol.translateToObjCObject(): ObjCClass? {
require(classKind == KtClassKind.OBJECT)
require(classKind == KtClassKind.OBJECT || classKind == KtClassKind.COMPANION_OBJECT)
if (!isVisibleInObjC()) return null
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()
val name = getObjCClassOrProtocolName()
val attributes = (if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList()) + name.toNameAttributes()
val comment: ObjCComment? = annotationsList.translateToObjCComment()
val origin: ObjCExportStubOrigin = getObjCExportStubOrigin()
val superProtocols: List<String> = superProtocols()
val categoryName: String? = null
val generics: List<ObjCGenericTypeDeclaration> = emptyList()
val objectMembers = getDefaultMembers()
val objectMembers = getDefaultMembers().toMutableList()
val superClass = translateSuperClass()
@@ -47,7 +47,7 @@ fun KtClassOrObjectSymbol.translateToObjCObject(): ObjCClass? {
}
context(KtAnalysisSession, KtObjCExportSession)
private fun KtClassOrObjectSymbol.getDefaultMembers(): MutableList<ObjCExportStub> {
private fun KtClassOrObjectSymbol.getDefaultMembers(): List<ObjCExportStub> {
val result = mutableListOf<ObjCExportStub>()
val allocWithZoneParameter = ObjCParameter("zone", null, ObjCRawType("struct _NSZone *"), null)
@@ -91,8 +91,9 @@ private fun KtClassOrObjectSymbol.getDefaultMembers(): MutableList<ObjCExportStu
* Use translateToObjCReferenceType() to make type
* See also: [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapReferenceType]
*/
context(KtAnalysisSession, KtObjCExportSession)
private fun KtClassOrObjectSymbol.toPropertyType() = ObjCClassType(
this.classIdIfNonLocal!!.shortClassName.asString(),
getObjCClassOrProtocolName().objCName,
emptyList(),
classIdIfNonLocal!!
)
@@ -102,7 +103,8 @@ private fun KtClassOrObjectSymbol.toPropertyType() = ObjCClassType(
*/
context(KtAnalysisSession, KtObjCExportSession)
private fun getObjectInstanceSelector(objectSymbol: KtClassOrObjectSymbol): String {
return objectSymbol.getObjCClassOrProtocolName().objCName.lowercase()
return if (objectSymbol.isCompanion) ObjCPropertyNames.companionObjectPropertyName
else objectSymbol.getObjCClassOrProtocolName().objCName.lowercase()
}
/**
@@ -112,5 +114,4 @@ context(KtAnalysisSession, KtObjCExportSession)
private fun getObjectPropertySelector(descriptor: KtClassOrObjectSymbol): String {
val collides = ObjCPropertyNames.objectPropertyName == getObjectInstanceSelector(descriptor)
return ObjCPropertyNames.objectPropertyName + (if (collides) "_" else "")
}
}
@@ -104,15 +104,13 @@ internal fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReferenc
}
if (fullyExpandedType is KtNonErrorClassType) {
val typeName = fullyExpandedType.classId.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName
val typeArguments = translateTypeArgumentsToObjC()
// TODO NOW: create type translation test
if (classSymbol?.classKind == KtClassKind.INTERFACE) {
return ObjCProtocolType(typeName, classId)
return if (classSymbol?.classKind == KtClassKind.INTERFACE) {
ObjCProtocolType(fullyExpandedType.objCTypeName, classId)
} else {
ObjCClassType(fullyExpandedType.objCTypeName, translateTypeArgumentsToObjC(), classId)
}
return ObjCClassType(typeName, typeArguments, classId)
}
if (fullyExpandedType is KtTypeParameterType) {
@@ -135,6 +133,12 @@ internal fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReferenc
return objCErrorType
}
context(KtAnalysisSession, KtObjCExportSession)
private val KtNonErrorClassType.objCTypeName: String
get() {
return getClassOrObjectSymbolByClassId(classId)?.getObjCClassOrProtocolName()?.objCName
?: classId.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName
}
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtType.translateTypeArgumentsToObjC(): List<ObjCNonNullReferenceType> {
@@ -0,0 +1,58 @@
package org.jetbrains.kotlin.objcexport.tests
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.objcexport.needsCompanionProperty
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class NeedsCompanionPropertyTest(
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
) {
@Test
fun `test - no companion`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class NoCompanion {
}
""".trimIndent()
)
analyze(file) {
assertFalse(file.getClassOrFail("NoCompanion").needsCompanionProperty)
}
}
@Test
fun `test - empty companion`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class EmptyCompanion {
companion object {}
}
""".trimIndent()
)
analyze(file) {
assertTrue(file.getClassOrFail("EmptyCompanion").needsCompanionProperty)
}
}
@Test
fun `test - simple companion`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class SimpleCompanion {
companion object {
const val a = 42
}
}
""".trimIndent()
)
analyze(file) {
assertTrue(file.getClassOrFail("SimpleCompanion").needsCompanionProperty)
}
}
}