diff --git a/compiler/testData/diagnostics/nativeTests/throws.kt b/compiler/testData/diagnostics/nativeTests/throws.kt index 70edb551462..2bac57550f6 100644 --- a/compiler/testData/diagnostics/nativeTests/throws.kt +++ b/compiler/testData/diagnostics/nativeTests/throws.kt @@ -1,4 +1,4 @@ -// FILE: throws.kt +// FILE: kotlin.kt package kotlin import kotlin.reflect.KClass @@ -7,13 +7,26 @@ import kotlin.reflect.KClass @Retention(AnnotationRetention.SOURCE) public annotation class Throws(vararg val exceptionClasses: KClass) +public open class Exception : Throwable() + +public open class RuntimeException : Exception() + +public open class IllegalStateException : RuntimeException() + // FILE: native.kt package kotlin.native @Deprecated("") public typealias Throws = kotlin.Throws +// FILE: CancellationException.kt +package kotlin.coroutines.cancellation + +public open class CancellationException() : IllegalStateException() + // FILE: test.kt +import kotlin.coroutines.cancellation.CancellationException + class Exception1 : Throwable() class Exception2 : Throwable() class Exception3 : Throwable() @@ -21,6 +34,9 @@ class Exception3 : Throwable() @Throws fun foo() {} +@Throws() +fun throwsEmptyParens() {} + interface Base0 { fun foo() } @@ -200,3 +216,88 @@ class OverrideIncompatibleThrowsOnFakeOverride2 : IncompatibleThrowsOnFakeOverri class InheritIncompatibleThrowsOnFakeOverride : IncompatibleThrowsOnFakeOverride { override fun foo() {} } + +@Throws +suspend fun suspendThrowsNothing() {} + +interface SuspendFun { + suspend fun foo() +} + +class OverrideImplicitThrowsOnSuspendWithExplicit : SuspendFun { + // Although `SuspendFun.foo` effectively has `@Throws(CancellationException::class)`, + // overriding it with equal explicit `@Throws` is forbidden: + @Throws(CancellationException::class) override suspend fun foo() {} +} + +interface SuspendFunThrows { + @Throws(CancellationException::class) suspend fun foo() {} +} + +class InheritExplicitThrowsOnSuspend : SuspendFunThrows { + override suspend fun foo() {} +} + +@Throws(Exception1::class) +suspend fun suspendDoesNotThrowCancellationException1() {} + +@Throws(Exception1::class, Exception2::class) +suspend fun suspendDoesNotThrowCancellationException2() {} + +@Throws(Exception1::class, CancellationException::class) +suspend fun suspendThrowsCancellationException1() {} + +@Throws(CancellationException::class, Exception1::class) +suspend fun suspendThrowsCancellationException2() {} + +typealias CancellationExceptionAlias = CancellationException + +@Throws(CancellationExceptionAlias::class) +suspend fun suspendThrowsCancellationExceptionTypealias() {} + +@Throws(IllegalStateException::class) +suspend fun suspendThrowsIllegalStateException1() {} + +@Throws(Exception2::class, IllegalStateException::class) +suspend fun suspendThrowsIllegalStateException2() {} + +typealias IllegalStateExceptionAlias = IllegalStateException + +@Throws(IllegalStateExceptionAlias::class) +suspend fun suspendThrowsIllegalStateExceptionTypealias() {} + +@Throws(RuntimeException::class) +suspend fun suspendThrowsRuntimeException1() {} + +@Throws(RuntimeException::class, Exception3::class) +suspend fun suspendThrowsRuntimeException2() {} + +typealias RuntimeExceptionAlias = RuntimeException + +@Throws(RuntimeExceptionAlias::class) +suspend fun suspendThrowsRuntimeExceptionTypealias() {} + +@Throws(Exception::class) +suspend fun suspendThrowsException1() {} + +@Throws(Exception1::class, Exception::class) +suspend fun suspendThrowsException2() {} + +typealias ExceptionAlias = Exception + +@Throws(ExceptionAlias::class) +suspend fun suspendThrowsExceptionTypealias() {} + +@Throws(Throwable::class) +suspend fun suspendThrowsThrowable1() {} + +@Throws(Throwable::class, Exception2::class) +suspend fun suspendThrowsThrowable2() {} + +@Throws(Throwable::class, CancellationException::class) +suspend fun suspendThrowsThrowable3() {} + +typealias ThrowableAlias = Throwable + +@Throws(ThrowableAlias::class) +suspend fun suspendThrowsThrowableTypealias() {} diff --git a/compiler/testData/diagnostics/nativeTests/throws.txt b/compiler/testData/diagnostics/nativeTests/throws.txt index 0e3d33776f5..70d66771eac 100644 --- a/compiler/testData/diagnostics/nativeTests/throws.txt +++ b/compiler/testData/diagnostics/nativeTests/throws.txt @@ -1,6 +1,26 @@ package @kotlin.Throws(exceptionClasses = {}) public fun foo(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {Exception1::class}) public suspend fun suspendDoesNotThrowCancellationException1(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {Exception1::class, Exception2::class}) public suspend fun suspendDoesNotThrowCancellationException2(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {Exception1::class, kotlin.coroutines.cancellation.CancellationException::class}) public suspend fun suspendThrowsCancellationException1(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.coroutines.cancellation.CancellationException::class, Exception1::class}) public suspend fun suspendThrowsCancellationException2(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.coroutines.cancellation.CancellationException::class}) public suspend fun suspendThrowsCancellationExceptionTypealias(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.Exception::class}) public suspend fun suspendThrowsException1(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {Exception1::class, kotlin.Exception::class}) public suspend fun suspendThrowsException2(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.Exception::class}) public suspend fun suspendThrowsExceptionTypealias(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.IllegalStateException::class}) public suspend fun suspendThrowsIllegalStateException1(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {Exception2::class, kotlin.IllegalStateException::class}) public suspend fun suspendThrowsIllegalStateException2(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.IllegalStateException::class}) public suspend fun suspendThrowsIllegalStateExceptionTypealias(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {}) public suspend fun suspendThrowsNothing(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.RuntimeException::class}) public suspend fun suspendThrowsRuntimeException1(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.RuntimeException::class, Exception3::class}) public suspend fun suspendThrowsRuntimeException2(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.RuntimeException::class}) public suspend fun suspendThrowsRuntimeExceptionTypealias(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.Throwable::class}) public suspend fun suspendThrowsThrowable1(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.Throwable::class, Exception2::class}) public suspend fun suspendThrowsThrowable2(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.Throwable::class, kotlin.coroutines.cancellation.CancellationException::class}) public suspend fun suspendThrowsThrowable3(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {kotlin.Throwable::class}) public suspend fun suspendThrowsThrowableTypealias(): kotlin.Unit +@kotlin.Throws(exceptionClasses = {}) public fun throwsEmptyParens(): kotlin.Unit public fun withLocalClass(): kotlin.Unit public interface Base0 { @@ -102,6 +122,14 @@ public interface IncompatibleThrowsOnFakeOverride : Base1, Base2 { public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String } +public final class InheritExplicitThrowsOnSuspend : SuspendFunThrows { + public constructor InheritExplicitThrowsOnSuspend() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ suspend fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + public final class InheritIncompatibleThrowsOnFakeOverride : IncompatibleThrowsOnFakeOverride { public constructor InheritIncompatibleThrowsOnFakeOverride() public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean @@ -222,6 +250,14 @@ public final class OverrideDifferentThrowsOnFakeOverride : ThrowsOnFakeOverride public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } +public final class OverrideImplicitThrowsOnSuspendWithExplicit : SuspendFun { + public constructor OverrideImplicitThrowsOnSuspendWithExplicit() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.Throws(exceptionClasses = {kotlin.coroutines.cancellation.CancellationException::class}) public open override /*1*/ suspend fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + public final class OverrideIncompatibleThrowsOnFakeOverride1 : IncompatibleThrowsOnFakeOverride { public constructor OverrideIncompatibleThrowsOnFakeOverride1() public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean @@ -342,6 +378,20 @@ public final class SameThrowsOnOverride : Base1 { public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } +public interface SuspendFun { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract suspend fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface SuspendFunThrows { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.Throws(exceptionClasses = {kotlin.coroutines.cancellation.CancellationException::class}) public open suspend fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + public interface ThrowsOnFakeOverride : Base1 { public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean @kotlin.Throws(exceptionClasses = {Exception1::class}) public abstract override /*1*/ /*fake_override*/ fun foo(): kotlin.Unit @@ -356,9 +406,41 @@ public final class ThrowsOnOverride : Base0 { public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } +public typealias CancellationExceptionAlias = kotlin.coroutines.cancellation.CancellationException +public typealias ExceptionAlias = kotlin.Exception +public typealias IllegalStateExceptionAlias = kotlin.IllegalStateException +public typealias RuntimeExceptionAlias = kotlin.RuntimeException +public typealias ThrowableAlias = kotlin.Throwable package kotlin { + public open class Exception : kotlin.Throwable { + public constructor Exception() + public open override /*1*/ /*fake_override*/ val cause: kotlin.Throwable? + public open override /*1*/ /*fake_override*/ val message: kotlin.String? + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public open class IllegalStateException : kotlin.RuntimeException { + public constructor IllegalStateException() + public open override /*1*/ /*fake_override*/ val cause: kotlin.Throwable? + public open override /*1*/ /*fake_override*/ val message: kotlin.String? + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public open class RuntimeException : kotlin.Exception { + public constructor RuntimeException() + public open override /*1*/ /*fake_override*/ val cause: kotlin.Throwable? + public open override /*1*/ /*fake_override*/ val message: kotlin.String? + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.CONSTRUCTOR}) @kotlin.annotation.Retention(value = AnnotationRetention.SOURCE) public final annotation class Throws : kotlin.Annotation { public constructor Throws(/*0*/ vararg exceptionClasses: kotlin.reflect.KClass /*kotlin.Array>*/) public final val exceptionClasses: kotlin.Array> @@ -367,6 +449,21 @@ package kotlin { public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } + package kotlin.coroutines { + + package kotlin.coroutines.cancellation { + + public open class CancellationException : kotlin.IllegalStateException { + public constructor CancellationException() + public open override /*1*/ /*fake_override*/ val cause: kotlin.Throwable? + public open override /*1*/ /*fake_override*/ val message: kotlin.String? + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } + } + package kotlin.native { @kotlin.Deprecated(message = "") public typealias Throws = kotlin.Throws } diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt index a9bec9d3117..8971687dffe 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.resolve.konan.diagnostics import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap +import org.jetbrains.kotlin.diagnostics.rendering.Renderer import org.jetbrains.kotlin.diagnostics.rendering.Renderers private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { @@ -20,6 +21,11 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { ErrorsNative.INCOMPATIBLE_THROWS_INHERITED, "Member inherits different @Throws filters from {0}", Renderers.commaSeparated(Renderers.NAME) ) + put( + ErrorsNative.MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND, + "@Throws on suspend declaration must have {0} (or any of its superclasses) listed", + Renderer { it.shortName().asString() } + ) put( ErrorsNative.INAPPLICABLE_SHARED_IMMUTABLE_PROPERTY, "@SharedImmutable is applicable only to val with backing field or to property with delegation" diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt index 46fde45fd7a..b97951bf852 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0 import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Severity +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtElement @@ -21,6 +22,8 @@ object ErrorsNative { @JvmField val INCOMPATIBLE_THROWS_INHERITED = DiagnosticFactory1.create>(Severity.ERROR) @JvmField + val MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND = DiagnosticFactory1.create(Severity.ERROR) + @JvmField val INAPPLICABLE_SHARED_IMMUTABLE_PROPERTY = DiagnosticFactory0.create(Severity.ERROR) @JvmField val INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL = DiagnosticFactory0.create(Severity.ERROR) diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThrowsChecker.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThrowsChecker.kt index 42a2e722df5..7274c4e2124 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThrowsChecker.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThrowsChecker.kt @@ -6,9 +6,13 @@ package org.jetbrains.kotlin.resolve.konan.diagnostics +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.lexer.KtTokens.SUSPEND_KEYWORD +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils @@ -17,20 +21,45 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.ConstantValue +import org.jetbrains.kotlin.resolve.constants.KClassValue import org.jetbrains.kotlin.resolve.descriptorUtil.firstArgument import org.jetbrains.kotlin.utils.DFS object NativeThrowsChecker : DeclarationChecker { private val throwsFqName = KOTLIN_THROWS_ANNOTATION_FQ_NAME + private val cancellationExceptionFqName = FqName("kotlin.coroutines.cancellation.CancellationException") + + // Note: can't use subtyping, because CancellationException can be missing (e.g. for common code). + private val cancellationExceptionAndSupersClassIds = sequenceOf( + KotlinBuiltIns.FQ_NAMES.throwable, + FqName("kotlin.Exception"), + FqName("kotlin.RuntimeException"), + FqName("kotlin.IllegalStateException"), + cancellationExceptionFqName + ).map { ClassId.topLevel(it) }.toSet() + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { val throwsAnnotation = descriptor.annotations.findAnnotation(throwsFqName) val reportLocation = throwsAnnotation?.let { DescriptorToSourceUtils.getSourceFromAnnotation(it) } ?: declaration if (!checkInheritance(declaration, descriptor, context, throwsAnnotation, reportLocation)) return - if (throwsAnnotation != null && throwsAnnotation.getVariadicArguments().isEmpty()) { + if (throwsAnnotation == null) return + + val classes = throwsAnnotation.getVariadicArguments() + if (classes.isEmpty()) { context.trace.report(ErrorsNative.THROWS_LIST_EMPTY.on(reportLocation)) + return + } + + if (declaration.hasModifier(SUSPEND_KEYWORD) && classes.none { it.isGlobalClassWithId(cancellationExceptionAndSupersClassIds) }) { + context.trace.report( + ErrorsNative.MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND.on( + reportLocation, + cancellationExceptionFqName + ) + ) } } @@ -98,4 +127,10 @@ object NativeThrowsChecker : DeclarationChecker { private data class ThrowsFilter(val classes: Set>?) + private fun ConstantValue<*>.isGlobalClassWithId(classIds: Set): Boolean = + this is KClassValue && when (val value = this.value) { + is KClassValue.Value.NormalClass -> value.classId in classIds + is KClassValue.Value.LocalClass -> false + } + }