From 1326ca674ed079b3ab402bb27cd017d8473d686d Mon Sep 17 00:00:00 2001 From: Stanislav Erokhin Date: Wed, 12 Jan 2022 11:48:22 +0100 Subject: [PATCH] Introduced IDEAPluginsCompatibilityAPI and returned some API Please read the description of IDEAPluginsCompatibilityAPI. Also, you may notice, that Deprecation annotation was removed. That is because with level ERROR it isn't possible to opt-in to such API in the legit cases. And with level WARNING it won't be reported because We have diagnostic with level ERROR about OptIn marker. Sadly, there is no way to add ReplaceWith to the OptIn marker --- .../arguments/CommonCompilerArguments.kt | 10 +++ .../kotlin/resolve/calls/callUtil/callUtil.kt | 63 +++++++++---------- .../utils/IDEAPluginsCompatibilityAPI.kt | 59 +++++++++++++++++ .../org/jetbrains/kotlin/utils/addToStdlib.kt | 25 +++++++- 4 files changed, 122 insertions(+), 35 deletions(-) create mode 100644 core/util.runtime/src/org/jetbrains/kotlin/utils/IDEAPluginsCompatibilityAPI.kt diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt index 48e507e95a0..5b6eeb471e3 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt @@ -10,6 +10,8 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.WARNING import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.config.* +import org.jetbrains.kotlin.utils.IDEAPlatforms +import org.jetbrains.kotlin.utils.IDEAPluginsCompatibilityAPI @SuppressWarnings("WeakerAccess") abstract class CommonCompilerArguments : CommonToolArguments() { @@ -149,6 +151,14 @@ abstract class CommonCompilerArguments : CommonToolArguments() { ) var readDeserializedContracts: Boolean by FreezableVar(false) + @IDEAPluginsCompatibilityAPI( + IDEAPlatforms._212, // maybe 211 AS used it too + IDEAPlatforms._213, + message = "Please migrate to -opt-in", + plugins = "Android" + ) + var experimental: Array? = null + @Argument( value = "-Xuse-experimental", valueDescription = "", diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/callUtil/callUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/callUtil/callUtil.kt index 49820f252d2..7023d6f3538 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/callUtil/callUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/callUtil/callUtil.kt @@ -17,59 +17,58 @@ import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny import org.jetbrains.kotlin.resolve.calls.util.createLookupLocation import org.jetbrains.kotlin.resolve.calls.util.getValueArgumentForExpression import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.utils.IDEAPlatforms +import org.jetbrains.kotlin.utils.IDEAPluginsCompatibilityAPI -@Deprecated( - "Use org.jetbrains.kotlin.resolve.calls.util.getResolvedCall instead.", - ReplaceWith("getResolvedCall", "org.jetbrains.kotlin.resolve.calls.util.getResolvedCall"), - level = DeprecationLevel.ERROR +@IDEAPluginsCompatibilityAPI( + IDEAPlatforms._213, // I'm not sure about 212 and 211 AS -- didn't check them + message = "Use org.jetbrains.kotlin.resolve.calls.util.getResolvedCall instead.", + plugins = "Android in IDEA" ) fun Call?.getResolvedCall(context: BindingContext): ResolvedCall? = getResolvedCall(context) -@Deprecated( - "Use org.jetbrains.kotlin.resolve.calls.util.getResolvedCall instead.", - ReplaceWith("getResolvedCall", "org.jetbrains.kotlin.resolve.calls.util.getResolvedCall"), - level = DeprecationLevel.ERROR +@IDEAPluginsCompatibilityAPI( + IDEAPlatforms._213, // I'm not sure about 212 and 211 AS -- didn't check them + message = "Use org.jetbrains.kotlin.resolve.calls.util.getResolvedCall instead.", + plugins = "Android in IDEA" ) fun KtElement?.getResolvedCall(context: BindingContext): ResolvedCall? = getResolvedCall(context) -@Deprecated( - "Use org.jetbrains.kotlin.resolve.calls.util.getType instead.", - ReplaceWith("getType", "org.jetbrains.kotlin.resolve.calls.util.getType"), - level = DeprecationLevel.ERROR + +// TODO: find what IDEA's used it +@IDEAPluginsCompatibilityAPI( + message = "Use org.jetbrains.kotlin.resolve.calls.util.getType instead." ) fun KtExpression.getType(context: BindingContext): KotlinType? = getType(context) -@Deprecated( - "Use org.jetbrains.kotlin.resolve.calls.util.getCall instead.", - ReplaceWith("getType", "org.jetbrains.kotlin.resolve.calls.util.getCall"), - level = DeprecationLevel.ERROR +@IDEAPluginsCompatibilityAPI( + IDEAPlatforms._213, // I'm not sure about 212 and 211 AS -- didn't check them + message = "Use org.jetbrains.kotlin.resolve.calls.util.getCall instead.", + plugins = "Android in IDEA" ) fun KtElement.getCall(context: BindingContext): Call? = getCall(context) -@Deprecated( - "Use org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny instead.", - ReplaceWith("getCalleeExpressionIfAny", "org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny"), - level = DeprecationLevel.ERROR +// TODO: find what IDEA's used it +@IDEAPluginsCompatibilityAPI( + message = "Use org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny instead." ) fun KtElement?.getCalleeExpressionIfAny(): KtExpression? = getCalleeExpressionIfAny() -@Deprecated( - "Use org.jetbrains.kotlin.resolve.calls.util.createLookupLocation instead.", - ReplaceWith("createLookupLocation", "org.jetbrains.kotlin.resolve.calls.util.createLookupLocation"), - level = DeprecationLevel.ERROR +// TODO: find what IDEA's used it +@IDEAPluginsCompatibilityAPI( + message = "Use org.jetbrains.kotlin.resolve.calls.util.createLookupLocation instead." ) fun Call.createLookupLocation(): KotlinLookupLocation = createLookupLocation() -@Deprecated( - "Use org.jetbrains.kotlin.resolve.calls.util.createLookupLocation instead.", - ReplaceWith("createLookupLocation", "org.jetbrains.kotlin.resolve.calls.util.createLookupLocation"), - level = DeprecationLevel.ERROR +// TODO: find what IDEA's used it +@IDEAPluginsCompatibilityAPI( + message = "Use org.jetbrains.kotlin.resolve.calls.util.createLookupLocation instead." ) fun KtExpression.createLookupLocation(): KotlinLookupLocation? = createLookupLocation() -@Deprecated( - "Use org.jetbrains.kotlin.resolve.calls.util.getValueArgumentForExpression instead.", - ReplaceWith("getValueArgumentForExpression", "org.jetbrains.kotlin.resolve.calls.util.getValueArgumentForExpression"), - level = DeprecationLevel.ERROR +@IDEAPluginsCompatibilityAPI( + IDEAPlatforms._213, // I'm not sure about 212 and 211 AS -- didn't check them + message = "Use org.jetbrains.kotlin.resolve.calls.util.getValueArgumentForExpression instead.", + plugins = "Android in IDEA" ) fun Call.getValueArgumentForExpression(expression: KtExpression): ValueArgument? = getValueArgumentForExpression(expression) diff --git a/core/util.runtime/src/org/jetbrains/kotlin/utils/IDEAPluginsCompatibilityAPI.kt b/core/util.runtime/src/org/jetbrains/kotlin/utils/IDEAPluginsCompatibilityAPI.kt new file mode 100644 index 00000000000..6da28c1e074 --- /dev/null +++ b/core/util.runtime/src/org/jetbrains/kotlin/utils/IDEAPluginsCompatibilityAPI.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.utils + +import kotlin.annotation.AnnotationTarget.* + +/** + * AS 211-based, IU211 or IC211 is the same _211 key. + * + * Usage in 213 platform means, that there was a **released** IDEA build or plugin on plugin marketplace compatible with + * `213.*` platform that has used this API. If you found a usage in the IDEA and removed it usage before the first official release of the + * corresponding platform it is considered as "not used" in this platform. In other words, if API was used in 221 EAP's + * or even 221 RC but not in the 221 release -- then API don't have the 221 usage. + */ +enum class IDEAPlatforms { + _211, + _212, + _213, + _221, + _222, + _223 +} + +/** + * This annotation is created to handle the following situation: + * Sometimes IDE plugins are using some API from Kotlin plugin. But it is known that Kotlin plugin includes some parts of + * the kotlin compiler. It means, that sometimes the IDE plugins are using API from kotlin compiler itself. + * Also, we are providing updates of the Kotlin plugin with the new kotlin front-end inside. + * But it also means, that because of such update some third-party plugins could be broken, because they have been using the API + * from kotlin compiler that was removed or changed in the new version of the compiler. + * + * To prevent that we have some teamcity configuration that are checking such compatibility with all the plugins published into plugin side. + * So, once we detected, that some API was broken, we should return corresponding API in the compiler and mark it. + * Exactly for these usages this annotation should be used. + * + * List of IDEA platforms is needed to be able to remove the API completely at some point, once we'll stop support of the corresponding + * IDEA platform. + * + * Instead of that we could use just Deprecation annotation with level Error, because it also preserves the binary compatibility and + * enforce third-party plugin migrate to the new API, but unfortunately it isn't always possible. With OptIn Annotation that is more + * flexible. And yes, please use Opt-in mechanic only as a last reserve. + * + * One of the legit use-cases for OptIn usage is the following: + * Sometimes third-party plugins sources located inside IDEA monorepo. And in kt-*- branches we don't want to change the sources outside + * the kotlin plugin, because it is incorrect. So instead of that we could add the corresponding OptIn in the corresponding module where + * old API is used and **remove** such usages in the IDEA master. + * + * P.S. usedIn is declared as first parameter to force its usage -- without it is easy enough forget about it and declare only message + * + * [usedIn] see description for [IDEAPlatforms] + * [message] -- you could add details what API should be used instead + * [plugins] -- if you want, you could mention what IDEA plugins used these APIs. It is not mandatory, but sometimes could be helpful. + */ +@Target(CLASS, ANNOTATION_CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS) +@RequiresOptIn("This API will be removed from the Kotlin compiler, you shouldn't use it", level = RequiresOptIn.Level.ERROR) +annotation class IDEAPluginsCompatibilityAPI(vararg val usedIn: IDEAPlatforms, val message: String, val plugins: String = "") diff --git a/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt b/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt index e2ebe53ca43..7c7542ca87e 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt @@ -5,6 +5,8 @@ package org.jetbrains.kotlin.utils.addToStdlib +import org.jetbrains.kotlin.utils.IDEAPlatforms +import org.jetbrains.kotlin.utils.IDEAPluginsCompatibilityAPI import java.lang.reflect.Modifier import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -99,10 +101,12 @@ fun String.indexOfOrNull(char: Char, startIndex: Int = 0, ignoreCase: Boolean = fun String.lastIndexOfOrNull(char: Char, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int? = lastIndexOf(char, startIndex, ignoreCase).takeIf { it >= 0 } -@Deprecated( +@IDEAPluginsCompatibilityAPI( + IDEAPlatforms._211, + IDEAPlatforms._212, + IDEAPlatforms._213, message = "Use firstNotNullOfOrNull from stdlib instead", - replaceWith = ReplaceWith("firstNotNullOfOrNull(transform)"), - level = DeprecationLevel.ERROR + plugins = "Android plugin in the IDEA, kotlin-ultimate.kotlin-ocswift" ) inline fun Iterable.firstNotNullResult(transform: (T) -> R?): R? { for (element in this) { @@ -112,6 +116,21 @@ inline fun Iterable.firstNotNullResult(transform: (T) -> R?): R? return null } +@IDEAPluginsCompatibilityAPI( + IDEAPlatforms._211, + IDEAPlatforms._212, + IDEAPlatforms._213, + message = "Use firstNotNullOfOrNull from stdlib instead", + plugins = "Android plugin in the IDEA" +) +inline fun Array.firstNotNullResult(transform: (T) -> R?): R? { + for (element in this) { + val result = transform(element) + if (result != null) return result + } + return null +} + inline fun Iterable.sumByLong(selector: (T) -> Long): Long { var sum: Long = 0 for (element in this) {