Introduce RequiresOptIn and OptIn annotations
RequiresOptIn should be used now instead of Experimental, OptIn instead of UseExperimental. See https://github.com/Kotlin/KEEP/pull/201. This change adds the new declarations only to the stdlib, and supports them in the compiler. Because of the way compiler loads annotations, we need to bootstrap it first before deprecating the old annotations and updating tests. #KT-34647 Fixed
This commit is contained in:
+8
-8
@@ -147,22 +147,22 @@ public class DefaultErrorMessages {
|
||||
MAP.put(ILLEGAL_KOTLIN_VERSION_STRING_VALUE, "Invalid @{0} annotation value (should be ''major.minor'' or ''major.minor.patch'')", TO_STRING);
|
||||
MAP.put(NEWER_VERSION_IN_SINCE_KOTLIN, "The version is greater than the specified API version {0}", STRING);
|
||||
|
||||
MAP.put(EXPERIMENTAL_API_USAGE, "This declaration is experimental and its usage should be marked with ''@{0}'' or ''@UseExperimental({0}::class)''", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_API_USAGE_ERROR, "This declaration is experimental and its usage must be marked with ''@{0}'' or ''@UseExperimental({0}::class)''", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_API_USAGE, "This declaration is experimental and its usage should be marked with ''@{0}'' or ''@OptIn({0}::class)''", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_API_USAGE_ERROR, "This declaration is experimental and its usage must be marked with ''@{0}'' or ''@OptIn({0}::class)''", TO_STRING);
|
||||
|
||||
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(EXPERIMENTAL_IS_NOT_ENABLED, "This class can only be used with the compiler argument '-Xuse-experimental=kotlin.Experimental'");
|
||||
MAP.put(EXPERIMENTAL_IS_NOT_ENABLED, "This class can only be used with the compiler argument '-Xopt-in=kotlin.RequiresOptIn'");
|
||||
MAP.put(EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION, "This class can only be used as an annotation");
|
||||
MAP.put(EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL, "This class can only be used as an annotation or as an argument to @UseExperimental");
|
||||
MAP.put(EXPERIMENTAL_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_USE_EXPERIMENTAL, "This class can only be used as an annotation or as an argument to @OptIn");
|
||||
|
||||
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_WITHOUT_ARGUMENTS, "@OptIn 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 @OptIn is ignored", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET, "Experimental annotation cannot be used on the following code elements: {0}. Please remove these targets", STRING);
|
||||
|
||||
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS, "Unsigned literals are experimental and their usages should be marked with ''@{0}'' or ''@UseExperimental({0}::class)''", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "Unsigned literals are experimental and their usages must be marked with ''@{0}'' or ''@UseExperimental({0}::class)''", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS, "Unsigned literals are experimental and their usages should be marked with ''@{0}'' or ''@OptIn({0}::class)''", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "Unsigned literals are experimental and their usages must be marked with ''@{0}'' or ''@OptIn({0}::class)''", TO_STRING);
|
||||
|
||||
MAP.put(NON_PARENTHESIZED_ANNOTATIONS_ON_FUNCTIONAL_TYPES, "Non-parenthesized annotations on function types without receiver aren't yet supported (see KT-31734 for details)");
|
||||
|
||||
|
||||
+5
-3
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.resolve.constants.ArrayValue
|
||||
import org.jetbrains.kotlin.resolve.constants.ConstantValue
|
||||
import org.jetbrains.kotlin.resolve.constants.KClassValue
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleDescriptor) : AdditionalAnnotationChecker {
|
||||
private val WRONG_TARGETS_FOR_MARKER = setOf(KotlinTarget.EXPRESSION, KotlinTarget.FILE)
|
||||
@@ -29,12 +30,13 @@ class ExperimentalMarkerDeclarationAnnotationChecker(private val module: ModuleD
|
||||
for (entry in entries) {
|
||||
val annotation = trace.bindingContext.get(BindingContext.ANNOTATION, entry)
|
||||
when (annotation?.fqName) {
|
||||
ExperimentalUsageChecker.USE_EXPERIMENTAL_FQ_NAME -> {
|
||||
in ExperimentalUsageChecker.USE_EXPERIMENTAL_FQ_NAMES -> {
|
||||
val annotationClasses =
|
||||
(annotation.allValueArguments[ExperimentalUsageChecker.USE_EXPERIMENTAL_ANNOTATION_CLASS] as? ArrayValue)?.value.orEmpty()
|
||||
annotation!!.allValueArguments[ExperimentalUsageChecker.USE_EXPERIMENTAL_ANNOTATION_CLASS]
|
||||
.safeAs<ArrayValue>()?.value.orEmpty()
|
||||
checkUseExperimentalUsage(annotationClasses, trace, entry)
|
||||
}
|
||||
ExperimentalUsageChecker.EXPERIMENTAL_FQ_NAME -> {
|
||||
in ExperimentalUsageChecker.EXPERIMENTAL_FQ_NAMES -> {
|
||||
isAnnotatedWithExperimental = true
|
||||
}
|
||||
}
|
||||
|
||||
+22
-14
@@ -43,7 +43,6 @@ import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationSettings
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.utils.SmartSet
|
||||
@@ -72,8 +71,14 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EXPERIMENTAL_FQ_NAME = FqName("kotlin.Experimental")
|
||||
val USE_EXPERIMENTAL_FQ_NAME = FqName("kotlin.UseExperimental")
|
||||
val OLD_EXPERIMENTAL_FQ_NAME = FqName("kotlin.Experimental")
|
||||
val OLD_USE_EXPERIMENTAL_FQ_NAME = FqName("kotlin.UseExperimental")
|
||||
val REQUIRES_OPT_IN_FQ_NAME = FqName("kotlin.RequiresOptIn")
|
||||
val OPT_IN_FQ_NAME = FqName("kotlin.OptIn")
|
||||
|
||||
val EXPERIMENTAL_FQ_NAMES = setOf(OLD_EXPERIMENTAL_FQ_NAME, REQUIRES_OPT_IN_FQ_NAME)
|
||||
val USE_EXPERIMENTAL_FQ_NAMES = setOf(OLD_USE_EXPERIMENTAL_FQ_NAME, OPT_IN_FQ_NAME)
|
||||
|
||||
internal val WAS_EXPERIMENTAL_FQ_NAME = FqName("kotlin.WasExperimental")
|
||||
internal val USE_EXPERIMENTAL_ANNOTATION_CLASS = Name.identifier("markerClass")
|
||||
internal val WAS_EXPERIMENTAL_ANNOTATION_CLASS = Name.identifier("markerClass")
|
||||
@@ -82,9 +87,6 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
private val WARNING_LEVEL = Name.identifier("WARNING")
|
||||
private val ERROR_LEVEL = Name.identifier("ERROR")
|
||||
|
||||
private val EXPERIMENTAL_SHORT_NAME = EXPERIMENTAL_FQ_NAME.shortName()
|
||||
private val USE_EXPERIMENTAL_SHORT_NAME = USE_EXPERIMENTAL_FQ_NAME.shortName()
|
||||
|
||||
private val EXPERIMENTAL_API_DIAGNOSTICS = ExperimentalityDiagnostics(
|
||||
Errors.EXPERIMENTAL_API_USAGE, Errors.EXPERIMENTAL_API_USAGE_ERROR
|
||||
)
|
||||
@@ -146,7 +148,10 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
}
|
||||
|
||||
internal fun ClassDescriptor.loadExperimentalityForMarkerAnnotation(): Experimentality? {
|
||||
val experimental = annotations.findAnnotation(EXPERIMENTAL_FQ_NAME) ?: return null
|
||||
val experimental =
|
||||
annotations.findAnnotation(REQUIRES_OPT_IN_FQ_NAME)
|
||||
?: annotations.findAnnotation(OLD_EXPERIMENTAL_FQ_NAME)
|
||||
?: return null
|
||||
|
||||
val severity = when ((experimental.allValueArguments[LEVEL] as? EnumValue)?.enumEntryName) {
|
||||
WARNING_LEVEL -> Experimentality.Severity.WARNING
|
||||
@@ -187,7 +192,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
private fun PsiElement.isElementAnnotatedWithUseExperimentalOf(annotationFqName: FqName, bindingContext: BindingContext): Boolean {
|
||||
return this is KtAnnotated && annotationEntries.any { entry ->
|
||||
val descriptor = bindingContext.get(BindingContext.ANNOTATION, entry)
|
||||
if (descriptor?.fqName == USE_EXPERIMENTAL_FQ_NAME) {
|
||||
if (descriptor != null && descriptor.fqName in USE_EXPERIMENTAL_FQ_NAMES) {
|
||||
val annotationClasses = descriptor.allValueArguments[USE_EXPERIMENTAL_ANNOTATION_CLASS]
|
||||
annotationClasses is ArrayValue && annotationClasses.value.any { annotationClass ->
|
||||
annotationClass is KClassValue && annotationClass.value.let { value ->
|
||||
@@ -244,7 +249,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
|
||||
val validExperimental = languageVersionSettings.getFlag(AnalysisFlags.experimental).filter(::checkAnnotation)
|
||||
val validUseExperimental = languageVersionSettings.getFlag(AnalysisFlags.useExperimental).filter { fqName ->
|
||||
fqName == EXPERIMENTAL_FQ_NAME.asString() || checkAnnotation(fqName)
|
||||
fqName == REQUIRES_OPT_IN_FQ_NAME.asString() || fqName == OLD_EXPERIMENTAL_FQ_NAME.asString() || checkAnnotation(fqName)
|
||||
}
|
||||
|
||||
for (fqName in validExperimental.intersect(validUseExperimental)) {
|
||||
@@ -258,9 +263,10 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
|
||||
override fun check(targetDescriptor: ClassifierDescriptor, element: PsiElement, context: ClassifierUsageCheckerContext) {
|
||||
val name = targetDescriptor.name
|
||||
if (name == EXPERIMENTAL_SHORT_NAME || name == USE_EXPERIMENTAL_SHORT_NAME) {
|
||||
val fqName = targetDescriptor.fqNameUnsafe
|
||||
if (fqName == EXPERIMENTAL_FQ_NAME.toUnsafe() || fqName == USE_EXPERIMENTAL_FQ_NAME.toUnsafe()) {
|
||||
if (name == OLD_EXPERIMENTAL_FQ_NAME.shortName() || name == REQUIRES_OPT_IN_FQ_NAME.shortName() ||
|
||||
name == OLD_USE_EXPERIMENTAL_FQ_NAME.shortName() || name == OPT_IN_FQ_NAME.shortName()) {
|
||||
val fqName = targetDescriptor.fqNameSafe
|
||||
if (fqName in EXPERIMENTAL_FQ_NAMES || fqName in USE_EXPERIMENTAL_FQ_NAMES) {
|
||||
checkUsageOfKotlinExperimentalOrUseExperimental(element, context)
|
||||
return
|
||||
}
|
||||
@@ -288,7 +294,9 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
}
|
||||
|
||||
private fun checkUsageOfKotlinExperimentalOrUseExperimental(element: PsiElement, context: CheckerContext) {
|
||||
if (EXPERIMENTAL_FQ_NAME.asString() !in context.languageVersionSettings.getFlag(AnalysisFlags.useExperimental)) {
|
||||
val useExperimentalFqNames = context.languageVersionSettings.getFlag(AnalysisFlags.useExperimental)
|
||||
if (REQUIRES_OPT_IN_FQ_NAME.asString() !in useExperimentalFqNames &&
|
||||
OLD_EXPERIMENTAL_FQ_NAME.asString() !in useExperimentalFqNames) {
|
||||
context.trace.report(Errors.EXPERIMENTAL_IS_NOT_ENABLED.on(element))
|
||||
}
|
||||
|
||||
@@ -317,7 +325,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
parent.parent.parent is KtValueArgumentList &&
|
||||
parent.parent.parent.parent.let { entry ->
|
||||
entry is KtAnnotationEntry && bindingContext.get(BindingContext.ANNOTATION, entry)?.let { annotation ->
|
||||
annotation.fqName == USE_EXPERIMENTAL_FQ_NAME || annotation.fqName == WAS_EXPERIMENTAL_FQ_NAME
|
||||
annotation.fqName in USE_EXPERIMENTAL_FQ_NAMES || annotation.fqName == WAS_EXPERIMENTAL_FQ_NAME
|
||||
} == true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user