[FIR] Assume SafeCallsAreAlwaysNullable is always on

This commit is contained in:
Nikolay Lunyak
2023-02-24 16:02:35 +02:00
committed by Space Team
parent be5850112a
commit 72823657c9
9 changed files with 48 additions and 17 deletions
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.fir.resolve
import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.builtins.functions.FunctionTypeKind import org.jetbrains.kotlin.builtins.functions.FunctionTypeKind
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fakeElement import org.jetbrains.kotlin.fakeElement
@@ -493,25 +492,15 @@ fun FirCheckedSafeCallSubject.propagateTypeFromOriginalReceiver(
} }
fun FirSafeCallExpression.propagateTypeFromQualifiedAccessAfterNullCheck( fun FirSafeCallExpression.propagateTypeFromQualifiedAccessAfterNullCheck(
nullableReceiverExpression: FirExpression,
session: FirSession, session: FirSession,
file: FirFile, file: FirFile,
) { ) {
val receiverType = nullableReceiverExpression.typeRef.coneTypeSafe<ConeKotlinType>()
val selector = selector val selector = selector
val resultingType = when { val resultingType = when {
selector is FirExpression && !selector.isCallToStatementLikeFunction -> { selector is FirExpression && !selector.isCallToStatementLikeFunction -> {
val type = selector.typeRef.coneTypeSafe<ConeKotlinType>() ?: return val type = selector.typeRef.coneTypeSafe<ConeKotlinType>() ?: return
type.withNullability(ConeNullability.NULLABLE, session.typeContext)
val isReceiverActuallyNullable = session.languageVersionSettings.supportsFeature(LanguageFeature.SafeCallsAreAlwaysNullable)
|| receiverType != null && session.typeContext.run { receiverType.isNullableType() }
if (isReceiverActuallyNullable) {
type.withNullability(ConeNullability.NULLABLE, session.typeContext)
} else {
type
}
} }
// Branch for things that shouldn't be used as expressions. // Branch for things that shouldn't be used as expressions.
// They are forced to return not-null `Unit`, regardless of the receiver. // They are forced to return not-null `Unit`, regardless of the receiver.
@@ -343,7 +343,7 @@ class FirCallCompletionResultsWriterTransformer(
)?.toExpectedType() )?.toExpectedType()
) )
safeCallExpression.propagateTypeFromQualifiedAccessAfterNullCheck(safeCallExpression.receiver, session, context.file) safeCallExpression.propagateTypeFromQualifiedAccessAfterNullCheck(session, context.file)
return safeCallExpression return safeCallExpression
} }
@@ -357,7 +357,7 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
safeCallExpression.apply { safeCallExpression.apply {
checkedSubjectRef.value.propagateTypeFromOriginalReceiver(receiver, components.session, components.file) checkedSubjectRef.value.propagateTypeFromOriginalReceiver(receiver, components.session, components.file)
transformSelector(this@FirExpressionsResolveTransformer, data) transformSelector(this@FirExpressionsResolveTransformer, data)
propagateTypeFromQualifiedAccessAfterNullCheck(receiver, session, context.file) propagateTypeFromQualifiedAccessAfterNullCheck(session, context.file)
} }
dataFlowAnalyzer.exitSafeCall(safeCallExpression) dataFlowAnalyzer.exitSafeCall(safeCallExpression)
@@ -1,4 +1,7 @@
// !LANGUAGE: -SafeCallsAreAlwaysNullable // !LANGUAGE: -SafeCallsAreAlwaysNullable
// IGNORE_BACKEND_K2: JVM_IR
// Status: Feature is always on in K2
fun test(s: String) = s?.length fun test(s: String) = s?.length
// 0 IFNULL // 0 IFNULL
@@ -0,0 +1,7 @@
// !LANGUAGE: -SafeCallsAreAlwaysNullable
// http://youtrack.jetbrains.net/issue/KT-418
fun ff() {
val i: Int = 1
val a: Int = <!INITIALIZER_TYPE_MISMATCH!>i<!SAFE_CALL_WILL_CHANGE_NULLABILITY!><!UNNECESSARY_SAFE_CALL!>?.<!>plus(2)<!><!>
}
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
// !LANGUAGE: -SafeCallsAreAlwaysNullable // !LANGUAGE: -SafeCallsAreAlwaysNullable
// http://youtrack.jetbrains.net/issue/KT-418 // http://youtrack.jetbrains.net/issue/KT-418
@@ -8,6 +8,6 @@ fun foo(): String {
} }
fun bar(): String { fun bar(): String {
val x = fn() ?: return "" val x = fn() ?: return ""
val y = x<!SAFE_CALL_WILL_CHANGE_NULLABILITY!><!UNNECESSARY_SAFE_CALL!>?.<!>let { throw Exception() }<!> <!USELESS_ELVIS!>?: "unreachable"<!> val y = x<!SAFE_CALL_WILL_CHANGE_NULLABILITY!><!UNNECESSARY_SAFE_CALL!>?.<!>let { throw Exception() }<!> ?: "unreachable"
return y return y
} }
@@ -0,0 +1,34 @@
// LANGUAGE: -SafeCallsAreAlwaysNullable
// DIAGNOSTICS: -UNNECESSARY_SAFE_CALL
// ISSUE: KT-46860
interface A {
fun id(): A
fun foo(): String
}
fun test_1(a: A) {
val s = a.id().id().id().id().id().id().id().id()<!SAFE_CALL_WILL_CHANGE_NULLABILITY!>?.foo()<!><!UNSAFE_CALL!>.<!>length
}
fun test_2(a: A) {
val s = a.id()
.id()
.id()
.id()
<!SAFE_CALL_WILL_CHANGE_NULLABILITY!>?.id()<!>
<!UNSAFE_CALL!>.<!>id()
.id()
.id()
<!SAFE_CALL_WILL_CHANGE_NULLABILITY!>?.foo()<!>
?.length
}
fun test_3(a: A) {
val s = a.id()<!SAFE_CALL_WILL_CHANGE_NULLABILITY!>?.
id()<!><!UNSAFE_CALL!>.<!>
id()<!SAFE_CALL_WILL_CHANGE_NULLABILITY!>?.
foo()<!><!UNSAFE_CALL!>.<!>
length
}
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
// LANGUAGE: -SafeCallsAreAlwaysNullable // LANGUAGE: -SafeCallsAreAlwaysNullable
// DIAGNOSTICS: -UNNECESSARY_SAFE_CALL // DIAGNOSTICS: -UNNECESSARY_SAFE_CALL
// ISSUE: KT-46860 // ISSUE: KT-46860