[ObjCExport] translateToObjCType: Filter konan primitives and include supertypes for mapped types

^KT-65348 Fixed
This commit is contained in:
Sebastian Sellmair
2024-02-09 14:57:11 +01:00
committed by Space Team
parent 7628d1a4aa
commit 7cf6a86083
5 changed files with 185 additions and 47 deletions
@@ -5,15 +5,62 @@
package org.jetbrains.kotlin.objcexport
interface KtObjCExportSession {
import org.jetbrains.kotlin.utils.getOrPutNullable
sealed interface KtObjCExportSession {
val configuration: KtObjCExportConfiguration
}
private val KtObjCExportSession.private: KtObjCExportSessionPrivate
get() = when (this) {
is KtObjCExportSessionPrivate -> this
}
private interface KtObjCExportSessionPrivate : KtObjCExportSession {
val cache: MutableMap<Any, Any?>
}
inline fun <T> KtObjCExportSession(
configuration: KtObjCExportConfiguration,
block: KtObjCExportSession.() -> T,
): T {
return object : KtObjCExportSession {
override val configuration: KtObjCExportConfiguration = configuration
}.block()
}
return KtObjCExportSessionImpl(configuration, hashMapOf()).block()
}
@PublishedApi
internal class KtObjCExportSessionImpl(
override val configuration: KtObjCExportConfiguration,
override val cache: MutableMap<Any, Any?>,
) : KtObjCExportSessionPrivate
/**
* Will cache the given [computation] in the current [KtObjCExportSession].
* Example Usage: Caching the ObjC name of 'MutableSet'
*
* ```kotlin
* context(KtObjCExportSession)
* val mutableSetObjCName get() = cached("mutableSetOfObjCName") {
* "MutableSet".getObjCKotlinStdlibClassOrProtocolName().objCName
* // ^
* // Requires KtObjCExportSession
* }
* ```
*/
context(KtObjCExportSession)
internal inline fun <reified T> cached(key: Any, noinline computation: () -> T): T {
return cached(T::class.java, key, computation)
}
/**
* @see cached
*/
context(KtObjCExportSession)
private fun <T> cached(typeOfT: Class<T>, key: Any, computation: () -> T): T {
val value = private.cache.getOrPutNullable(key) {
computation()
}
return typeOfT.cast(value)
}
@@ -0,0 +1,56 @@
/*
* 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.types.KtType
import org.jetbrains.kotlin.backend.konan.objcexport.NSNumberKind
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCClassType
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.name.ClassId
/**
* Will translate [this] type to the corresponding ObjC equivalent.
* e.g. `kotlin.String` -> `NSString`
* e.g. `kotlin.collections.List` -> `NSArray`
*
* This function will also look through supertypes (e.g., custom implementations of List will still be mapped to NSArray).
* Returns `null` if the type is not mapped to any ObjC equivalent
*/
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtType.translateToMappedObjCTypeOrNull(): ObjCClassType? {
return listOf(this).plus(this.getAllSuperTypes()).firstNotNullOfOrNull find@{ type ->
val classId = type.expandedClassSymbol?.classIdIfNonLocal ?: return@find null
mappedObjCTypeNames[classId]?.let { mappedTypeName ->
return@find ObjCClassType(mappedTypeName, type.translateTypeArgumentsToObjC())
}
}
}
context(KtAnalysisSession, KtObjCExportSession)
private val mappedObjCTypeNames: Map<ClassId, String>
get() = cached("mappedObjCTypeNames") {
buildMap {
this[ClassId.topLevel(StandardNames.FqNames.list)] = "NSArray"
this[ClassId.topLevel(StandardNames.FqNames.mutableList)] = "NSMutableArray"
this[ClassId.topLevel(StandardNames.FqNames.set)] = "NSSet"
this[ClassId.topLevel(StandardNames.FqNames.mutableSet)] = "MutableSet".getObjCKotlinStdlibClassOrProtocolName().objCName
this[ClassId.topLevel(StandardNames.FqNames.map)] = "NSDictionary"
this[ClassId.topLevel(StandardNames.FqNames.mutableMap)] = "MutableDictionary".getObjCKotlinStdlibClassOrProtocolName().objCName
this[ClassId.topLevel(StandardNames.FqNames.string.toSafe())] = "NSString"
NSNumberKind.entries.forEach { number ->
val numberClassId = number.mappedKotlinClassId
if (numberClassId != null) {
this[numberClassId] = numberClassId.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName
}
}
}
}
@@ -11,8 +11,8 @@ import org.jetbrains.kotlin.analysis.api.types.KtErrorType
import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.analysis.api.types.KtTypeParameterType
import org.jetbrains.kotlin.backend.konan.KonanPrimitiveType
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.StandardClassIds
@@ -76,6 +76,16 @@ internal fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReferenc
return ObjCIdType
}
/* Priority: Check if this type is mapped into a known ObjCType (e.g. String -> NSString */
translateToMappedObjCTypeOrNull()?.let { mappedObjCType ->
return mappedObjCType
}
/* Kotlin Native Primitive Type cannot be mapped to a reference type here */
if (classId in kotlinNativePrimitiveClassIds) {
return ObjCIdType
}
if (isObjCObjectType()) {
return ObjCIdType
}
@@ -93,34 +103,9 @@ internal fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReferenc
return inlineTargetType.mapToReferenceTypeIgnoringNullability()
}
/**
* Simplified version of [org.jetbrains.kotlin.backend.konan.objcexport.CustomTypeMapper]
*/
val typesMap = mutableMapOf<ClassId, String>().apply {
this[ClassId.topLevel(StandardNames.FqNames.list)] = "NSArray"
this[ClassId.topLevel(StandardNames.FqNames.mutableList)] = "NSMutableArray"
this[ClassId.topLevel(StandardNames.FqNames.set)] = "NSSet"
this[ClassId.topLevel(StandardNames.FqNames.mutableSet)] = "MutableSet".getObjCKotlinStdlibClassOrProtocolName().objCName
this[ClassId.topLevel(StandardNames.FqNames.map)] = "NSDictionary"
this[ClassId.topLevel(StandardNames.FqNames.mutableMap)] = "MutableDictionary".getObjCKotlinStdlibClassOrProtocolName().objCName
this[ClassId.topLevel(StandardNames.FqNames.string.toSafe())] = "NSString"
}
NSNumberKind.entries.forEach { number ->
val numberClassId = number.mappedKotlinClassId
if (numberClassId != null) {
typesMap[numberClassId] = numberClassId.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName
}
}
if (fullyExpandedType is KtNonErrorClassType) {
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()
val typeArguments = translateTypeArgumentsToObjC()
// TODO NOW: create type translation test
if (classSymbol?.classKind == KtClassKind.INTERFACE) {
@@ -152,7 +137,7 @@ internal fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReferenc
context(KtAnalysisSession, KtObjCExportSession)
private fun KtType.translateToObjCTypeArguments(): List<ObjCNonNullReferenceType> {
internal fun KtType.translateTypeArgumentsToObjC(): List<ObjCNonNullReferenceType> {
if (this !is KtNonErrorClassType) return emptyList()
/* See special casing below */
@@ -197,10 +182,10 @@ private fun KtType.isBinaryRepresentationNullable(): Boolean {
/**
* Types to be "hidden" during mapping, i.e. represented as `id`.
* Types to be "hidden" during mapping, i.e., represented as `id`.
*
* Currently contains super types of classes handled by custom type mappers.
* Note: can be generated programmatically, but requires stdlib in this case.
* Currently, it contains super types of classes handled by custom type mappers.
* Note: It can be generated programmatically, but requires stdlib in this case.
*/
private val hiddenClassIds: Set<ClassId> = listOf(
"kotlin.Any",
@@ -214,6 +199,10 @@ private val hiddenClassIds: Set<ClassId> = listOf(
"kotlin.collections.MutableIterable"
).map { ClassId.topLevel(FqName(it)) }.toSet()
private val kotlinNativePrimitiveClassIds: Set<ClassId> =
KonanPrimitiveType.entries.map { it.classId }.toSet()
private val collectionClassIds = setOf(
StandardClassIds.List, StandardClassIds.MutableList,
StandardClassIds.Set, StandardClassIds.MutableSet,
@@ -0,0 +1,34 @@
/*
* 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.KtObjCExportConfiguration
import org.jetbrains.kotlin.objcexport.KtObjCExportSession
import org.jetbrains.kotlin.objcexport.cached
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertSame
import kotlin.test.fail
class KtObjCExportSessionCacheTest {
data class Payload(val key: Any)
@Test
fun `test - cache with string key`() {
KtObjCExportSession(KtObjCExportConfiguration()) {
val cachedInstance1 = cached("instance1") { Payload("1") }
val cachedInstance2 = cached("instance2") { Payload("2") }
assertSame(cachedInstance1, cached("instance1") { fail("Expected instance1 to be cached") })
assertSame(cachedInstance2, cached("instance2") { fail("Expected instance2 to be cached") })
assertEquals(cachedInstance1, Payload("1"))
assertEquals(cachedInstance2, Payload("2"))
}
}
}