[ObjCExport] translateToObjCType: Filter konan primitives and include supertypes for mapped types
^KT-65348 Fixed
This commit is contained in:
committed by
Space Team
parent
7628d1a4aa
commit
7cf6a86083
+52
-5
@@ -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)
|
||||
}
|
||||
|
||||
+56
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-31
@@ -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,
|
||||
|
||||
+34
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user