Report experimental diagnostics on inaccessible (wrt SinceKotlin) API
If a declaration is annotated both with SinceKotlin and WasExperimental and the SinceKotlin version makes it inaccessible, the experimental checker must regard it as experimental, with markers specified in the WasExperimental annotation arguments. So only errors/warnings about the experimentality are going to be reported in this case, and no error that the declaration is unavailable because of a low API version
This commit is contained in:
+17
-7
@@ -56,7 +56,8 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
}
|
||||
|
||||
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
|
||||
val experimentalities = resolvedCall.resultingDescriptor.loadExperimentalities(moduleAnnotationsResolver)
|
||||
val experimentalities =
|
||||
resolvedCall.resultingDescriptor.loadExperimentalities(moduleAnnotationsResolver, context.languageVersionSettings)
|
||||
reportNotAcceptedExperimentalities(experimentalities, reportOn, context)
|
||||
}
|
||||
|
||||
@@ -89,7 +90,8 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
}
|
||||
|
||||
private fun DeclarationDescriptor.loadExperimentalities(
|
||||
moduleAnnotationsResolver: ModuleAnnotationsResolver
|
||||
moduleAnnotationsResolver: ModuleAnnotationsResolver,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
): Set<Experimentality> {
|
||||
val result = SmartSet.create<Experimentality>()
|
||||
|
||||
@@ -97,9 +99,16 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
result.addIfNotNull(annotation.annotationClass?.loadExperimentalityForMarkerAnnotation())
|
||||
}
|
||||
|
||||
if (annotations.any { it.fqName == WAS_EXPERIMENTAL_FQ_NAME }) {
|
||||
val accessibility = checkSinceKotlinVersionAccessibility(languageVersionSettings)
|
||||
if (accessibility is SinceKotlinAccessibility.NotAccessibleButWasExperimental) {
|
||||
result.addAll(accessibility.markerClasses.map { it.loadExperimentalityForMarkerAnnotation() })
|
||||
}
|
||||
}
|
||||
|
||||
val container = containingDeclaration
|
||||
if (container is ClassDescriptor && this !is ConstructorDescriptor) {
|
||||
result.addAll(container.loadExperimentalities(moduleAnnotationsResolver))
|
||||
result.addAll(container.loadExperimentalities(moduleAnnotationsResolver, languageVersionSettings))
|
||||
}
|
||||
|
||||
for (moduleAnnotationClassId in moduleAnnotationsResolver.getAnnotationsOnContainingModule(this)) {
|
||||
@@ -169,13 +178,13 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Annotated.loadWasExperimentalMarkerFqNames(): List<FqName> {
|
||||
internal fun Annotated.loadWasExperimentalMarkerClasses(): List<ClassDescriptor> {
|
||||
val wasExperimental = annotations.findAnnotation(WAS_EXPERIMENTAL_FQ_NAME)
|
||||
if (wasExperimental != null) {
|
||||
val annotationClasses = wasExperimental.allValueArguments[WAS_EXPERIMENTAL_ANNOTATION_CLASS]
|
||||
if (annotationClasses is ArrayValue) {
|
||||
return annotationClasses.value.mapNotNull { annotationClass ->
|
||||
(annotationClass as? KClassValue)?.value?.constructor?.declarationDescriptor?.fqNameSafe
|
||||
(annotationClass as? KClassValue)?.value?.constructor?.declarationDescriptor as? ClassDescriptor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +252,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
}
|
||||
}
|
||||
|
||||
val experimentalities = targetDescriptor.loadExperimentalities(moduleAnnotationsResolver)
|
||||
val experimentalities = targetDescriptor.loadExperimentalities(moduleAnnotationsResolver, context.languageVersionSettings)
|
||||
reportNotAcceptedExperimentalities(experimentalities, element, context)
|
||||
|
||||
val targetClass = when (targetDescriptor) {
|
||||
@@ -311,7 +320,8 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
if (descriptor !is CallableMemberDescriptor) return
|
||||
|
||||
val experimentalOverridden = descriptor.overriddenDescriptors.flatMap { member ->
|
||||
member.loadExperimentalities(moduleAnnotationsResolver).map { experimentality -> experimentality to member }
|
||||
member.loadExperimentalities(moduleAnnotationsResolver, context.languageVersionSettings)
|
||||
.map { experimentality -> experimentality to member }
|
||||
}.toMap()
|
||||
|
||||
for ((experimentality, member) in experimentalOverridden) {
|
||||
|
||||
@@ -227,8 +227,8 @@ class DeprecationResolver(
|
||||
if (sinceKotlinAccessibility is SinceKotlinAccessibility.NotAccessibleButWasExperimental) {
|
||||
if (call != null && bindingContext != null) {
|
||||
return with(ExperimentalUsageChecker) {
|
||||
sinceKotlinAccessibility.annotationFqNames.any { fqName ->
|
||||
!call.callElement.isExperimentalityAccepted(fqName, languageVersionSettings, bindingContext)
|
||||
sinceKotlinAccessibility.markerClasses.any { classDescriptor ->
|
||||
!call.callElement.isExperimentalityAccepted(classDescriptor.fqNameSafe, languageVersionSettings, bindingContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.config.ApiVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForTypeAliasObject
|
||||
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker
|
||||
|
||||
@@ -18,7 +17,7 @@ sealed class SinceKotlinAccessibility {
|
||||
|
||||
data class NotAccessibleButWasExperimental(
|
||||
val version: ApiVersion,
|
||||
val annotationFqNames: List<FqName>
|
||||
val markerClasses: List<ClassDescriptor>
|
||||
) : SinceKotlinAccessibility()
|
||||
|
||||
data class NotAccessible(
|
||||
@@ -38,7 +37,7 @@ fun DeclarationDescriptor.checkSinceKotlinVersionAccessibility(languageVersionSe
|
||||
// 3) The value as a version is not greater than our API version
|
||||
if (version == null || version <= languageVersionSettings.apiVersion) return SinceKotlinAccessibility.Accessible
|
||||
|
||||
val wasExperimentalFqNames = value.wasExperimentalMarkerFqNames
|
||||
val wasExperimentalFqNames = value.wasExperimentalMarkerClasses
|
||||
if (wasExperimentalFqNames.isNotEmpty()) {
|
||||
return SinceKotlinAccessibility.NotAccessibleButWasExperimental(version, wasExperimentalFqNames)
|
||||
}
|
||||
@@ -48,7 +47,7 @@ fun DeclarationDescriptor.checkSinceKotlinVersionAccessibility(languageVersionSe
|
||||
|
||||
private data class SinceKotlinValue(
|
||||
val apiVersion: ApiVersion,
|
||||
val wasExperimentalMarkerFqNames: List<FqName>
|
||||
val wasExperimentalMarkerClasses: List<ClassDescriptor>
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -56,7 +55,7 @@ private data class SinceKotlinValue(
|
||||
* or the minimal value of the version from all declarations annotated with [SinceKotlin] otherwise.
|
||||
*/
|
||||
private fun getSinceKotlinVersionByOverridden(descriptor: CallableMemberDescriptor): SinceKotlinValue? {
|
||||
// TODO: combine wasExperimentalMarkerFqNames in case of several members with the same minimal API version
|
||||
// TODO: combine wasExperimentalMarkerClasses in case of several members with the same minimal API version
|
||||
return DescriptorUtils.getAllOverriddenDeclarations(descriptor).map { it.getOwnSinceKotlinVersion() ?: return null }
|
||||
.minBy { it.apiVersion }
|
||||
}
|
||||
@@ -73,11 +72,11 @@ private fun DeclarationDescriptor.getOwnSinceKotlinVersion(): SinceKotlinValue?
|
||||
val apiVersion = (annotations.findAnnotation(SINCE_KOTLIN_FQ_NAME)?.allValueArguments?.values?.singleOrNull()?.value as? String)
|
||||
?.let(ApiVersion.Companion::parse)
|
||||
if (apiVersion != null) {
|
||||
// TODO: combine wasExperimentalMarkerFqNames in case of several associated declarations with the same maximal API version
|
||||
// TODO: combine wasExperimentalMarkerClasses in case of several associated declarations with the same maximal API version
|
||||
if (result == null || apiVersion > result!!.apiVersion) {
|
||||
result = SinceKotlinValue(
|
||||
apiVersion,
|
||||
with(ExperimentalUsageChecker) { loadWasExperimentalMarkerFqNames() }
|
||||
with(ExperimentalUsageChecker) { loadWasExperimentalMarkerClasses() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user