From a2d76d739c93c181af3e7043bb9a24206ddd5d05 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Thu, 15 Feb 2024 13:28:41 +0100 Subject: [PATCH] [ObjCExport] Support translation of enum classes ^KT-64953 Fixed --- .../getInlineTargetTypeOrNull.kt | 2 +- .../analysisApiUtils/getMethodBridge.kt | 16 ++-- .../analysisApiUtils/isFakeOverride.kt | 27 ++++++ .../objcexport/analysisApiUtils/isHashCode.kt | 19 ++++ .../objcexport/analysisApiUtils/members.kt | 52 ----------- .../kotlin/objcexport/getObjCPropertyName.kt | 6 +- .../kotlin/objcexport/translateEnumMembers.kt | 86 +++++++++++++++++++ .../kotlin/objcexport/translateToObjCClass.kt | 41 ++++++--- .../objcexport/translateToObjCConstructor.kt | 34 ++++++-- ...tStubs.kt => translateToObjCExportStub.kt} | 12 ++- .../objcexport/translateToObjCHeader.kt | 2 +- .../objcexport/translateToObjCMethod.kt | 27 +++--- .../objcexport/translateToObjCObject.kt | 21 ++--- .../objcexport/translateToObjCProtocol.kt | 4 +- ...nslateToObjCTopLevelInterfaceFileFacade.kt | 2 +- .../kotlin/objcexport/tests/IsHashCodeTest.kt | 67 +++++++++++++++ .../tests/ObjCExportHeaderGeneratorTest.kt | 1 - 17 files changed, 292 insertions(+), 127 deletions(-) create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isFakeOverride.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isHashCode.kt delete mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateEnumMembers.kt rename native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/{translateToObjCExportStubs.kt => translateToObjCExportStub.kt} (53%) create mode 100644 native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsHashCodeTest.kt diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getInlineTargetTypeOrNull.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getInlineTargetTypeOrNull.kt index 16de1053ce5..3330736d41f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getInlineTargetTypeOrNull.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getInlineTargetTypeOrNull.kt @@ -25,7 +25,7 @@ context(KtAnalysisSession) internal fun KtNamedClassOrObjectSymbol.getInlineTargetTypeOrNull(): KtType? { if (!isInlineIncludingKotlinNativeSpecialClasses()) return null - val constructor = getMemberScope().getConstructors() + val constructor = getDeclaredMemberScope().getConstructors() .find { constructor -> constructor.isPrimary && constructor.valueParameters.size == 1 } ?: return null diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt index d5a96c580a7..727f75a1dad 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt @@ -1,10 +1,7 @@ package org.jetbrains.kotlin.objcexport.analysisApiUtils import org.jetbrains.kotlin.analysis.api.KtAnalysisSession -import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol -import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol -import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol -import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol +import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.types.KtFunctionalType import org.jetbrains.kotlin.analysis.api.types.KtType import org.jetbrains.kotlin.backend.konan.KonanPrimitiveType @@ -35,7 +32,7 @@ internal fun KtFunctionLikeSymbol.getFunctionMethodBridge(): MethodBridge { } context(KtAnalysisSession, KtObjCExportSession) -internal fun KtPropertySymbol.getPropertyMethodBridge(): MethodBridge { +internal fun KtVariableLikeSymbol.getPropertyMethodBridge(): MethodBridge { return MethodBridge( bridgeReturnType(), receiverType, @@ -143,12 +140,9 @@ private fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue { return MethodBridge.ReturnValue.Suspend } - //TODO: handle hashCode -// descriptor.containingDeclaration.let { it is ClassDescriptor && KotlinBuiltIns.isAny(it) } && -// descriptor.name.asString() == "hashCode" -> { -// assert(!convertExceptionsToErrors) -// MethodBridge.ReturnValue.HashCode -// } + if (isHashCode) { + return MethodBridge.ReturnValue.HashCode + } //TODO: handle getter // descriptor is PropertyGetterDescriptor -> { diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isFakeOverride.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isFakeOverride.kt new file mode 100644 index 00000000000..e559eeb15f2 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isFakeOverride.kt @@ -0,0 +1,27 @@ +/* + * 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.symbols.KtSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolOrigin + +/** + * @return `true` when the declaration is considered a `fake override`. + * K2 will differentiate fake-overrides into 'intersection override' and 'substitution override' + * `false` if the symbol is not a fake override + * + * See: + * - [org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.isReal] + * - [KtSymbolOrigin.INTERSECTION_OVERRIDE] + * - [KtSymbolOrigin.SUBSTITUTION_OVERRIDE] + */ +internal val KtSymbol.isFakeOverride: Boolean + get() { + val origin = this.origin + if (origin == KtSymbolOrigin.INTERSECTION_OVERRIDE) return true + if (origin == KtSymbolOrigin.SUBSTITUTION_OVERRIDE) return true + return false + } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isHashCode.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isHashCode.kt new file mode 100644 index 00000000000..6672d6736da --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isHashCode.kt @@ -0,0 +1,19 @@ +/* + * 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.KtCallableSymbol +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.StandardClassIds + +private val hashCodeCallableId = CallableId(StandardClassIds.Any, Name.identifier("hashCode")) + +context(KtAnalysisSession) +internal val KtCallableSymbol.isHashCode: Boolean + get() = this.callableIdIfNonLocal == hashCodeCallableId || + getAllOverriddenSymbols().any { overriddenSymbol -> overriddenSymbol.callableIdIfNonLocal == hashCodeCallableId } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt deleted file mode 100644 index 73c3d7d4056..00000000000 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.jetbrains.kotlin.objcexport.analysisApiUtils - -import org.jetbrains.kotlin.analysis.api.KtAnalysisSession -import org.jetbrains.kotlin.analysis.api.symbols.* - -context(KtAnalysisSession) -internal fun KtClassOrObjectSymbol.getAllMembers(): List { - return getMemberScope() - .getAllSymbols() - .sortedBy { sortMembers(it) } - .filter { member -> member.isVisibleInObjC() } - .toList() -} - -/** - * Returns members explicitly defined in the symbol. All super methods are excluded. - * - * Also handles edge case with covariant method overwrite: - * - * ``` - * interface A { - * fun hello(): Any - * } - * - * interface B: A { - * override fun hello(): String - * } - * - * B.getBaseMembers().isEmpty() == true - * ``` - * - * More context is around [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.isBaseMethod] - */ -context(KtAnalysisSession) -internal fun KtClassOrObjectSymbol.getDeclaredMembers(): List { - return getDeclaredMemberScope() - .getAllSymbols() - .sortedBy { sortMembers(it) } - .filter { member -> - member.isVisibleInObjC() && (member !is KtCallableSymbol || member.getAllOverriddenSymbols().isEmpty()) - } - .toList() -} - -/** - * Temp workaround of [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorKt.makeMethodsOrderStable] - */ -private fun sortMembers(it: KtDeclarationSymbol) = when (it) { - is KtConstructorSymbol -> 0 - is KtFunctionSymbol -> 1 - else -> 2 -} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCPropertyName.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCPropertyName.kt index c14c3aecb5d..c7f1050934d 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCPropertyName.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCPropertyName.kt @@ -6,12 +6,12 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.analysis.api.KtAnalysisSession -import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtVariableLikeSymbol import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportPropertyName -context(KtAnalysisSession, KtObjCExportSession) -fun KtPropertySymbol.getObjCPropertyName(): ObjCExportPropertyName { +context(KtAnalysisSession) +fun KtVariableLikeSymbol.getObjCPropertyName(): ObjCExportPropertyName { val resolveObjCNameAnnotation = resolveObjCNameAnnotation() return ObjCExportPropertyName( diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateEnumMembers.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateEnumMembers.kt new file mode 100644 index 00000000000..13d172856aa --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateEnumMembers.kt @@ -0,0 +1,86 @@ +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.symbols.KtEnumEntrySymbol +import org.jetbrains.kotlin.backend.konan.objcexport.* +import org.jetbrains.kotlin.name.Name + +context(KtAnalysisSession, KtObjCExportSession) +internal fun KtClassOrObjectSymbol.translateEnumMembers(): List { + if (classKind != KtClassKind.ENUM_CLASS) return emptyList() + return getEnumEntries() + listOf(getEnumValuesMethod(), getEnumEntriesProperty()) +} + +context(KtAnalysisSession, KtObjCExportSession) +private fun KtClassOrObjectSymbol.getEnumEntries(): List { + val staticMembers = this.getStaticDeclaredMemberScope().getCallableSymbols().toList() + return staticMembers.filterIsInstance().map { entry -> + + val entryName = entry.getEnumEntryName(false) + val swiftName = entry.getEnumEntryName(true) + ObjCProperty( + name = entryName, + comment = null, + origin = null, + type = entry.returnType.mapToReferenceTypeIgnoringNullability(), + propertyAttributes = listOf("class", "readonly"), + declarationAttributes = listOf(swiftNameAttribute(swiftName)) + ) + } +} + +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.buildEnumValuesMethod] + */ +context(KtAnalysisSession, KtObjCExportSession) +private fun KtClassOrObjectSymbol.getEnumValuesMethod(): ObjCMethod { + val valuesFunctionSymbol = getStaticMemberScope().getCallableSymbols(Name.identifier("values")).firstOrNull() + return ObjCMethod( + comment = null, + isInstanceMethod = false, + returnType = valuesFunctionSymbol?.returnType?.translateToObjCReferenceType() ?: ObjCIdType, + selectors = listOf("values"), + parameters = emptyList(), + attributes = listOf(swiftNameAttribute("values()")), + origin = null + ) +} + +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.buildEnumEntriesProperty] + */ +context(KtAnalysisSession, KtObjCExportSession) +private fun KtClassOrObjectSymbol.getEnumEntriesProperty(): ObjCProperty { + val entriesSymbol = getStaticMemberScope().getCallableSymbols(Name.identifier("entries")).firstOrNull() + + return ObjCProperty( + name = "entries", + comment = null, + type = entriesSymbol?.returnType?.translateToObjCReferenceType() ?: ObjCIdType, + propertyAttributes = listOf("class", "readonly"), + declarationAttributes = listOf(swiftNameAttribute("entries")), + origin = null, + setterName = null, + getterName = null + ) +} + +context(KtAnalysisSession) +private fun KtEnumEntrySymbol.getEnumEntryName(forSwift: Boolean): String { + + val propertyName: String = getObjCPropertyName().run { + when { + forSwift -> this.swiftName + else -> this.objCName + } + } + + // FOO_BAR_BAZ -> fooBarBaz: + val name = propertyName.split('_').mapIndexed { index, s -> + val lower = s.lowercase() + if (index == 0) lower else lower.replaceFirstChar(Char::uppercaseChar) + }.joinToString("").toIdentifier() + return name +} \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt index 831f90a257b..2d4b1e1433f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt @@ -8,7 +8,7 @@ import org.jetbrains.kotlin.analysis.api.symbols.nameOrAnonymous import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType import org.jetbrains.kotlin.backend.konan.objcexport.* import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.objcexport.analysisApiUtils.hasExportForCompilerAnnotation +import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC context(KtAnalysisSession, KtObjCExportSession) @@ -27,14 +27,27 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? { val superClass = translateSuperClass() val superProtocols: List = superProtocols() - val constructors = getMemberScope().getConstructors().filter { !it.hasExportForCompilerAnnotation } - val members = getMemberScope().getCallableSymbols().plus(constructors) - .sortedWith(StableCallableOrder) - .flatMap { it.translateToObjCExportStubs() } - .toMutableList() + val members = buildList { + /* The order of members tries to replicate the K1 implementation explicitly */ + this += translateToObjCConstructors() - if (needsCompanionProperty) members.add(buildCompanionProperty()) + if (needsCompanionProperty) { + this += buildCompanionProperty() + } + + /* Special case so far: Just for 'Enum' we actually want to add this clone method to match K1 */ + if (classIdIfNonLocal == StandardClassIds.Enum) { + this += cloneMethod + } + + this += getDeclaredMemberScope().getCallableSymbols().sortedWith(StableCallableOrder) + .mapNotNull { it.translateToObjCExportStub() } + + if (classKind == KtClassKind.ENUM_CLASS) { + this += translateEnumMembers() + } + } val categoryName: String? = null @@ -59,10 +72,18 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? { ) } - - context(KtAnalysisSession, KtObjCExportSession) internal fun KtNonErrorClassType.getSuperClassName(): ObjCExportClassOrProtocolName? { val classSymbol = expandedClassSymbol ?: return null return classSymbol.getObjCClassOrProtocolName() -} \ No newline at end of file +} + +private val cloneMethod = ObjCMethod( + selectors = listOf("clone"), + comment = ObjCComment(contentLines = listOf("@note This method has protected visibility in Kotlin source and is intended only for use by subclasses.")), + origin = null, + returnType = ObjCIdType, + parameters = emptyList(), + isInstanceMethod = true, + attributes = listOf(swiftNameAttribute("clone()")) +) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt index 787a8156155..ff3d7d081de 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt @@ -1,23 +1,31 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.analysis.api.KtAnalysisSession -import org.jetbrains.kotlin.analysis.api.symbols.KtConstructorSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.backend.konan.descriptors.arrayTypes import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInstanceType import org.jetbrains.kotlin.backend.konan.objcexport.ObjCMethod import org.jetbrains.kotlin.backend.konan.objcexport.ObjCParameter import org.jetbrains.kotlin.backend.konan.objcexport.ObjCRawType -import org.jetbrains.kotlin.objcexport.analysisApiUtils.isArrayConstructor +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getSuperClassSymbolNotAny +import org.jetbrains.kotlin.objcexport.analysisApiUtils.hasExportForCompilerAnnotation import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC context(KtAnalysisSession, KtObjCExportSession) -fun KtConstructorSymbol.translateToObjCConstructors(): List { - if (!isVisibleInObjC()) return emptyList() +fun KtClassOrObjectSymbol.translateToObjCConstructors(): List { val result = mutableListOf() - result.add(buildObjCMethod()) - - if (isArrayConstructor) { + /* Translate declared constructors */ + result += getDeclaredMemberScope().getConstructors() + .filter { !it.hasExportForCompilerAnnotation } + .filter { it.isVisibleInObjC() } + .map { it.buildObjCMethod() } + /* Create special 'alloc' constructors */ + if (this.classIdIfNonLocal?.asFqNameString() in arrayTypes || + classKind.isObject || classKind == KtClassKind.ENUM_CLASS + ) { result.add( ObjCMethod( comment = null, @@ -43,6 +51,16 @@ fun KtConstructorSymbol.translateToObjCConstructors(): List { ) } + // Hide "unimplemented" super constructors: + getSuperClassSymbolNotAny()?.getMemberScope()?.getConstructors().orEmpty() + .filter { it.isVisibleInObjC() } + .forEach { superClassConstructor -> + val translatedSuperClassConstructor = superClassConstructor.buildObjCMethod(unavailable = true) + if (result.none { it.name == translatedSuperClassConstructor.name }) { + result.add(translatedSuperClassConstructor) + } + } + if (result.size == 1 && result.first().name == "init") { result.add( ObjCMethod( @@ -60,4 +78,4 @@ fun KtConstructorSymbol.translateToObjCConstructors(): List { } return result -} \ No newline at end of file +} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStubs.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt similarity index 53% rename from native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStubs.kt rename to native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt index d881f577b95..9b21c62edc1 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStubs.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt @@ -2,17 +2,15 @@ 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 { +internal fun KtCallableSymbol.translateToObjCExportStub(): ObjCExportStub? { return when (this) { - is KtConstructorSymbol -> translateToObjCConstructors() - is KtPropertySymbol -> listOfNotNull(translateToObjCProperty()) - is KtFunctionSymbol -> listOfNotNull(translateToObjCMethod()) - else -> emptyList() + is KtPropertySymbol -> translateToObjCProperty() + is KtFunctionSymbol -> translateToObjCMethod() + else -> null } -} \ No newline at end of file +} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt index 728bdae4fb2..5ae79b262b9 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt @@ -61,7 +61,7 @@ fun translateToObjCHeader(files: List): ObjCHeader { } symbol.getSuperClassSymbolNotAny()?.let { superClassSymbol -> - process(superClassSymbol, true) + result.addAll(process(superClassSymbol, true)) } result.add(translatedObjCClassOrProtocol) 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 e04c14779a4..25cde6fd8f0 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 @@ -6,12 +6,14 @@ import com.intellij.openapi.util.io.FileUtil import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.annotations.annotationInfos import org.jetbrains.kotlin.analysis.api.symbols.* +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi import org.jetbrains.kotlin.backend.konan.KonanFqNames import org.jetbrains.kotlin.backend.konan.objcexport.* import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.NameUtils import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSelectors +import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSwiftNames import org.jetbrains.kotlin.objcexport.analysisApiUtils.* import org.jetbrains.kotlin.psi.KtFile @@ -19,12 +21,10 @@ internal val KtCallableSymbol.isConstructor: Boolean get() = this is KtConstructorSymbol context(KtAnalysisSession, KtObjCExportSession) -fun KtFunctionSymbol.translateToObjCMethod( -): ObjCMethod? { +fun KtFunctionSymbol.translateToObjCMethod(): ObjCMethod? { if (!isVisibleInObjC()) return null - if (anyMethodSelectors.containsKey(this.name)) return null //temp, find replacement for org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.isReal + if (isFakeOverride) return null if (isClone) return null - return buildObjCMethod() } @@ -110,9 +110,10 @@ internal fun KtCallableSymbol.isRefinedInSwift(): Boolean = when { context(KtAnalysisSession, KtObjCExportSession) internal fun KtFunctionLikeSymbol.getSwiftName(methodBridge: MethodBridge): String { - //assert(mapper.isBaseMethod(method)) //TODO: implement isBaseMethod - getPredefined(this, Predefined.anyMethodSwiftNames)?.let { return it } + if (this is KtNamedSymbol) { + anyMethodSwiftNames[name]?.let { return it } + } val parameters = methodBridge.valueParametersAssociated(this) val method = this @@ -172,12 +173,6 @@ private fun splitSelector(selector: String): List { } } -/** - * Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getPredefined] - */ -private fun getPredefined(method: KtFunctionLikeSymbol, predefinedForAny: Map): T? { - return null -} /** * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getSelector] @@ -185,7 +180,9 @@ private fun getPredefined(method: KtFunctionLikeSymbol, predefinedForA context(KtAnalysisSession, KtObjCExportSession) fun KtFunctionLikeSymbol.getSelector(methodBridge: MethodBridge): String { - getPredefined(this, anyMethodSelectors)?.let { return it } + if (this is KtNamedSymbol) { + anyMethodSelectors[this.name]?.let { return it } + } val parameters = methodBridge.valueParametersAssociated(this) @@ -275,7 +272,7 @@ fun MethodBridge.valueParametersAssociated( } private fun String.startsWithWords(words: String) = this.startsWith(words) && - (this.length == words.length || !this[words.length].isLowerCase()) + (this.length == words.length || !this[words.length].isLowerCase()) /** * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapReturnType] @@ -295,7 +292,7 @@ fun KtFunctionLikeSymbol.mapReturnType(returnBridge: MethodBridge.ReturnValue): if (!returnBridge.successMayBeZero) { check( successReturnType is ObjCNonNullReferenceType - || (successReturnType is ObjCPointerType && !successReturnType.nullable) + || (successReturnType is ObjCPointerType && !successReturnType.nullable) ) { "Unexpected return type: $successReturnType in $this" } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt index 232dc3fbcf3..0a0f62e99cc 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt @@ -23,14 +23,14 @@ fun KtClassOrObjectSymbol.translateToObjCObject(): ObjCClass? { val superProtocols: List = superProtocols() val categoryName: String? = null val generics: List = emptyList() - val objectMembers = getDefaultMembers().toMutableList() - val superClass = translateSuperClass() - getMemberScope().getCallableSymbols() + val objectMembers = mutableListOf() + objectMembers += translateToObjCConstructors() + objectMembers += getDefaultMembers() + objectMembers += getDeclaredMemberScope().getCallableSymbols() .sortedWith(StableCallableOrder) - .flatMap { it.translateToObjCExportStubs() } - .forEach { objectMembers.add(it) } + .mapNotNull { it.translateToObjCExportStub() } return ObjCInterfaceImpl( name = name.objCName, @@ -50,15 +50,6 @@ context(KtAnalysisSession, KtObjCExportSession) private fun KtClassOrObjectSymbol.getDefaultMembers(): List { val result = mutableListOf() - val allocWithZoneParameter = ObjCParameter("zone", null, ObjCRawType("struct _NSZone *"), null) - - result.add( - ObjCMethod(null, null, false, ObjCInstanceType, listOf("alloc"), emptyList(), listOf("unavailable")) - ) - - result.add( - ObjCMethod(null, null, false, ObjCInstanceType, listOf("allocWithZone:"), listOf(allocWithZoneParameter), listOf("unavailable")) - ) result.add( ObjCMethod( @@ -114,4 +105,4 @@ context(KtAnalysisSession, KtObjCExportSession) private fun getObjectPropertySelector(descriptor: KtClassOrObjectSymbol): String { val collides = ObjCPropertyNames.objectPropertyName == getObjectInstanceSelector(descriptor) return ObjCPropertyNames.objectPropertyName + (if (collides) "_" else "") -} \ No newline at end of file +} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt index 127ffc66e3d..1e077fa8697 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt @@ -24,9 +24,9 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? { // TODO: Check error type! val name = getObjCClassOrProtocolName() - val members = getMemberScope().getCallableSymbols() + val members = getDeclaredMemberScope().getCallableSymbols() .sortedWith(StableCallableOrder) - .flatMap { it.translateToObjCExportStubs() } + .mapNotNull { it.translateToObjCExportStub() } .toList() val comment: ObjCComment? = annotationsList.translateToObjCComment() diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt index 043b8a71454..da1bfa11a25 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt @@ -45,7 +45,7 @@ context(KtAnalysisSession, KtObjCExportSession) fun KtFileSymbol.translateToObjCTopLevelInterfaceFileFacade(): ObjCInterface? { val topLevelCallableStubs = getFileScope().getCallableSymbols() .sortedWith(StableCallableOrder) - .flatMap { callableSymbol -> callableSymbol.translateToObjCExportStubs() } + .mapNotNull { callableSymbol -> callableSymbol.translateToObjCExportStub() } .toList() /* If there are no top level functions or properties, we do not need to export a file facade */ .ifEmpty { return null } diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsHashCodeTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsHashCodeTest.kt new file mode 100644 index 00000000000..2e58e80eae3 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsHashCodeTest.kt @@ -0,0 +1,67 @@ +/* + * 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.name.StandardClassIds +import org.jetbrains.kotlin.objcexport.analysisApiUtils.isHashCode +import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis +import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail +import org.jetbrains.kotlin.objcexport.testUtils.getFunctionOrFail +import org.junit.jupiter.api.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class IsHashCodeTest( + private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis, +) { + + @Test + fun `test - Any - hashCode`() { + val file = inlineSourceCodeAnalysis.createKtFile("") + analyze(file) { + val anySymbol = getClassOrObjectSymbolByClassId(StandardClassIds.Any) ?: error("Missing kotlin.Any") + val hashCodeSymbol = anySymbol.getFunctionOrFail("hashCode") + assertTrue(hashCodeSymbol.isHashCode) + } + } + + @Test + fun `test - data class hashCode`() { + val file = inlineSourceCodeAnalysis.createKtFile("data class Foo(val x: Int)") + analyze(file) { + val fooSymbol = file.getClassOrFail("Foo") + val hashCodeSymbol = fooSymbol.getFunctionOrFail("hashCode") + assertTrue(hashCodeSymbol.isHashCode) + } + } + + @Test + fun `test - overridden hashCode`() { + val file = inlineSourceCodeAnalysis.createKtFile( + """ + class Foo { + override fun hashCode() = 42 + } + """.trimIndent() + ) + + analyze(file) { + val hashCodeSymbol = file.getClassOrFail("Foo").getFunctionOrFail("hashCode") + assertTrue(hashCodeSymbol.isHashCode) + } + } + + @Test + fun `test - Any equals`() { + val file = inlineSourceCodeAnalysis.createKtFile("") + analyze(file) { + val anySymbol = getClassOrObjectSymbolByClassId(StandardClassIds.Any) ?: error("Missing kotlin.Any") + val hashCodeSymbol = anySymbol.getFunctionOrFail("equals") + assertFalse(hashCodeSymbol.isHashCode) + } + } +} \ No newline at end of file diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt index b85f6740080..86e502864bd 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt @@ -44,7 +44,6 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) { } @Test - @TodoAnalysisApi fun `test - simpleEnumClass`() { doTest(headersTestDataDir.resolve("simpleEnumClass")) }