K1: introduce smartcast deprecation for a public API property in invisible class

#KT-56511 Fixed
Related to KT-57290
This commit is contained in:
Mikhail Glukhikh
2023-03-13 13:44:24 +01:00
committed by Space Team
parent 0b31e5ad63
commit 41c868445c
7 changed files with 61 additions and 16 deletions
@@ -57,6 +57,13 @@ class DataFlowValue(
// Should be unstable, but can be used as stable with deprecation warning // Should be unstable, but can be used as stable with deprecation warning
LEGACY_ALIEN_BASE_PROPERTY("alien derived", "property declared in base class from different module"), LEGACY_ALIEN_BASE_PROPERTY("alien derived", "property declared in base class from different module"),
// Protected / public member value from derived class from another module, in case the derived class is non-public API
// Should be unstable, but can be used as stable with deprecation warning
LEGACY_ALIEN_BASE_PROPERTY_INHERITED_IN_INVISIBLE_CLASS(
"alien inherited in invisible",
"property declared in base class from different module inherited in non-public API class"
),
// Protected / public member value from another module // Protected / public member value from another module
// Smart casts are not safe // Smart casts are not safe
ALIEN_PUBLIC_PROPERTY("alien public", "public API property declared in different module"), ALIEN_PUBLIC_PROPERTY("alien public", "public API property declared in different module"),
@@ -88,7 +95,8 @@ class DataFlowValue(
kind == Kind.STABLE_VARIABLE || kind == Kind.STABLE_VARIABLE ||
kind == Kind.STABLE_COMPLEX_EXPRESSION || kind == Kind.STABLE_COMPLEX_EXPRESSION ||
kind == Kind.LEGACY_STABLE_LOCAL_DELEGATED_PROPERTY || kind == Kind.LEGACY_STABLE_LOCAL_DELEGATED_PROPERTY ||
kind == Kind.LEGACY_ALIEN_BASE_PROPERTY kind == Kind.LEGACY_ALIEN_BASE_PROPERTY ||
kind == Kind.LEGACY_ALIEN_BASE_PROPERTY_INHERITED_IN_INVISIBLE_CLASS
val canBeBound get() = identifierInfo.canBeBound val canBeBound get() = identifierInfo.canBeBound
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.resolve.calls.smartcasts
import org.jetbrains.kotlin.cfg.getDeclarationDescriptorIncludingConstructors import org.jetbrains.kotlin.cfg.getDeclarationDescriptorIncludingConstructors
import org.jetbrains.kotlin.cfg.getElementParentDeclaration import org.jetbrains.kotlin.cfg.getElementParentDeclaration
import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageFeature.*
import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
@@ -26,21 +26,29 @@ internal fun PropertyDescriptor.propertyKind(
if (isVar) return DataFlowValue.Kind.MUTABLE_PROPERTY if (isVar) return DataFlowValue.Kind.MUTABLE_PROPERTY
if (isOverridable) return DataFlowValue.Kind.PROPERTY_WITH_GETTER if (isOverridable) return DataFlowValue.Kind.PROPERTY_WITH_GETTER
if (!hasDefaultGetter()) return DataFlowValue.Kind.PROPERTY_WITH_GETTER if (!hasDefaultGetter()) return DataFlowValue.Kind.PROPERTY_WITH_GETTER
if (!isInvisibleFromOtherModules()) { val originalDescriptor = DescriptorUtils.unwrapFakeOverride(this)
val declarationModule = DescriptorUtils.getContainingModule(this) val isInvisibleFromOtherModuleUnwrappedFakeOverride = originalDescriptor.isInvisibleFromOtherModules()
if (!areCompiledTogether(usageModule, declarationModule)) { if (isInvisibleFromOtherModuleUnwrappedFakeOverride) return DataFlowValue.Kind.STABLE_VALUE
return DataFlowValue.Kind.ALIEN_PUBLIC_PROPERTY
} if (kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) {
if (kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { if (overriddenDescriptors.any { isDeclaredInAnotherModule(usageModule) }) {
if (overriddenDescriptors.any { isDeclaredInAnotherModule(usageModule) }) { val deprecationForInvisibleFakeOverride =
return if (!languageVersionSettings.supportsFeature(LanguageFeature.ProhibitSmartcastsOnPropertyFromAlienBaseClass)) { isInvisibleFromOtherModules() != isInvisibleFromOtherModuleUnwrappedFakeOverride &&
!languageVersionSettings.supportsFeature(ProhibitSmartcastsOnPropertyFromAlienBaseClassInheritedInInvisibleClass)
return when {
deprecationForInvisibleFakeOverride ->
DataFlowValue.Kind.LEGACY_ALIEN_BASE_PROPERTY_INHERITED_IN_INVISIBLE_CLASS
!languageVersionSettings.supportsFeature(ProhibitSmartcastsOnPropertyFromAlienBaseClass) ->
DataFlowValue.Kind.LEGACY_ALIEN_BASE_PROPERTY DataFlowValue.Kind.LEGACY_ALIEN_BASE_PROPERTY
} else { else ->
DataFlowValue.Kind.ALIEN_PUBLIC_PROPERTY DataFlowValue.Kind.ALIEN_PUBLIC_PROPERTY
}
} }
} }
} }
val declarationModule = DescriptorUtils.getContainingModule(originalDescriptor)
if (!areCompiledTogether(usageModule, declarationModule)) return DataFlowValue.Kind.ALIEN_PUBLIC_PROPERTY
return DataFlowValue.Kind.STABLE_VALUE return DataFlowValue.Kind.STABLE_VALUE
} }
@@ -76,7 +84,7 @@ internal fun VariableDescriptor.variableKind(
if (this is LocalVariableDescriptor && this.isDelegated) { if (this is LocalVariableDescriptor && this.isDelegated) {
// Local delegated property: normally unstable, but can be treated as stable in legacy mode // Local delegated property: normally unstable, but can be treated as stable in legacy mode
return if (languageVersionSettings.supportsFeature(LanguageFeature.ProhibitSmartcastsOnLocalDelegatedProperty)) return if (languageVersionSettings.supportsFeature(ProhibitSmartcastsOnLocalDelegatedProperty))
DataFlowValue.Kind.PROPERTY_WITH_GETTER DataFlowValue.Kind.PROPERTY_WITH_GETTER
else else
DataFlowValue.Kind.LEGACY_STABLE_LOCAL_DELEGATED_PROPERTY DataFlowValue.Kind.LEGACY_STABLE_LOCAL_DELEGATED_PROPERTY
@@ -101,7 +109,7 @@ internal fun VariableDescriptor.variableKind(
val variableContainingDeclaration = this.containingDeclaration val variableContainingDeclaration = this.containingDeclaration
if (isAccessedInsideClosure(variableContainingDeclaration, bindingContext, accessElement)) { if (isAccessedInsideClosure(variableContainingDeclaration, bindingContext, accessElement)) {
// stable iff we have no writers in closures AND this closure is AFTER all writers // stable iff we have no writers in closures AND this closure is AFTER all writers
return if (preliminaryVisitor.languageVersionSettings.supportsFeature(LanguageFeature.CapturedInClosureSmartCasts) && return if (preliminaryVisitor.languageVersionSettings.supportsFeature(CapturedInClosureSmartCasts) &&
hasNoWritersInClosures(variableContainingDeclaration, writers, bindingContext) && hasNoWritersInClosures(variableContainingDeclaration, writers, bindingContext) &&
isAccessedInsideClosureAfterAllWriters(writers, accessElement) isAccessedInsideClosureAfterAllWriters(writers, accessElement)
) { ) {
@@ -157,7 +165,6 @@ private fun isAccessedBeforeAllClosureWriters(
return true return true
} }
private fun DeclarationDescriptorWithVisibility.isInvisibleFromOtherModules(): Boolean { private fun DeclarationDescriptorWithVisibility.isInvisibleFromOtherModules(): Boolean {
if (DescriptorVisibilities.INVISIBLE_FROM_OTHER_MODULES.contains(visibility)) return true if (DescriptorVisibilities.INVISIBLE_FROM_OTHER_MODULES.contains(visibility)) return true
@@ -226,6 +226,7 @@ class SmartCastManager(private val argumentTypeResolver: ArgumentTypeResolver) {
if (KotlinBuiltIns.isNullableNothing(type)) return if (KotlinBuiltIns.isNullableNothing(type)) return
if (dataFlowValue.isStable) { if (dataFlowValue.isStable) {
if (dataFlowValue.kind == DataFlowValue.Kind.LEGACY_ALIEN_BASE_PROPERTY || if (dataFlowValue.kind == DataFlowValue.Kind.LEGACY_ALIEN_BASE_PROPERTY ||
dataFlowValue.kind == DataFlowValue.Kind.LEGACY_ALIEN_BASE_PROPERTY_INHERITED_IN_INVISIBLE_CLASS ||
dataFlowValue.kind == DataFlowValue.Kind.LEGACY_STABLE_LOCAL_DELEGATED_PROPERTY dataFlowValue.kind == DataFlowValue.Kind.LEGACY_STABLE_LOCAL_DELEGATED_PROPERTY
) { ) {
trace.report(Errors.DEPRECATED_SMARTCAST.on(expression, type, expression.text, dataFlowValue.kind.description)) trace.report(Errors.DEPRECATED_SMARTCAST.on(expression, type, expression.text, dataFlowValue.kind.description))
@@ -0,0 +1,10 @@
// -- Module: <m1> --
// -- Module: <m2> --
/B.kt:13:13: warning: smart cast to 'String' is deprecated, because 'x' is a property declared in base class from different module inherited in non-public API class
x.length
^
/B.kt:22:9: warning: smart cast to 'String' is deprecated, because 'i.x' is a property declared in base class from different module inherited in non-public API class
i.x.length
^
@@ -1,3 +1,4 @@
// RENDER_DIAGNOSTICS_FULL_TEXT
// MODULE: m1 // MODULE: m1
// FILE: A.kt // FILE: A.kt
@@ -13,3 +14,11 @@ private class Derived : Base("123") {
} }
} }
} }
internal class Internal : Base("456")
internal fun bar(i: Internal) {
if (i.x is String) {
i.x.length
}
}
@@ -1,3 +1,4 @@
// RENDER_DIAGNOSTICS_FULL_TEXT
// MODULE: m1 // MODULE: m1
// FILE: A.kt // FILE: A.kt
@@ -9,7 +10,15 @@ open class Base(val x: Any)
private class Derived : Base("123") { private class Derived : Base("123") {
fun foo() { fun foo() {
if (x is String) { if (x is String) {
<!DEBUG_INFO_SMARTCAST!>x<!>.length <!DEBUG_INFO_SMARTCAST, DEPRECATED_SMARTCAST!>x<!>.length
} }
} }
} }
internal class Internal : Base("456")
internal fun bar(i: Internal) {
if (i.x is String) {
<!DEBUG_INFO_SMARTCAST, DEPRECATED_SMARTCAST!>i.x<!>.length
}
}
@@ -285,6 +285,7 @@ enum class LanguageFeature(
EnhanceNullabilityOfPrimitiveArrays(KOTLIN_2_0, kind = BUG_FIX), // KT-54521 EnhanceNullabilityOfPrimitiveArrays(KOTLIN_2_0, kind = BUG_FIX), // KT-54521
AllowEmptyIntersectionsInResultTypeResolver(KOTLIN_2_0, kind = OTHER), // KT-51221 AllowEmptyIntersectionsInResultTypeResolver(KOTLIN_2_0, kind = OTHER), // KT-51221
ProhibitSmartcastsOnPropertyFromAlienBaseClassInheritedInInvisibleClass(KOTLIN_2_0, kind = BUG_FIX), // KT-57290
// End of 2.* language features -------------------------------------------------- // End of 2.* language features --------------------------------------------------