From 0aaf29c0459b6c6a8e2070077ea6f663ad2f4b2c Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 7 Sep 2018 16:24:06 +0300 Subject: [PATCH] Introduce DeprecatedSinceKotlin annotation This annotation will be used in the standard library to prevent the new compiler from reporting deprecation diagnostics in case an older API version is used (where the declaration was not deprecated yet). #KT-23575 Fixed --- ...irOldFrontendDiagnosticsTestGenerated.java | 28 ++++++++ .../kotlin/resolve/deprecation/Deprecation.kt | 70 ++++++++++++++++--- .../deprecation/DeprecationResolver.kt | 22 +++--- .../resolve/deprecation/deprecationUtil.kt | 11 +-- .../kotlin/asJava/classes/ultraLightUtils.kt | 16 ++--- .../org/jetbrains/kotlin/psi/KtPsiUtil.java | 3 +- .../deprecatedSinceKotlin/error.fir.kt | 29 ++++++++ .../deprecated/deprecatedSinceKotlin/error.kt | 29 ++++++++ .../deprecatedSinceKotlin/error.txt | 21 ++++++ .../deprecatedSinceKotlin/hidden.fir.kt | 29 ++++++++ .../deprecatedSinceKotlin/hidden.kt | 29 ++++++++ .../deprecatedSinceKotlin/hidden.txt | 21 ++++++ .../deprecatedSinceKotlin/warning.fir.kt | 29 ++++++++ .../deprecatedSinceKotlin/warning.kt | 29 ++++++++ .../deprecatedSinceKotlin/warning.txt | 21 ++++++ .../checkers/DiagnosticsTestGenerated.java | 28 ++++++++ .../DiagnosticsUsingJavacTestGenerated.java | 28 ++++++++ core/builtins/src/kotlin/Annotations.kt | 26 +++++++ .../kotlin/builtins/KotlinBuiltIns.java | 2 + ...recatedCallableAddReplaceWithInspection.kt | 1 + .../DeprecatedSymbolUsageFixBase.kt | 1 + 21 files changed, 435 insertions(+), 38 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.fir.kt create mode 100644 compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.kt create mode 100644 compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.txt create mode 100644 compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.fir.kt create mode 100644 compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.kt create mode 100644 compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.txt create mode 100644 compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.fir.kt create mode 100644 compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.kt create mode 100644 compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.txt diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirOldFrontendDiagnosticsTestGenerated.java index 72e10ea193f..08f8a5c0232 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirOldFrontendDiagnosticsTestGenerated.java @@ -6644,6 +6644,34 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirOldFronte public void testWarningOnConstructorErrorOnClass() throws Exception { runTest("compiler/testData/diagnostics/tests/deprecated/warningOnConstructorErrorOnClass.kt"); } + + @TestMetadata("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class DeprecatedSinceKotlin extends AbstractFirOldFrontendDiagnosticsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInDeprecatedSinceKotlin() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); + } + + @TestMetadata("error.kt") + public void testError() throws Exception { + runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.kt"); + } + + @TestMetadata("hidden.kt") + public void testHidden() throws Exception { + runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.kt"); + } + + @TestMetadata("warning.kt") + public void testWarning() throws Exception { + runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.kt"); + } + } } @TestMetadata("compiler/testData/diagnostics/tests/duplicateJvmSignature") diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/Deprecation.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/Deprecation.kt index 99f445f7478..ced9e9aefe2 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/Deprecation.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/Deprecation.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.resolve.deprecation +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.config.ApiVersion import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.DeclarationDescriptor @@ -14,27 +15,78 @@ import org.jetbrains.kotlin.metadata.deserialization.VersionRequirement import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.annotations.argumentValue import org.jetbrains.kotlin.resolve.calls.checkers.shouldWarnAboutDeprecatedModFromBuiltIns +import org.jetbrains.kotlin.resolve.constants.AnnotationValue import org.jetbrains.kotlin.resolve.constants.EnumValue import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue.* import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.utils.addToStdlib.safeAs -internal data class DeprecatedByAnnotation( +internal sealed class DeprecatedByAnnotation( val annotation: AnnotationDescriptor, override val target: DeclarationDescriptor, override val propagatesToOverrides: Boolean ) : Deprecation { - override val deprecationLevel: DeprecationLevelValue - get() = when (annotation.argumentValue("level")?.safeAs()?.enumEntryName?.asString()) { - "WARNING" -> WARNING - "ERROR" -> ERROR - "HIDDEN" -> HIDDEN - else -> WARNING - } - override val message: String? get() = annotation.argumentValue("message")?.safeAs()?.value + + internal val replaceWithValue: String? + get() { + val replaceWithAnnotation = annotation.argumentValue(Deprecated::replaceWith.name)?.safeAs()?.value + return replaceWithAnnotation?.argumentValue(ReplaceWith::expression.name)?.safeAs()?.value + } + + class StandardDeprecated( + annotation: AnnotationDescriptor, + target: DeclarationDescriptor, + propagatesToOverrides: Boolean + ) : DeprecatedByAnnotation(annotation, target, propagatesToOverrides) { + override val deprecationLevel: DeprecationLevelValue + get() = when (annotation.argumentValue("level")?.safeAs()?.enumEntryName?.asString()) { + "WARNING" -> WARNING + "ERROR" -> ERROR + "HIDDEN" -> HIDDEN + else -> WARNING + } + } + + class DeprecatedSince( + annotation: AnnotationDescriptor, + target: DeclarationDescriptor, + propagatesToOverrides: Boolean, + override val deprecationLevel: DeprecationLevelValue + ) : DeprecatedByAnnotation(annotation, target, propagatesToOverrides) + + companion object { + fun create( + annotation: AnnotationDescriptor, + target: DeclarationDescriptor, + propagatesToOverrides: Boolean, + apiVersion: ApiVersion + ): DeprecatedByAnnotation? { + if (annotation.fqName == KotlinBuiltIns.FQ_NAMES.deprecatedSinceKotlin) { + val level = computeLevelForDeprecatedSinceKotlin(annotation, apiVersion) ?: return null + return DeprecatedSince(annotation, target, propagatesToOverrides, level) + } + return StandardDeprecated(annotation, target, propagatesToOverrides) + } + + private fun computeLevelForDeprecatedSinceKotlin(annotation: AnnotationDescriptor, apiVersion: ApiVersion): DeprecationLevelValue? { + fun getSinceVersion(name: String): ApiVersion? = + annotation.argumentValue(name)?.safeAs()?.value?.takeUnless(String::isEmpty)?.let(ApiVersion.Companion::parse) + + val hiddenSince = getSinceVersion("hiddenSince") + if (hiddenSince != null && apiVersion >= hiddenSince) return HIDDEN + + val errorSince = getSinceVersion("errorSince") + if (errorSince != null && apiVersion >= errorSince) return ERROR + + val warningSince = getSinceVersion("warningSince") + if (warningSince != null && apiVersion >= warningSince) return WARNING + + return null + } + } } internal data class DeprecatedByOverridden(private val deprecations: Collection) : Deprecation { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/DeprecationResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/DeprecationResolver.kt index aabc42767f1..f25634d90e9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/DeprecationResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/DeprecationResolver.kt @@ -187,23 +187,27 @@ class DeprecationResolver( private fun DeclarationDescriptor.addDeprecationIfPresent(result: MutableList) { val annotation = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecated) + ?: annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecatedSinceKotlin) ?: annotations.findAnnotation(JAVA_DEPRECATED) if (annotation != null) { val deprecatedByAnnotation = - DeprecatedByAnnotation( + DeprecatedByAnnotation.create( annotation, this, - deprecationSettings.propagatedToOverrides(annotation) + deprecationSettings.propagatedToOverrides(annotation), + languageVersionSettings.apiVersion ) - val deprecation = when { - this is TypeAliasConstructorDescriptor -> - DeprecatedTypealiasByAnnotation(typeAliasDescriptor, deprecatedByAnnotation) + if (deprecatedByAnnotation != null) { + val deprecation = when { + this is TypeAliasConstructorDescriptor -> + DeprecatedTypealiasByAnnotation(typeAliasDescriptor, deprecatedByAnnotation) - isBuiltInOperatorMod -> - DeprecatedOperatorMod(languageVersionSettings, deprecatedByAnnotation) + isBuiltInOperatorMod -> + DeprecatedOperatorMod(languageVersionSettings, deprecatedByAnnotation) - else -> deprecatedByAnnotation + else -> deprecatedByAnnotation + } + result.add(deprecation) } - result.add(deprecation) } for (deprecation in getDeprecationByVersionRequirement(this)) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/deprecationUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/deprecationUtil.kt index bb2f9712fad..77305d7d841 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/deprecationUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecation/deprecationUtil.kt @@ -13,20 +13,11 @@ import org.jetbrains.kotlin.container.PlatformSpecificExtension import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors -import org.jetbrains.kotlin.resolve.annotations.argumentValue -import org.jetbrains.kotlin.resolve.constants.AnnotationValue -import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue.* -import org.jetbrains.kotlin.utils.addToStdlib.safeAs fun Deprecation.deprecatedByOverriddenMessage(): String? = (this as? DeprecatedByOverridden)?.additionalMessage() -fun Deprecation.deprecatedByAnnotationReplaceWithExpression(): String? { - val annotation = (this as? DeprecatedByAnnotation)?.annotation ?: return null - val replaceWithAnnotation = - annotation.argumentValue(kotlin.Deprecated::replaceWith.name)?.safeAs()?.value ?: return null - return replaceWithAnnotation.argumentValue(kotlin.ReplaceWith::expression.name)?.safeAs()?.value -} +fun Deprecation.deprecatedByAnnotationReplaceWithExpression(): String? = (this as? DeprecatedByAnnotation)?.replaceWithValue internal fun createDeprecationDiagnostic( element: PsiElement, deprecation: Deprecation, languageVersionSettings: LanguageVersionSettings diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightUtils.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightUtils.kt index dd40600e8ab..ff585a554a7 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightUtils.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightUtils.kt @@ -358,22 +358,20 @@ internal fun KtDeclaration.simpleVisibility(): String = when { } internal fun KtModifierListOwner.isDeprecated(support: KtUltraLightSupport? = null): Boolean { - val jetModifierList = this.modifierList ?: return false - if (jetModifierList.annotationEntries.isEmpty()) return false + val modifierList = this.modifierList ?: return false + if (modifierList.annotationEntries.isEmpty()) return false val deprecatedFqName = KotlinBuiltIns.FQ_NAMES.deprecated val deprecatedName = deprecatedFqName.shortName().asString() - for (annotationEntry in jetModifierList.annotationEntries) { - val typeReference = annotationEntry.typeReference ?: continue - - val typeElement = typeReference.typeElement as? KtUserType ?: continue - // If it's not a user type, it's definitely not a ref to deprecated + for (annotationEntry in modifierList.annotationEntries) { + // If it's not a user type, it's definitely not a reference to deprecated + val typeElement = annotationEntry.typeReference?.typeElement as? KtUserType ?: continue val fqName = toQualifiedName(typeElement) ?: continue - if (deprecatedFqName == fqName) return true - if (deprecatedName == fqName.asString()) return true + if (fqName == deprecatedFqName || fqName == KotlinBuiltIns.FQ_NAMES.deprecatedSinceKotlin) return true + if (fqName.asString() == deprecatedName) return true } return support?.findAnnotation(this, KotlinBuiltIns.FQ_NAMES.deprecated) !== null diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtPsiUtil.java b/compiler/psi/src/org/jetbrains/kotlin/psi/KtPsiUtil.java index 67b7a99d193..e6b60f7315f 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/KtPsiUtil.java +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtPsiUtil.java @@ -178,7 +178,8 @@ public class KtPsiUtil { List annotationEntries = modifierList.getAnnotationEntries(); for (KtAnnotationEntry annotation : annotationEntries) { Name shortName = annotation.getShortName(); - if (KotlinBuiltIns.FQ_NAMES.deprecated.shortName().equals(shortName)) { + if (KotlinBuiltIns.FQ_NAMES.deprecated.shortName().equals(shortName) || + KotlinBuiltIns.FQ_NAMES.deprecatedSinceKotlin.shortName().equals(shortName)) { return true; } } diff --git a/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.fir.kt b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.fir.kt new file mode 100644 index 00000000000..7fbeabf6a5e --- /dev/null +++ b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.fir.kt @@ -0,0 +1,29 @@ +// !API_VERSION: 1.3 + +@DeprecatedSinceKotlin("", errorSince = "1.3") +class ClassCur + +@DeprecatedSinceKotlin("", errorSince = "1.3") +fun funCur() {} + +@DeprecatedSinceKotlin("", errorSince = "1.3") +val valCur = Unit + +@DeprecatedSinceKotlin("", errorSince = "1.4") +class ClassNext + +@DeprecatedSinceKotlin("", errorSince = "1.4") +fun funNext() {} + +@DeprecatedSinceKotlin("", errorSince = "1.4") +val valNext = Unit + +fun usage() { + ClassCur() + funCur() + valCur + + ClassNext() + funNext() + valNext +} diff --git a/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.kt b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.kt new file mode 100644 index 00000000000..7fbeabf6a5e --- /dev/null +++ b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.kt @@ -0,0 +1,29 @@ +// !API_VERSION: 1.3 + +@DeprecatedSinceKotlin("", errorSince = "1.3") +class ClassCur + +@DeprecatedSinceKotlin("", errorSince = "1.3") +fun funCur() {} + +@DeprecatedSinceKotlin("", errorSince = "1.3") +val valCur = Unit + +@DeprecatedSinceKotlin("", errorSince = "1.4") +class ClassNext + +@DeprecatedSinceKotlin("", errorSince = "1.4") +fun funNext() {} + +@DeprecatedSinceKotlin("", errorSince = "1.4") +val valNext = Unit + +fun usage() { + ClassCur() + funCur() + valCur + + ClassNext() + funNext() + valNext +} diff --git a/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.txt b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.txt new file mode 100644 index 00000000000..1e458887acf --- /dev/null +++ b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.txt @@ -0,0 +1,21 @@ +package + +@kotlin.DeprecatedSinceKotlin(errorSince = "1.3", message = "") public val valCur: kotlin.Unit +@kotlin.DeprecatedSinceKotlin(errorSince = "1.4", message = "") public val valNext: kotlin.Unit +@kotlin.DeprecatedSinceKotlin(errorSince = "1.3", message = "") public fun funCur(): kotlin.Unit +@kotlin.DeprecatedSinceKotlin(errorSince = "1.4", message = "") public fun funNext(): kotlin.Unit +public fun usage(): kotlin.Unit + +@kotlin.DeprecatedSinceKotlin(errorSince = "1.3", message = "") public final class ClassCur { + public constructor ClassCur() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +@kotlin.DeprecatedSinceKotlin(errorSince = "1.4", message = "") public final class ClassNext { + public constructor ClassNext() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.fir.kt b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.fir.kt new file mode 100644 index 00000000000..27c21f92b44 --- /dev/null +++ b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.fir.kt @@ -0,0 +1,29 @@ +// !API_VERSION: 1.3 + +@DeprecatedSinceKotlin("", hiddenSince = "1.3") +class ClassCur + +@DeprecatedSinceKotlin("", hiddenSince = "1.3") +fun funCur() {} + +@DeprecatedSinceKotlin("", hiddenSince = "1.3") +val valCur = Unit + +@DeprecatedSinceKotlin("", hiddenSince = "1.4") +class ClassNext + +@DeprecatedSinceKotlin("", hiddenSince = "1.4") +fun funNext() {} + +@DeprecatedSinceKotlin("", hiddenSince = "1.4") +val valNext = Unit + +fun usage() { + ClassCur() + funCur() + valCur + + ClassNext() + funNext() + valNext +} diff --git a/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.kt b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.kt new file mode 100644 index 00000000000..27c21f92b44 --- /dev/null +++ b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.kt @@ -0,0 +1,29 @@ +// !API_VERSION: 1.3 + +@DeprecatedSinceKotlin("", hiddenSince = "1.3") +class ClassCur + +@DeprecatedSinceKotlin("", hiddenSince = "1.3") +fun funCur() {} + +@DeprecatedSinceKotlin("", hiddenSince = "1.3") +val valCur = Unit + +@DeprecatedSinceKotlin("", hiddenSince = "1.4") +class ClassNext + +@DeprecatedSinceKotlin("", hiddenSince = "1.4") +fun funNext() {} + +@DeprecatedSinceKotlin("", hiddenSince = "1.4") +val valNext = Unit + +fun usage() { + ClassCur() + funCur() + valCur + + ClassNext() + funNext() + valNext +} diff --git a/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.txt b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.txt new file mode 100644 index 00000000000..4d90a846206 --- /dev/null +++ b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.txt @@ -0,0 +1,21 @@ +package + +@kotlin.DeprecatedSinceKotlin(hiddenSince = "1.3", message = "") public val valCur: kotlin.Unit +@kotlin.DeprecatedSinceKotlin(hiddenSince = "1.4", message = "") public val valNext: kotlin.Unit +@kotlin.DeprecatedSinceKotlin(hiddenSince = "1.3", message = "") public fun funCur(): kotlin.Unit +@kotlin.DeprecatedSinceKotlin(hiddenSince = "1.4", message = "") public fun funNext(): kotlin.Unit +public fun usage(): kotlin.Unit + +@kotlin.DeprecatedSinceKotlin(hiddenSince = "1.3", message = "") public final class ClassCur { + public constructor ClassCur() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +@kotlin.DeprecatedSinceKotlin(hiddenSince = "1.4", message = "") public final class ClassNext { + public constructor ClassNext() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.fir.kt b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.fir.kt new file mode 100644 index 00000000000..1fd556775bf --- /dev/null +++ b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.fir.kt @@ -0,0 +1,29 @@ +// !API_VERSION: 1.3 + +@DeprecatedSinceKotlin("", warningSince = "1.3") +class ClassCur + +@DeprecatedSinceKotlin("", warningSince = "1.3") +fun funCur() {} + +@DeprecatedSinceKotlin("", warningSince = "1.3") +val valCur = Unit + +@DeprecatedSinceKotlin("", warningSince = "1.4") +class ClassNext + +@DeprecatedSinceKotlin("", warningSince = "1.4") +fun funNext() {} + +@DeprecatedSinceKotlin("", warningSince = "1.4") +val valNext = Unit + +fun usage() { + ClassCur() + funCur() + valCur + + ClassNext() + funNext() + valNext +} diff --git a/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.kt b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.kt new file mode 100644 index 00000000000..1fd556775bf --- /dev/null +++ b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.kt @@ -0,0 +1,29 @@ +// !API_VERSION: 1.3 + +@DeprecatedSinceKotlin("", warningSince = "1.3") +class ClassCur + +@DeprecatedSinceKotlin("", warningSince = "1.3") +fun funCur() {} + +@DeprecatedSinceKotlin("", warningSince = "1.3") +val valCur = Unit + +@DeprecatedSinceKotlin("", warningSince = "1.4") +class ClassNext + +@DeprecatedSinceKotlin("", warningSince = "1.4") +fun funNext() {} + +@DeprecatedSinceKotlin("", warningSince = "1.4") +val valNext = Unit + +fun usage() { + ClassCur() + funCur() + valCur + + ClassNext() + funNext() + valNext +} diff --git a/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.txt b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.txt new file mode 100644 index 00000000000..0837a112476 --- /dev/null +++ b/compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.txt @@ -0,0 +1,21 @@ +package + +@kotlin.DeprecatedSinceKotlin(message = "", warningSince = "1.3") public val valCur: kotlin.Unit +@kotlin.DeprecatedSinceKotlin(message = "", warningSince = "1.4") public val valNext: kotlin.Unit +@kotlin.DeprecatedSinceKotlin(message = "", warningSince = "1.3") public fun funCur(): kotlin.Unit +@kotlin.DeprecatedSinceKotlin(message = "", warningSince = "1.4") public fun funNext(): kotlin.Unit +public fun usage(): kotlin.Unit + +@kotlin.DeprecatedSinceKotlin(message = "", warningSince = "1.3") public final class ClassCur { + public constructor ClassCur() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +@kotlin.DeprecatedSinceKotlin(message = "", warningSince = "1.4") public final class ClassNext { + public constructor ClassNext() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index dc6ea1a20cc..78f7dd73aea 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -6651,6 +6651,34 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTestWithFirVali public void testWarningOnConstructorErrorOnClass() throws Exception { runTest("compiler/testData/diagnostics/tests/deprecated/warningOnConstructorErrorOnClass.kt"); } + + @TestMetadata("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class DeprecatedSinceKotlin extends AbstractDiagnosticsTestWithFirValidation { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInDeprecatedSinceKotlin() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); + } + + @TestMetadata("error.kt") + public void testError() throws Exception { + runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.kt"); + } + + @TestMetadata("hidden.kt") + public void testHidden() throws Exception { + runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.kt"); + } + + @TestMetadata("warning.kt") + public void testWarning() throws Exception { + runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.kt"); + } + } } @TestMetadata("compiler/testData/diagnostics/tests/duplicateJvmSignature") diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index edbeaba0daf..880c4f5e8f3 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -6646,6 +6646,34 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing public void testWarningOnConstructorErrorOnClass() throws Exception { runTest("compiler/testData/diagnostics/tests/deprecated/warningOnConstructorErrorOnClass.kt"); } + + @TestMetadata("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class DeprecatedSinceKotlin extends AbstractDiagnosticsUsingJavacTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInDeprecatedSinceKotlin() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); + } + + @TestMetadata("error.kt") + public void testError() throws Exception { + runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.kt"); + } + + @TestMetadata("hidden.kt") + public void testHidden() throws Exception { + runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/hidden.kt"); + } + + @TestMetadata("warning.kt") + public void testWarning() throws Exception { + runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/warning.kt"); + } + } } @TestMetadata("compiler/testData/diagnostics/tests/duplicateJvmSignature") diff --git a/core/builtins/src/kotlin/Annotations.kt b/core/builtins/src/kotlin/Annotations.kt index 79d3ccbfe55..4890646e580 100644 --- a/core/builtins/src/kotlin/Annotations.kt +++ b/core/builtins/src/kotlin/Annotations.kt @@ -37,6 +37,32 @@ public annotation class Deprecated( val level: DeprecationLevel = DeprecationLevel.WARNING ) +/** + * Marks the annotated declaration as deprecated. In contrast to [Deprecated], severity of the reported diagnostic is not a constant value, + * but differs depending on the API version of the usage (the value of the `-api-version` argument when compiling the module where + * the usage is located). If the API version is greater or equal than [hiddenSince], the declaration will not be accessible from the code + * (as if it was deprecated with level [DeprecationLevel.HIDDEN]), otherwise if the API version is greater or equal than [errorSince], + * the usage will be marked as an error (as with [DeprecationLevel.ERROR]), otherwise if the API version is greater or equal + * than [warningSince], the usage will be marked as a warning (as with [DeprecationLevel.WARNING]), otherwise the annotation is ignored. + * + * @property message the message explaining the deprecation and recommending an alternative API to use. + * @property replaceWith if present, specifies a code fragment which should be used as a replacement for + * the deprecated API usage. + * @property warningSince the version, since which this deprecation should be reported as a warning. + * @property errorSince the version, since which this deprecation should be reported as a error. + * @property hiddenSince the version, since which the annotated declaration should not be available in code. + */ +@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS) +@MustBeDocumented +@SinceKotlin("1.3") +public annotation class DeprecatedSinceKotlin( + val message: String, + val replaceWith: ReplaceWith = ReplaceWith(""), + val warningSince: String = "", + val errorSince: String = "", + val hiddenSince: String = "" +) + /** * Specifies a code fragment that can be used to replace a deprecated function, property or class. Tools such * as IDEs can automatically apply the replacements specified through this annotation. diff --git a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java index 870f412722e..50eb9e1d2ef 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java +++ b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java @@ -211,6 +211,7 @@ public abstract class KotlinBuiltIns { public final FqNameUnsafe longRange = rangesFqName("LongRange"); public final FqName deprecated = fqName("Deprecated"); + public final FqName deprecatedSinceKotlin = fqName("DeprecatedSinceKotlin"); public final FqName deprecationLevel = fqName("DeprecationLevel"); public final FqName replaceWith = fqName("ReplaceWith"); public final FqName extensionFunctionType = fqName("ExtensionFunctionType"); @@ -1116,6 +1117,7 @@ public abstract class KotlinBuiltIns { return classFqNameEquals(descriptor, FQ_NAMES.cloneable); } + // TODO: support DeprecatedSinceKotlin public static boolean isDeprecated(@NotNull DeclarationDescriptor declarationDescriptor) { if (declarationDescriptor.getOriginal().getAnnotations().hasAnnotation(FQ_NAMES.deprecated)) return true; diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/DeprecatedCallableAddReplaceWithInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/DeprecatedCallableAddReplaceWithInspection.kt index 517923b474c..27e11d14893 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/DeprecatedCallableAddReplaceWithInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/DeprecatedCallableAddReplaceWithInspection.kt @@ -103,6 +103,7 @@ class DeprecatedCallableAddReplaceWithInspection : AbstractApplicabilityBasedIns private fun KtCallableDeclaration.deprecatedAnnotationWithNoReplaceWith(): KtAnnotationEntry? { for (entry in annotationEntries) { + // TODO: support DeprecatedSinceKotlin here if (entry.shortName != DEPRECATED_NAME) continue val bindingContext = entry.analyze() val resolvedCall = entry.calleeExpression.getResolvedCall(bindingContext) ?: continue diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt index e79b5f4e113..6b227db8d05 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt @@ -79,6 +79,7 @@ abstract class DeprecatedSymbolUsageFixBase( contextElement: KtSimpleNameExpression?, replaceInWholeProject: Boolean ): ReplaceWith? { + // TODO: support DeprecatedSinceKotlin val annotation = descriptor.annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecated) ?: return null val replaceWithValue = annotation.argumentValue(Deprecated::replaceWith.name)?.safeAs()?.value ?: return null