From 66533141b847c34a665e518d237e9efdad37d078 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Thu, 14 Dec 2023 13:11:43 +0100 Subject: [PATCH] [ObjCExport] Implement getSuperClassSymbolNotAny util KT-64226 --- .../getSuperClassSymbolNotAny.kt | 42 +++++++++ .../tests/GetSuperClassSymbolNotAnyTest.kt | 88 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getSuperClassSymbolNotAny.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetSuperClassSymbolNotAnyTest.kt diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getSuperClassSymbolNotAny.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getSuperClassSymbolNotAny.kt new file mode 100644 index 00000000000..0d0daade898 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getSuperClassSymbolNotAny.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2023 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.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.types.KtClassType +import org.jetbrains.kotlin.analysis.api.types.KtClassTypeQualifier + +/** + * Tries to find the superclass [KtClassOrObjectSymbol] symbol which is *not* kotlin.Any + * + * e.g. + * ```kotlin + * abstract class A + * + * class B: A + * + * fun example() { + * val symbolOfB = // ... + * val symbolOfA = symbolOfB.getSuperClassSymbolNotAny() + * } + * ``` + */ +context(KtAnalysisSession) +internal fun KtClassOrObjectSymbol.getSuperClassSymbolNotAny(): KtClassOrObjectSymbol? { + return superTypes.firstNotNullOfOrNull find@{ superType -> + if (superType.isAny) return@find null + if (superType is KtClassType) { + val classifier = superType.qualifiers.firstNotNullOfOrNull { qualifier -> + (qualifier as? KtClassTypeQualifier.KtResolvedClassTypeQualifier)?.symbol + } + + if (classifier is KtClassOrObjectSymbol && classifier.classKind.isClass) return@find classifier + } + + null + } +} \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetSuperClassSymbolNotAnyTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetSuperClassSymbolNotAnyTest.kt new file mode 100644 index 00000000000..7ae952174aa --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetSuperClassSymbolNotAnyTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2023 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.objcexport.analysisApiUtils.getSuperClassSymbolNotAny +import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis +import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class GetSuperClassSymbolNotAnyTest( + private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis, +) { + + @Test + fun `test - no declared superclass - returns null`() { + val file = inlineSourceCodeAnalysis.createKtFile("""class Foo""") + analyze(file) { + val foo = file.getClassOrFail("Foo") + assertNull(foo.getSuperClassSymbolNotAny()) + } + } + + @Test + fun `test - single abstract super class`() { + val file = inlineSourceCodeAnalysis.createKtFile( + """ + abstract class Bar + class Foo: Bar() + """.trimIndent() + ) + + analyze(file) { + val fooSymbol = file.getClassOrFail("Foo") + val barSymbol = fooSymbol.getSuperClassSymbolNotAny() + assertEquals(barSymbol, file.getClassOrFail("Bar")) + } + } + + @Test + fun `test - multiple abstract classes`() { + val file = inlineSourceCodeAnalysis.createKtFile( + """ + abstract class A + abstract class B: A() + class C: B() + """.trimIndent() + ) + + analyze(file) { + val aSymbol = file.getClassOrFail("A") + val bSymbol = file.getClassOrFail("B") + val cSymbol = file.getClassOrFail("C") + + assertEquals(cSymbol.getSuperClassSymbolNotAny(), bSymbol) + assertEquals(bSymbol.getSuperClassSymbolNotAny(), aSymbol) + assertNull(aSymbol.getSuperClassSymbolNotAny()) + } + } + + @Test + fun `test - abstract class and interfaces`() { + val file = inlineSourceCodeAnalysis.createKtFile( + """ + abstract class A + interface I1 + interface I2 + class Foo: I1, I2, A() + """.trimIndent() + ) + + analyze(file) { + val aSymbol = file.getClassOrFail("A") + val i1Symbol = file.getClassOrFail("I1") + val i2Symbol = file.getClassOrFail("I2") + val fooSymbol = file.getClassOrFail("Foo") + + assertEquals(fooSymbol.getSuperClassSymbolNotAny(), aSymbol) + assertNull(i1Symbol.getSuperClassSymbolNotAny()) + assertNull(i2Symbol.getSuperClassSymbolNotAny()) + } + } +} \ No newline at end of file