Nullable function-like property call is prohibited now #KT-8252 Fixed
This commit is contained in:
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.resolve.calls.callResolverUtil.getEffectiveExpectedT
|
|||||||
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.getErasedReceiverType
|
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.getErasedReceiverType
|
||||||
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInvokeCallOnExpressionWithBothReceivers
|
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInvokeCallOnExpressionWithBothReceivers
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.isExplicitSafeCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.isExplicitSafeCall
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
|
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
|
||||||
import org.jetbrains.kotlin.resolve.calls.context.*
|
import org.jetbrains.kotlin.resolve.calls.context.*
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.SubstitutionFilteringInternalResolveAnnotations
|
import org.jetbrains.kotlin.resolve.calls.inference.SubstitutionFilteringInternalResolveAnnotations
|
||||||
@@ -454,13 +455,25 @@ class CandidateResolver(
|
|||||||
|
|
||||||
// Here we know that receiver is OK ignoring nullability and check that nullability is OK too
|
// Here we know that receiver is OK ignoring nullability and check that nullability is OK too
|
||||||
// Doing it simply as full subtyping check (receiverValueType <: receiverParameterType)
|
// Doing it simply as full subtyping check (receiverValueType <: receiverParameterType)
|
||||||
val safeAccess = isExplicitReceiver && !implicitInvokeCheck && candidateCall.call.isExplicitSafeCall()
|
val call = candidateCall.call
|
||||||
|
val safeAccess = isExplicitReceiver && !implicitInvokeCheck && call.isExplicitSafeCall()
|
||||||
val expectedReceiverParameterType = if (safeAccess) TypeUtils.makeNullable(receiverParameter.type) else receiverParameter.type
|
val expectedReceiverParameterType = if (safeAccess) TypeUtils.makeNullable(receiverParameter.type) else receiverParameter.type
|
||||||
val smartCastNeeded = !ArgumentTypeResolver.isSubtypeOfForArgumentType(receiverArgument.type, expectedReceiverParameterType)
|
val smartCastNeeded = !ArgumentTypeResolver.isSubtypeOfForArgumentType(receiverArgument.type, expectedReceiverParameterType)
|
||||||
var reportUnsafeCall = false
|
var reportUnsafeCall = false
|
||||||
|
|
||||||
val dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiverArgument, this)
|
val dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiverArgument, this)
|
||||||
val nullability = dataFlowInfo.getPredictableNullability(dataFlowValue)
|
val nullability = dataFlowInfo.getPredictableNullability(dataFlowValue)
|
||||||
|
var nullableImplicitInvokeReceiver = false
|
||||||
|
if (implicitInvokeCheck && call is CallForImplicitInvoke && call.isSafeCall()) {
|
||||||
|
val outerCallReceiver = call.outerCall.explicitReceiver
|
||||||
|
if (outerCallReceiver != call.explicitReceiver && outerCallReceiver is ReceiverValue) {
|
||||||
|
val outerReceiverDataFlowValue = DataFlowValueFactory.createDataFlowValue(outerCallReceiver, this)
|
||||||
|
val outerReceiverNullability = dataFlowInfo.getPredictableNullability(outerReceiverDataFlowValue)
|
||||||
|
if (outerReceiverNullability.canBeNull() && !TypeUtils.isNullableType(expectedReceiverParameterType)) {
|
||||||
|
nullableImplicitInvokeReceiver = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
val expression = (receiverArgument as? ExpressionReceiver)?.expression
|
val expression = (receiverArgument as? ExpressionReceiver)?.expression
|
||||||
if (nullability.canBeNull() && !nullability.canBeNonNull()) {
|
if (nullability.canBeNull() && !nullability.canBeNonNull()) {
|
||||||
if (!TypeUtils.isNullableType(expectedReceiverParameterType)) {
|
if (!TypeUtils.isNullableType(expectedReceiverParameterType)) {
|
||||||
@@ -470,7 +483,7 @@ class CandidateResolver(
|
|||||||
expression?.let { trace.record(BindingContext.SMARTCAST_NULL, it) }
|
expression?.let { trace.record(BindingContext.SMARTCAST_NULL, it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (smartCastNeeded) {
|
else if (!nullableImplicitInvokeReceiver && smartCastNeeded) {
|
||||||
// Look if smart cast has some useful nullability info
|
// Look if smart cast has some useful nullability info
|
||||||
|
|
||||||
val smartCastResult = SmartCastManager.checkAndRecordPossibleCast(
|
val smartCastResult = SmartCastManager.checkAndRecordPossibleCast(
|
||||||
@@ -488,7 +501,7 @@ class CandidateResolver(
|
|||||||
|
|
||||||
val receiverArgumentType = receiverArgument.type
|
val receiverArgumentType = receiverArgument.type
|
||||||
|
|
||||||
if (reportUnsafeCall) {
|
if (reportUnsafeCall || nullableImplicitInvokeReceiver) {
|
||||||
tracing.unsafeCall(trace, receiverArgumentType, implicitInvokeCheck)
|
tracing.unsafeCall(trace, receiverArgumentType, implicitInvokeCheck)
|
||||||
return UNSAFE_CALL_ERROR
|
return UNSAFE_CALL_ERROR
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun bar(doIt: Int.() -> Int) {
|
||||||
|
1.doIt()
|
||||||
|
1<!UNNECESSARY_SAFE_CALL!>?.<!>doIt()
|
||||||
|
val i: Int? = 1
|
||||||
|
i<!UNSAFE_CALL!>.<!>doIt()
|
||||||
|
i?.doIt()
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun bar(/*0*/ doIt: kotlin.Int.() -> kotlin.Int): kotlin.Unit
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
class Rule(val apply:() -> Unit)
|
||||||
|
|
||||||
|
fun bar() {}
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
val rule: Rule? = Rule { bar() }
|
||||||
|
|
||||||
|
// this compiles and works
|
||||||
|
val apply = rule?.apply
|
||||||
|
if (apply != null) <!DEBUG_INFO_SMARTCAST!>apply<!>()
|
||||||
|
|
||||||
|
// this compiles and works
|
||||||
|
rule?.apply?.invoke()
|
||||||
|
|
||||||
|
// this should be an error
|
||||||
|
rule?.<!UNSAFE_CALL!>apply<!>()
|
||||||
|
|
||||||
|
// these both also ok (with smart cast / unnecessary safe call)
|
||||||
|
if (rule != null) {
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>rule<!>.apply()
|
||||||
|
rule<!UNNECESSARY_SAFE_CALL!>?.<!>apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun bar(): kotlin.Unit
|
||||||
|
public fun foo(): kotlin.Unit
|
||||||
|
|
||||||
|
public final class Rule {
|
||||||
|
public constructor Rule(/*0*/ apply: () -> kotlin.Unit)
|
||||||
|
public final val apply: () -> 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
|
||||||
|
}
|
||||||
@@ -241,6 +241,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ExtensionCallInvoke.kt")
|
||||||
|
public void testExtensionCallInvoke() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/ExtensionCallInvoke.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("fileDependencyRecursion.kt")
|
@TestMetadata("fileDependencyRecursion.kt")
|
||||||
public void testFileDependencyRecursion() throws Exception {
|
public void testFileDependencyRecursion() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/fileDependencyRecursion.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/fileDependencyRecursion.kt");
|
||||||
@@ -577,6 +583,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("SafeCallInvoke.kt")
|
||||||
|
public void testSafeCallInvoke() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/SafeCallInvoke.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("SafeCallNonNullReceiver.kt")
|
@TestMetadata("SafeCallNonNullReceiver.kt")
|
||||||
public void testSafeCallNonNullReceiver() throws Exception {
|
public void testSafeCallNonNullReceiver() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/SafeCallNonNullReceiver.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/SafeCallNonNullReceiver.kt");
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ fun box(): String {
|
|||||||
return "Bad call getNullA()?.someFun(). result: $n1, counters: ${toStr()}"
|
return "Bad call getNullA()?.someFun(). result: $n1, counters: ${toStr()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
val n2 = getNullA()?.b()
|
val n2 = getNullA()?.b?.invoke()
|
||||||
if (n2 != null || toStr() != "20000") {
|
if (n2 != null || toStr() != "20000") {
|
||||||
return "Bad call getNullA()?.b(). result: $n2, counters: ${toStr()}"
|
return "Bad call getNullA()?.b(). result: $n2, counters: ${toStr()}"
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ fun box(): String {
|
|||||||
return "Bad call getA()?.someFun(). result: $i1, counters: ${toStr()}"
|
return "Bad call getA()?.someFun(). result: $i1, counters: ${toStr()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
val i2 = getA()?.b()
|
val i2 = getA()?.b?.invoke()
|
||||||
if (i2 != 2 || toStr() != "51101") {
|
if (i2 != 2 || toStr() != "51101") {
|
||||||
return "Bad call getA()?.b(). result: $i2, counters: ${toStr()}"
|
return "Bad call getA()?.b(). result: $i2, counters: ${toStr()}"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user