Do not implicitly propagate deprecations originated in Java

^KT-29604 Fixed
This commit is contained in:
Denis Zharkov
2019-02-11 12:40:24 +03:00
parent 2f546d1003
commit abad408d7b
17 changed files with 307 additions and 6 deletions
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.resolve.constants.KClassValue
import org.jetbrains.kotlin.resolve.deprecation.CoroutineCompatibilitySupport
import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue
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
@@ -215,7 +216,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
// "-Xuse-experimental" arguments. However, it's not easy to do this. This should be solved in the future with the support of
// module annotations. For now, we only check deprecations because this is needed to correctly retire unneeded compiler arguments.
val deprecationResolver =
DeprecationResolver(LockBasedStorageManager("ExperimentalUsageChecker"), languageVersionSettings, CoroutineCompatibilitySupport.ENABLED)
DeprecationResolver(LockBasedStorageManager("ExperimentalUsageChecker"), languageVersionSettings, CoroutineCompatibilitySupport.ENABLED, DeprecationSettings.Default)
// Returns true if fqName refers to a valid experimental API marker.
fun checkAnnotation(fqName: String): Boolean {
@@ -20,7 +20,11 @@ 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(val annotation: AnnotationDescriptor, override val target: DeclarationDescriptor) : Deprecation {
internal data class DeprecatedByAnnotation(
val annotation: AnnotationDescriptor,
override val target: DeclarationDescriptor,
override val propagatesToOverrides: Boolean
) : Deprecation {
override val deprecationLevel: DeprecationLevelValue
get() = when (annotation.argumentValue("level")?.safeAs<EnumValue>()?.enumEntryName?.asString()) {
"WARNING" -> WARNING
@@ -36,7 +36,8 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class DeprecationResolver(
storageManager: StorageManager,
private val languageVersionSettings: LanguageVersionSettings,
private val coroutineCompatibilitySupport: CoroutineCompatibilitySupport
private val coroutineCompatibilitySupport: CoroutineCompatibilitySupport,
private val deprecationSettings: DeprecationSettings
) {
private val deprecations = storageManager.createMemoizedFunction { descriptor: DeclarationDescriptor ->
val deprecations = descriptor.getOwnDeprecations()
@@ -126,6 +127,28 @@ class DeprecationResolver(
if (hasUndeprecatedOverridden || deprecations.isEmpty()) return null
// We might've filtered out not-propagating deprecations already in the initializer of `deprecationsByAnnotation` in the code above.
// But it would lead to treating Java overridden as not-deprecated at all that works controversially in case of mixed J/K override:
// interface J {
// @Deprecated
// void foo();
// }
//
// interface K {
// @Deprecated("")
// fun foo();
// }
//
// class K1 : K, J {
// // We'd probably better treating it as deprecated
// // Basically, it's just a corner case and we may change the behavior if it's too annoying
// override fun foo() {}
// }
//
// Also, we don't ignore non-propagating deprecations in case of fake overrides
// Because we don't want to depend on the choice of the base descriptor
if (root.kind.isReal && deprecations.none(Deprecation::propagatesToOverrides)) return null
return DeprecatedByOverridden(deprecations)
}
@@ -166,7 +189,11 @@ class DeprecationResolver(
val annotation = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecated)
?: annotations.findAnnotation(JAVA_DEPRECATED)
if (annotation != null) {
val deprecatedByAnnotation = DeprecatedByAnnotation(annotation, this)
val deprecatedByAnnotation =
DeprecatedByAnnotation(
annotation, this,
deprecationSettings.propagatedToOverrides(annotation)
)
val deprecation = when {
this is TypeAliasConstructorDescriptor ->
DeprecatedTypealiasByAnnotation(typeAliasDescriptor, deprecatedByAnnotation)
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.resolve.deprecation
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.container.DefaultImplementation
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
@@ -70,3 +71,12 @@ class CoroutineCompatibilitySupport private constructor(val enabled: Boolean) {
val DISABLED = CoroutineCompatibilitySupport(false)
}
}
@DefaultImplementation(DeprecationSettings.Default::class)
interface DeprecationSettings {
fun propagatedToOverrides(deprecationAnnotation: AnnotationDescriptor): Boolean
object Default : DeprecationSettings {
override fun propagatedToOverrides(deprecationAnnotation: AnnotationDescriptor) = true
}
}