From 42d8db1c52c8be0f389d14898da7c2df347cce57 Mon Sep 17 00:00:00 2001 From: "eugene.levenetc" Date: Fri, 1 Mar 2024 10:36:47 +0100 Subject: [PATCH] [ObjCExport] Fix suspend function and @Throws translations KT-66115, KT-66239 --- .../analysisApiUtils/definedThrows.kt | 34 +++++ .../analysisApiUtils/effectiveThrows.kt | 37 +++++ .../analysisApiUtils/getMethodBridge.kt | 27 +++- .../analysisApiUtils/hasThrowsAnnotation.kt | 11 ++ .../objcexport/analysisApiUtils/isSuspend.kt | 7 + .../analysisApiUtils/isThrowable.kt | 34 +++++ .../kotlin/objcexport/translateToObjCClass.kt | 7 + .../objcexport/translateToObjCComment.kt | 20 +-- .../objcexport/translateToObjCConstructor.kt | 9 +- .../kotlin/objcexport/translationOrder.kt | 7 + .../objcexport/tests/ThrowsAnnotationTest.kt | 139 ++++++++++++++++++ 11 files changed, 310 insertions(+), 22 deletions(-) create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/definedThrows.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/effectiveThrows.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/hasThrowsAnnotation.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isSuspend.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isThrowable.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ThrowsAnnotationTest.kt diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/definedThrows.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/definedThrows.kt new file mode 100644 index 00000000000..92fb1038969 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/definedThrows.kt @@ -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 + 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() + .flatMap { arrayAnnotationValue -> arrayAnnotationValue.values } + .filterIsInstance() + .map { it.classId } + .toList() + } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/effectiveThrows.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/effectiveThrows.kt new file mode 100644 index 00000000000..060122ec604 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/effectiveThrows.kt @@ -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 + get() { + getAllOverriddenSymbols().firstOrNull()?.let { return (it as KtFunctionLikeSymbol).effectiveThrows } + return definedThrows + } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt index 6379401bf5a..0f0446e2827 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt @@ -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 +} \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/hasThrowsAnnotation.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/hasThrowsAnnotation.kt new file mode 100644 index 00000000000..6234775d6e1 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/hasThrowsAnnotation.kt @@ -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)) + } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isSuspend.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isSuspend.kt new file mode 100644 index 00000000000..1423b37cf16 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isSuspend.kt @@ -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 \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isThrowable.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isThrowable.kt new file mode 100644 index 00000000000..c4830a44828 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isThrowable.kt @@ -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 + ) +} \ No newline at end of file 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 5ba15ba8a13..e7d1dfd1ab4 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 @@ -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 diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCComment.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCComment.kt index 3c54ea31c36..dded941e578 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCComment.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCComment.kt @@ -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): 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 { - return emptySequence() -} - /** * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.visibilityComments] */ diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt index ff3d7d081de..6ae28317ee7 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt @@ -20,6 +20,7 @@ fun KtClassOrObjectSymbol.translateToObjCConstructors(): List { 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 { } } - 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, diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt index f19030274ef..b55c79358bd 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt @@ -28,6 +28,12 @@ internal val StableFunctionOrder: Comparator */ ) +internal val StableConstructorOrder: Comparator + get() = compareBy( + { it.valueParameters.size }, + // TODO NOW! { KonanManglerDesc.run { it.signatureString(false) } } + ) + internal val StableClassifierOrder: Comparator = compareBy { classifier -> if (classifier !is KtClassOrObjectSymbol) return@compareBy 0 @@ -45,6 +51,7 @@ internal val StableCallableOrder: Comparator = compareBy 3 } } + .thenComparing(StableConstructorOrder) .thenComparing(StablePropertyOrder) .thenComparing(StableFunctionOrder) diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ThrowsAnnotationTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ThrowsAnnotationTest.kt new file mode 100644 index 00000000000..a1c8a7bc143 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ThrowsAnnotationTest.kt @@ -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) + + @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.mapName(): List { + return map { it.shortClassName.asString() } +} \ No newline at end of file