[ObjCExport] Fix data class predefined methods translation

KT-65800
This commit is contained in:
eugene.levenetc
2024-02-19 18:50:45 +01:00
committed by Space Team
parent 3e5afcaaba
commit 83ddf904aa
5 changed files with 141 additions and 12 deletions
@@ -4,9 +4,15 @@ 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.analysis.api.symbols.KtSymbolOrigin
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.name.Name
/**
* Note: At the time of writing this function (and comment) we found it easiest
* to construct the functions manually. Potentially, there is a way to get those functions from
* the Analysis API by requesting the combined member scope and looking for [KtSymbolOrigin.SOURCE_MEMBER_GENERATED].
*/
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtClassOrObjectSymbol.translateEnumMembers(): List<ObjCExportStub> {
if (classKind != KtClassKind.ENUM_CLASS) return emptyList()
@@ -1,10 +1,8 @@
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.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality
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
@@ -41,7 +39,8 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
this += cloneMethod
}
this += getDeclaredMemberScope().getCallableSymbols().sortedWith(StableCallableOrder)
this += getCallableSymbolsForObjCMemberTranslation()
.sortedWith(StableCallableOrder)
.mapNotNull { it.translateToObjCExportStub() }
if (classKind == KtClassKind.ENUM_CLASS) {
@@ -72,6 +71,48 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
)
}
/**
* Resolves all [KtCallableSymbol] symbols that are to be translated to ObjC for [this] [KtClassOrObjectSymbol].
* Note: This will return only 'declared' members (aka members written on this class/interface/object) and 'synthetic'/'generated' members.
*
* ## Example regular class
* ```kotlin
* open class Base {
* fun base() = Unit
* }
*
* class Foo : Base() {
* fun foo() = Unit
* ```
*
* In this example `Foo` will return the function `foo` (as declared in `Foo`), but not the function `base` (as declared in `Base` and
* not directly in `Foo`).
*
* ## Example data class
* ```kotlin
* data class Foo(val x: Int)
* ```
*
* Will return `x` as directly declared in `Foo`, but also the `copy`, `equals`,`hashCode`, `toString` and `componentX` functions that
* are generated by the compiler for the *data* class `Foo`
*
* Note: Some methods like `hashCode`, `toString`, ... have predefined selectors and ObjC names.
* @see [Predefined]
*/
context(KtAnalysisSession)
internal fun KtClassOrObjectSymbol.getCallableSymbolsForObjCMemberTranslation(): Set<KtCallableSymbol> {
val generatedCallableSymbols = getMemberScope()
.getCallableSymbols()
.filter { it.origin == KtSymbolOrigin.SOURCE_MEMBER_GENERATED }
.toSet()
val declaredCallableSymbols = getDeclaredMemberScope()
.getCallableSymbols()
.toSet()
return generatedCallableSymbols + declaredCallableSymbols
}
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtNonErrorClassType.getSuperClassName(): ObjCExportClassOrProtocolName? {
val classSymbol = expandedClassSymbol ?: return null
@@ -86,4 +127,4 @@ private val cloneMethod = ObjCMethod(
parameters = emptyList(),
isInstanceMethod = true,
attributes = listOf(swiftNameAttribute("clone()"))
)
)
@@ -0,0 +1,85 @@
/*
* 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.analysis.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.objcexport.StableCallableOrder
import org.jetbrains.kotlin.objcexport.getCallableSymbolsForObjCMemberTranslation
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class GetCallableSymbolsForObjCMemberTranslationTest(
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
) {
@Test
fun `test - regular class`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
abstract class Base {
open fun base() = Unit
abstract fun abstractFun()
}
class Foo: Base() {
fun bar() {}
override fun abstractFun(): Unit = error("stub")
}
""".trimIndent()
)
analyze(file) {
val fooSymbol = file.getClassOrFail("Foo")
assertEquals(
listOf("bar", "abstractFun"),
fooSymbol.getCallableSymbolsForObjCMemberTranslation()
.map { it as KtFunctionSymbol }
.map { it.name.asString() }
)
}
}
@Test
fun `test - data class`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
data class Foo(val a: Int)
""".trimIndent()
)
analyze(file) {
val foo = file.getClassOrFail("Foo")
assertEquals(
listOf("component1", "copy", "equals", "hashCode", "toString", "a"),
foo.getCallableSymbolsForObjCMemberTranslation()
.sortedWith(StableCallableOrder)
.map { it as KtNamedSymbol }
.map { it.name.asString() }
)
}
}
@Test
fun `test - enum class`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
enum class Foo {
A, B, C
}
""".trimIndent()
)
analyze(file) {
val foo = file.getClassOrFail("Foo")
assertEquals(
emptyList(),
foo.getCallableSymbolsForObjCMemberTranslation()
.sortedWith(StableCallableOrder)
.map { it as KtNamedSymbol }
.map { it.name.asString() }
)
}
}
}
@@ -12,6 +12,7 @@ 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.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@@ -56,12 +57,12 @@ class IsHashCodeTest(
}
@Test
fun `test - Any equals`() {
fun `test - Any equals method is returning false for isHashCode`() {
val file = inlineSourceCodeAnalysis.createKtFile("")
analyze(file) {
val anySymbol = getClassOrObjectSymbolByClassId(StandardClassIds.Any) ?: error("Missing kotlin.Any")
val hashCodeSymbol = anySymbol.getFunctionOrFail("equals")
assertFalse(hashCodeSymbol.isHashCode)
val equalsSymbol = anySymbol.getFunctionOrFail("equals")
assertFalse(equalsSymbol.isHashCode)
}
}
}
@@ -297,11 +297,7 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
doTest(headersTestDataDir.resolve("samInterface"))
}
/**
* Requires some fixes: KT-65800
*/
@Test
@TodoAnalysisApi
fun `test - simple data class`() {
doTest(headersTestDataDir.resolve("simpleDataClass"))
}