Report diagnostic on overrides of experimental members

Unless they're experimental themselves

 #KT-22759 In Progress
This commit is contained in:
Alexander Udalov
2018-01-03 15:02:19 +01:00
parent 29c35e6686
commit 153c86c069
12 changed files with 382 additions and 17 deletions
@@ -229,6 +229,9 @@ public interface Errors {
DiagnosticFactory2<PsiElement, FqName, Boolean> EXPERIMENTAL_API_USAGE = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<PsiElement, FqName, Boolean> EXPERIMENTAL_API_USAGE_ERROR = DiagnosticFactory2.create(ERROR);
DiagnosticFactory2<PsiElement, FqName, DeclarationDescriptor> EXPERIMENTAL_OVERRIDE = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<PsiElement, FqName, DeclarationDescriptor> EXPERIMENTAL_OVERRIDE_ERROR = DiagnosticFactory2.create(ERROR);
DiagnosticFactory0<KtAnnotationEntry> USE_EXPERIMENTAL_WITHOUT_ARGUMENTS = DiagnosticFactory0.create(WARNING);
DiagnosticFactory1<KtAnnotationEntry, FqName> USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<KtAnnotationEntry, FqName> USE_EXPERIMENTAL_ARGUMENT_HAS_NON_COMPILATION_IMPACT = DiagnosticFactory1.create(ERROR);
@@ -151,6 +151,9 @@ public class DefaultErrorMessages {
MAP.put(EXPERIMENTAL_API_USAGE, "This declaration is experimental and its usage should be marked with ''@{0}''{1}", TO_STRING, renderUseExperimental);
MAP.put(EXPERIMENTAL_API_USAGE_ERROR, "This declaration is experimental and its usage must be marked with ''@{0}''{1}", TO_STRING, renderUseExperimental);
MAP.put(EXPERIMENTAL_OVERRIDE, "This declaration overrides experimental member of supertype ''{1}'' and should be annotated with ''@{0}''", TO_STRING, NAME);
MAP.put(EXPERIMENTAL_OVERRIDE_ERROR, "This declaration overrides experimental member of supertype ''{1}'' and must be annotated with ''@{0}''", TO_STRING, NAME);
MAP.put(USE_EXPERIMENTAL_WITHOUT_ARGUMENTS, "@UseExperimental without any arguments has no effect");
MAP.put(USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER, "Annotation ''{0}'' is not an experimental API marker, therefore its usage in @UseExperimental is ignored", TO_STRING);
MAP.put(USE_EXPERIMENTAL_ARGUMENT_HAS_NON_COMPILATION_IMPACT,
@@ -81,7 +81,8 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf(
DelegationChecker(),
KClassWithIncorrectTypeArgumentChecker,
SuspendOperatorsCheckers,
InlineClassDeclarationChecker
InlineClassDeclarationChecker,
ExperimentalUsageChecker.Overrides
)
private val DEFAULT_CALL_CHECKERS = listOf(
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
@@ -70,15 +69,32 @@ object ExperimentalUsageChecker : CallChecker {
}
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
checkExperimental(resolvedCall.resultingDescriptor, reportOn, context.trace, context.moduleDescriptor)
checkExperimental(resolvedCall.resultingDescriptor, reportOn, context)
}
private fun checkExperimental(descriptor: DeclarationDescriptor, element: PsiElement, trace: BindingTrace, module: ModuleDescriptor) {
private fun checkExperimental(descriptor: DeclarationDescriptor, element: PsiElement, context: CheckerContext) {
val experimentalities = descriptor.loadExperimentalities()
if (experimentalities.isEmpty()) return
if (experimentalities.isNotEmpty()) {
checkExperimental(experimentalities, element, context.trace.bindingContext, context.moduleDescriptor) {
experimentality, isBodyUsageOfSourceOnlyExperimentality ->
val diagnostic = when (experimentality.severity) {
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_API_USAGE
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_API_USAGE_ERROR
}
context.trace.report(diagnostic.on(element, experimentality.annotationFqName, isBodyUsageOfSourceOnlyExperimentality))
}
}
}
val isBodyUsageExceptPublicInline = element.isBodyUsage(trace.bindingContext, allowPublicInline = false)
val isBodyUsage = isBodyUsageExceptPublicInline || element.isBodyUsage(trace.bindingContext, allowPublicInline = true)
private fun checkExperimental(
experimentalities: Collection<Experimentality>,
element: PsiElement,
bindingContext: BindingContext,
module: ModuleDescriptor,
report: (experimentality: Experimentality, isBodyUsageOfCompilationExperimentality: Boolean) -> Unit
) {
val isBodyUsageExceptPublicInline = element.isBodyUsage(bindingContext, allowPublicInline = false)
val isBodyUsage = isBodyUsageExceptPublicInline || element.isBodyUsage(bindingContext, allowPublicInline = true)
for (experimentality in experimentalities) {
val isBodyUsageOfCompilationExperimentality =
@@ -87,20 +103,14 @@ object ExperimentalUsageChecker : CallChecker {
val isBodyUsageInSameModule =
experimentality.markerDescriptor.module == module && isBodyUsageExceptPublicInline
val annotationFqName = experimentality.annotationFqName
val isExperimentalityAccepted =
isBodyUsageInSameModule ||
(isBodyUsageOfCompilationExperimentality &&
element.hasContainerAnnotatedWithUseExperimental(annotationFqName, trace.bindingContext)) ||
element.propagates(annotationFqName, trace.bindingContext)
element.hasContainerAnnotatedWithUseExperimental(experimentality.annotationFqName, bindingContext)) ||
element.propagates(experimentality.annotationFqName, bindingContext)
if (!isExperimentalityAccepted) {
val diagnostic = when (experimentality.severity) {
ExperimentalUsageChecker.Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_API_USAGE
ExperimentalUsageChecker.Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_API_USAGE_ERROR
}
trace.report(diagnostic.on(element, annotationFqName, isBodyUsageOfCompilationExperimentality))
report(experimentality, isBodyUsageOfCompilationExperimentality)
}
}
}
@@ -214,7 +224,30 @@ object ExperimentalUsageChecker : CallChecker {
object ClassifierUsage : ClassifierUsageChecker {
override fun check(targetDescriptor: ClassifierDescriptor, element: PsiElement, context: ClassifierUsageCheckerContext) {
checkExperimental(targetDescriptor, element, context.trace, context.moduleDescriptor)
checkExperimental(targetDescriptor, element, context)
}
}
object Overrides : DeclarationChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (descriptor !is CallableMemberDescriptor) return
val experimentalOverridden = descriptor.overriddenDescriptors.flatMap { member ->
member.loadExperimentalities().map { experimentality -> experimentality to member }
}.toMap()
val module = descriptor.module
for ((experimentality, member) in experimentalOverridden) {
checkExperimental(listOf(experimentality), declaration, context.trace.bindingContext, module) { _, _ ->
val diagnostic = when (experimentality.severity) {
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_OVERRIDE
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_OVERRIDE_ERROR
}
val reportOn = (declaration as? KtNamedDeclaration)?.nameIdentifier ?: declaration
context.trace.report(diagnostic.on(reportOn, experimentality.annotationFqName, member.containingDeclaration))
}
}
}
}