[FE 1.0] Report warning on non-deprecated overrides of deprecated members

Also don't propagate deprecation status to overrides after 1.7

^KT-47902 Fixed
This commit is contained in:
Dmitriy Novozhilov
2021-08-27 15:14:39 +03:00
parent 762d225bd7
commit ef9ad5af51
25 changed files with 993 additions and 52 deletions
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.VarianceConflictDiagnosticData;
import org.jetbrains.kotlin.resolve.calls.inference.InferenceErrorData;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.tower.WrongResolutionToClassifier;
import org.jetbrains.kotlin.resolve.deprecation.DescriptorBasedDeprecation;
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible;
import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData;
import org.jetbrains.kotlin.types.KotlinType;
@@ -98,6 +99,8 @@ public interface Errors {
VERSION_REQUIREMENT_DEPRECATION = DiagnosticFactory3.create(WARNING);
DiagnosticFactory3<PsiElement, DeclarationDescriptor, VersionRequirement.Version, Pair<LanguageVersion, String>>
VERSION_REQUIREMENT_DEPRECATION_ERROR = DiagnosticFactory3.create(ERROR);
// descriptor and deprecation infos are needed only for IDE quickfix for this warning
DiagnosticFactory3<KtNamedDeclaration, String, CallableMemberDescriptor, List<DescriptorBasedDeprecation>> OVERRIDE_DEPRECATION = DiagnosticFactory3.create(WARNING, DECLARATION_NAME);
DiagnosticFactory0<PsiElement> DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL = DiagnosticFactory0.create(ERROR);
@@ -394,6 +394,8 @@ public class DefaultErrorMessages {
(obj, renderingContext) -> obj.equals(VersionRequirement.Version.INFINITY) ? "" : " is only available since Kotlin " + obj.asString() + " and",
versionRequirementMessage);
MAP.put(OVERRIDE_DEPRECATION, "This declaration overrides deprecated member but not marked as deprecated itself. {0}Please add @Deprecated annotation or suppress", TO_STRING, TO_STRING, TO_STRING);
MAP.put(DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED, "DeprecatedSinceKotlin annotation can be used only together with Deprecated annotation");
MAP.put(DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL, "DeprecatedSinceKotlin annotation can be used only with unspecified deprecation level of Deprecated annotation");
MAP.put(DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS, "Values of DeprecatedSinceKotlin annotation should be ordered so 'warningSince' <= 'errorSince' <= 'hiddenSince' if specified");
@@ -38,6 +38,7 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf(
TrailingCommaDeclarationChecker,
MissingDependencySupertypeChecker.ForDeclarations,
FunInterfaceDeclarationChecker(),
DeprecationInheritanceChecker,
DeprecatedSinceKotlinAnnotationChecker,
ContractDescriptionBlockChecker,
PrivateInlineFunctionsReturningAnonymousObjectsChecker,
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2021 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.resolve.checkers
import org.jetbrains.kotlin.config.LanguageFeature.StopPropagatingDeprecationThroughOverrides
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedDeclaration
object DeprecationInheritanceChecker : DeclarationChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (declaration !is KtNamedDeclaration) return
val deprecationResolver = context.deprecationResolver
if (!deprecationResolver.areDeprecationsInheritedFromOverriden(descriptor)) return
val (deprecations, message) = if (context.languageVersionSettings.supportsFeature(StopPropagatingDeprecationThroughOverrides)) {
deprecationResolver.getHiddenDeprecationsFromOverriden(descriptor) to ""
} else {
deprecationResolver.getDeprecations(descriptor) to "This deprecation won't be inherited in kotlin 1.7. "
}
context.trace.report(Errors.OVERRIDE_DEPRECATION.on(declaration, message, descriptor, deprecations))
}
}
@@ -7,10 +7,7 @@ package org.jetbrains.kotlin.resolve.deprecation
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.MavenComparableVersion
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.DescriptorDerivedFromTypeAlias
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
@@ -27,23 +24,46 @@ import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor
import org.jetbrains.kotlin.storage.MemoizedFunctionToNotNull
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
class DeprecationResolver(
storageManager: StorageManager,
private val languageVersionSettings: LanguageVersionSettings,
private val deprecationSettings: DeprecationSettings
) {
private val deprecations = storageManager.createMemoizedFunction { descriptor: DeclarationDescriptor ->
val deprecations = descriptor.getOwnDeprecations()
when {
deprecations.isNotEmpty() -> deprecations
descriptor is CallableMemberDescriptor -> listOfNotNull(deprecationByOverridden(descriptor))
else -> emptyList()
private val deprecations: MemoizedFunctionToNotNull<DeclarationDescriptor, DeprecationInfo> =
storageManager.createMemoizedFunction { descriptor ->
val deprecations = descriptor.getOwnDeprecations()
when {
deprecations.isNotEmpty() -> DeprecationInfo(deprecations, hasInheritedDeprecations = false)
descriptor is CallableMemberDescriptor -> {
val inheritedDeprecations = listOfNotNull(deprecationByOverridden(descriptor))
when (inheritedDeprecations.isNotEmpty()) {
true -> when (languageVersionSettings.supportsFeature(LanguageFeature.StopPropagatingDeprecationThroughOverrides)) {
true -> DeprecationInfo(emptyList(), hasInheritedDeprecations = true, inheritedDeprecations)
false -> DeprecationInfo(inheritedDeprecations, hasInheritedDeprecations = true)
}
false -> DeprecationInfo.EMPTY
}
}
else -> DeprecationInfo.EMPTY
}
}
private data class DeprecationInfo(
val deprecations: List<DescriptorBasedDeprecation>,
val hasInheritedDeprecations: Boolean,
val hiddenInheritedDeprecations: List<DescriptorBasedDeprecation> = emptyList()
) {
companion object {
val EMPTY = DeprecationInfo(emptyList(), hasInheritedDeprecations = false, emptyList())
}
}
@@ -52,7 +72,18 @@ class DeprecationResolver(
}
fun getDeprecations(descriptor: DeclarationDescriptor): List<DescriptorBasedDeprecation> =
deprecations(descriptor.original)
deprecations(descriptor.original).deprecations
@OptIn(ExperimentalContracts::class)
fun areDeprecationsInheritedFromOverriden(descriptor: DeclarationDescriptor): Boolean {
contract {
returns(true) implies (descriptor is CallableMemberDescriptor)
}
return deprecations(descriptor.original).hasInheritedDeprecations
}
fun getHiddenDeprecationsFromOverriden(descriptor: DeclarationDescriptor): List<DescriptorBasedDeprecation> =
deprecations(descriptor.original).hiddenInheritedDeprecations
fun isDeprecatedHidden(descriptor: DeclarationDescriptor): Boolean =
getDeprecations(descriptor).any { it.deprecationLevel == DeprecationLevelValue.HIDDEN }