Add Native diagnostics for @Throws suspend fun

`@Throws suspend fun` must have CancellationException accepted.

Also add more tests.
This commit is contained in:
Svyatoslav Scherbina
2020-05-20 17:59:03 +03:00
parent 3e8b0055ab
commit 242d0aa26e
5 changed files with 244 additions and 2 deletions
@@ -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"
@@ -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<KtDeclaration, Collection<DeclarationDescriptor>>(Severity.ERROR)
@JvmField
val MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND = DiagnosticFactory1.create<KtElement, FqName>(Severity.ERROR)
@JvmField
val INAPPLICABLE_SHARED_IMMUTABLE_PROPERTY = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
@JvmField
val INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
@@ -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<ConstantValue<*>>?)
private fun ConstantValue<*>.isGlobalClassWithId(classIds: Set<ClassId>): Boolean =
this is KClassValue && when (val value = this.value) {
is KClassValue.Value.NormalClass -> value.classId in classIds
is KClassValue.Value.LocalClass -> false
}
}