Skip to content

Commit 1049d4c

Browse files
committed
Nullable function-like property call is prohibited now #KT-8252 Fixed
1 parent 0f80df7 commit 1049d4c

File tree

7 files changed

+75
-5
lines changed

7 files changed

+75
-5
lines changed

compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.resolve.calls.callResolverUtil.getEffectiveExpectedT
3434
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.getErasedReceiverType
3535
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInvokeCallOnExpressionWithBothReceivers
3636
import org.jetbrains.kotlin.resolve.calls.callUtil.isExplicitSafeCall
37+
import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
3738
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
3839
import org.jetbrains.kotlin.resolve.calls.context.*
3940
import org.jetbrains.kotlin.resolve.calls.inference.SubstitutionFilteringInternalResolveAnnotations
@@ -454,13 +455,25 @@ class CandidateResolver(
454455

455456
// Here we know that receiver is OK ignoring nullability and check that nullability is OK too
456457
// Doing it simply as full subtyping check (receiverValueType <: receiverParameterType)
457-
val safeAccess = isExplicitReceiver && !implicitInvokeCheck && candidateCall.call.isExplicitSafeCall()
458+
val call = candidateCall.call
459+
val safeAccess = isExplicitReceiver && !implicitInvokeCheck && call.isExplicitSafeCall()
458460
val expectedReceiverParameterType = if (safeAccess) TypeUtils.makeNullable(receiverParameter.type) else receiverParameter.type
459461
val smartCastNeeded = !ArgumentTypeResolver.isSubtypeOfForArgumentType(receiverArgument.type, expectedReceiverParameterType)
460462
var reportUnsafeCall = false
461463

462464
val dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiverArgument, this)
463465
val nullability = dataFlowInfo.getPredictableNullability(dataFlowValue)
466+
var nullableImplicitInvokeReceiver = false
467+
if (implicitInvokeCheck && call is CallForImplicitInvoke && call.isSafeCall()) {
468+
val outerCallReceiver = call.outerCall.explicitReceiver
469+
if (outerCallReceiver != call.explicitReceiver && outerCallReceiver is ReceiverValue) {
470+
val outerReceiverDataFlowValue = DataFlowValueFactory.createDataFlowValue(outerCallReceiver, this)
471+
val outerReceiverNullability = dataFlowInfo.getPredictableNullability(outerReceiverDataFlowValue)
472+
if (outerReceiverNullability.canBeNull() && !TypeUtils.isNullableType(expectedReceiverParameterType)) {
473+
nullableImplicitInvokeReceiver = true
474+
}
475+
}
476+
}
464477
val expression = (receiverArgument as? ExpressionReceiver)?.expression
465478
if (nullability.canBeNull() && !nullability.canBeNonNull()) {
466479
if (!TypeUtils.isNullableType(expectedReceiverParameterType)) {
@@ -470,7 +483,7 @@ class CandidateResolver(
470483
expression?.let { trace.record(BindingContext.SMARTCAST_NULL, it) }
471484
}
472485
}
473-
else if (smartCastNeeded) {
486+
else if (!nullableImplicitInvokeReceiver && smartCastNeeded) {
474487
// Look if smart cast has some useful nullability info
475488

476489
val smartCastResult = SmartCastManager.checkAndRecordPossibleCast(
@@ -488,7 +501,7 @@ class CandidateResolver(
488501

489502
val receiverArgumentType = receiverArgument.type
490503

491-
if (reportUnsafeCall) {
504+
if (reportUnsafeCall || nullableImplicitInvokeReceiver) {
492505
tracing.unsafeCall(trace, receiverArgumentType, implicitInvokeCheck)
493506
return UNSAFE_CALL_ERROR
494507
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fun bar(doIt: Int.() -> Int) {
2+
1.doIt()
3+
1<!UNNECESSARY_SAFE_CALL!>?.<!>doIt()
4+
val i: Int? = 1
5+
i<!UNSAFE_CALL!>.<!>doIt()
6+
i?.doIt()
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package
2+
3+
public fun bar(/*0*/ doIt: kotlin.Int.() -> kotlin.Int): kotlin.Unit
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class Rule(val apply:() -> Unit)
2+
3+
fun bar() {}
4+
5+
fun foo() {
6+
val rule: Rule? = Rule { bar() }
7+
8+
// this compiles and works
9+
val apply = rule?.apply
10+
if (apply != null) <!DEBUG_INFO_SMARTCAST!>apply<!>()
11+
12+
// this compiles and works
13+
rule?.apply?.invoke()
14+
15+
// this should be an error
16+
rule?.<!UNSAFE_CALL!>apply<!>()
17+
18+
// these both also ok (with smart cast / unnecessary safe call)
19+
if (rule != null) {
20+
<!DEBUG_INFO_SMARTCAST!>rule<!>.apply()
21+
rule<!UNNECESSARY_SAFE_CALL!>?.<!>apply()
22+
}
23+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package
2+
3+
public fun bar(): kotlin.Unit
4+
public fun foo(): kotlin.Unit
5+
6+
public final class Rule {
7+
public constructor Rule(/*0*/ apply: () -> kotlin.Unit)
8+
public final val apply: () -> kotlin.Unit
9+
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
10+
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
11+
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
12+
}

compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,12 @@ public void testEnumEntryAsType() throws Exception {
241241
doTest(fileName);
242242
}
243243

244+
@TestMetadata("ExtensionCallInvoke.kt")
245+
public void testExtensionCallInvoke() throws Exception {
246+
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/ExtensionCallInvoke.kt");
247+
doTest(fileName);
248+
}
249+
244250
@TestMetadata("fileDependencyRecursion.kt")
245251
public void testFileDependencyRecursion() throws Exception {
246252
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/fileDependencyRecursion.kt");
@@ -577,6 +583,12 @@ public void testReturn() throws Exception {
577583
doTest(fileName);
578584
}
579585

586+
@TestMetadata("SafeCallInvoke.kt")
587+
public void testSafeCallInvoke() throws Exception {
588+
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/SafeCallInvoke.kt");
589+
doTest(fileName);
590+
}
591+
580592
@TestMetadata("SafeCallNonNullReceiver.kt")
581593
public void testSafeCallNonNullReceiver() throws Exception {
582594
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/SafeCallNonNullReceiver.kt");

js/js.translator/testData/safeCall/cases/safeCallAndSideEffect.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ fun box(): String {
5050
return "Bad call getNullA()?.someFun(). result: $n1, counters: ${toStr()}"
5151
}
5252

53-
val n2 = getNullA()?.b()
53+
val n2 = getNullA()?.b?.invoke()
5454
if (n2 != null || toStr() != "20000") {
5555
return "Bad call getNullA()?.b(). result: $n2, counters: ${toStr()}"
5656
}
@@ -65,7 +65,7 @@ fun box(): String {
6565
return "Bad call getA()?.someFun(). result: $i1, counters: ${toStr()}"
6666
}
6767

68-
val i2 = getA()?.b()
68+
val i2 = getA()?.b?.invoke()
6969
if (i2 != 2 || toStr() != "51101") {
7070
return "Bad call getA()?.b(). result: $i2, counters: ${toStr()}"
7171
}

0 commit comments

Comments
 (0)