[ObjCExport] Fix suspend function and @Throws translations
KT-66115, KT-66239
This commit is contained in:
committed by
Space Team
parent
b7d1126861
commit
42d8db1c52
+34
@@ -0,0 +1,34 @@
|
||||
package org.jetbrains.kotlin.objcexport.analysisApiUtils
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.annotations.KtArrayAnnotationValue
|
||||
import org.jetbrains.kotlin.analysis.api.annotations.KtKClassAnnotationValue
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
|
||||
import org.jetbrains.kotlin.backend.konan.KonanFqNames
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
|
||||
/**
|
||||
* Returns [Throws] classIds defined only for current function
|
||||
*
|
||||
* See [effectiveThrows]
|
||||
* See K1: org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.getDefinedThrows
|
||||
*/
|
||||
context(KtAnalysisSession)
|
||||
internal val KtFunctionLikeSymbol.definedThrows: List<ClassId>
|
||||
get() {
|
||||
if (isSuspend) return listOf(ClassId.topLevel(KonanFqNames.cancellationException))
|
||||
if (!hasThrowsAnnotation) return emptyList()
|
||||
|
||||
val throwsAnnotations = annotationsList.annotations
|
||||
.filter { annotation -> annotation.classId?.asSingleFqName() == KonanFqNames.throws }
|
||||
.asSequence()
|
||||
|
||||
return throwsAnnotations
|
||||
.flatMap { annotation -> annotation.arguments }
|
||||
.map { argument -> argument.expression }
|
||||
.filterIsInstance<KtArrayAnnotationValue>()
|
||||
.flatMap { arrayAnnotationValue -> arrayAnnotationValue.values }
|
||||
.filterIsInstance<KtKClassAnnotationValue.KtNonLocalKClassAnnotationValue>()
|
||||
.map { it.classId }
|
||||
.toList()
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package org.jetbrains.kotlin.objcexport.analysisApiUtils
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
|
||||
/**
|
||||
* Returns root [Throws] classIds
|
||||
*
|
||||
* ```kotlin
|
||||
* class A {
|
||||
* @Throws(ExcA)
|
||||
* foo()
|
||||
* }
|
||||
*
|
||||
* class B: A {
|
||||
* @Throws(ExcB)
|
||||
* override foo()
|
||||
* }
|
||||
*
|
||||
* class C: B {
|
||||
* @Throws(ExcC)
|
||||
* override foo()
|
||||
* }
|
||||
*
|
||||
* C.foo.effectiveThrows = [ExcA]
|
||||
* ```
|
||||
*
|
||||
* See [definedThrows]
|
||||
* See K1: [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.getEffectiveThrows]
|
||||
*/
|
||||
context(KtAnalysisSession)
|
||||
internal val KtFunctionLikeSymbol.effectiveThrows: List<ClassId>
|
||||
get() {
|
||||
getAllOverriddenSymbols().firstOrNull()?.let { return (it as KtFunctionLikeSymbol).effectiveThrows }
|
||||
return definedThrows
|
||||
}
|
||||
+20
-7
@@ -20,8 +20,14 @@ internal fun KtFunctionLikeSymbol.getFunctionMethodBridge(): MethodBridge {
|
||||
valueParameters += bridgeParameter(it.returnType)
|
||||
}
|
||||
|
||||
if (this is KtFunctionSymbol && isSuspend) {
|
||||
valueParameters += MethodBridgeValueParameter.SuspendCompletion(false)
|
||||
if (isSuspend) {
|
||||
valueParameters += MethodBridgeValueParameter.SuspendCompletion(true)
|
||||
} else if (hasThrowsAnnotation) {
|
||||
// Add error out parameter before tail block parameters. The convention allows this.
|
||||
// Placing it after would trigger https://bugs.swift.org/browse/SR-12201
|
||||
// (see also https://github.com/JetBrains/kotlin-native/issues/3825).
|
||||
val tailBlocksCount = valueParameters.reversed().takeWhile { it.isBlockPointer() }.count()
|
||||
valueParameters.add(valueParameters.size - tailBlocksCount, MethodBridgeValueParameter.ErrorOutParameter)
|
||||
}
|
||||
|
||||
return MethodBridge(
|
||||
@@ -125,13 +131,11 @@ private fun bridgeFunctionType(type: KtType): TypeBridge {
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
private fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue {
|
||||
|
||||
val convertExceptionsToErrors = false // TODO: Add exception handling and return MethodBridge.ReturnValue.WithError.ZeroForError
|
||||
|
||||
if (isArrayConstructor) {
|
||||
return MethodBridge.ReturnValue.Instance.FactoryResult
|
||||
} else if (isConstructor) {
|
||||
val result = MethodBridge.ReturnValue.Instance.InitResult
|
||||
if (convertExceptionsToErrors) {
|
||||
if (hasThrowsAnnotation) {
|
||||
MethodBridge.ReturnValue.WithError.ZeroForError(result, successMayBeZero = false)
|
||||
} else {
|
||||
return result
|
||||
@@ -151,7 +155,7 @@ private fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue {
|
||||
// }
|
||||
|
||||
if (returnType.isUnit || returnType.isNothing) {
|
||||
return if (convertExceptionsToErrors) {
|
||||
return if (hasThrowsAnnotation) {
|
||||
MethodBridge.ReturnValue.WithError.Success
|
||||
} else {
|
||||
MethodBridge.ReturnValue.Void
|
||||
@@ -162,7 +166,7 @@ private fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue {
|
||||
val returnTypeBridge = bridgeType(returnType)
|
||||
val successReturnValueBridge = MethodBridge.ReturnValue.Mapped(returnTypeBridge)
|
||||
|
||||
return if (convertExceptionsToErrors) {
|
||||
return if (hasThrowsAnnotation) {
|
||||
val canReturnZero = !returnTypeBridge.isReferenceOrPointer() || returnType.canBeNull
|
||||
MethodBridge.ReturnValue.WithError.ZeroForError(
|
||||
successReturnValueBridge,
|
||||
@@ -180,3 +184,12 @@ private fun TypeBridge.isReferenceOrPointer(): Boolean = when (this) {
|
||||
ReferenceBridge, is BlockPointerBridge -> true
|
||||
is ValueTypeBridge -> this.objCValueType == ObjCValueType.POINTER
|
||||
}
|
||||
|
||||
private fun MethodBridgeValueParameter.isBlockPointer(): Boolean = when (this) {
|
||||
is MethodBridgeValueParameter.Mapped -> when (this.bridge) {
|
||||
ReferenceBridge, is ValueTypeBridge -> false
|
||||
is BlockPointerBridge -> true
|
||||
}
|
||||
MethodBridgeValueParameter.ErrorOutParameter -> false
|
||||
is MethodBridgeValueParameter.SuspendCompletion -> true
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.jetbrains.kotlin.objcexport.analysisApiUtils
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
|
||||
import org.jetbrains.kotlin.backend.konan.KonanFqNames
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
|
||||
|
||||
internal val KtCallableSymbol.hasThrowsAnnotation: Boolean
|
||||
get() {
|
||||
return annotationsList.hasAnnotation(ClassId.topLevel(KonanFqNames.throws))
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package org.jetbrains.kotlin.objcexport.analysisApiUtils
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
|
||||
|
||||
internal val KtFunctionLikeSymbol.isSuspend: Boolean
|
||||
get() = if (this is KtFunctionSymbol) isSuspend else false
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
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.backend.konan.objcexport.ObjCClassType
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCMethod
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.swiftNameAttribute
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
|
||||
context(KtAnalysisSession)
|
||||
internal val KtClassOrObjectSymbol.isThrowable: Boolean
|
||||
get() {
|
||||
val classId = classIdIfNonLocal ?: return false
|
||||
return classId.isThrowable
|
||||
}
|
||||
|
||||
context(KtAnalysisSession)
|
||||
internal val ClassId.isThrowable: Boolean
|
||||
get() {
|
||||
return StandardNames.FqNames.throwable == this.asSingleFqName()
|
||||
}
|
||||
|
||||
internal fun buildThrowableAsErrorMethod(): ObjCMethod {
|
||||
return ObjCMethod(
|
||||
comment = null,
|
||||
isInstanceMethod = true,
|
||||
returnType = ObjCClassType("NSError"),
|
||||
selectors = listOf("asError"),
|
||||
parameters = emptyList(),
|
||||
attributes = listOf(swiftNameAttribute("asError()")),
|
||||
origin = null
|
||||
)
|
||||
}
|
||||
+7
@@ -6,6 +6,9 @@ import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality
|
||||
import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.name.StandardClassIds
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.buildThrowableAsErrorMethod
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isThrowable
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
||||
|
||||
context(KtAnalysisSession, KtObjCExportSession)
|
||||
@@ -40,6 +43,10 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
|
||||
if (classKind == KtClassKind.ENUM_CLASS) {
|
||||
this += translateEnumMembers()
|
||||
}
|
||||
|
||||
if (isThrowable) {
|
||||
this += buildThrowableAsErrorMethod()
|
||||
}
|
||||
}
|
||||
|
||||
val categoryName: String? = null
|
||||
|
||||
+7
-13
@@ -6,9 +6,11 @@
|
||||
package org.jetbrains.kotlin.objcexport
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.annotations.*
|
||||
import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo
|
||||
import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationsList
|
||||
import org.jetbrains.kotlin.analysis.api.annotations.KtNamedAnnotationValue
|
||||
import org.jetbrains.kotlin.analysis.api.annotations.renderAsSourceCode
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithVisibility
|
||||
@@ -19,7 +21,9 @@ import org.jetbrains.kotlin.backend.konan.objcexport.plus
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.StandardClassIds
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.effectiveThrows
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getObjCDocumentedAnnotations
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isSuspend
|
||||
|
||||
|
||||
context(KtAnalysisSession)
|
||||
@@ -36,10 +40,8 @@ internal fun KtAnnotationsList.translateToObjCComment(): ObjCComment? {
|
||||
*/
|
||||
context(KtAnalysisSession)
|
||||
internal fun KtFunctionLikeSymbol.translateToObjCComment(bridge: MethodBridge, parameters: List<ObjCParameter>): ObjCComment? {
|
||||
val isSuspend: Boolean = if (this is KtFunctionSymbol) this.isSuspend else false
|
||||
|
||||
val throwsComments = if (isSuspend || bridge.returnsError) {
|
||||
val effectiveThrows = getEffectiveThrows(this).toSet()
|
||||
val effectiveThrows = effectiveThrows.toSet()
|
||||
when {
|
||||
effectiveThrows.contains(StandardClassIds.Throwable) -> {
|
||||
listOf("@note This method converts all Kotlin exceptions to errors.")
|
||||
@@ -105,14 +107,6 @@ private fun KtNamedAnnotationValue.render(): String {
|
||||
return "$name=${expression.renderAsSourceCode()}"
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.getEffectiveThrows]
|
||||
*/
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun getEffectiveThrows(method: KtFunctionLikeSymbol): Sequence<ClassId> {
|
||||
return emptySequence()
|
||||
}
|
||||
|
||||
/**
|
||||
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.visibilityComments]
|
||||
*/
|
||||
|
||||
+7
-2
@@ -20,6 +20,7 @@ fun KtClassOrObjectSymbol.translateToObjCConstructors(): List<ObjCMethod> {
|
||||
result += getDeclaredMemberScope().getConstructors()
|
||||
.filter { !it.hasExportForCompilerAnnotation }
|
||||
.filter { it.isVisibleInObjC() }
|
||||
.sortedWith(StableCallableOrder)
|
||||
.map { it.buildObjCMethod() }
|
||||
|
||||
/* Create special 'alloc' constructors */
|
||||
@@ -61,9 +62,13 @@ fun KtClassOrObjectSymbol.translateToObjCConstructors(): List<ObjCMethod> {
|
||||
}
|
||||
}
|
||||
|
||||
if (result.size == 1 && result.first().name == "init") {
|
||||
val initIndex = result.indexOfFirst { it.name == "init" }
|
||||
if (initIndex > -1) {
|
||||
/**
|
||||
* If there is "init" we need always add this special constructor
|
||||
*/
|
||||
result.add(
|
||||
ObjCMethod(
|
||||
initIndex + 1, ObjCMethod(
|
||||
comment = null,
|
||||
origin = getObjCExportStubOrigin(),
|
||||
isInstanceMethod = false,
|
||||
|
||||
+7
@@ -28,6 +28,12 @@ internal val StableFunctionOrder: Comparator<KtFunctionSymbol>
|
||||
*/
|
||||
)
|
||||
|
||||
internal val StableConstructorOrder: Comparator<KtConstructorSymbol>
|
||||
get() = compareBy(
|
||||
{ it.valueParameters.size },
|
||||
// TODO NOW! { KonanManglerDesc.run { it.signatureString(false) } }
|
||||
)
|
||||
|
||||
internal val StableClassifierOrder: Comparator<KtClassifierSymbol> =
|
||||
compareBy<KtClassifierSymbol> { classifier ->
|
||||
if (classifier !is KtClassOrObjectSymbol) return@compareBy 0
|
||||
@@ -45,6 +51,7 @@ internal val StableCallableOrder: Comparator<KtCallableSymbol> = compareBy<KtCal
|
||||
else -> 3
|
||||
}
|
||||
}
|
||||
.thenComparing(StableConstructorOrder)
|
||||
.thenComparing(StablePropertyOrder)
|
||||
.thenComparing(StableFunctionOrder)
|
||||
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package org.jetbrains.kotlin.objcexport.tests
|
||||
|
||||
import org.jetbrains.kotlin.analysis.api.analyze
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.definedThrows
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.effectiveThrows
|
||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.hasThrowsAnnotation
|
||||
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
|
||||
|
||||
class ThrowsAnnotationTest(private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis) {
|
||||
|
||||
@Test
|
||||
fun `test - has throws annotation`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
@Throws
|
||||
fun foo() = Unit
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
analyze(file) {
|
||||
assertTrue(file.getFunctionOrFail("foo").hasThrowsAnnotation)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - override has no throws annotation`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
open class A {
|
||||
@Throws
|
||||
open fun foo() = Unit
|
||||
}
|
||||
class B: A() {
|
||||
override fun foo() = Unit
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
analyze(file) {
|
||||
assertTrue(file.getClassOrFail("A").getFunctionOrFail("foo").hasThrowsAnnotation)
|
||||
assertFalse(file.getClassOrFail("B").getFunctionOrFail("foo").hasThrowsAnnotation)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - defined function throws`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
@Throws(IllegalStateException::class, RuntimeException::class)
|
||||
fun foo() = Unit
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
analyze(file) {
|
||||
val foo = file.getFunctionOrFail("foo")
|
||||
assertEquals(listOf("IllegalStateException", "RuntimeException"), foo.definedThrows.mapName())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - effective and defined classes throws`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
abstract class A {
|
||||
@Throws(IllegalStateException::class)
|
||||
abstract fun foo()
|
||||
}
|
||||
|
||||
class B : A() {
|
||||
@Throws(RuntimeException::class)
|
||||
override fun foo() = Unit
|
||||
}
|
||||
class C : B() {
|
||||
@Throws(IndexOutOfBoundsException::class)
|
||||
override fun foo() = Unit
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
analyze(file) {
|
||||
|
||||
val fooA = file.getClassOrFail("A").getMemberScope().getFunctionOrFail("foo")
|
||||
assertEquals(listOf("IllegalStateException"), fooA.effectiveThrows.mapName())
|
||||
assertEquals(listOf("IllegalStateException"), fooA.definedThrows.mapName())
|
||||
|
||||
val fooB = file.getClassOrFail("B").getMemberScope().getFunctionOrFail("foo")
|
||||
assertEquals(listOf("IllegalStateException"), fooB.effectiveThrows.mapName())
|
||||
assertEquals(listOf("RuntimeException"), fooB.definedThrows.mapName())
|
||||
|
||||
val fooC = file.getClassOrFail("C").getMemberScope().getFunctionOrFail("foo")
|
||||
assertEquals(listOf("IllegalStateException"), fooC.effectiveThrows.mapName())
|
||||
assertEquals(listOf("IndexOutOfBoundsException"), fooC.definedThrows.mapName())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - constructor throws`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
class Foo @Throws(IllegalStateException::class) constructor()
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
analyze(file) {
|
||||
val foo = file.getClassOrFail("Foo").getMemberScope().getConstructors().first()
|
||||
assertEquals(listOf("IllegalStateException"), foo.effectiveThrows.mapName())
|
||||
assertEquals(listOf("IllegalStateException"), foo.definedThrows.mapName())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - non throws annotation`() {
|
||||
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||
"""
|
||||
annotation class NonThrows(vararg val exceptionClasses: KClass<out Throwable>)
|
||||
|
||||
@Throws(RuntimeException::class)
|
||||
@NonThrows(IllegalStateException::class)
|
||||
fun foo() = Unit
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
analyze(file) {
|
||||
val foo = file.getFunctionOrFail("foo")
|
||||
assertEquals(listOf("RuntimeException"), foo.definedThrows.mapName())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<ClassId>.mapName(): List<String> {
|
||||
return map { it.shortClassName.asString() }
|
||||
}
|
||||
Reference in New Issue
Block a user