org.jetbrains.kotlin.resolve.konan.diagnostics.NativeThrowsChecker.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* Copyright 2010-2020 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.resolve.konan.diagnostics
import org.jetbrains.kotlin.builtins.StandardNames
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.Call
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.StatementFilter
import org.jetbrains.kotlin.resolve.annotations.KOTLIN_THROWS_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.calls.util.getCall
import org.jetbrains.kotlin.resolve.calls.util.hasUnresolvedArguments
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(
StandardNames.FqNames.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 throwsAnnotationEntry = throwsAnnotation?.let { DescriptorToSourceUtils.getSourceFromAnnotation(it) }
val reportLocation = throwsAnnotationEntry ?: declaration
if (!checkInheritance(declaration, descriptor, context, throwsAnnotation, reportLocation)) return
if (throwsAnnotation == null) return
val bindingContext = context.trace.bindingContext
if (throwsAnnotationEntry?.getCall(bindingContext)?.hasUnresolvedArgumentsRecursive(bindingContext) == true) 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
)
)
}
}
private fun checkInheritance(
declaration: KtDeclaration,
descriptor: DeclarationDescriptor,
context: DeclarationCheckerContext,
throwsAnnotation: AnnotationDescriptor?,
reportLocation: KtElement
): Boolean {
if (descriptor !is CallableMemberDescriptor || descriptor.overriddenDescriptors.isEmpty()) return true
val inherited = findInheritedThrows(descriptor).entries.distinctBy { it.value }
if (inherited.size >= 2) {
context.trace.report(ErrorsNative.INCOMPATIBLE_THROWS_INHERITED.on(declaration, inherited.map { it.key.containingDeclaration }))
return false
}
if (throwsAnnotation == null) return true
val (overriddenMember, overriddenThrows) = inherited.firstOrNull()
?: return true // Should not happen though.
if (decodeThrowsFilter(throwsAnnotation) != overriddenThrows) {
context.trace.report(ErrorsNative.INCOMPATIBLE_THROWS_OVERRIDE.on(reportLocation, overriddenMember.containingDeclaration))
return false
}
return true
}
private fun findInheritedThrows(descriptor: CallableMemberDescriptor): Map {
val result = mutableMapOf()
DFS.dfs(
descriptor.overriddenDescriptors,
{ current -> current.overriddenDescriptors },
object : DFS.AbstractNodeHandler() {
override fun beforeChildren(current: CallableMemberDescriptor): Boolean {
val throwsAnnotation = current.annotations.findAnnotation(throwsFqName).takeIf { current.kind.isReal }
return if (throwsAnnotation == null && current.overriddenDescriptors.isNotEmpty()) {
// Visit overridden members:
true
} else {
// Take current and ignore overridden:
result[current.original] = decodeThrowsFilter(throwsAnnotation)
false
}
}
override fun result() {}
})
return result
}
private fun AnnotationDescriptor.getVariadicArguments(): List> {
val argument = this.firstArgument() as? ArrayValue ?: return emptyList()
return argument.value
}
private fun decodeThrowsFilter(throwsAnnotation: AnnotationDescriptor?) =
ThrowsFilter(throwsAnnotation?.getVariadicArguments()?.toSet())
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
}
}
private fun Call.hasUnresolvedArgumentsRecursive(context: BindingContext): Boolean {
return this.hasUnresolvedArguments(context, StatementFilter.NONE) ||
valueArguments.any { it.getArgumentExpression()?.getCall(context)?.hasUnresolvedArgumentsRecursive(context) == true }
}