diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.dot index 466f75cc5be..b3c10d671bb 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.dot @@ -40,51 +40,116 @@ digraph safeCalls_kt { 10 [label="Access variable R|/x|"]; 11 [label="Enter safe call"]; 12 [label="Function call: $subj$.R|/A.foo|()"]; - 13 [label="Exit safe call"]; - 14 [label="Enter safe call"]; - 15 [label="Function call: $subj$.R|/A.bar|()"]; + 13 [label="Enter safe call"]; + 14 [label="Const: String()"]; + 15 [label="Function call: $subj$.R|/A.bar|(...)"]; 16 [label="Exit safe call"]; - 17 [label="Exit block"]; + 17 [label="Exit safe call"]; + 18 [label="Exit block"]; } - 18 [label="Exit function test_1" style="filled" fillcolor=red]; + 19 [label="Exit function test_1" style="filled" fillcolor=red]; } 8 -> {9}; 9 -> {10}; - 10 -> {11 13}; + 10 -> {11 16}; 11 -> {12}; - 12 -> {13}; - 13 -> {14 16}; + 12 -> {16 13}; + 13 -> {14}; 14 -> {15}; - 15 -> {16}; + 15 -> {17}; 16 -> {17}; 17 -> {18}; + 18 -> {19}; subgraph cluster_6 { color=red - 19 [label="Enter function test_2" style="filled" fillcolor=red]; + 20 [label="Enter function test_2" style="filled" fillcolor=red]; subgraph cluster_7 { color=blue - 20 [label="Enter block"]; - 21 [label="Access variable R|/x|"]; - 22 [label="Enter safe call"]; - 23 [label="Access variable R|/B.foo|"]; - 24 [label="Exit safe call"]; + 21 [label="Enter block"]; + 22 [label="Access variable R|/x|"]; + 23 [label="Enter safe call"]; + 24 [label="Access variable R|/B.foo|"]; 25 [label="Enter safe call"]; 26 [label="Access variable R|/B.bar|"]; 27 [label="Exit safe call"]; - 28 [label="Exit block"]; + 28 [label="Exit safe call"]; + 29 [label="Exit block"]; } - 29 [label="Exit function test_2" style="filled" fillcolor=red]; + 30 [label="Exit function test_2" style="filled" fillcolor=red]; } - 19 -> {20}; 20 -> {21}; - 21 -> {22 24}; - 22 -> {23}; + 21 -> {22}; + 22 -> {23 27}; 23 -> {24}; - 24 -> {25 27}; + 24 -> {27 25}; 25 -> {26}; - 26 -> {27}; + 26 -> {28}; 27 -> {28}; 28 -> {29}; + 29 -> {30}; + + subgraph cluster_8 { + color=red + 31 [label="Enter function test_3" style="filled" fillcolor=red]; + subgraph cluster_9 { + color=blue + 32 [label="Enter block"]; + subgraph cluster_10 { + color=blue + 33 [label="Enter when"]; + subgraph cluster_11 { + color=blue + 34 [label="Enter when branch condition "]; + 35 [label="Access variable R|/x|"]; + 36 [label="Enter safe call"]; + 37 [label="Access variable R|/y|"]; + 38 [label="Type operator: (R|/y| as R|kotlin/String|)"]; + 39 [label="Function call: $subj$.R|/A.bar|(...)"]; + 40 [label="Exit safe call"]; + 41 [label="Const: Null(null)"]; + 42 [label="Equality operator !="]; + 43 [label="Exit when branch condition"]; + } + 44 [label="Synthetic else branch"]; + 45 [label="Enter when branch result"]; + subgraph cluster_12 { + color=blue + 46 [label="Enter block"]; + 47 [label="Access variable R|/y|"]; + 48 [label="Smart cast: R|/y|"]; + 49 [label="Access variable R|kotlin/String.length|"]; + 50 [label="Exit block"]; + } + 51 [label="Exit when branch result"]; + 52 [label="Exit when"]; + } + 53 [label="Exit block"]; + } + 54 [label="Exit function test_3" style="filled" fillcolor=red]; + } + 31 -> {32}; + 32 -> {33}; + 33 -> {34}; + 34 -> {35}; + 35 -> {36 40}; + 36 -> {37}; + 37 -> {38}; + 38 -> {39}; + 39 -> {40}; + 40 -> {41}; + 41 -> {42}; + 42 -> {43}; + 43 -> {45 44}; + 44 -> {52}; + 45 -> {46}; + 46 -> {47}; + 47 -> {48}; + 48 -> {49}; + 49 -> {50}; + 50 -> {51}; + 51 -> {52}; + 52 -> {53}; + 53 -> {54}; } diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.fir.txt b/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.fir.txt index 4d467b4aa05..f6c8ddc122e 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.fir.txt @@ -2,7 +2,7 @@ FILE: safeCalls.kt public abstract interface A : R|kotlin/Any| { public abstract fun foo(): R|A| - public abstract fun bar(): R|A| + public abstract fun bar(x: R|kotlin/String|): R|A| } public abstract interface B : R|kotlin/Any| { @@ -14,8 +14,16 @@ FILE: safeCalls.kt } public final fun test_1(x: R|A?|): R|kotlin/Unit| { - R|/x|?.{ $subj$.R|/A.foo|() }?.{ $subj$.R|/A.bar|() } + R|/x|?.{ $subj$.R|/A.foo|() }?.{ $subj$.R|/A.bar|(String()) } } public final fun test_2(x: R|B?|): R|kotlin/Unit| { R|/x|?.{ $subj$.R|/B.foo| }?.{ $subj$.R|/B.bar| } } + public final fun test_3(x: R|A?|, y: R|kotlin/String?|): R|kotlin/Unit| { + when () { + !=(R|/x|?.{ $subj$.R|/A.bar|((R|/y| as R|kotlin/String|)) }, Null(null)) -> { + R|/y|.R|kotlin/String.length| + } + } + + } diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.kt b/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.kt index e17b7a1c802..f1e8859e0bc 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.kt +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/safeCalls.kt @@ -1,7 +1,7 @@ // !DUMP_CFG interface A { fun foo(): A - fun bar(): A + fun bar(x: String): A } interface B { @@ -10,9 +10,15 @@ interface B { } fun test_1(x: A?) { - x?.foo()?.bar() + x?.foo()?.bar("") } fun test_2(x: B?) { x?.foo?.bar } + +fun test_3(x: A?, y: String?) { + if (x?.bar(y as String) != null) { + y.length + } +} diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/controlStructures/elvis.dot b/compiler/fir/analysis-tests/testData/resolve/smartcasts/controlStructures/elvis.dot index fb9870e7817..73dc7ddea88 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/controlStructures/elvis.dot +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/controlStructures/elvis.dot @@ -62,9 +62,7 @@ digraph elvis_kt { 5 -> {6}; 6 -> {7}; 7 -> {8}; - 8 -> {9}; - 8 -> {11} [color=red]; - 8 -> {13} [color=green]; + 8 -> {9 13}; 9 -> {10}; 10 -> {11}; 11 -> {12}; diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/controlStructures/returns.dot b/compiler/fir/analysis-tests/testData/resolve/smartcasts/controlStructures/returns.dot index 8d5d2067783..4b80c880e60 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/controlStructures/returns.dot +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/controlStructures/returns.dot @@ -490,9 +490,7 @@ digraph returns_kt { 161 -> {162}; 162 -> {163}; 163 -> {164}; - 164 -> {165}; - 164 -> {167} [color=red]; - 164 -> {169} [color=green]; + 164 -> {165 169}; 165 -> {166}; 166 -> {167}; 167 -> {168}; diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.dot b/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.dot index 120f811f84b..f7d377a7193 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.dot +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/nullability.dot @@ -484,12 +484,12 @@ digraph nullability_kt { 165 [label="Access variable R|/q|"]; 166 [label="Enter safe call"]; 167 [label="Access variable R|/Q.data|"]; - 168 [label="Exit safe call"]; - 169 [label="Enter safe call"]; - 170 [label="Access variable R|/MyData.s|"]; - 171 [label="Exit safe call"]; - 172 [label="Enter safe call"]; - 173 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 168 [label="Enter safe call"]; + 169 [label="Access variable R|/MyData.s|"]; + 170 [label="Enter safe call"]; + 171 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 172 [label="Exit safe call"]; + 173 [label="Exit safe call"]; 174 [label="Exit safe call"]; 175 [label="Const: Null(null)"]; 176 [label="Equality operator !="]; @@ -528,13 +528,13 @@ digraph nullability_kt { 162 -> {163}; 163 -> {164}; 164 -> {165}; - 165 -> {166 168}; + 165 -> {166 172}; 166 -> {167}; - 167 -> {168}; - 168 -> {169 171}; - 169 -> {170}; + 167 -> {172 168}; + 168 -> {169}; + 169 -> {173 170}; 170 -> {171}; - 171 -> {172 174}; + 171 -> {174}; 172 -> {173}; 173 -> {174}; 174 -> {175}; @@ -573,18 +573,18 @@ digraph nullability_kt { 203 [label="Access variable R|/q|"]; 204 [label="Enter safe call"]; 205 [label="Access variable R|/Q.data|"]; - 206 [label="Exit safe call"]; - 207 [label="Enter safe call"]; - 208 [label="Access variable R|/MyData.s|"]; - 209 [label="Exit safe call"]; - 210 [label="Enter safe call"]; - 211 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; - 212 [label="Exit safe call"]; - 213 [label="Exit lhs of ?:"]; - 214 [label="Enter rhs of ?:"]; - 215 [label="Jump: ^test_6 Unit"]; - 216 [label="Stub" style="filled" fillcolor=gray]; - 217 [label="Lhs of ?: is not null"]; + 206 [label="Enter safe call"]; + 207 [label="Access variable R|/MyData.s|"]; + 208 [label="Enter safe call"]; + 209 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 210 [label="Exit safe call"]; + 211 [label="Exit lhs of ?:"]; + 212 [label="Lhs of ?: is not null"]; + 213 [label="Exit safe call"]; + 214 [label="Exit safe call"]; + 215 [label="Enter rhs of ?:"]; + 216 [label="Jump: ^test_6 Unit"]; + 217 [label="Stub" style="filled" fillcolor=gray]; 218 [label="Exit ?:"]; 219 [label="Access variable R|/q|"]; 220 [label="Smart cast: R|/q|"]; @@ -607,24 +607,22 @@ digraph nullability_kt { } 201 -> {202}; 202 -> {203}; - 203 -> {204 206}; + 203 -> {204 213}; 204 -> {205}; - 205 -> {206}; - 206 -> {207 209}; - 207 -> {208}; + 205 -> {213 206}; + 206 -> {207}; + 207 -> {214 208}; 208 -> {209}; 209 -> {210}; - 209 -> {212} [color=red]; - 209 -> {214} [color=green]; 210 -> {211}; - 211 -> {212}; - 212 -> {213}; - 213 -> {217 214}; + 211 -> {212 215}; + 212 -> {218}; + 213 -> {214}; 214 -> {215}; - 215 -> {235}; - 215 -> {216} [style=dotted]; - 216 -> {218} [style=dotted]; - 217 -> {218}; + 215 -> {216}; + 216 -> {235}; + 216 -> {217} [style=dotted]; + 217 -> {218} [style=dotted]; 218 -> {219}; 219 -> {220}; 220 -> {221}; @@ -658,12 +656,12 @@ digraph nullability_kt { 240 [label="Access variable R|/q|"]; 241 [label="Enter safe call"]; 242 [label="Function call: $subj$.R|/Q.fdata|()"]; - 243 [label="Exit safe call"]; - 244 [label="Enter safe call"]; - 245 [label="Function call: $subj$.R|/MyData.fs|()"]; - 246 [label="Exit safe call"]; - 247 [label="Enter safe call"]; - 248 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 243 [label="Enter safe call"]; + 244 [label="Function call: $subj$.R|/MyData.fs|()"]; + 245 [label="Enter safe call"]; + 246 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 247 [label="Exit safe call"]; + 248 [label="Exit safe call"]; 249 [label="Exit safe call"]; 250 [label="Const: Null(null)"]; 251 [label="Equality operator !="]; @@ -699,13 +697,13 @@ digraph nullability_kt { 237 -> {238}; 238 -> {239}; 239 -> {240}; - 240 -> {241 243}; + 240 -> {241 247}; 241 -> {242}; - 242 -> {243}; - 243 -> {244 246}; - 244 -> {245}; + 242 -> {247 243}; + 243 -> {244}; + 244 -> {248 245}; 245 -> {246}; - 246 -> {247 249}; + 246 -> {249}; 247 -> {248}; 248 -> {249}; 249 -> {250}; @@ -1164,12 +1162,12 @@ digraph nullability_kt { 436 [label="Access variable R|/q|"]; 437 [label="Enter safe call"]; 438 [label="Access variable R|/QImpl.data|"]; - 439 [label="Exit safe call"]; - 440 [label="Enter safe call"]; - 441 [label="Access variable R|/MyData.s|"]; - 442 [label="Exit safe call"]; - 443 [label="Enter safe call"]; - 444 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 439 [label="Enter safe call"]; + 440 [label="Access variable R|/MyData.s|"]; + 441 [label="Enter safe call"]; + 442 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 443 [label="Exit safe call"]; + 444 [label="Exit safe call"]; 445 [label="Exit safe call"]; 446 [label="Const: Null(null)"]; 447 [label="Equality operator !="]; @@ -1248,13 +1246,13 @@ digraph nullability_kt { 433 -> {434}; 434 -> {435}; 435 -> {436}; - 436 -> {437 439}; + 436 -> {437 443}; 437 -> {438}; - 438 -> {439}; - 439 -> {440 442}; - 440 -> {441}; + 438 -> {443 439}; + 439 -> {440}; + 440 -> {444 441}; 441 -> {442}; - 442 -> {443 445}; + 442 -> {445}; 443 -> {444}; 444 -> {445}; 445 -> {446}; @@ -1330,12 +1328,12 @@ digraph nullability_kt { 507 [label="Access variable R|/q|"]; 508 [label="Enter safe call"]; 509 [label="Access variable R|/QImplWithCustomGetter.data|"]; - 510 [label="Exit safe call"]; - 511 [label="Enter safe call"]; - 512 [label="Access variable R|/MyData.s|"]; - 513 [label="Exit safe call"]; - 514 [label="Enter safe call"]; - 515 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 510 [label="Enter safe call"]; + 511 [label="Access variable R|/MyData.s|"]; + 512 [label="Enter safe call"]; + 513 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 514 [label="Exit safe call"]; + 515 [label="Exit safe call"]; 516 [label="Exit safe call"]; 517 [label="Const: Null(null)"]; 518 [label="Equality operator !="]; @@ -1374,13 +1372,13 @@ digraph nullability_kt { 504 -> {505}; 505 -> {506}; 506 -> {507}; - 507 -> {508 510}; + 507 -> {508 514}; 508 -> {509}; - 509 -> {510}; - 510 -> {511 513}; - 511 -> {512}; + 509 -> {514 510}; + 510 -> {511}; + 511 -> {515 512}; 512 -> {513}; - 513 -> {514 516}; + 513 -> {516}; 514 -> {515}; 515 -> {516}; 516 -> {517}; @@ -1425,12 +1423,12 @@ digraph nullability_kt { 547 [label="Access variable R|/q|"]; 548 [label="Enter safe call"]; 549 [label="Access variable R|/QImplMutable.data|"]; - 550 [label="Exit safe call"]; - 551 [label="Enter safe call"]; - 552 [label="Access variable R|/MyData.s|"]; - 553 [label="Exit safe call"]; - 554 [label="Enter safe call"]; - 555 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 550 [label="Enter safe call"]; + 551 [label="Access variable R|/MyData.s|"]; + 552 [label="Enter safe call"]; + 553 [label="Function call: $subj$.R|kotlin/Int.inc|()"]; + 554 [label="Exit safe call"]; + 555 [label="Exit safe call"]; 556 [label="Exit safe call"]; 557 [label="Const: Null(null)"]; 558 [label="Equality operator !="]; @@ -1469,13 +1467,13 @@ digraph nullability_kt { 544 -> {545}; 545 -> {546}; 546 -> {547}; - 547 -> {548 550}; + 547 -> {548 554}; 548 -> {549}; - 549 -> {550}; - 550 -> {551 553}; - 551 -> {552}; + 549 -> {554 550}; + 550 -> {551}; + 551 -> {555 552}; 552 -> {553}; - 553 -> {554 556}; + 553 -> {556}; 554 -> {555}; 555 -> {556}; 556 -> {557}; diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/safeCalls/safeCalls.dot b/compiler/fir/analysis-tests/testData/resolve/smartcasts/safeCalls/safeCalls.dot index f6d65704225..aa684ef8c9e 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/safeCalls/safeCalls.dot +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/safeCalls/safeCalls.dot @@ -142,15 +142,13 @@ digraph safeCalls_kt { 50 [label="Access variable R|/x|"]; 51 [label="Smart cast: R|/x|"]; 52 [label="Function call: $subj$.R|/A.bar|(...)"]; - 53 [label="Exit safe call"]; - 54 [label="Enter safe call"]; - 55 [label="Access variable R|/x|"]; - 56 [label="Smart cast: R|/x|"]; - 57 [label="Function call: R|/x|.R|/A.bool|()"]; - 58 [label="Function call: $subj$.R|/foo|(...)"]; - 59 [label="Exit safe call"]; - 60 [label="Enter safe call"]; - 61 [label="Postponed enter to lambda"]; + 53 [label="Enter safe call"]; + 54 [label="Access variable R|/x|"]; + 55 [label="Smart cast: R|/x|"]; + 56 [label="Function call: R|/x|.R|/A.bool|()"]; + 57 [label="Function call: $subj$.R|/foo|(...)"]; + 58 [label="Enter safe call"]; + 59 [label="Postponed enter to lambda"]; subgraph cluster_14 { color=blue 69 [label="Enter function anonymousFunction" style="filled" fillcolor=red]; @@ -164,8 +162,10 @@ digraph safeCalls_kt { } 75 [label="Exit function anonymousFunction" style="filled" fillcolor=red]; } - 62 [label="Postponed exit from lambda"]; - 63 [label="Function call: $subj$.R|/let|(...)"]; + 60 [label="Postponed exit from lambda"]; + 61 [label="Function call: $subj$.R|/let|(...)"]; + 62 [label="Exit safe call"]; + 63 [label="Exit safe call"]; 64 [label="Exit safe call"]; 65 [label="Access variable R|/x|"]; 66 [label="Function call: R|/x|.#()"]; @@ -176,21 +176,21 @@ digraph safeCalls_kt { 45 -> {46}; 46 -> {47}; 47 -> {48}; - 48 -> {49 53}; + 48 -> {49 62}; 49 -> {50}; 50 -> {51}; 51 -> {52}; - 52 -> {53}; - 53 -> {54 59}; + 52 -> {62 53}; + 53 -> {54}; 54 -> {55}; 55 -> {56}; 56 -> {57}; - 57 -> {58}; + 57 -> {63 58}; 58 -> {59}; - 59 -> {60 64}; + 59 -> {60 69}; + 59 -> {69} [style=dashed]; 60 -> {61}; - 61 -> {62 69}; - 61 -> {69} [style=dashed]; + 61 -> {64}; 62 -> {63}; 63 -> {64}; 64 -> {65}; @@ -213,9 +213,9 @@ digraph safeCalls_kt { 78 [label="Access variable R|/x|"]; 79 [label="Enter safe call"]; 80 [label="Function call: $subj$.R|/A.id|()"]; - 81 [label="Exit safe call"]; - 82 [label="Enter safe call"]; - 83 [label="Function call: $subj$.R|/A.bool|()"]; + 81 [label="Enter safe call"]; + 82 [label="Function call: $subj$.R|/A.bool|()"]; + 83 [label="Exit safe call"]; 84 [label="Exit safe call"]; 85 [label="Access variable R|/x|"]; 86 [label="Function call: R|/x|.#()"]; @@ -225,11 +225,11 @@ digraph safeCalls_kt { } 76 -> {77}; 77 -> {78}; - 78 -> {79 81}; + 78 -> {79 83}; 79 -> {80}; - 80 -> {81}; - 81 -> {82 84}; - 82 -> {83}; + 80 -> {83 81}; + 81 -> {82}; + 82 -> {84}; 83 -> {84}; 84 -> {85}; 85 -> {86}; @@ -275,12 +275,12 @@ digraph safeCalls_kt { 99 [label="Postponed exit from lambda" style="filled" fillcolor=gray]; 100 [label="Function call: $subj$.R|kotlin/let|(...)" style="filled" fillcolor=gray]; 101 [label="Stub" style="filled" fillcolor=gray]; - 102 [label="Exit safe call"]; - 103 [label="Enter safe call"]; - 104 [label="Access variable R|/x|"]; - 105 [label="Smart cast: R|/x|"]; - 106 [label="Function call: R|/x|.R|/A.bool|()"]; - 107 [label="Function call: $subj$.R|/boo|(...)"]; + 102 [label="Enter safe call" style="filled" fillcolor=gray]; + 103 [label="Access variable R|/x|" style="filled" fillcolor=gray]; + 104 [label="Smart cast: R|/x|" style="filled" fillcolor=gray]; + 105 [label="Function call: R|/x|.R|/A.bool|()" style="filled" fillcolor=gray]; + 106 [label="Function call: $subj$.R|/boo|(...)" style="filled" fillcolor=gray]; + 107 [label="Exit safe call"]; 108 [label="Exit safe call"]; 109 [label="Access variable R|/x|"]; 110 [label="Function call: R|/x|.#()"]; @@ -290,7 +290,7 @@ digraph safeCalls_kt { } 93 -> {94}; 94 -> {95}; - 95 -> {96 102}; + 95 -> {96 107}; 96 -> {97}; 97 -> {113}; 97 -> {99} [color=red]; @@ -299,12 +299,12 @@ digraph safeCalls_kt { 99 -> {100} [style=dotted]; 100 -> {101} [style=dotted]; 100 -> {112} [style=dotted] [label=onUncaughtException]; - 101 -> {102} [style=dotted]; - 102 -> {103 108}; - 103 -> {104}; - 104 -> {105}; - 105 -> {106}; - 106 -> {107}; + 101 -> {107 102} [style=dotted]; + 102 -> {103} [style=dotted]; + 103 -> {104} [style=dotted]; + 104 -> {105} [style=dotted]; + 105 -> {106} [style=dotted]; + 106 -> {108} [style=dotted]; 107 -> {108}; 108 -> {109}; 109 -> {110}; diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/smartcastInByClause.dot b/compiler/fir/analysis-tests/testData/resolve/smartcasts/smartcastInByClause.dot index 4176fbc4f0e..611fc675245 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/smartcastInByClause.dot +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/smartcastInByClause.dot @@ -139,9 +139,7 @@ digraph smartcastInByClause_kt { } 24 -> {25}; 25 -> {26}; - 26 -> {27}; - 26 -> {29} [color=red]; - 26 -> {31} [color=green]; + 26 -> {27 31}; 27 -> {28}; 28 -> {29}; 29 -> {30}; diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt index cb8f0282897..e3e47842077 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt @@ -812,14 +812,7 @@ abstract class FirDataFlowAnalyzer( } fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) { - val node = graphBuilder.enterSafeCall(safeCall) - val flow = node.mergeIncomingFlow() - // When calling `c` in `a?.b?.c`, all type information obtained after calling `b` is valid as we know `a` - // is non-null. In theory, this should be unnecessary if the TODOs below are implemented. - val flowFromPreviousSafeCall = (node.firstPreviousNode as? ExitSafeCallNode)?.lastNodeInNotNullCase?.flow - if (flowFromPreviousSafeCall != null) { - flow.copyAllInformationFrom(flowFromPreviousSafeCall) - } + val flow = graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow() val receiverVariable = variableStorage.getOrCreateIfReal(flow, safeCall.receiver) ?: return flow.commitOperationStatement(receiverVariable notEq null) } @@ -828,17 +821,17 @@ abstract class FirDataFlowAnalyzer( val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitSafeCall() val flow = node.mergeIncomingFlow() mergePostponedLambdaExitsNode?.mergeIncomingFlow() - - val receiverLastNode = node.firstPreviousNode - val receiverVariable = variableStorage.getOrCreateIfRealAndUnchanged(receiverLastNode.flow, flow, safeCall.receiver) ?: return + // If there is only 1 previous node, then this is LHS of `a?.b ?: c`; then the null-case + // edge from `a` goes directly to `c` and this node's flow already assumes `b` executed. + if (node.previousNodes.size < 2) return + // Otherwise if the result is non-null, then `b` executed, which implies `a` is not null + // and every statement from `b` holds. val expressionVariable = variableStorage.getOrCreate(flow, safeCall) // TODO? if the callee has non-null return type, then safe-call == null => receiver == null // if (x?.toString() == null) { /* x == null */ } - // TODO? all new statements in previous node's flow are valid here if receiver != null - // if (x?.whatever(y as String) != null) { /* y is String */ } // TODO? all new implications in previous node's flow are valid here if receiver != null // (that requires a second level of implications: receiver != null => condition => effect). - flow.addImplication((expressionVariable notEq null) implies (receiverVariable notEq null)) + flow.addAllConditionally(expressionVariable notEq null, node.lastPreviousNode.flow) } fun exitResolvedQualifierNode(resolvedQualifier: FirResolvedQualifier) { @@ -1193,22 +1186,17 @@ abstract class FirDataFlowAnalyzer( } fun exitElvis(elvisExpression: FirElvisExpression, isLhsNotNull: Boolean) { - val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitElvis() + val (node, mergePostponedLambdaExitsNode) = graphBuilder.exitElvis(isLhsNotNull) val flow = node.mergeIncomingFlow() mergePostponedLambdaExitsNode?.mergeIncomingFlow() - - val rhs = elvisExpression.rhs - // No need to check for reassignments - if we can make any statements about LHS, then RHS has not executed. - val lhsVariable = variableStorage.getOrCreateIfReal(flow, elvisExpression.lhs) ?: return - if (isLhsNotNull) { - flow.commitOperationStatement(lhsVariable notEq null) - } else if (rhs is FirConstExpression<*> && rhs.kind == ConstantValueKind.Boolean) { - // (a ?: x) != x -> a != null. The logic system can only handle cases where x is Boolean. - // Or null...but nobody would write that, right? - val elvisVariable = variableStorage.createSynthetic(elvisExpression) - val value = rhs.value as Boolean - flow.addImplication((elvisVariable eq !value) implies (lhsVariable notEq null)) - } + // If LHS is never null, then the edge from RHS is dead and this node's flow already contains + // all statements from LHS unconditionally. + if (isLhsNotNull) return + // For any predicate P(x), if P(v) != P(u ?: v) then u != null. In general this requires two levels of + // implications, but for constant v the logic system can handle some basic cases of P(x). + val rhs = (elvisExpression.rhs as? FirConstExpression<*>)?.value as? Boolean ?: return + val elvisVariable = variableStorage.createSynthetic(elvisExpression) + flow.addAllConditionally(elvisVariable eq !rhs, node.firstPreviousNode.flow) } // Callable reference @@ -1291,13 +1279,6 @@ abstract class FirDataFlowAnalyzer( } } - private fun FLOW.copyAllInformationFrom(other: FLOW) { - if (this === currentReceiverState) { - updateAllReceivers(this, other) - } - logicSystem.copyAllInformation(other, this) - } - private fun FLOW.addImplication(statement: Implication) { logicSystem.addImplication(this, statement) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt index 3d59bf2edec..7fb8657e07a 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt @@ -1348,31 +1348,29 @@ class ControlFlowGraphBuilder { // ----------------------------------- Safe calls ----------------------------------- fun enterSafeCall(safeCall: FirSafeCallExpression): EnterSafeCallNode { - /* - * We create - * lastNode -> enterNode - * lastNode -> exitNode - * instead of - * lastNode -> enterNode -> exitNode - * because we need to fork flow before `enterNode`, so `exitNode` - * will have unchanged flow from `lastNode` - * which corresponds to a path with nullable receiver. - */ - val lastNode = lastNodes.pop() val enterNode = createEnterSafeCallNode(safeCall) - lastNodes.push(enterNode) val exitNode = createExitSafeCallNode(safeCall) exitSafeCallNodes.push(exitNode) - addEdge(lastNode, enterNode) - if (elvisRhsEnterNodes.topOrNull()?.fir?.lhs === safeCall) { - //if this is safe call in lhs of elvis, we make two edges - // 1. Df-only edge to exit node, to get not null implications there - // 2. Cf-only edge to elvis rhs - addEdge(lastNode, exitNode, preferredKind = EdgeKind.DfgForward) - addEdge(lastNode, elvisRhsEnterNodes.top(), preferredKind = EdgeKind.CfgForward) + val lastNode = lastNodes.pop() + if (lastNode is ExitSafeCallNode) { + // Only the non-null branch of the previous safe call can enter this one. + // a ----> a.b -----> a?.b.c ------> a?.b?.c + // \-----\-> a?.b (null) ---^ + addEdge(lastNode.lastPreviousNode, enterNode) + } else { + addEdge(lastNode, enterNode) + } + val nextElvisRHS = elvisRhsEnterNodes.topOrNull() + if (nextElvisRHS?.fir?.lhs === safeCall) { + // Can skip the null edge directly to elvis RHS. + // /-----------v + // a ----> a.b ----> a?.b ----> c ----> a?.b ?: c + // \------------------------^ + addEdge(lastNode, nextElvisRHS) } else { addEdge(lastNode, exitNode) } + lastNodes.push(enterNode) splitDataFlowForPostponedLambdas() return enterNode } @@ -1403,34 +1401,28 @@ class ControlFlowGraphBuilder { exitElvisExpressionNodes.push(it) } - val typedFir = lastNodes.topOrNull()?.fir as? FirExpression - val type = typedFir?.typeRef?.coneTypeSafe() - val lhsExitNode = createElvisLhsExitNode(elvisExpression).also { popAndAddEdge(it) } val lhsIsNotNullNode = createElvisLhsIsNotNullNode(elvisExpression).also { - val preferredKind = if (type?.isNullableNothing == true) { - EdgeKind.DeadForward - } else { - EdgeKind.Forward - } - - addEdge(lhsExitNode, it, preferredKind = preferredKind) + val lhsIsNull = elvisExpression.lhs.typeRef.coneTypeSafe()?.isNullableNothing == true + addEdge(lhsExitNode, it, isDead = lhsIsNull) addEdge(it, exitNode, propagateDeadness = false) } val rhsEnterNode = elvisRhsEnterNodes.pop().also { - addEdge(lhsExitNode, it) + // Can only have a previous node if the LHS is a safe call, in which case it's the safe + // call's receiver - then RHS is not dead unless said receiver is dead (or never null). + addEdge(lhsExitNode, it, propagateDeadness = it.previousNodes.isEmpty()) } lastNodes.push(rhsEnterNode) return Triple(lhsExitNode, lhsIsNotNullNode, rhsEnterNode) } - fun exitElvis(): Pair { + fun exitElvis(lhsIsNotNull: Boolean): Pair { val exitNode = exitElvisExpressionNodes.pop() - addNewSimpleNode(exitNode) + addNewSimpleNode(exitNode, isDead = lhsIsNotNull) exitNode.updateDeadStatus() return exitNode to joinDataFlowFromPostponedLambdasWith(exitNode) } diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt index 5449f414b60..59188dcbbdf 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt @@ -21,7 +21,6 @@ abstract class LogicSystem(protected val context: ConeInferenceCont abstract fun addImplication(flow: FLOW, implication: Implication) abstract fun addLocalVariableAlias(flow: FLOW, alias: RealVariable, underlyingVariable: RealVariable) abstract fun recordNewAssignment(flow: FLOW, variable: RealVariable, index: Int) - abstract fun copyAllInformation(from: FLOW, to: FLOW) abstract fun isSameValueIn(a: FLOW, b: FLOW, variable: RealVariable): Boolean abstract fun translateVariableFromConditionInStatements( diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt index 5619e8a865f..77fc176cbe4 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt @@ -79,14 +79,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste override fun forkFlow(flow: PersistentFlow): PersistentFlow = PersistentFlow(flow) - override fun copyAllInformation(from: PersistentFlow, to: PersistentFlow) { - to.approvedTypeStatements = from.approvedTypeStatements - to.logicStatements = from.logicStatements - to.directAliasMap = from.directAliasMap - to.backwardsAliasMap = from.backwardsAliasMap - to.assignmentIndex = from.assignmentIndex - } - override fun joinFlow(flows: Collection): PersistentFlow = foldFlow(flows, allExecute = false) diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt index fe372bb4312..799adef7127 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt @@ -781,8 +781,6 @@ class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallEx } } class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode(owner, level, id) { - val lastNodeInNotNullCase: CFGNode<*>? get() = previousNodes.getOrNull(1) - override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitExitSafeCallNode(this, data) } diff --git a/compiler/testData/codegen/box/smartCasts/kt44814.dot b/compiler/testData/codegen/box/smartCasts/kt44814.dot index e462235543b..8608957cb23 100644 --- a/compiler/testData/codegen/box/smartCasts/kt44814.dot +++ b/compiler/testData/codegen/box/smartCasts/kt44814.dot @@ -634,9 +634,8 @@ digraph kt44814_kt { 210 [label="Type operator: (this@R|/FirModifierList.Companion.getModifierList|.R|/FirPsiSourceElement.psi| as? R|KtModifierListOwner|) [6]"]; 211 [label="Enter safe call [6]"]; 212 [label="Access variable R|/KtModifierListOwner.modifierList| [6]"]; - 213 [label="Exit safe call [6]"]; - 214 [label="Enter safe call [6]"]; - 215 [label="Postponed enter to lambda [7]"]; + 213 [label="Enter safe call [6]"]; + 214 [label="Postponed enter to lambda [7]"]; subgraph cluster_65 { color=blue 233 [label="Enter function anonymousFunction [8]" style="filled" fillcolor=red]; @@ -649,8 +648,9 @@ digraph kt44814_kt { } 238 [label="Exit function anonymousFunction [8]" style="filled" fillcolor=red]; } - 216 [label="Postponed exit from lambda [7]"]; - 217 [label="Function call: $subj$.R|kotlin/let|(...) [6]"]; + 215 [label="Postponed exit from lambda [7]"]; + 216 [label="Function call: $subj$.R|kotlin/let|(...) [6]"]; + 217 [label="Exit safe call [6]"]; 218 [label="Exit safe call [6]"]; 219 [label="Exit block [6]"]; } @@ -734,15 +734,15 @@ digraph kt44814_kt { 207 -> {208}; 208 -> {209}; 209 -> {210}; - 210 -> {211 213}; + 210 -> {211 217}; 211 -> {212}; - 212 -> {213}; - 213 -> {214 218}; - 214 -> {215}; - 215 -> {233}; - 215 -> {216} [color=red]; - 215 -> {233} [style=dashed]; - 216 -> {217}; + 212 -> {217 213}; + 213 -> {214}; + 214 -> {233}; + 214 -> {215} [color=red]; + 214 -> {233} [style=dashed]; + 215 -> {216}; + 216 -> {218}; 217 -> {218}; 218 -> {221 219}; 219 -> {220}; @@ -764,7 +764,7 @@ digraph kt44814_kt { 236 -> {237}; 237 -> {238}; 238 -> {221} [color=red]; - 238 -> {216} [color=green]; + 238 -> {215} [color=green]; 239 -> {249 240}; 240 -> {241}; 241 -> {242 244};