[ObjCExport] Implement isVisibleInObjC util
KT-64226
This commit is contained in:
committed by
Space Team
parent
66533141b8
commit
2f759243ba
+171
@@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* 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.annotations.annotationInfos
|
||||||
|
import org.jetbrains.kotlin.analysis.api.symbols.*
|
||||||
|
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind.*
|
||||||
|
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtAnnotatedSymbol
|
||||||
|
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtPossibleMultiplatformSymbol
|
||||||
|
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality
|
||||||
|
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithVisibility
|
||||||
|
import org.jetbrains.kotlin.backend.konan.KonanFqNames
|
||||||
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
|
import org.jetbrains.kotlin.resolve.DataClassResolver
|
||||||
|
import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
internal fun KtCallableSymbol.isVisibleInObjC(): Boolean {
|
||||||
|
if (this is KtSymbolWithVisibility && !isPublicApi(this)) return false
|
||||||
|
if (this is KtPossibleMultiplatformSymbol && isExpect) return false
|
||||||
|
|
||||||
|
if (this.isHiddenFromObjCByDeprecation()) return false
|
||||||
|
if (this.isHiddenFromObjCByAnnotation()) return false
|
||||||
|
if (this.isSealedClassConstructor()) return false
|
||||||
|
if (this.isComponentNMethod() && this.getDirectlyOverriddenSymbols().isEmpty()) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
internal fun KtClassOrObjectSymbol.isVisibleInObjC(): Boolean {
|
||||||
|
// TODO if(specialMapped()) return false
|
||||||
|
// TODO if(!defaultType.isObjCObjectType()) return false
|
||||||
|
|
||||||
|
if (this is KtSymbolWithVisibility && !isPublicApi(this)) return false
|
||||||
|
if (this.isHiddenFromObjCByDeprecation()) return false
|
||||||
|
if (this.isHiddenFromObjCByAnnotation()) return false
|
||||||
|
if (!this.classKind.isVisibleInObjC()) return false
|
||||||
|
if (this.isExpect) return false
|
||||||
|
if (this.isInlined()) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Private utility functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
private fun KtSymbol.isSealedClassConstructor(): Boolean {
|
||||||
|
if (this !is KtConstructorSymbol) return false
|
||||||
|
val containingSymbol = this.getContainingSymbol() as? KtSymbolWithModality ?: return false
|
||||||
|
return containingSymbol.modality == Modality.SEALED
|
||||||
|
}
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
private fun KtSymbol.isComponentNMethod(): Boolean {
|
||||||
|
contract {
|
||||||
|
returns(true) implies (this@isComponentNMethod is KtFunctionSymbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this !is KtFunctionSymbol) return false
|
||||||
|
if (!this.isOperator) return false
|
||||||
|
val containingClassSymbol = this.getContainingSymbol() as? KtNamedClassOrObjectSymbol ?: return false
|
||||||
|
if (!containingClassSymbol.isData) return false
|
||||||
|
return DataClassResolver.isComponentLike(this.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
private fun KtCallableSymbol.isHiddenFromObjCByAnnotation(): Boolean {
|
||||||
|
val overwrittenSymbols = getDirectlyOverriddenSymbols()
|
||||||
|
if (overwrittenSymbols.isNotEmpty()) return overwrittenSymbols.first().isHiddenFromObjCByAnnotation()
|
||||||
|
return this.containsHidesFromObjCAnnotation()
|
||||||
|
}
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
private fun KtClassOrObjectSymbol.isHiddenFromObjCByAnnotation(): Boolean {
|
||||||
|
val containingSymbol = getContainingSymbol()
|
||||||
|
if (containingSymbol is KtClassOrObjectSymbol && containingSymbol.isHiddenFromObjCByAnnotation()) return true
|
||||||
|
return this.containsHidesFromObjCAnnotation()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if [this] symbol is annotated with some annotation that effectively hides the symbol from ObjC.
|
||||||
|
*
|
||||||
|
* **Example: Using pre-defined 'HiddenFromObjC' annotation**
|
||||||
|
* ```kotlin
|
||||||
|
* @HiddenFromObjC
|
||||||
|
* fun foo() = Unit
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* **Example: Using a custom 'internal api marker'**
|
||||||
|
* ```kotlin
|
||||||
|
* @HidesFromObjC
|
||||||
|
* annotation class MyInternalApi
|
||||||
|
*
|
||||||
|
* @MyInternalApi
|
||||||
|
* fun foo()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
private fun KtAnnotatedSymbol.containsHidesFromObjCAnnotation(): Boolean {
|
||||||
|
return annotationsList.annotations.any { annotation ->
|
||||||
|
val annotationClassId = annotation.classId ?: return@any false
|
||||||
|
val annotationClassSymbol = getClassOrObjectSymbolByClassId(annotationClassId) ?: return@any false
|
||||||
|
annotationClassSymbol.annotationInfos.any { annotationAnnotation ->
|
||||||
|
annotationAnnotation.classId?.asSingleFqName() == KonanFqNames.hidesFromObjC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
private fun KtCallableSymbol.isHiddenFromObjCByDeprecation(): Boolean {
|
||||||
|
/*
|
||||||
|
Note: ObjCExport generally expect overrides of exposed methods to be exposed.
|
||||||
|
So don't hide a "deprecated hidden" method which overrides non-hidden one:
|
||||||
|
*/
|
||||||
|
if (deprecationStatus?.deprecationLevel == DeprecationLevelValue.HIDDEN &&
|
||||||
|
getDirectlyOverriddenSymbols().all { overridden -> overridden.isHiddenFromObjCByDeprecation() }
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val containingClassSymbol = getContainingSymbol() as? KtClassOrObjectSymbol
|
||||||
|
if (containingClassSymbol?.deprecationStatus?.deprecationLevel == DeprecationLevelValue.HIDDEN) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
private fun KtClassOrObjectSymbol.isHiddenFromObjCByDeprecation(): Boolean {
|
||||||
|
if (this.deprecationStatus?.deprecationLevel == DeprecationLevelValue.HIDDEN) return true
|
||||||
|
|
||||||
|
// Note: ObjCExport requires super class of exposed class to be exposed.
|
||||||
|
// So hide a class if its super class is hidden:
|
||||||
|
val superClass = getSuperClassSymbolNotAny()
|
||||||
|
if (superClass != null && superClass.isHiddenFromObjCByDeprecation()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: ObjCExport requires enclosing class of exposed class to be exposed.
|
||||||
|
// Also in Kotlin hidden class members (including other classes) aren't directly accessible.
|
||||||
|
// So hide a class if its enclosing class is hidden:
|
||||||
|
val containingSymbol = getContainingSymbol()
|
||||||
|
if (containingSymbol is KtClassOrObjectSymbol && containingSymbol.isHiddenFromObjCByDeprecation()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
private fun KtClassOrObjectSymbol.isInlined(): Boolean {
|
||||||
|
if (this !is KtNamedClassOrObjectSymbol) return false
|
||||||
|
if (this.isInline) return true
|
||||||
|
// TODO: There are some native types that are 'implicitly inlined'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtClassKind.isVisibleInObjC(): Boolean = when (this) {
|
||||||
|
CLASS, ENUM_CLASS, INTERFACE, OBJECT -> true
|
||||||
|
COMPANION_OBJECT, ANONYMOUS_OBJECT, ANNOTATION_CLASS -> false
|
||||||
|
}
|
||||||
+184
@@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* 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.isVisibleInObjC
|
||||||
|
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 IsVisibleInObjCTest(
|
||||||
|
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
|
||||||
|
) {
|
||||||
|
@Test
|
||||||
|
fun `test - simple public function`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile("fun foo() = Unit")
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getFunctionOrFail("foo")
|
||||||
|
assertTrue(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - internal function`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile("internal fun foo() = Unit")
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getFunctionOrFail("foo")
|
||||||
|
assertFalse(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - HiddenFromObjC function`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
@kotlin.native.HiddenFromObjC
|
||||||
|
fun foo() = Unit
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getFunctionOrFail("foo")
|
||||||
|
assertFalse(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - custom HidesFromObjC annotation function`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
@kotlin.native.HidesFromObjC
|
||||||
|
annotation class MyInternalApi
|
||||||
|
|
||||||
|
@MyInternalApi
|
||||||
|
fun foo() = Unit
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getFunctionOrFail("foo")
|
||||||
|
assertFalse(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - deprecation warning function`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
@Deprecated(level = DeprecationLevel.WARNING)
|
||||||
|
fun foo() = Unit
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getFunctionOrFail("foo")
|
||||||
|
assertTrue(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - deprecation hidden function`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
@Deprecated(level = DeprecationLevel.HIDDEN)
|
||||||
|
fun foo() = Unit
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getFunctionOrFail("foo")
|
||||||
|
assertFalse(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - public class`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile("class Foo")
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getClassOrFail("Foo")
|
||||||
|
assertTrue(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - internal class`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile("internal class Foo")
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getClassOrFail("Foo")
|
||||||
|
assertFalse(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - PublishedApi class`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
@PublishedApi
|
||||||
|
internal class Foo
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getClassOrFail("Foo")
|
||||||
|
assertTrue(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - inline class`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile("inline class Foo(val x: Int)")
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getClassOrFail("Foo")
|
||||||
|
assertFalse(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - expect class`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile("expect class Foo")
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getClassOrFail("Foo")
|
||||||
|
assertFalse(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - deprecation hidden class`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
@Deprecated(level = DeprecationLevel.HIDDEN)
|
||||||
|
class Foo
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getClassOrFail("Foo")
|
||||||
|
assertFalse(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - custom HidesFromObjC annotation class`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
@kotlin.native.HidesFromObjC
|
||||||
|
annotation class MyInternalApi
|
||||||
|
|
||||||
|
@MyInternalApi
|
||||||
|
class Foo
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
analyze(file) {
|
||||||
|
val fooSymbol = file.getClassOrFail("Foo")
|
||||||
|
assertFalse(fooSymbol.isVisibleInObjC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user