[ObjCExport] Support translation of enum classes

^KT-64953 Fixed
This commit is contained in:
Sebastian Sellmair
2024-02-15 13:28:41 +01:00
committed by Space Team
parent 2fb6815d7f
commit a2d76d739c
17 changed files with 292 additions and 127 deletions
@@ -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
@@ -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 -> {
@@ -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
}
@@ -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 }
@@ -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<KtSymbol> {
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<KtSymbol> {
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
}
@@ -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(
@@ -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<ObjCExportStub> {
if (classKind != KtClassKind.ENUM_CLASS) return emptyList()
return getEnumEntries() + listOf(getEnumValuesMethod(), getEnumEntriesProperty())
}
context(KtAnalysisSession, KtObjCExportSession)
private fun KtClassOrObjectSymbol.getEnumEntries(): List<ObjCProperty> {
val staticMembers = this.getStaticDeclaredMemberScope().getCallableSymbols().toList()
return staticMembers.filterIsInstance<KtEnumEntrySymbol>().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
}
@@ -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<String> = superProtocols()
val constructors = getMemberScope().getConstructors().filter { !it.hasExportForCompilerAnnotation }
val members = getMemberScope().getCallableSymbols().plus(constructors)
.sortedWith(StableCallableOrder)
.flatMap { it.translateToObjCExportStubs() }
.toMutableList()
val members = buildList<ObjCExportStub> {
/* 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()
}
}
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()"))
)
@@ -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<ObjCMethod> {
if (!isVisibleInObjC()) return emptyList()
fun KtClassOrObjectSymbol.translateToObjCConstructors(): List<ObjCMethod> {
val result = mutableListOf<ObjCMethod>()
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<ObjCMethod> {
)
}
// 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<ObjCMethod> {
}
return result
}
}
@@ -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<ObjCExportStub> {
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
}
}
}
@@ -61,7 +61,7 @@ fun translateToObjCHeader(files: List<KtFile>): ObjCHeader {
}
symbol.getSuperClassSymbolNotAny()?.let { superClassSymbol ->
process(superClassSymbol, true)
result.addAll(process(superClassSymbol, true))
}
result.add(translatedObjCClassOrProtocol)
@@ -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<String> {
}
}
/**
* Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getPredefined]
*/
private fun <T : Any> getPredefined(method: KtFunctionLikeSymbol, predefinedForAny: Map<Name, T>): T? {
return null
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getSelector]
@@ -185,7 +180,9 @@ private fun <T : Any> 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"
}
@@ -23,14 +23,14 @@ fun KtClassOrObjectSymbol.translateToObjCObject(): ObjCClass? {
val superProtocols: List<String> = superProtocols()
val categoryName: String? = null
val generics: List<ObjCGenericTypeDeclaration> = emptyList()
val objectMembers = getDefaultMembers().toMutableList()
val superClass = translateSuperClass()
getMemberScope().getCallableSymbols()
val objectMembers = mutableListOf<ObjCExportStub>()
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<ObjCExportStub> {
val result = mutableListOf<ObjCExportStub>()
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 "")
}
}
@@ -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()
@@ -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 }
@@ -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)
}
}
}
@@ -44,7 +44,6 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
}
@Test
@TodoAnalysisApi
fun `test - simpleEnumClass`() {
doTest(headersTestDataDir.resolve("simpleEnumClass"))
}