Call completer: safe call with nullable receiver has nullable return type #KT-11007 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
4cd7193047
commit
17593e4ef6
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.Constrain
|
|||||||
import org.jetbrains.kotlin.resolve.calls.inference.filterConstraintsOut
|
import org.jetbrains.kotlin.resolve.calls.inference.filterConstraintsOut
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.toHandle
|
import org.jetbrains.kotlin.resolve.calls.inference.toHandle
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.*
|
import org.jetbrains.kotlin.resolve.calls.model.*
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.makeNullableTypeIfSafeReceiver
|
||||||
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsImpl
|
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsImpl
|
||||||
import org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus
|
import org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus
|
||||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
||||||
@@ -274,7 +275,10 @@ class CallCompleter(
|
|||||||
val results = completeCallForArgument(deparenthesized, context)
|
val results = completeCallForArgument(deparenthesized, context)
|
||||||
if (results != null && results.isSingleResult) {
|
if (results != null && results.isSingleResult) {
|
||||||
val resolvedCall = results.resultingCall
|
val resolvedCall = results.resultingCall
|
||||||
updatedType = if (resolvedCall.hasInferredReturnType()) resolvedCall.resultingDescriptor?.returnType else null
|
updatedType = if (resolvedCall.hasInferredReturnType()) {
|
||||||
|
resolvedCall.makeNullableTypeIfSafeReceiver(resolvedCall.resultingDescriptor?.returnType, context)
|
||||||
|
}
|
||||||
|
else null
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the cases like 'foo(1)' the type of '1' depends on expected type (it can be Int, Byte, etc.),
|
// For the cases like 'foo(1)' the type of '1' depends on expected type (it can be Int, Byte, etc.),
|
||||||
|
|||||||
@@ -16,16 +16,22 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.resolve.calls.resolvedCallUtil
|
package org.jetbrains.kotlin.resolve.calls.resolvedCallUtil
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
import org.jetbrains.kotlin.psi.KtPsiUtil
|
import org.jetbrains.kotlin.psi.KtPsiUtil
|
||||||
import org.jetbrains.kotlin.psi.KtThisExpression
|
import org.jetbrains.kotlin.psi.KtThisExpression
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
|
||||||
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
|
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getOwnerForEffectiveDispatchReceiverParameter
|
import org.jetbrains.kotlin.resolve.descriptorUtil.getOwnerForEffectiveDispatchReceiverParameter
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
|
|
||||||
// it returns true if call has no dispatch receiver (e.g. resulting descriptor is top-level function or local variable)
|
// it returns true if call has no dispatch receiver (e.g. resulting descriptor is top-level function or local variable)
|
||||||
// or call receiver is effectively `this` instance (explicitly or implicitly) of resulting descriptor
|
// or call receiver is effectively `this` instance (explicitly or implicitly) of resulting descriptor
|
||||||
@@ -72,4 +78,14 @@ fun ResolvedCall<*>.getImplicitReceiverValue(): ReceiverValue? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ResolvedCall<*>.hasSafeNullableReceiver(context: CallResolutionContext<*>): Boolean {
|
||||||
|
if (!isSafeCall) return false
|
||||||
|
val receiverValue = getExplicitReceiverValue()?.let { DataFlowValueFactory.createDataFlowValue(it, context) }
|
||||||
|
?: return false
|
||||||
|
return context.dataFlowInfo.getPredictableNullability(receiverValue).canBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ResolvedCall<*>.makeNullableTypeIfSafeReceiver(type: KotlinType?, context: CallResolutionContext<*>) =
|
||||||
|
type?.let { TypeUtils.makeNullableIfNeeded(type, hasSafeNullableReceiver(context)) }
|
||||||
|
|
||||||
fun ResolvedCall<*>.hasBothReceivers() = dispatchReceiver != null && extensionReceiver != null
|
fun ResolvedCall<*>.hasBothReceivers() = dispatchReceiver != null && extensionReceiver != null
|
||||||
@@ -53,9 +53,8 @@ L3 [after local declaration]:
|
|||||||
r({ throw Exception() }) -> <v1> PREV:[jmp?(L3)]
|
r({ throw Exception() }) -> <v1> PREV:[jmp?(L3)]
|
||||||
mark(let { throw Exception() })
|
mark(let { throw Exception() })
|
||||||
call(let { throw Exception() }, let|<v0>, <v1>)
|
call(let { throw Exception() }, let|<v0>, <v1>)
|
||||||
jmp(error) NEXT:[<ERROR>]
|
|
||||||
L2 [result of call]:
|
L2 [result of call]:
|
||||||
mark(fn()?.let { throw Exception() } ?: "unreachable?") PREV:[jf(L2)]
|
mark(fn()?.let { throw Exception() } ?: "unreachable?") PREV:[jf(L2), call(let { throw Exception() }, let|<v0>, <v1>)]
|
||||||
jt(L6|!<v2>) NEXT:[mark("unreachable?"), merge(fn()?.let { throw Exception() } ?: "unreachable?"|!<v2>, <v3>) -> <v4>]
|
jt(L6|!<v2>) NEXT:[mark("unreachable?"), merge(fn()?.let { throw Exception() } ?: "unreachable?"|!<v2>, <v3>) -> <v4>]
|
||||||
mark("unreachable?")
|
mark("unreachable?")
|
||||||
r("unreachable?") -> <v3>
|
r("unreachable?") -> <v3>
|
||||||
@@ -67,7 +66,7 @@ L6 [after elvis operator]:
|
|||||||
L1:
|
L1:
|
||||||
1 <END> NEXT:[<SINK>]
|
1 <END> NEXT:[<SINK>]
|
||||||
error:
|
error:
|
||||||
<ERROR> PREV:[jmp(error)]
|
<ERROR> PREV:[]
|
||||||
sink:
|
sink:
|
||||||
<SINK> PREV:[<ERROR>, <END>, d({ throw Exception() })]
|
<SINK> PREV:[<ERROR>, <END>, d({ throw Exception() })]
|
||||||
=====================
|
=====================
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// See KT-11007: Wrong smart cast to not-null type after safe calls in if / when expression
|
||||||
|
|
||||||
|
val String.copy: String
|
||||||
|
get() = this
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
val s: String? = null
|
||||||
|
val ss = if (true) {
|
||||||
|
s?.length
|
||||||
|
} else {
|
||||||
|
s?.length
|
||||||
|
}
|
||||||
|
ss<!UNSAFE_CALL!>.<!>hashCode() // Smart-cast to Int, should be unsafe call
|
||||||
|
val sss = if (true) {
|
||||||
|
s?.copy
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s?.copy
|
||||||
|
}
|
||||||
|
sss<!UNSAFE_CALL!>.<!>length
|
||||||
|
}
|
||||||
|
|
||||||
|
class My {
|
||||||
|
val String.copy2: String
|
||||||
|
get() = this
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
val s: String? = null
|
||||||
|
val ss = if (true) {
|
||||||
|
s?.length
|
||||||
|
} else {
|
||||||
|
s?.length
|
||||||
|
}
|
||||||
|
ss<!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
val sss = if (true) {
|
||||||
|
s?.copy2
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s?.copy2
|
||||||
|
}
|
||||||
|
sss<!UNSAFE_CALL!>.<!>length
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public val kotlin.String.copy: kotlin.String
|
||||||
|
public fun foo(): kotlin.Unit
|
||||||
|
|
||||||
|
public final class My {
|
||||||
|
public constructor My()
|
||||||
|
public final val kotlin.String.copy2: kotlin.String
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -17252,6 +17252,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("insideIfExpr.kt")
|
||||||
|
public void testInsideIfExpr() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/insideIfExpr.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("longChain.kt")
|
@TestMetadata("longChain.kt")
|
||||||
public void testLongChain() throws Exception {
|
public void testLongChain() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/longChain.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/longChain.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user