[ObjCExport] Fix data class predefined methods translation
KT-65800
This commit is contained in:
committed by
Space Team
parent
3e5afcaaba
commit
83ddf904aa
+6
@@ -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.KtClassKind
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtEnumEntrySymbol
|
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.backend.konan.objcexport.*
|
||||||
import org.jetbrains.kotlin.name.Name
|
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)
|
context(KtAnalysisSession, KtObjCExportSession)
|
||||||
internal fun KtClassOrObjectSymbol.translateEnumMembers(): List<ObjCExportStub> {
|
internal fun KtClassOrObjectSymbol.translateEnumMembers(): List<ObjCExportStub> {
|
||||||
if (classKind != KtClassKind.ENUM_CLASS) return emptyList()
|
if (classKind != KtClassKind.ENUM_CLASS) return emptyList()
|
||||||
|
|||||||
+46
-5
@@ -1,10 +1,8 @@
|
|||||||
package org.jetbrains.kotlin.objcexport
|
package org.jetbrains.kotlin.objcexport
|
||||||
|
|
||||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
|
import org.jetbrains.kotlin.analysis.api.symbols.*
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality
|
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.analysis.api.types.KtNonErrorClassType
|
||||||
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
@@ -41,7 +39,8 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
|
|||||||
this += cloneMethod
|
this += cloneMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
this += getDeclaredMemberScope().getCallableSymbols().sortedWith(StableCallableOrder)
|
this += getCallableSymbolsForObjCMemberTranslation()
|
||||||
|
.sortedWith(StableCallableOrder)
|
||||||
.mapNotNull { it.translateToObjCExportStub() }
|
.mapNotNull { it.translateToObjCExportStub() }
|
||||||
|
|
||||||
if (classKind == KtClassKind.ENUM_CLASS) {
|
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)
|
context(KtAnalysisSession, KtObjCExportSession)
|
||||||
internal fun KtNonErrorClassType.getSuperClassName(): ObjCExportClassOrProtocolName? {
|
internal fun KtNonErrorClassType.getSuperClassName(): ObjCExportClassOrProtocolName? {
|
||||||
val classSymbol = expandedClassSymbol ?: return null
|
val classSymbol = expandedClassSymbol ?: return null
|
||||||
@@ -86,4 +127,4 @@ private val cloneMethod = ObjCMethod(
|
|||||||
parameters = emptyList(),
|
parameters = emptyList(),
|
||||||
isInstanceMethod = true,
|
isInstanceMethod = true,
|
||||||
attributes = listOf(swiftNameAttribute("clone()"))
|
attributes = listOf(swiftNameAttribute("clone()"))
|
||||||
)
|
)
|
||||||
+85
@@ -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() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-3
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
|
|||||||
import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
|
import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
|
||||||
import org.jetbrains.kotlin.objcexport.testUtils.getFunctionOrFail
|
import org.jetbrains.kotlin.objcexport.testUtils.getFunctionOrFail
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
@@ -56,12 +57,12 @@ class IsHashCodeTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test - Any equals`() {
|
fun `test - Any equals method is returning false for isHashCode`() {
|
||||||
val file = inlineSourceCodeAnalysis.createKtFile("")
|
val file = inlineSourceCodeAnalysis.createKtFile("")
|
||||||
analyze(file) {
|
analyze(file) {
|
||||||
val anySymbol = getClassOrObjectSymbolByClassId(StandardClassIds.Any) ?: error("Missing kotlin.Any")
|
val anySymbol = getClassOrObjectSymbolByClassId(StandardClassIds.Any) ?: error("Missing kotlin.Any")
|
||||||
val hashCodeSymbol = anySymbol.getFunctionOrFail("equals")
|
val equalsSymbol = anySymbol.getFunctionOrFail("equals")
|
||||||
assertFalse(hashCodeSymbol.isHashCode)
|
assertFalse(equalsSymbol.isHashCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-4
@@ -297,11 +297,7 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
|
|||||||
doTest(headersTestDataDir.resolve("samInterface"))
|
doTest(headersTestDataDir.resolve("samInterface"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Requires some fixes: KT-65800
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
@TodoAnalysisApi
|
|
||||||
fun `test - simple data class`() {
|
fun `test - simple data class`() {
|
||||||
doTest(headersTestDataDir.resolve("simpleDataClass"))
|
doTest(headersTestDataDir.resolve("simpleDataClass"))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user