From 7d74508529f0ad6efabfd63e34f7ff028f4c7527 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 4 May 2018 16:23:40 +0200 Subject: [PATCH] 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 --- .../checkers/ExperimentalUsageChecker.kt | 24 +++++++++++++------ .../kotlin/resolve/deprecationUtil.kt | 4 ++-- .../kotlin/resolve/sinceKotlinUtil.kt | 13 +++++----- .../experimental/wasExperimental.kt | 22 +++++++++++++---- .../experimental/wasExperimental.txt | 7 +++--- .../src/kotlin/annotations/Experimental.kt | 2 +- 6 files changed, 48 insertions(+), 24 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt index 73d03f49eb7..320d7c416f6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt @@ -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 { val result = SmartSet.create() @@ -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 { + internal fun Annotated.loadWasExperimentalMarkerClasses(): List { 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) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecationUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecationUtil.kt index ab5c91ae716..e79af8b1aa0 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecationUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecationUtil.kt @@ -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) } } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt index 6dfc6075f72..10077bb2787 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/sinceKotlinUtil.kt @@ -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 + val markerClasses: List ) : 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 + val wasExperimentalMarkerClasses: List ) /** @@ -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() } ) } } diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.kt index b50c25ef9ad..ff0991c2663 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.kt @@ -1,6 +1,6 @@ // !API_VERSION: 1.2 // !USE_EXPERIMENTAL: kotlin.Experimental -// !DIAGNOSTICS: -INVISIBLE_MEMBER -INVISIBLE_REFERENCE -NEWER_VERSION_IN_SINCE_KOTLIN +// !DIAGNOSTICS: -INVISIBLE_MEMBER -INVISIBLE_REFERENCE -NEWER_VERSION_IN_SINCE_KOTLIN -UNUSED_PARAMETER @SinceKotlin("1.3") fun newPublishedFun() {} @@ -21,7 +21,15 @@ val newValExperimentalInThePast = "" @WasExperimental(Marker::class) class NewClassExperimentalInThePast -fun use1() { +@SinceKotlin("1.3") +@WasExperimental(Marker::class) +typealias TypeAliasToNewClass = NewClassExperimentalInThePast + + +fun use1( + c1: NewClassExperimentalInThePast, + t1: TypeAliasToNewClass +) { newPublishedFun() newFunExperimentalInThePast() newValExperimentalInThePast @@ -29,7 +37,10 @@ fun use1() { } @UseExperimental(Marker::class) -fun use2() { +fun use2( + c2: NewClassExperimentalInThePast, + t2: TypeAliasToNewClass +) { newPublishedFun() newFunExperimentalInThePast() newValExperimentalInThePast @@ -37,7 +48,10 @@ fun use2() { } @Marker -fun use3() { +fun use3( + c3: NewClassExperimentalInThePast, + t3: TypeAliasToNewClass +) { newPublishedFun() newFunExperimentalInThePast() newValExperimentalInThePast diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.txt b/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.txt index 57823a54dff..32385bba6b2 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.txt +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/wasExperimental.txt @@ -3,9 +3,9 @@ package @kotlin.SinceKotlin(version = "1.3") @kotlin.WasExperimental(markerClass = {Marker::class}) public val newValExperimentalInThePast: kotlin.String = "" @kotlin.SinceKotlin(version = "1.3") @kotlin.WasExperimental(markerClass = {Marker::class}) public fun newFunExperimentalInThePast(): kotlin.Unit @kotlin.SinceKotlin(version = "1.3") public fun newPublishedFun(): kotlin.Unit -public fun use1(): kotlin.Unit -@kotlin.UseExperimental(markerClass = {Marker::class}) public fun use2(): kotlin.Unit -@Marker public fun use3(): kotlin.Unit +public fun use1(/*0*/ c1: NewClassExperimentalInThePast, /*1*/ t1: TypeAliasToNewClass /* = NewClassExperimentalInThePast */): kotlin.Unit +@kotlin.UseExperimental(markerClass = {Marker::class}) public fun use2(/*0*/ c2: NewClassExperimentalInThePast, /*1*/ t2: TypeAliasToNewClass /* = NewClassExperimentalInThePast */): kotlin.Unit +@Marker public fun use3(/*0*/ c3: NewClassExperimentalInThePast, /*1*/ t3: TypeAliasToNewClass /* = NewClassExperimentalInThePast */): kotlin.Unit @kotlin.Experimental public final annotation class Marker : kotlin.Annotation { public constructor Marker() @@ -20,3 +20,4 @@ public fun use1(): kotlin.Unit public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } +@kotlin.SinceKotlin(version = "1.3") @kotlin.WasExperimental(markerClass = {Marker::class}) public typealias TypeAliasToNewClass = NewClassExperimentalInThePast diff --git a/libraries/stdlib/src/kotlin/annotations/Experimental.kt b/libraries/stdlib/src/kotlin/annotations/Experimental.kt index c59339066f3..772101f281a 100644 --- a/libraries/stdlib/src/kotlin/annotations/Experimental.kt +++ b/libraries/stdlib/src/kotlin/annotations/Experimental.kt @@ -52,7 +52,7 @@ annotation class UseExperimental( ) -@Target(CLASS, PROPERTY, CONSTRUCTOR, FUNCTION) +@Target(CLASS, PROPERTY, CONSTRUCTOR, FUNCTION, TYPEALIAS) @Retention(BINARY) internal annotation class WasExperimental( vararg val markerClass: KClass