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 a51c3743dfa..98636041c30 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 @@ -27416,6 +27416,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt"); } + @Test + @TestMetadata("safeCallIsAlwaysNullable.kt") + public void testSafeCallIsAlwaysNullable() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.kt"); + } + @Test @TestMetadata("simple.kt") public void testSimple() 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 cf981a6390f..7e28aa7cc29 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 @@ -27416,6 +27416,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt"); } + @Test + @TestMetadata("safeCallIsAlwaysNullable.kt") + public void testSafeCallIsAlwaysNullable() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.kt"); + } + @Test @TestMetadata("simple.kt") public void testSimple() throws Exception { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt index 8f56e44e99e..17a8d8fba63 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt @@ -359,8 +359,10 @@ class CallExpressionResolver( KotlinTypeInfo { var initialDataFlowInfoForArguments = context.dataFlowInfo val receiverDataFlowValue = (receiver as? ReceiverValue)?.let { dataFlowValueFactory.createDataFlowValue(it, context) } - val receiverCanBeNull = receiverDataFlowValue != null && - initialDataFlowInfoForArguments.getStableNullability(receiverDataFlowValue).canBeNull() + + val receiverCanBeNull = receiverDataFlowValue != null && initialDataFlowInfoForArguments.getStableNullability(receiverDataFlowValue).canBeNull() + val shouldNullifySafeCallType = + receiverCanBeNull || context.languageVersionSettings.supportsFeature(LanguageFeature.SafeCallsAreAlwaysNullable) val callOperationNode = AstLoadingFilter.forceAllowTreeLoading(element.qualified.containingFile, ThrowableComputable { element.node @@ -368,11 +370,12 @@ class CallExpressionResolver( if (receiverDataFlowValue != null && element.safe) { // Additional "receiver != null" information should be applied if we consider a safe call - if (receiverCanBeNull) { + if (shouldNullifySafeCallType) { initialDataFlowInfoForArguments = initialDataFlowInfoForArguments.disequate( receiverDataFlowValue, DataFlowValue.nullValue(builtIns), languageVersionSettings ) - } else { + } + if (!receiverCanBeNull) { reportUnnecessarySafeCall(context.trace, receiver.type, callOperationNode, receiver) } } @@ -393,7 +396,7 @@ class CallExpressionResolver( val selectorType = selectorTypeInfo.type if (selectorType != null) { - if (element.safe && receiverCanBeNull) { + if (element.safe && shouldNullifySafeCallType) { selectorTypeInfo = selectorTypeInfo.replaceType(TypeUtils.makeNullable(selectorType)) } // TODO : this is suspicious: remove this code? diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/redundantSafeCall_1_4.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/redundantSafeCall_1_4.kt index 06686adedc9..535a79a29ec 100644 --- a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/redundantSafeCall_1_4.kt +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/redundantSafeCall_1_4.kt @@ -5,4 +5,4 @@ fun test(s: String) = s?.length // 0 IFNULL // 0 IFNONNULL // 0 intValue -// 0 valueOf +// 1 valueOf diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.fir.kt new file mode 100644 index 00000000000..c29d2d705a5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.fir.kt @@ -0,0 +1,20 @@ +// LANGUAGE: +SafeCallsAreAlwaysNullable +// ISSUE: KT-46860 + +interface A { + fun foo(): Int +} + +fun takeInt(x: Int) {} + +fun test_1(a: A) { + val x = a?.foo() + takeInt(x) // should be an error +} + +fun test_2(a: A?) { + if (a != null) { + val x = a?.foo() + takeInt(x) // should be an error + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.kt new file mode 100644 index 00000000000..697dd87a7ae --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.kt @@ -0,0 +1,20 @@ +// LANGUAGE: +SafeCallsAreAlwaysNullable +// ISSUE: KT-46860 + +interface A { + fun foo(): Int +} + +fun takeInt(x: Int) {} + +fun test_1(a: A) { + val x = a?.foo() + takeInt(x) // should be an error +} + +fun test_2(a: A?) { + if (a != null) { + val x = a?.foo() + takeInt(x) // should be an error + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.txt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.txt new file mode 100644 index 00000000000..8f6cb91889a --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.txt @@ -0,0 +1,12 @@ +package + +public fun takeInt(/*0*/ x: kotlin.Int): kotlin.Unit +public fun test_1(/*0*/ a: A): kotlin.Unit +public fun test_2(/*0*/ a: A?): kotlin.Unit + +public interface A { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract fun foo(): kotlin.Int + 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 2984137331b..8a197022733 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 @@ -27506,6 +27506,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt"); } + @Test + @TestMetadata("safeCallIsAlwaysNullable.kt") + public void testSafeCallIsAlwaysNullable() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.kt"); + } + @Test @TestMetadata("simple.kt") public void testSimple() 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 c69e51e1c0c..0658697c77b 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -211,6 +211,7 @@ enum class LanguageFeature( UnrestrictedBuilderInference(KOTLIN_1_6), ProperTypeInferenceConstraintsProcessing(KOTLIN_1_6, kind = BUG_FIX), ClassTypeParameterAnnotations(KOTLIN_1_6), + SafeCallsAreAlwaysNullable(KOTLIN_1_6), // 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 5a613a801e5..d9e13138229 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 @@ -27416,6 +27416,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt"); } + @Test + @TestMetadata("safeCallIsAlwaysNullable.kt") + public void testSafeCallIsAlwaysNullable() throws Exception { + runTest("compiler/testData/diagnostics/tests/smartCasts/safecalls/safeCallIsAlwaysNullable.kt"); + } + @Test @TestMetadata("simple.kt") public void testSimple() throws Exception {