Report diagnostic on overrides of experimental members
Unless they're experimental themselves #KT-22759 In Progress
This commit is contained in:
@@ -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);
|
||||
|
||||
+3
@@ -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(
|
||||
|
||||
+49
-16
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user