From 83ddf904aa1376662dfb97dd6e02ce57fc30ea2f Mon Sep 17 00:00:00 2001 From: "eugene.levenetc" Date: Mon, 19 Feb 2024 18:50:45 +0100 Subject: [PATCH] [ObjCExport] Fix data class predefined methods translation KT-65800 --- .../kotlin/objcexport/translateEnumMembers.kt | 6 ++ .../kotlin/objcexport/translateToObjCClass.kt | 51 +++++++++-- ...ableSymbolsForObjCMemberTranslationTest.kt | 85 +++++++++++++++++++ .../kotlin/objcexport/tests/IsHashCodeTest.kt | 7 +- .../tests/ObjCExportHeaderGeneratorTest.kt | 4 - 5 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetCallableSymbolsForObjCMemberTranslationTest.kt 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 index 13d172856aa..a75c0d5dc87 100644 --- 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 @@ -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 { if (classKind != KtClassKind.ENUM_CLASS) return emptyList() 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 2d4b1e1433f..236044562ef 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 @@ -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 { + 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()")) -) +) \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetCallableSymbolsForObjCMemberTranslationTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetCallableSymbolsForObjCMemberTranslationTest.kt new file mode 100644 index 00000000000..345bbf37561 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetCallableSymbolsForObjCMemberTranslationTest.kt @@ -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() } + ) + } + } +} \ No newline at end of file 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 index 2e58e80eae3..e88ea2da8f6 100644 --- 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 @@ -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) } } } \ 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 fb040a4bde8..e284747a411 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 @@ -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")) }