From 3cd20bd7b0d0e0707f14f35136e677e61678ff76 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Wed, 24 Jan 2024 13:48:59 +0100 Subject: [PATCH] [ObjCExport] Support generic type parameters for ObjC translation ^KT-65167 --- .../objcexport/translateToObjCMethod.kt | 26 ++++++---- .../kotlin/objcexport/translateToObjCType.kt | 52 +++++++++++++------ .../tests/ObjCExportTypeTranslationTest.kt | 41 +++++++++++++++ 3 files changed, 93 insertions(+), 26 deletions(-) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt index 264ce97349a..163229263a8 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt @@ -293,23 +293,27 @@ fun MethodBridge.valueParametersAssociated( function: KtFunctionLikeSymbol, ): List> { - val allParameters = function.valueParameters.iterator() - if (!allParameters.hasNext()) return emptyList() - val skipFirstKotlinParameter = when (this.receiver) { MethodBridgeReceiver.Static -> false - MethodBridgeReceiver.Factory, MethodBridgeReceiver.Instance -> true - } - if (skipFirstKotlinParameter && allParameters.hasNext()) { - allParameters.next() + MethodBridgeReceiver.Instance -> false + MethodBridgeReceiver.Factory -> true } - return this.valueParameters.map { - when (it) { - is MethodBridgeValueParameter.Mapped -> it to allParameters.next() + val allParameters = function.valueParameters.let { valueParameters -> + if (skipFirstKotlinParameter) valueParameters.drop(1) + else valueParameters + } + + if (allParameters.isEmpty()) return emptyList() + + + return this.valueParameters.mapIndexed { index, valueParameterBridge -> + when (valueParameterBridge) { + is MethodBridgeValueParameter.Mapped -> valueParameterBridge to allParameters[index] + is MethodBridgeValueParameter.SuspendCompletion, is MethodBridgeValueParameter.ErrorOutParameter, - -> it to null + -> valueParameterBridge to null } } } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt index 3c7744d2a87..5a2f186019f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt @@ -1,9 +1,13 @@ package org.jetbrains.kotlin.objcexport 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.KtNamedClassOrObjectSymbol 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.objcexport.* import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.name.ClassId @@ -45,10 +49,10 @@ internal fun KtType.translateToObjCType(typeBridge: TypeBridge): ObjCType { context(KtAnalysisSession) private fun KtType.isBinaryRepresentationNullable(): Boolean { - if (fullyExpandedType.isMarkedNullable) return true + if (fullyExpandedType.canBeNull) return true getInlineTargetTypeOrNull()?.let { inlineTargetType -> - if (inlineTargetType.isMarkedNullable) return true + if (inlineTargetType.canBeNull) return true } return false @@ -116,12 +120,26 @@ private fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReference } } - val typeName = typesMap[classId] - ?: classId!!.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName + if (fullyExpandedType is KtNonErrorClassType) { + val typeName = typesMap[classId] + ?: fullyExpandedType.classId.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName - val typeArguments = translateToObjCTypeArguments() + val typeArguments = translateToObjCTypeArguments() + return ObjCClassType(typeName, typeArguments) + } - return ObjCClassType(typeName, typeArguments) + if (fullyExpandedType is KtTypeParameterType) { + if (fullyExpandedType.symbol.getContainingSymbol() is KtCallableSymbol) { + return ObjCIdType + } + /* + Todo: K1 has some name mangling logic here? + */ + return ObjCGenericTypeParameterUsage(fullyExpandedType.name.asString().toValidObjCSwiftIdentifier()) + } + + /* We cannot translate this, lets try to be lenient and emit the error type? */ + return objCErrorType } @@ -132,19 +150,23 @@ private fun KtType.translateToObjCTypeArguments(): List typeArgument.type } - .map { typeArgumentType -> - - /* - Kotlin `null` keys and values are represented as `NSNull` singleton in collections - */ - if (isKnownCollectionType && typeArgumentType.isMarkedNullable) return@map ObjCIdType - typeArgumentType.mapToReferenceTypeIgnoringNullability() + return ownTypeArguments.map { typeArgument -> + when (typeArgument) { + is KtStarTypeProjection -> ObjCIdType + is KtTypeArgumentWithVariance -> { + /* + Kotlin `null` keys and values are represented as `NSNull` singleton in collections + */ + if (isKnownCollectionType && typeArgument.type.isMarkedNullable) return@map ObjCIdType + typeArgument.type.mapToReferenceTypeIgnoringNullability() + } } + } } +context(KtAnalysisSession) private fun ObjCNonNullReferenceType.withNullabilityOf(kotlinType: KtType): ObjCReferenceType { - return if (kotlinType.nullability.isNullable) { + return if (kotlinType.isBinaryRepresentationNullable()) { ObjCNullableReferenceType(this) } else { this diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportTypeTranslationTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportTypeTranslationTest.kt index 84df84c04c5..876b976a72f 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportTypeTranslationTest.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportTypeTranslationTest.kt @@ -403,6 +403,19 @@ class ObjCExportTypeTranslationTest( assertEquals("T _Nullable -> void", header.renderTypesOfSymbol("foo")) } + @Test + fun `test - class with generic function`() { + val header = header( + """ + class A { + fun foo(value: T) = Unit + } + """.trimIndent() + ) + + assertEquals("id -> void", header.renderTypesOfSymbol("foo")) + } + @Test fun `test - generic class with bounds with function`() { val header = header( @@ -416,6 +429,34 @@ class ObjCExportTypeTranslationTest( assertEquals("T -> void", header.renderTypesOfSymbol("foo")) } + @Test + fun `test - nested classes with same type parameter`() { + val header = header( + """ + class A { + class B { + fun foo(value: T) = Unit + } + } + """.trimIndent() + ) + + assertEquals("T -> void", header.renderTypesOfSymbol("foo")) + } + + @Test + fun `test - classes with same type parameter as function`() { + val header = header( + """ + class A { + fun foo(value: T) = Unit + } + """.trimIndent() + ) + + assertEquals("id -> void", header.renderTypesOfSymbol("foo")) + } + @Test fun `test - unresolved error type`() { val header = header(