Partial rename of 'Experimentality' around K1 checkers

This commit is contained in:
Mikhail Glukhikh
2022-05-31 10:35:51 +02:00
committed by Space
parent 8d2868da32
commit 8baff4404d
9 changed files with 108 additions and 139 deletions
@@ -33,7 +33,7 @@ import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.load.java.components.TraceBasedErrorReporter import org.jetbrains.kotlin.load.java.components.TraceBasedErrorReporter
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker import org.jetbrains.kotlin.resolve.checkers.OptInUsageChecker
import org.jetbrains.kotlin.resolve.jvm.JvmBindingContextSlices import org.jetbrains.kotlin.resolve.jvm.JvmBindingContextSlices
class AnalyzerWithCompilerReport( class AnalyzerWithCompilerReport(
@@ -114,7 +114,7 @@ class AnalyzerWithCompilerReport(
override fun analyzeAndReport(files: Collection<KtFile>, analyze: () -> AnalysisResult) { override fun analyzeAndReport(files: Collection<KtFile>, analyze: () -> AnalysisResult) {
analysisResult = analyze() analysisResult = analyze()
if (!analysisResult.isError()) { if (!analysisResult.isError()) {
ExperimentalUsageChecker.checkCompilerArguments( OptInUsageChecker.checkCompilerArguments(
analysisResult.moduleDescriptor, languageVersionSettings, analysisResult.moduleDescriptor, languageVersionSettings,
reportError = { message -> messageCollector.report(ERROR, message) }, reportError = { message -> messageCollector.report(ERROR, message) },
reportWarning = { message -> messageCollector.report(WARNING, message) } reportWarning = { message -> messageCollector.report(WARNING, message) }
@@ -14,7 +14,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirRegularClass import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
import org.jetbrains.kotlin.resolve.checkers.Experimentality import org.jetbrains.kotlin.resolve.checkers.OptInDescription
import org.jetbrains.kotlin.resolve.checkers.OptInNames import org.jetbrains.kotlin.resolve.checkers.OptInNames
object FirOptInAnnotationClassChecker : FirRegularClassChecker() { object FirOptInAnnotationClassChecker : FirRegularClassChecker() {
@@ -26,7 +26,7 @@ object FirOptInAnnotationClassChecker : FirRegularClassChecker() {
reporter.reportOn(target?.source, FirErrors.OPT_IN_MARKER_WITH_WRONG_RETENTION, context) reporter.reportOn(target?.source, FirErrors.OPT_IN_MARKER_WITH_WRONG_RETENTION, context)
} }
val wrongTargets = declaration.getAllowedAnnotationTargets().intersect(Experimentality.WRONG_TARGETS_FOR_MARKER) val wrongTargets = declaration.getAllowedAnnotationTargets().intersect(OptInDescription.WRONG_TARGETS_FOR_MARKER)
if (wrongTargets.isNotEmpty()) { if (wrongTargets.isNotEmpty()) {
val target = declaration.getTargetAnnotation() val target = declaration.getTargetAnnotation()
reporter.reportOn( reporter.reportOn(
@@ -39,7 +39,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.components.ClassicConstraint
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactoryImpl import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactoryImpl
import org.jetbrains.kotlin.resolve.calls.tower.KotlinResolutionStatelessCallbacksImpl import org.jetbrains.kotlin.resolve.calls.tower.KotlinResolutionStatelessCallbacksImpl
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker import org.jetbrains.kotlin.resolve.checkers.OptInUsageChecker
import org.jetbrains.kotlin.resolve.descriptorUtil.isTypeRefinementEnabled import org.jetbrains.kotlin.resolve.descriptorUtil.isTypeRefinementEnabled
import org.jetbrains.kotlin.resolve.lazy.* import org.jetbrains.kotlin.resolve.lazy.*
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory
@@ -105,9 +105,9 @@ private fun StorageComponentContainer.configurePlatformIndependentComponents() {
useImpl<KotlinResolutionStatelessCallbacksImpl>() useImpl<KotlinResolutionStatelessCallbacksImpl>()
useImpl<DataFlowValueFactoryImpl>() useImpl<DataFlowValueFactoryImpl>()
useImpl<ExperimentalUsageChecker>() useImpl<OptInUsageChecker>()
useImpl<ExperimentalUsageChecker.Overrides>() useImpl<OptInUsageChecker.Overrides>()
useImpl<ExperimentalUsageChecker.ClassifierUsage>() useImpl<OptInUsageChecker.ClassifierUsage>()
useImpl<ContractDeserializerImpl>() useImpl<ContractDeserializerImpl>()
useImpl<CompilerDeserializationConfiguration>() useImpl<CompilerDeserializationConfiguration>()
@@ -123,7 +123,7 @@ abstract class PlatformConfiguratorBase(
} }
override fun configureModuleDependentCheckers(container: StorageComponentContainer) { override fun configureModuleDependentCheckers(container: StorageComponentContainer) {
container.useImpl<ExperimentalMarkerDeclarationAnnotationChecker>() container.useImpl<OptInMarkerDeclarationAnnotationChecker>()
} }
fun configureExtensionsAndCheckers(container: StorageComponentContainer) { fun configureExtensionsAndCheckers(container: StorageComponentContainer) {
@@ -6,9 +6,7 @@
package org.jetbrains.kotlin.resolve.checkers package org.jetbrains.kotlin.resolve.checkers
import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
@@ -16,12 +14,8 @@ import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.* import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.*
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotated import org.jetbrains.kotlin.psi.KtAnnotated
import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker
import org.jetbrains.kotlin.resolve.AnnotationChecker import org.jetbrains.kotlin.resolve.AnnotationChecker
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
@@ -34,7 +28,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getAnnotationRetention import org.jetbrains.kotlin.resolve.descriptorUtil.getAnnotationRetention
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleDescriptor) : AdditionalAnnotationChecker { class OptInMarkerDeclarationAnnotationChecker(private val module: ModuleDescriptor) : AdditionalAnnotationChecker {
override fun checkEntries( override fun checkEntries(
entries: List<KtAnnotationEntry>, entries: List<KtAnnotationEntry>,
actualTargets: List<KotlinTarget>, actualTargets: List<KotlinTarget>,
@@ -42,7 +36,7 @@ class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleD
annotated: KtAnnotated?, annotated: KtAnnotated?,
languageVersionSettings: LanguageVersionSettings languageVersionSettings: LanguageVersionSettings
) { ) {
var isAnnotatedWithExperimental = false var hasOptIn = false
for (entry in entries) { for (entry in entries) {
val annotation = trace.bindingContext.get(BindingContext.ANNOTATION, entry) ?: continue val annotation = trace.bindingContext.get(BindingContext.ANNOTATION, entry) ?: continue
@@ -54,7 +48,7 @@ class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleD
checkOptInUsage(annotationClasses, trace, entry) checkOptInUsage(annotationClasses, trace, entry)
} }
in OptInNames.REQUIRES_OPT_IN_FQ_NAMES -> { in OptInNames.REQUIRES_OPT_IN_FQ_NAMES -> {
isAnnotatedWithExperimental = true hasOptIn = true
} }
} }
val annotationClass = annotation.annotationClass ?: continue val annotationClass = annotation.annotationClass ?: continue
@@ -85,36 +79,11 @@ class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleD
} }
} }
if (isAnnotatedWithExperimental) { if (hasOptIn) {
checkMarkerTargetsAndRetention(entries, trace) checkMarkerTargetsAndRetention(entries, trace)
} }
} }
private fun CallableMemberDescriptor.hasExperimentalOverriddenDescriptors(
experimentalFqName: FqName,
visited: MutableSet<CallableMemberDescriptor> = mutableSetOf()
): Boolean {
if (!visited.add(this)) return false
for (overridden in overriddenDescriptors) {
if (overridden.annotations.any { it.fqName == experimentalFqName }) {
return true
}
var containingDeclaration = overridden.containingDeclaration
while (containingDeclaration is ClassDescriptor) {
if (containingDeclaration.annotations.any { it.fqName == experimentalFqName }) {
return true
}
containingDeclaration = containingDeclaration.containingDeclaration
}
if (overridden.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE &&
overridden.hasExperimentalOverriddenDescriptors(experimentalFqName)
) {
return true
}
}
return false
}
private fun checkOptInUsage(annotationClasses: List<ConstantValue<*>>, trace: BindingTrace, entry: KtAnnotationEntry) { private fun checkOptInUsage(annotationClasses: List<ConstantValue<*>>, trace: BindingTrace, entry: KtAnnotationEntry) {
if (annotationClasses.isEmpty()) { if (annotationClasses.isEmpty()) {
trace.report(Errors.OPT_IN_WITHOUT_ARGUMENTS.on(entry)) trace.report(Errors.OPT_IN_WITHOUT_ARGUMENTS.on(entry))
@@ -125,10 +94,10 @@ class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleD
val classDescriptor = val classDescriptor =
(annotationClass as? KClassValue)?.getArgumentType(module)?.constructor?.declarationDescriptor as? ClassDescriptor (annotationClass as? KClassValue)?.getArgumentType(module)?.constructor?.declarationDescriptor as? ClassDescriptor
?: continue ?: continue
val experimentality = with(ExperimentalUsageChecker) { val optInDescription = with(OptInUsageChecker) {
classDescriptor.loadExperimentalityForMarkerAnnotation() classDescriptor.loadOptInForMarkerAnnotation()
} }
if (experimentality == null) { if (optInDescription == null) {
trace.report(Errors.OPT_IN_ARGUMENT_IS_NOT_MARKER.on(entry, classDescriptor.fqNameSafe)) trace.report(Errors.OPT_IN_ARGUMENT_IS_NOT_MARKER.on(entry, classDescriptor.fqNameSafe))
} }
} }
@@ -145,7 +114,7 @@ class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleD
if (targetEntry != null) { if (targetEntry != null) {
val (entry, descriptor) = targetEntry val (entry, descriptor) = targetEntry
val allowedTargets = AnnotationChecker.loadAnnotationTargets(descriptor!!) ?: return val allowedTargets = AnnotationChecker.loadAnnotationTargets(descriptor!!) ?: return
val wrongTargets = allowedTargets.intersect(Experimentality.WRONG_TARGETS_FOR_MARKER) val wrongTargets = allowedTargets.intersect(OptInDescription.WRONG_TARGETS_FOR_MARKER)
if (wrongTargets.isNotEmpty()) { if (wrongTargets.isNotEmpty()) {
trace.report( trace.report(
Errors.OPT_IN_MARKER_WITH_WRONG_TARGET.on( Errors.OPT_IN_MARKER_WITH_WRONG_TARGET.on(
@@ -59,45 +59,45 @@ import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.utils.SmartSet import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
class ExperimentalUsageChecker(project: Project) : CallChecker { class OptInUsageChecker(project: Project) : CallChecker {
private val moduleAnnotationsResolver = ModuleAnnotationsResolver.getInstance(project) private val moduleAnnotationsResolver = ModuleAnnotationsResolver.getInstance(project)
interface ExperimentalityDiagnostic { interface OptInDiagnosticReporter {
fun report(trace: BindingTrace, element: PsiElement, fqName: FqName, message: String?) fun report(trace: BindingTrace, element: PsiElement, fqName: FqName, message: String?)
} }
class ExperimentalityDiagnostic2( class OptInFactoryBasedReporter(
val factory: DiagnosticFactory2<PsiElement, FqName, String>, val factory: DiagnosticFactory2<PsiElement, FqName, String>,
val defaultMessage: (FqName) -> String val defaultMessage: (FqName) -> String
) : ExperimentalityDiagnostic { ) : OptInDiagnosticReporter {
override fun report(trace: BindingTrace, element: PsiElement, fqName: FqName, message: String?) { override fun report(trace: BindingTrace, element: PsiElement, fqName: FqName, message: String?) {
trace.reportDiagnosticOnce(factory.on(element, fqName, message?.takeIf { it.isNotBlank() } ?: defaultMessage(fqName))) trace.reportDiagnosticOnce(factory.on(element, fqName, message?.takeIf { it.isNotBlank() } ?: defaultMessage(fqName)))
} }
} }
data class ExperimentalityDiagnostics( data class OptInReporterMultiplexer(
val warning: ExperimentalityDiagnostic, val warning: OptInDiagnosticReporter,
val error: ExperimentalityDiagnostic, val error: OptInDiagnosticReporter,
val futureError: ExperimentalityDiagnostic val futureError: OptInDiagnosticReporter
) )
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val resultingDescriptor = resolvedCall.resultingDescriptor val resultingDescriptor = resolvedCall.resultingDescriptor
val experimentalities = resultingDescriptor.loadExperimentalities(moduleAnnotationsResolver, context.languageVersionSettings) val optIns = resultingDescriptor.loadOptIns(moduleAnnotationsResolver, context.languageVersionSettings)
if (resultingDescriptor is FunctionDescriptor && if (resultingDescriptor is FunctionDescriptor &&
resultingDescriptor.kind == CallableMemberDescriptor.Kind.SYNTHESIZED resultingDescriptor.kind == CallableMemberDescriptor.Kind.SYNTHESIZED
) { ) {
val propertyDescriptor = resultingDescriptor.findRelevantDataClassPropertyIfAny(context) val propertyDescriptor = resultingDescriptor.findRelevantDataClassPropertyIfAny(context)
if (propertyDescriptor != null) { if (propertyDescriptor != null) {
reportNotAcceptedExperimentalities( reportNotAllowedOptIns(
experimentalities + propertyDescriptor.loadExperimentalities( optIns + propertyDescriptor.loadOptIns(
moduleAnnotationsResolver, context.languageVersionSettings moduleAnnotationsResolver, context.languageVersionSettings
), reportOn, context ), reportOn, context
) )
return return
} }
} }
reportNotAcceptedExperimentalities(experimentalities, reportOn, context) reportNotAllowedOptIns(optIns, reportOn, context)
} }
private fun FunctionDescriptor.findRelevantDataClassPropertyIfAny(context: CallCheckerContext): PropertyDescriptor? { private fun FunctionDescriptor.findRelevantDataClassPropertyIfAny(context: CallCheckerContext): PropertyDescriptor? {
@@ -122,61 +122,61 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
OptInNames.buildDefaultDiagnosticMessage(prefix, fqName.asString()) OptInNames.buildDefaultDiagnosticMessage(prefix, fqName.asString())
} }
private val USAGE_DIAGNOSTICS = ExperimentalityDiagnostics( private val USAGE_DIAGNOSTICS = OptInReporterMultiplexer(
warning = ExperimentalityDiagnostic2( warning = OptInFactoryBasedReporter(
Errors.OPT_IN_USAGE, Errors.OPT_IN_USAGE,
getDefaultDiagnosticMessage(OptInNames.buildMessagePrefix("should")) getDefaultDiagnosticMessage(OptInNames.buildMessagePrefix("should"))
), ),
error = ExperimentalityDiagnostic2( error = OptInFactoryBasedReporter(
Errors.OPT_IN_USAGE_ERROR, Errors.OPT_IN_USAGE_ERROR,
getDefaultDiagnosticMessage(OptInNames.buildMessagePrefix("must")) getDefaultDiagnosticMessage(OptInNames.buildMessagePrefix("must"))
), ),
futureError = ExperimentalityDiagnostic2( futureError = OptInFactoryBasedReporter(
Errors.OPT_IN_USAGE_FUTURE_ERROR, Errors.OPT_IN_USAGE_FUTURE_ERROR,
getDefaultDiagnosticMessage("This declaration is experimental due to signature types and its usage must be marked (will become an error in 1.6)") getDefaultDiagnosticMessage("This declaration is experimental due to signature types and its usage must be marked (will become an error in 1.6)")
), ),
) )
fun reportNotAcceptedExperimentalities( fun reportNotAllowedOptIns(
experimentalities: Collection<Experimentality>, element: PsiElement, context: CheckerContext descriptions: Collection<OptInDescription>, element: PsiElement, context: CheckerContext
) { ) {
reportNotAcceptedExperimentalities( reportNotAllowedOptIns(
experimentalities, element, context.languageVersionSettings, context.trace, USAGE_DIAGNOSTICS descriptions, element, context.languageVersionSettings, context.trace, USAGE_DIAGNOSTICS
) )
} }
fun reportNotAcceptedExperimentalities( fun reportNotAllowedOptIns(
experimentalities: Collection<Experimentality>, descriptions: Collection<OptInDescription>,
element: PsiElement, element: PsiElement,
languageVersionSettings: LanguageVersionSettings, languageVersionSettings: LanguageVersionSettings,
trace: BindingTrace, trace: BindingTrace,
diagnostics: ExperimentalityDiagnostics diagnostics: OptInReporterMultiplexer
) { ) {
for ((annotationFqName, severity, message) in experimentalities) { for ((annotationFqName, severity, message) in descriptions) {
if (!element.isExperimentalityAccepted(annotationFqName, languageVersionSettings, trace.bindingContext)) { if (!element.isOptInAllowed(annotationFqName, languageVersionSettings, trace.bindingContext)) {
val diagnostic = when (severity) { val diagnostic = when (severity) {
Experimentality.Severity.WARNING -> diagnostics.warning OptInDescription.Severity.WARNING -> diagnostics.warning
Experimentality.Severity.ERROR -> diagnostics.error OptInDescription.Severity.ERROR -> diagnostics.error
Experimentality.Severity.FUTURE_ERROR -> diagnostics.futureError OptInDescription.Severity.FUTURE_ERROR -> diagnostics.futureError
} }
diagnostic.report(trace, element, annotationFqName, message) diagnostic.report(trace, element, annotationFqName, message)
} }
} }
} }
fun DeclarationDescriptor.loadExperimentalities( fun DeclarationDescriptor.loadOptIns(
moduleAnnotationsResolver: ModuleAnnotationsResolver, moduleAnnotationsResolver: ModuleAnnotationsResolver,
languageVersionSettings: LanguageVersionSettings, languageVersionSettings: LanguageVersionSettings,
visited: MutableSet<DeclarationDescriptor> = mutableSetOf(), visited: MutableSet<DeclarationDescriptor> = mutableSetOf(),
useFutureError: Boolean = false, useFutureError: Boolean = false,
useMarkersFromContainer: Boolean = true, useMarkersFromContainer: Boolean = true,
): Set<Experimentality> { ): Set<OptInDescription> {
if (!visited.add(this)) return emptySet() if (!visited.add(this)) return emptySet()
val result = SmartSet.create<Experimentality>() val result = SmartSet.create<OptInDescription>()
if (this is CallableMemberDescriptor && kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { if (this is CallableMemberDescriptor && kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) {
for (overridden in overriddenDescriptors) { for (overridden in overriddenDescriptors) {
result.addAll( result.addAll(
overridden.loadExperimentalities( overridden.loadOptIns(
moduleAnnotationsResolver, moduleAnnotationsResolver,
languageVersionSettings, languageVersionSettings,
visited, visited,
@@ -187,7 +187,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
if (useMarkersFromContainer) { if (useMarkersFromContainer) {
(containingDeclaration as? ClassDescriptor)?.let { (containingDeclaration as? ClassDescriptor)?.let {
result.addAll( result.addAll(
it.loadExperimentalities(moduleAnnotationsResolver, languageVersionSettings, visited, useFutureError) it.loadOptIns(moduleAnnotationsResolver, languageVersionSettings, visited, useFutureError)
) )
} }
} }
@@ -196,22 +196,22 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
} }
for (annotation in annotations) { for (annotation in annotations) {
result.addIfNotNull(annotation.annotationClass?.loadExperimentalityForMarkerAnnotation(useFutureError)) result.addIfNotNull(annotation.annotationClass?.loadOptInForMarkerAnnotation(useFutureError))
} }
if (this is CallableDescriptor && this !is ClassConstructorDescriptor) { if (this is CallableDescriptor && this !is ClassConstructorDescriptor) {
result.addAll( result.addAll(
returnType.loadExperimentalities(moduleAnnotationsResolver, languageVersionSettings, visited) returnType.loadOptIns(moduleAnnotationsResolver, languageVersionSettings, visited)
) )
result.addAll( result.addAll(
extensionReceiverParameter?.type.loadExperimentalities( extensionReceiverParameter?.type.loadOptIns(
moduleAnnotationsResolver, languageVersionSettings, visited moduleAnnotationsResolver, languageVersionSettings, visited
) )
) )
if (this is FunctionDescriptor) { if (this is FunctionDescriptor) {
valueParameters.forEach { valueParameters.forEach {
result.addAll( result.addAll(
it.type.loadExperimentalities( it.type.loadOptIns(
moduleAnnotationsResolver, languageVersionSettings, visited moduleAnnotationsResolver, languageVersionSettings, visited
) )
) )
@@ -220,76 +220,76 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
} }
if (this is TypeAliasDescriptor) { if (this is TypeAliasDescriptor) {
result.addAll(expandedType.loadExperimentalities(moduleAnnotationsResolver, languageVersionSettings, visited)) result.addAll(expandedType.loadOptIns(moduleAnnotationsResolver, languageVersionSettings, visited))
} }
if (annotations.any { it.fqName == WAS_EXPERIMENTAL_FQ_NAME }) { if (annotations.any { it.fqName == WAS_EXPERIMENTAL_FQ_NAME }) {
val accessibility = checkSinceKotlinVersionAccessibility(languageVersionSettings) val accessibility = checkSinceKotlinVersionAccessibility(languageVersionSettings)
if (accessibility is SinceKotlinAccessibility.NotAccessibleButWasExperimental) { if (accessibility is SinceKotlinAccessibility.NotAccessibleButWasExperimental) {
result.addAll(accessibility.markerClasses.mapNotNull { it.loadExperimentalityForMarkerAnnotation() }) result.addAll(accessibility.markerClasses.mapNotNull { it.loadOptInForMarkerAnnotation() })
} }
} }
val container = containingDeclaration val container = containingDeclaration
if (useMarkersFromContainer && container is ClassDescriptor && this !is ConstructorDescriptor) { if (useMarkersFromContainer && container is ClassDescriptor && this !is ConstructorDescriptor) {
result.addAll(container.loadExperimentalities(moduleAnnotationsResolver, languageVersionSettings, visited, useFutureError)) result.addAll(container.loadOptIns(moduleAnnotationsResolver, languageVersionSettings, visited, useFutureError))
} }
return result return result
} }
private fun KotlinType?.loadExperimentalities( private fun KotlinType?.loadOptIns(
moduleAnnotationsResolver: ModuleAnnotationsResolver, moduleAnnotationsResolver: ModuleAnnotationsResolver,
languageVersionSettings: LanguageVersionSettings, languageVersionSettings: LanguageVersionSettings,
visitedClassifiers: MutableSet<DeclarationDescriptor> visitedClassifiers: MutableSet<DeclarationDescriptor>
): Set<Experimentality> = ): Set<OptInDescription> =
when { when {
this?.isError != false -> emptySet() this?.isError != false -> emptySet()
this is AbbreviatedType -> abbreviation.constructor.declarationDescriptor?.loadExperimentalities( this is AbbreviatedType -> abbreviation.constructor.declarationDescriptor?.loadOptIns(
moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers, moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers,
useFutureError = !languageVersionSettings.supportsFeature(LanguageFeature.OptInContagiousSignatures) useFutureError = !languageVersionSettings.supportsFeature(LanguageFeature.OptInContagiousSignatures)
).orEmpty() + expandedType.loadExperimentalities( ).orEmpty() + expandedType.loadOptIns(
moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers
) )
else -> constructor.declarationDescriptor?.loadExperimentalities( else -> constructor.declarationDescriptor?.loadOptIns(
moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers, moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers,
useFutureError = !languageVersionSettings.supportsFeature(LanguageFeature.OptInContagiousSignatures) useFutureError = !languageVersionSettings.supportsFeature(LanguageFeature.OptInContagiousSignatures)
).orEmpty() + arguments.flatMap { ).orEmpty() + arguments.flatMap {
if (it.isStarProjection) emptySet() if (it.isStarProjection) emptySet()
else it.type.loadExperimentalities(moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers) else it.type.loadOptIns(moduleAnnotationsResolver, languageVersionSettings, visitedClassifiers)
} }
} }
internal fun ClassDescriptor.loadExperimentalityForMarkerAnnotation(useFutureError: Boolean = false): Experimentality? { internal fun ClassDescriptor.loadOptInForMarkerAnnotation(useFutureError: Boolean = false): OptInDescription? {
val experimental = val optInAnnotationDescriptor =
annotations.findAnnotation(REQUIRES_OPT_IN_FQ_NAME) annotations.findAnnotation(REQUIRES_OPT_IN_FQ_NAME)
?: annotations.findAnnotation(OLD_EXPERIMENTAL_FQ_NAME) ?: annotations.findAnnotation(OLD_EXPERIMENTAL_FQ_NAME)
?: return null ?: return null
val arguments = experimental.allValueArguments val arguments = optInAnnotationDescriptor.allValueArguments
val severity = when ((arguments[LEVEL] as? EnumValue)?.enumEntryName) { val severity = when ((arguments[LEVEL] as? EnumValue)?.enumEntryName) {
WARNING_LEVEL -> Experimentality.Severity.WARNING WARNING_LEVEL -> OptInDescription.Severity.WARNING
ERROR_LEVEL -> if (useFutureError) Experimentality.Severity.FUTURE_ERROR else Experimentality.Severity.ERROR ERROR_LEVEL -> if (useFutureError) OptInDescription.Severity.FUTURE_ERROR else OptInDescription.Severity.ERROR
else -> if (Experimentality.DEFAULT_SEVERITY == Experimentality.Severity.ERROR && useFutureError) { else -> if (OptInDescription.DEFAULT_SEVERITY == OptInDescription.Severity.ERROR && useFutureError) {
Experimentality.Severity.FUTURE_ERROR OptInDescription.Severity.FUTURE_ERROR
} else { } else {
Experimentality.DEFAULT_SEVERITY OptInDescription.DEFAULT_SEVERITY
} }
} }
val message = (arguments[MESSAGE] as? StringValue)?.value val message = (arguments[MESSAGE] as? StringValue)?.value
return Experimentality(fqNameSafe, severity, message) return OptInDescription(fqNameSafe, severity, message)
} }
private fun PsiElement.isExperimentalityAccepted(annotationFqName: FqName, context: CheckerContext): Boolean = private fun PsiElement.isOptInAllowed(annotationFqName: FqName, context: CheckerContext): Boolean =
isExperimentalityAccepted(annotationFqName, context.languageVersionSettings, context.trace.bindingContext) isOptInAllowed(annotationFqName, context.languageVersionSettings, context.trace.bindingContext)
/** /**
* Checks whether there's an element lexically above in the tree, annotated with `@UseExperimental(X::class)`, or a declaration * Checks whether there's an element lexically above in the tree, annotated with `@UseExperimental(X::class)`, or a declaration
* annotated with `@X` where [annotationFqName] is the FQ name of X * annotated with `@X` where [annotationFqName] is the FQ name of X
*/ */
fun PsiElement.isExperimentalityAccepted( fun PsiElement.isOptInAllowed(
annotationFqName: FqName, annotationFqName: FqName,
languageVersionSettings: LanguageVersionSettings, languageVersionSettings: LanguageVersionSettings,
bindingContext: BindingContext bindingContext: BindingContext
@@ -340,7 +340,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
// "-opt-in" arguments. However, it's not easy to do this. This should be solved in the future with the support of // "-opt-in" arguments. However, it's not easy to do this. This should be solved in the future with the support of
// module annotations. For now, we only check deprecations because this is needed to correctly retire unneeded compiler arguments. // module annotations. For now, we only check deprecations because this is needed to correctly retire unneeded compiler arguments.
val deprecationResolver = DeprecationResolver( val deprecationResolver = DeprecationResolver(
LockBasedStorageManager("ExperimentalUsageChecker"), LockBasedStorageManager("OptInUsageChecker"),
languageVersionSettings, languageVersionSettings,
DeprecationSettings.Default DeprecationSettings.Default
) )
@@ -353,7 +353,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
return false return false
} }
if (descriptor.loadExperimentalityForMarkerAnnotation() == null) { if (descriptor.loadOptInForMarkerAnnotation() == null) {
reportWarning("Class $fqName is not an opt-in requirement marker") reportWarning("Class $fqName is not an opt-in requirement marker")
return false return false
} }
@@ -396,7 +396,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
is TypeAliasDescriptor -> targetDescriptor.classDescriptor is TypeAliasDescriptor -> targetDescriptor.classDescriptor
else -> null else -> null
} }
if (targetClass != null && targetClass.loadExperimentalityForMarkerAnnotation() != null) { if (targetClass != null && targetClass.loadOptInForMarkerAnnotation() != null) {
if (!element.isUsageAsAnnotationOrImport() && if (!element.isUsageAsAnnotationOrImport() &&
!element.isUsageAsOptInArgument(context.trace.bindingContext) !element.isUsageAsOptInArgument(context.trace.bindingContext)
) { ) {
@@ -407,16 +407,16 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
} }
if (element.getParentOfType<KtImportDirective>(false) == null) { if (element.getParentOfType<KtImportDirective>(false) == null) {
val experimentalities = mutableSetOf<Experimentality>() val descriptions = mutableSetOf<OptInDescription>()
experimentalities += targetDescriptor.loadExperimentalities(moduleAnnotationsResolver, context.languageVersionSettings) descriptions += targetDescriptor.loadOptIns(moduleAnnotationsResolver, context.languageVersionSettings)
if (targetDescriptor is TypeAliasDescriptor) { if (targetDescriptor is TypeAliasDescriptor) {
experimentalities.addAll( descriptions.addAll(
targetDescriptor.expandedType.loadExperimentalities( targetDescriptor.expandedType.loadOptIns(
moduleAnnotationsResolver, context.languageVersionSettings, mutableSetOf(targetDescriptor) moduleAnnotationsResolver, context.languageVersionSettings, mutableSetOf(targetDescriptor)
) )
) )
} }
reportNotAcceptedExperimentalities(experimentalities, element, context) reportNotAllowedOptIns(descriptions, element, context)
} }
} }
@@ -466,27 +466,27 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (descriptor !is CallableMemberDescriptor) return if (descriptor !is CallableMemberDescriptor) return
val experimentalOverridden = descriptor.overriddenDescriptors.flatMap { member -> val optInOverriddenDescriptorMap = descriptor.overriddenDescriptors.flatMap { overriddenMember ->
member.loadExperimentalities(moduleAnnotationsResolver, context.languageVersionSettings) overriddenMember.loadOptIns(moduleAnnotationsResolver, context.languageVersionSettings)
.map { experimentality -> experimentality to member } .map { description -> description to overriddenMember }
}.toMap() }.toMap()
for ((experimentality, member) in experimentalOverridden) { for ((description, overriddenMember) in optInOverriddenDescriptorMap) {
if (!declaration.isExperimentalityAccepted(experimentality.annotationFqName, context)) { if (!declaration.isOptInAllowed(description.annotationFqName, context)) {
val reportOn = (declaration as? KtNamedDeclaration)?.nameIdentifier ?: declaration val reportOn = (declaration as? KtNamedDeclaration)?.nameIdentifier ?: declaration
val (diagnostic, defaultMessageVerb) = when (experimentality.severity) { val (diagnostic, defaultMessageVerb) = when (description.severity) {
Experimentality.Severity.WARNING -> Errors.OPT_IN_OVERRIDE to "should" OptInDescription.Severity.WARNING -> Errors.OPT_IN_OVERRIDE to "should"
Experimentality.Severity.ERROR -> Errors.OPT_IN_OVERRIDE_ERROR to "must" OptInDescription.Severity.ERROR -> Errors.OPT_IN_OVERRIDE_ERROR to "must"
Experimentality.Severity.FUTURE_ERROR -> Errors.OPT_IN_OVERRIDE_ERROR to "must" OptInDescription.Severity.FUTURE_ERROR -> Errors.OPT_IN_OVERRIDE_ERROR to "must"
} }
val message = OptInNames.buildOverrideMessage( val message = OptInNames.buildOverrideMessage(
supertypeName = member.containingDeclaration.name.asString(), supertypeName = overriddenMember.containingDeclaration.name.asString(),
markerMessage = experimentality.message, markerMessage = description.message,
verb = defaultMessageVerb, verb = defaultMessageVerb,
markerName = experimentality.annotationFqName.asString() markerName = description.annotationFqName.asString()
) )
context.trace.report(diagnostic.on(reportOn, experimentality.annotationFqName, message)) context.trace.report(diagnostic.on(reportOn, description.annotationFqName, message))
} }
} }
} }
@@ -35,7 +35,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.util.getEffectiveExpectedType import org.jetbrains.kotlin.resolve.calls.util.getEffectiveExpectedType
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker import org.jetbrains.kotlin.resolve.checkers.OptInUsageChecker
import org.jetbrains.kotlin.resolve.constants.* import org.jetbrains.kotlin.resolve.constants.*
import org.jetbrains.kotlin.resolve.constants.evaluate.CompileTimeType.* import org.jetbrains.kotlin.resolve.constants.evaluate.CompileTimeType.*
import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.resolve.descriptorUtil.classId
@@ -322,12 +322,12 @@ class ConstantExpressionEvaluator(
if (!UnsignedTypes.isUnsignedType(constantType)) return if (!UnsignedTypes.isUnsignedType(constantType)) return
with(ExperimentalUsageChecker) { with(OptInUsageChecker) {
val descriptor = constantType.constructor.declarationDescriptor ?: return val descriptor = constantType.constructor.declarationDescriptor ?: return
val experimentalities = descriptor.loadExperimentalities(moduleAnnotationsResolver, languageVersionSettings) val optInDescriptions = descriptor.loadOptIns(moduleAnnotationsResolver, languageVersionSettings)
reportNotAcceptedExperimentalities( reportNotAllowedOptIns(
experimentalities, expression, languageVersionSettings, trace, EXPERIMENTAL_UNSIGNED_LITERALS_DIAGNOSTICS optInDescriptions, expression, languageVersionSettings, trace, EXPERIMENTAL_UNSIGNED_LITERALS_DIAGNOSTICS
) )
} }
} }
@@ -336,16 +336,16 @@ class ConstantExpressionEvaluator(
private class ExperimentalityDiagnostic1( private class ExperimentalityDiagnostic1(
val factory: DiagnosticFactory1<PsiElement, String>, val factory: DiagnosticFactory1<PsiElement, String>,
val verb: String val verb: String
) : ExperimentalUsageChecker.ExperimentalityDiagnostic { ) : OptInUsageChecker.OptInDiagnosticReporter {
override fun report(trace: BindingTrace, element: PsiElement, fqName: FqName, message: String?) { override fun report(trace: BindingTrace, element: PsiElement, fqName: FqName, message: String?) {
val defaultMessage = ExperimentalUsageChecker.getDefaultDiagnosticMessage( val defaultMessage = OptInUsageChecker.getDefaultDiagnosticMessage(
"Unsigned literals are experimental and their usages $verb be marked" "Unsigned literals are experimental and their usages $verb be marked"
) )
trace.reportDiagnosticOnce(factory.on(element, defaultMessage(fqName))) trace.reportDiagnosticOnce(factory.on(element, defaultMessage(fqName)))
} }
} }
private val EXPERIMENTAL_UNSIGNED_LITERALS_DIAGNOSTICS = ExperimentalUsageChecker.ExperimentalityDiagnostics( private val EXPERIMENTAL_UNSIGNED_LITERALS_DIAGNOSTICS = OptInUsageChecker.OptInReporterMultiplexer(
warning = ExperimentalityDiagnostic1(Errors.EXPERIMENTAL_UNSIGNED_LITERALS, "should"), warning = ExperimentalityDiagnostic1(Errors.EXPERIMENTAL_UNSIGNED_LITERALS, "should"),
error = ExperimentalityDiagnostic1(Errors.EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "must"), error = ExperimentalityDiagnostic1(Errors.EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "must"),
futureError = ExperimentalityDiagnostic1(Errors.EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "must"), futureError = ExperimentalityDiagnostic1(Errors.EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "must"),
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.resolve.SinceKotlinAccessibility
import org.jetbrains.kotlin.resolve.calls.checkers.isOperatorMod import org.jetbrains.kotlin.resolve.calls.checkers.isOperatorMod
import org.jetbrains.kotlin.resolve.calls.checkers.shouldWarnAboutDeprecatedModFromBuiltIns import org.jetbrains.kotlin.resolve.calls.checkers.shouldWarnAboutDeprecatedModFromBuiltIns
import org.jetbrains.kotlin.resolve.checkSinceKotlinVersionAccessibility import org.jetbrains.kotlin.resolve.checkSinceKotlinVersionAccessibility
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker import org.jetbrains.kotlin.resolve.checkers.OptInUsageChecker
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor
@@ -115,9 +115,9 @@ class DeprecationResolver(
if (sinceKotlinAccessibility is SinceKotlinAccessibility.NotAccessibleButWasExperimental) { if (sinceKotlinAccessibility is SinceKotlinAccessibility.NotAccessibleButWasExperimental) {
return if (callElement != null && bindingContext != null) { return if (callElement != null && bindingContext != null) {
with(ExperimentalUsageChecker) { with(OptInUsageChecker) {
sinceKotlinAccessibility.markerClasses.any { classDescriptor -> sinceKotlinAccessibility.markerClasses.any { classDescriptor ->
!callElement.isExperimentalityAccepted(classDescriptor.fqNameSafe, languageVersionSettings, bindingContext) !callElement.isOptInAllowed(classDescriptor.fqNameSafe, languageVersionSettings, bindingContext)
} }
} }
} else { } else {
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.resolve.checkers
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
data class Experimentality(val annotationFqName: FqName, val severity: Severity, val message: String?) { data class OptInDescription(val annotationFqName: FqName, val severity: Severity, val message: String?) {
enum class Severity { WARNING, ERROR, FUTURE_ERROR } enum class Severity { WARNING, ERROR, FUTURE_ERROR }
companion object { companion object {