diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 92cd20293fb..fb4f4e73a81 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -28094,6 +28094,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.kt"); } + @Test + @TestMetadata("otherModuleInheritance_after.kt") + public void testOtherModuleInheritance_after() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance_after.kt"); + } + @Test @TestMetadata("protected.kt") public void testProtected() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index 3bedbef075b..53ad6cc29d5 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -28094,6 +28094,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.kt"); } + @Test + @TestMetadata("otherModuleInheritance_after.kt") + public void testOtherModuleInheritance_after() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance_after.kt"); + } + @Test @TestMetadata("protected.kt") public void testProtected() throws Exception { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.kt index ac3c8350d9d..adaa9a35c2b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValue.kt @@ -41,25 +41,37 @@ class DataFlowValue( // or protected / public member value from the same module without open / custom getter // Smart casts are completely safe STABLE_VALUE("stable val"), + // Block, or if / else, or when STABLE_COMPLEX_EXPRESSION("complex expression", ""), + // Should be unstable, but can be used as stable with deprecation warning LEGACY_STABLE_LOCAL_DELEGATED_PROPERTY("local delegated property"), + // Member value with open / custom getter // Smart casts are not safe PROPERTY_WITH_GETTER("custom getter", "property that has open or custom getter"), + + // Protected / public member value from derived class from another module + // 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"), + // Protected / public member value from another module // Smart casts are not safe ALIEN_PUBLIC_PROPERTY("alien public", "public API property declared in different module"), + // Local variable not yet captured by a changing closure // Smart casts are safe but possible changes in loops / closures ahead must be taken into account STABLE_VARIABLE("stable var", "local variable that can be changed since the check in a loop"), + // Local variable already captured by a changing closure // Smart casts are not safe CAPTURED_VARIABLE("captured var", "local variable that is captured by a changing closure"), + // Member variable regardless of its visibility // Smart casts are not safe MUTABLE_PROPERTY("member", "mutable property that could have been changed by this time"), + // Some complex expression // Smart casts are not safe OTHER("other", "complex expression"); @@ -74,7 +86,8 @@ class DataFlowValue( val isStable = kind == Kind.STABLE_VALUE || kind == Kind.STABLE_VARIABLE || 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 val canBeBound get() = identifierInfo.canBeBound diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueKindUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueKindUtils.kt index 2c1faf66851..42ad7cb7908 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueKindUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueKindUtils.kt @@ -19,7 +19,10 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.types.expressions.AssignedVariablesSearcher import org.jetbrains.kotlin.types.expressions.PreliminaryDeclarationVisitor -internal fun PropertyDescriptor.propertyKind(usageModule: ModuleDescriptor?): DataFlowValue.Kind { +internal fun PropertyDescriptor.propertyKind( + usageModule: ModuleDescriptor?, + languageVersionSettings: LanguageVersionSettings +): DataFlowValue.Kind { if (isVar) return DataFlowValue.Kind.MUTABLE_PROPERTY if (isOverridable) return DataFlowValue.Kind.PROPERTY_WITH_GETTER if (!hasDefaultGetter()) return DataFlowValue.Kind.PROPERTY_WITH_GETTER @@ -28,11 +31,30 @@ internal fun PropertyDescriptor.propertyKind(usageModule: ModuleDescriptor?): Da if (!areCompiledTogether(usageModule, declarationModule)) { return DataFlowValue.Kind.ALIEN_PUBLIC_PROPERTY } + if (kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { + if (overriddenDescriptors.any { isDeclaredInAnotherModule(usageModule) }) { + return if (!languageVersionSettings.supportsFeature(LanguageFeature.ProhibitSmartcastsOnPropertyFromAlienBaseClass)) { + DataFlowValue.Kind.LEGACY_ALIEN_BASE_PROPERTY + } else { + DataFlowValue.Kind.ALIEN_PUBLIC_PROPERTY + } + } + } } return DataFlowValue.Kind.STABLE_VALUE } -internal fun areCompiledTogether( +private fun PropertyDescriptor.isDeclaredInAnotherModule(usageModule: ModuleDescriptor?): Boolean { + if (!areCompiledTogether(usageModule, DescriptorUtils.getContainingModule(this))) { + return true + } + if (kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { + return overriddenDescriptors.any { it.isDeclaredInAnotherModule(usageModule) } + } + return false +} + +private fun areCompiledTogether( usageModule: ModuleDescriptor?, declarationModule: ModuleDescriptor, ): Boolean { @@ -49,7 +71,7 @@ internal fun VariableDescriptor.variableKind( languageVersionSettings: LanguageVersionSettings ): DataFlowValue.Kind { if (this is PropertyDescriptor) { - return propertyKind(usageModule) + return propertyKind(usageModule, languageVersionSettings) } if (this is LocalVariableDescriptor && this.isDelegated) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt index 1d2c0053ac3..f6c39dbbc8e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt @@ -226,7 +226,9 @@ class SmartCastManager(private val argumentTypeResolver: ArgumentTypeResolver) { ) { if (KotlinBuiltIns.isNullableNothing(type)) return if (dataFlowValue.isStable) { - if (dataFlowValue.kind == DataFlowValue.Kind.LEGACY_STABLE_LOCAL_DELEGATED_PROPERTY) { + if (dataFlowValue.kind == DataFlowValue.Kind.LEGACY_ALIEN_BASE_PROPERTY || + dataFlowValue.kind == DataFlowValue.Kind.LEGACY_STABLE_LOCAL_DELEGATED_PROPERTY + ) { trace.report(Errors.DEPRECATED_SMARTCAST.on(expression, type, expression.text, dataFlowValue.kind.description)) } diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.fir.kt index d421d8e7e6c..7c8fac2d653 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.fir.kt @@ -1,3 +1,4 @@ +// !LANGUAGE: -ProhibitSmartcastsOnPropertyFromAlienBaseClass // MODULE: m1 // FILE: A.kt diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.kt index 5c115f269db..e1e433a58a1 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.kt @@ -1,3 +1,4 @@ +// !LANGUAGE: -ProhibitSmartcastsOnPropertyFromAlienBaseClass // MODULE: m1 // FILE: A.kt @@ -10,7 +11,7 @@ open class Generic(val y: T) class Derived : Base("123") { fun foo() { if (x is String) { - x.length // impossible since `x` is in another module. FE1.0 allows this due to KT-47225 + x.length // impossible since `x` is in another module. FE1.0 allows this due to KT-47225 } } } @@ -19,7 +20,7 @@ class MyGeneric : Generic(42) { private fun baz(arg: Int) {} fun bar() { if (y is Int) { - baz(y) // impossible since `y` is in another module. FE1.0 allows this due to KT-47225 + baz(y) // impossible since `y` is in another module. FE1.0 allows this due to KT-47225 } } } diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance_after.kt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance_after.kt new file mode 100644 index 00000000000..fcc9118c262 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance_after.kt @@ -0,0 +1,27 @@ +// FIR_IDENTICAL +// !LANGUAGE: +ProhibitSmartcastsOnPropertyFromAlienBaseClass +// MODULE: m1 +// FILE: A.kt + +open class Base(val x: Any) +open class Generic(val y: T) + +// MODULE: m2(m1) +// FILE: B.kt + +class Derived : Base("123") { + fun foo() { + if (x is String) { + x.length // impossible since `x` is in another module. FE1.0 allows this due to KT-47225 + } + } +} + +class MyGeneric : Generic(42) { + private fun baz(arg: Int) {} + fun bar() { + if (y is Int) { + baz(y) // impossible since `y` is in another module. FE1.0 allows this due to KT-47225 + } + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance_after.txt b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance_after.txt new file mode 100644 index 00000000000..371750913dc --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance_after.txt @@ -0,0 +1,40 @@ +// -- Module: -- +package + +public open class Base { + public constructor Base(/*0*/ x: kotlin.Any) + public final val x: kotlin.Any + 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 +} + +public open class Generic { + public constructor Generic(/*0*/ y: T) + public final val y: T + 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 +} + +// -- Module: -- +package + +public final class Derived : Base { + public constructor Derived() + public final override /*1*/ /*fake_override*/ val x: kotlin.Any + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final 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 final class MyGeneric : Generic { + public constructor MyGeneric() + public final override /*1*/ /*fake_override*/ val y: kotlin.Number + public final fun bar(): kotlin.Unit + private final fun baz(/*0*/ arg: kotlin.Int): kotlin.Unit + 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 +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index b1b77945c14..440bb5f3673 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -28184,6 +28184,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.kt"); } + @Test + @TestMetadata("otherModuleInheritance_after.kt") + public void testOtherModuleInheritance_after() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance_after.kt"); + } + @Test @TestMetadata("protected.kt") public void testProtected() throws Exception { diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index 61bb3134be5..19b52ae3033 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -227,6 +227,7 @@ enum class LanguageFeature( OptInRelease(KOTLIN_1_7), ProhibitNonExhaustiveWhenOnAlgebraicTypes(KOTLIN_1_7, kind = BUG_FIX), UseBuilderInferenceWithoutAnnotation(KOTLIN_1_7), + ProhibitSmartcastsOnPropertyFromAlienBaseClass(KOTLIN_1_7, kind = BUG_FIX), // Temporarily disabled, see KT-27084/KT-22379 SoundSmartcastFromLoopConditionForLoopAssignedVariables(sinceVersion = null, kind = BUG_FIX), diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index b2c731bc30a..7bc16eb9726 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -28094,6 +28094,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance.kt"); } + @Test + @TestMetadata("otherModuleInheritance_after.kt") + public void testOtherModuleInheritance_after() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/publicVals/otherModuleInheritance_after.kt"); + } + @Test @TestMetadata("protected.kt") public void testProtected() throws Exception {