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
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.idea.MainFunctionDetector
import org.jetbrains.kotlin.load.java.components.JavaDeprecationSettings
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.modules.TargetId
@@ -34,6 +35,7 @@ import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.deprecation.CoroutineCompatibilitySupport
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
import org.jetbrains.kotlin.resolve.deprecation.DeprecationSettings
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
@@ -143,7 +145,7 @@ class GenerationState private constructor(
CompilerDeserializationConfiguration(configuration.languageVersionSettings)
val deprecationProvider =
DeprecationResolver(LockBasedStorageManager.NO_LOCKS, configuration.languageVersionSettings, CoroutineCompatibilitySupport.ENABLED)
DeprecationResolver(LockBasedStorageManager.NO_LOCKS, configuration.languageVersionSettings, CoroutineCompatibilitySupport.ENABLED, JavaDeprecationSettings)
init {
val icComponents = configuration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS)
@@ -72,6 +72,8 @@ private fun StorageComponentContainer.configureJavaTopDownAnalysis(
useInstance(InternalFlexibleTypeTransformer)
useImpl<CompilerDeserializationConfiguration>()
useInstance(JavaDeprecationSettings)
}
fun createContainerForLazyResolveWithJava(
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. 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.load.java.components
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.resolve.deprecation.DeprecationSettings
object JavaDeprecationSettings : DeprecationSettings {
override fun propagatedToOverrides(deprecationAnnotation: AnnotationDescriptor): Boolean {
if (deprecationAnnotation is JavaDeprecatedAnnotationDescriptor) return false
return true
}
}
@@ -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
}
}
@@ -40,7 +40,7 @@ public class C extends A {
fun use(a: A, b: B, c: C) {
a.<!DEPRECATION!>f<!>()
b.<!DEPRECATION!>f<!>()
b.f()
c.<!DEPRECATION!>f<!>()
A.<!DEPRECATION!>D<!>
@@ -0,0 +1,36 @@
// FILE: J.java
public interface J {
@Deprecated
public void foo();
@Deprecated
public String bar();
@Deprecated
public CharSequence baz();
}
// FILE: J2.java
public interface J2 extends J, K {
}
// FILE: main.kt
interface K {
fun bar(): CharSequence
fun baz(): String
}
interface A : J, K
fun main(j: J, j2: J2, a: A) {
j.<!DEPRECATION!>foo<!>()
j2.<!DEPRECATION!>foo<!>()
a.<!DEPRECATION!>foo<!>()
j.<!DEPRECATION!>bar<!>()
j2.<!DEPRECATION!>bar<!>()
a.<!DEPRECATION!>bar<!>()
j.<!DEPRECATION!>baz<!>()
j2.baz()
a.baz()
}
@@ -0,0 +1,38 @@
package
public fun main(/*0*/ j: J, /*1*/ j2: J2, /*2*/ a: A): kotlin.Unit
public interface A : J, K {
@kotlin.Deprecated(message = "Deprecated in Java") public abstract override /*2*/ /*fake_override*/ fun bar(): kotlin.String!
public abstract override /*2*/ /*fake_override*/ fun baz(): kotlin.String
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
@kotlin.Deprecated(message = "Deprecated in Java") public abstract override /*1*/ /*fake_override*/ fun foo(): kotlin.Unit
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
}
public interface J {
@kotlin.Deprecated(message = "Deprecated in Java") public abstract fun bar(): kotlin.String!
@kotlin.Deprecated(message = "Deprecated in Java") public abstract fun baz(): kotlin.CharSequence!
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
@kotlin.Deprecated(message = "Deprecated in Java") public abstract fun foo(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public interface J2 : J, K {
@kotlin.Deprecated(message = "Deprecated in Java") public abstract override /*2*/ /*fake_override*/ fun bar(): kotlin.String
public abstract override /*2*/ /*fake_override*/ fun baz(): kotlin.String
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
@kotlin.Deprecated(message = "Deprecated in Java") public abstract override /*1*/ /*fake_override*/ fun foo(): kotlin.Unit
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
}
public interface K {
public abstract fun bar(): kotlin.CharSequence
public abstract fun baz(): kotlin.String
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
}
@@ -0,0 +1,27 @@
// FILE: J.java
public class J {
@Deprecated
public void foo() {}
}
// FILE: J2.java
public class J2 extends J implements WithDeprecation {
@Override
public void foo() {}
}
// FILE: main.kt
interface WithDeprecation {
@Deprecated("")
fun foo()
}
class A : J(), WithDeprecation {
override fun foo() {}
}
fun main() {
J().<!DEPRECATION!>foo<!>()
J2().<!DEPRECATION!>foo<!>()
A().<!DEPRECATION!>foo<!>()
}
@@ -0,0 +1,34 @@
package
public fun main(): kotlin.Unit
public final class A : J, WithDeprecation {
public constructor A()
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*2*/ fun foo(): kotlin.Unit
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
}
public open class J {
public constructor J()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
@kotlin.Deprecated(message = "Deprecated in Java") public open fun foo(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public open class J2 : J, WithDeprecation {
public constructor J2()
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
@java.lang.Override public open override /*2*/ fun foo(): kotlin.Unit
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
}
public interface WithDeprecation {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
@kotlin.Deprecated(message = "") public abstract fun foo(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,23 @@
// FILE: J.java
public interface J {
@Deprecated
public void foo();
}
// FILE: J2.java
public interface J2 extends J {
@Override
public void foo();
}
// FILE: main.kt
interface A : J {
override fun foo()
}
fun main(j: J, j2: J2, a: A) {
j.<!DEPRECATION!>foo<!>()
j2.foo()
a.foo()
}
@@ -0,0 +1,24 @@
package
public fun main(/*0*/ j: J, /*1*/ j2: J2, /*2*/ a: A): kotlin.Unit
public interface A : J {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public abstract override /*1*/ fun foo(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public interface J {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
@kotlin.Deprecated(message = "Deprecated in Java") public abstract fun foo(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public interface J2 : J {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
@java.lang.Override public abstract override /*1*/ fun foo(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -11978,6 +11978,34 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
}
}
@TestMetadata("compiler/testData/diagnostics/tests/j+k/deprecations")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Deprecations extends AbstractDiagnosticsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInDeprecations() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/j+k/deprecations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("forFakeOverrides.kt")
public void testForFakeOverrides() throws Exception {
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forFakeOverrides.kt");
}
@TestMetadata("forMixedOverride.kt")
public void testForMixedOverride() throws Exception {
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forMixedOverride.kt");
}
@TestMetadata("forOverrides.kt")
public void testForOverrides() throws Exception {
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forOverrides.kt");
}
}
@TestMetadata("compiler/testData/diagnostics/tests/j+k/genericConstructor")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -11973,6 +11973,34 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
}
}
@TestMetadata("compiler/testData/diagnostics/tests/j+k/deprecations")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Deprecations extends AbstractDiagnosticsUsingJavacTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInDeprecations() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/j+k/deprecations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("forFakeOverrides.kt")
public void testForFakeOverrides() throws Exception {
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forFakeOverrides.kt");
}
@TestMetadata("forMixedOverride.kt")
public void testForMixedOverride() throws Exception {
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forMixedOverride.kt");
}
@TestMetadata("forOverrides.kt")
public void testForOverrides() throws Exception {
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forOverrides.kt");
}
}
@TestMetadata("compiler/testData/diagnostics/tests/j+k/genericConstructor")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -12,6 +12,7 @@ interface Deprecation {
val deprecationLevel: DeprecationLevelValue
val message: String?
val target: DeclarationDescriptor
val propagatesToOverrides: Boolean get() = true
}
// values from kotlin.DeprecationLevel