From 660bcce90a03c09746d1a260e614a4b7ffb050b0 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Wed, 13 Nov 2019 14:23:01 +0300 Subject: [PATCH] [FIR] Add smartcasts for reassigned vars --- .../fir/resolve/dfa/FirDataFlowAnalyzer.kt | 20 ++- .../resolve/smartcasts/delayedAssignment.dot | 107 ++++++++++++++ .../resolve/smartcasts/delayedAssignment.kt | 14 ++ .../resolve/smartcasts/delayedAssignment.txt | 24 ++++ .../smartcasts/smartcastAfterReassignment.dot | 131 ++++++++++++++++++ .../smartcasts/smartcastAfterReassignment.kt | 21 +++ .../smartcasts/smartcastAfterReassignment.txt | 23 +++ .../testData/resolve/stdlib/hashSet.txt | 2 +- .../FirDiagnosticsWithCfgTestGenerated.java | 10 ++ .../localDelegatedProperties.fir.txt | 16 ++- 10 files changed, 356 insertions(+), 12 deletions(-) create mode 100644 compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.dot create mode 100644 compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.kt create mode 100644 compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.txt create mode 100644 compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.dot create mode 100644 compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.kt create mode 100644 compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.txt 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 2acb0b17d99..173dba74ea4 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 @@ -584,10 +584,22 @@ class FirDataFlowAnalyzer(private val components: FirAbstractBodyResolveTransfor } fun exitVariableAssignment(assignment: FirVariableAssignment) { - graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow() - val lhsVariable = variableStorage[assignment.resolvedSymbol ?: return] ?: return - val rhsVariable = variableStorage[assignment.rValue.resolvedSymbol ?: return]?.takeIf { !it.isSynthetic() } ?: return - variableStorage.rebindAliasVariable(lhsVariable, rhsVariable) + val node = graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow() + val lhsSymbol: AbstractFirBasedSymbol<*> = (assignment.lValue as? FirResolvedNamedReference)?.resolvedSymbol ?: return + val lhsVariable = getOrCreateRealVariable(lhsSymbol.fir) ?: return + val rhsSymbol = assignment.rValue.resolvedSymbol + val rhsVariable = rhsSymbol?.let { variableStorage[it]?.takeIf { !it.isSynthetic() } } + if (rhsVariable == null) { + val type = assignment.rValue.typeRef.coneTypeSafe() ?: return + logicSystem.addApprovedInfo( + node.flow, + lhsVariable, + FirDataFlowInfo(setOf(type), emptySet()) + ) + return + } else { + variableStorage.rebindAliasVariable(lhsVariable, rhsVariable) + } } fun exitThrowExceptionNode(throwExpression: FirThrowExpression) { diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.dot b/compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.dot new file mode 100644 index 00000000000..fbb26a71c22 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.dot @@ -0,0 +1,107 @@ +digraph delayedAssignment_kt { + graph [splines=ortho nodesep=3] + node [shape=box penwidth=2] + edge [penwidth=2] + + subgraph cluster_0 { + color=red + 0 [label="Enter function " style="filled" fillcolor=red]; + 1 [label="Exit function " style="filled" fillcolor=red]; + } + + 0 -> {1}; + + subgraph cluster_1 { + color=red + 2 [label="Enter function foo" style="filled" fillcolor=red]; + subgraph cluster_2 { + color=blue + 3 [label="Enter block"]; + 4 [label="Exit block"]; + } + 5 [label="Exit function foo" style="filled" fillcolor=red]; + } + + 2 -> {3}; + 3 -> {4}; + 4 -> {5}; + + subgraph cluster_3 { + color=red + 6 [label="Enter function test" style="filled" fillcolor=red]; + subgraph cluster_4 { + color=blue + 7 [label="Enter block"]; + 8 [label="Variable declaration: lval a: R|A?|"]; + subgraph cluster_5 { + color=blue + 9 [label="Enter when"]; + subgraph cluster_6 { + color=blue + 10 [label="Enter when branch condition "]; + 11 [label="Access variable R|/b|"]; + 12 [label="Exit when branch condition"]; + } + subgraph cluster_7 { + color=blue + 13 [label="Enter when branch condition else"]; + 14 [label="Exit when branch condition"]; + } + 15 [label="Enter when branch result"]; + subgraph cluster_8 { + color=blue + 16 [label="Enter block"]; + 17 [label="Const: Null(null)"]; + 18 [label="Assignmenet: R|/a|"]; + 19 [label="Exit block"]; + } + 20 [label="Exit when branch result"]; + 21 [label="Enter when branch result"]; + subgraph cluster_9 { + color=blue + 22 [label="Enter block"]; + 23 [label="Function call: R|/A.A|()"]; + 24 [label="Assignmenet: R|/a|"]; + 25 [label="Access variable R|/a|"]; + 26 [label="Function call: R|/a|.R|/A.foo|()"]; + 27 [label="Exit block"]; + } + 28 [label="Exit when branch result"]; + 29 [label="Exit when"]; + } + 30 [label="Access variable R|/a|"]; + 31 [label="Function call: R|/a|.#()"]; + 32 [label="Exit block"]; + } + 33 [label="Exit function test" style="filled" fillcolor=red]; + } + + 6 -> {7}; + 7 -> {8}; + 8 -> {9}; + 9 -> {10}; + 10 -> {11}; + 11 -> {12}; + 12 -> {21 13}; + 13 -> {14}; + 14 -> {15}; + 15 -> {16}; + 16 -> {17}; + 17 -> {18}; + 18 -> {19}; + 19 -> {20}; + 20 -> {29}; + 21 -> {22}; + 22 -> {23}; + 23 -> {24}; + 24 -> {25}; + 25 -> {26}; + 26 -> {27}; + 27 -> {28}; + 28 -> {29}; + 29 -> {30}; + 30 -> {31}; + 31 -> {32}; + 32 -> {33}; + +} diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.kt b/compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.kt new file mode 100644 index 00000000000..9487454844b --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.kt @@ -0,0 +1,14 @@ +class A { + fun foo() {} +} + +fun test(b: Boolean) { + val a: A? + if (b) { + a = A() + a.foo() + } else { + a = null + } + a.foo() +} \ No newline at end of file diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.txt b/compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.txt new file mode 100644 index 00000000000..fe6a3e460b9 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.txt @@ -0,0 +1,24 @@ +FILE: delayedAssignment.kt + public final class A : R|kotlin/Any| { + public constructor(): R|A| { + super() + } + + public final fun foo(): R|kotlin/Unit| { + } + + } + public final fun test(b: R|kotlin/Boolean|): R|kotlin/Unit| { + lval a: R|A?| + when () { + R|/b| -> { + R|/a| = R|/A.A|() + R|/a|.R|/A.foo|() + } + else -> { + R|/a| = Null(null) + } + } + + R|/a|.#() + } diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.dot b/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.dot new file mode 100644 index 00000000000..9f1ae3d7f02 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.dot @@ -0,0 +1,131 @@ +digraph smartcastAfterReassignment_kt { + graph [splines=ortho nodesep=3] + node [shape=box penwidth=2] + edge [penwidth=2] + + subgraph cluster_0 { + color=red + 0 [label="Enter function test_1" style="filled" fillcolor=red]; + subgraph cluster_1 { + color=blue + 1 [label="Enter block"]; + 2 [label="Const: Int(1)"]; + 3 [label="Variable declaration: lvar x: R|kotlin/Any|"]; + 4 [label="Const: String()"]; + 5 [label="Assignmenet: R|/x|"]; + 6 [label="Access variable R|/x|"]; + 7 [label="Access variable R|kotlin/String.length|"]; + 8 [label="Exit block"]; + } + 9 [label="Exit function test_1" style="filled" fillcolor=red]; + } + + 0 -> {1}; + 1 -> {2}; + 2 -> {3}; + 3 -> {4}; + 4 -> {5}; + 5 -> {6}; + 6 -> {7}; + 7 -> {8}; + 8 -> {9}; + + subgraph cluster_2 { + color=red + 10 [label="Enter function test_2" style="filled" fillcolor=red]; + subgraph cluster_3 { + color=blue + 11 [label="Enter block"]; + 12 [label="Const: Null(null)"]; + 13 [label="Variable declaration: lvar x: R|kotlin/String?|"]; + subgraph cluster_4 { + color=blue + 14 [label="Enter when"]; + subgraph cluster_5 { + color=blue + 15 [label="Enter when branch condition "]; + 16 [label="Access variable R|/x|"]; + 17 [label="Const: Null(null)"]; + 18 [label="Operator =="]; + 19 [label="Exit when branch condition"]; + } + 20 [label="Synthetic else branch"]; + 21 [label="Enter when branch result"]; + subgraph cluster_6 { + color=blue + 22 [label="Enter block"]; + 23 [label="Const: String()"]; + 24 [label="Assignmenet: R|/x|"]; + 25 [label="Exit block"]; + } + 26 [label="Exit when branch result"]; + 27 [label="Exit when"]; + } + 28 [label="Access variable R|/x|"]; + 29 [label="Access variable R|kotlin/String.length|"]; + 30 [label="Exit block"]; + } + 31 [label="Exit function test_2" style="filled" fillcolor=red]; + } + + 10 -> {11}; + 11 -> {12}; + 12 -> {13}; + 13 -> {14}; + 14 -> {15}; + 15 -> {16}; + 16 -> {17}; + 17 -> {18}; + 18 -> {19}; + 19 -> {21 20}; + 20 -> {27}; + 21 -> {22}; + 22 -> {23}; + 23 -> {24}; + 24 -> {25}; + 25 -> {26}; + 26 -> {27}; + 27 -> {28}; + 28 -> {29}; + 29 -> {30}; + 30 -> {31}; + + subgraph cluster_7 { + color=red + 32 [label="Enter function test_3" style="filled" fillcolor=red]; + subgraph cluster_8 { + color=blue + 33 [label="Enter block"]; + 34 [label="Const: Null(null)"]; + 35 [label="Variable declaration: lvar x: R|kotlin/String?|"]; + 36 [label="Const: String()"]; + 37 [label="Assignmenet: R|/x|"]; + 38 [label="Access variable R|/x|"]; + 39 [label="Access variable R|kotlin/String.length|"]; + 40 [label="Const: Null(null)"]; + 41 [label="Assignmenet: R|/x|"]; + 42 [label="Access variable R|/x|"]; + 43 [label="Stub" style="filled" fillcolor=gray]; + 44 [label="Access variable #" style="filled" fillcolor=gray]; + 45 [label="Exit block" style="filled" fillcolor=gray]; + } + 46 [label="Exit function test_3" style="filled" fillcolor=red]; + } + + 32 -> {33}; + 33 -> {34}; + 34 -> {35}; + 35 -> {36}; + 36 -> {37}; + 37 -> {38}; + 38 -> {39}; + 39 -> {40}; + 40 -> {41}; + 41 -> {42}; + 42 -> {46}; + 42 -> {43} [style=dotted]; + 43 -> {44} [style=dotted]; + 44 -> {45} [style=dotted]; + 45 -> {46} [style=dotted]; + +} diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.kt b/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.kt new file mode 100644 index 00000000000..3ab2a944798 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.kt @@ -0,0 +1,21 @@ +fun test_1() { + var x: Any = 1 + x = "" + x.length +} + +fun test_2() { + var x: String? = null + if (x == null) { + x = "" + } + x.length +} + +fun test_3() { + var x: String? = null + x = "" + x.length + x = null + x.length +} \ No newline at end of file diff --git a/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.txt b/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.txt new file mode 100644 index 00000000000..67786b1a543 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.txt @@ -0,0 +1,23 @@ +FILE: smartcastAfterReassignment.kt + public final fun test_1(): R|kotlin/Unit| { + lvar x: R|kotlin/Any| = Int(1) + R|/x| = String() + R|/x|.R|kotlin/String.length| + } + public final fun test_2(): R|kotlin/Unit| { + lvar x: R|kotlin/String?| = Null(null) + when () { + ==(R|/x|, Null(null)) -> { + R|/x| = String() + } + } + + R|/x|.R|kotlin/String.length| + } + public final fun test_3(): R|kotlin/Unit| { + lvar x: R|kotlin/String?| = Null(null) + R|/x| = String() + R|/x|.R|kotlin/String.length| + R|/x| = Null(null) + R|/x|.# + } diff --git a/compiler/fir/resolve/testData/resolve/stdlib/hashSet.txt b/compiler/fir/resolve/testData/resolve/stdlib/hashSet.txt index 618dacdc50f..4b7abb20640 100644 --- a/compiler/fir/resolve/testData/resolve/stdlib/hashSet.txt +++ b/compiler/fir/resolve/testData/resolve/stdlib/hashSet.txt @@ -18,7 +18,7 @@ FILE: hashSet.kt public final fun foo(): R|kotlin/Unit| { lvar c: R|kotlin/collections/MutableSet?| = Null(null) R|/c| = R|java/util/HashSet.HashSet|() - when (lval : R|kotlin/collections/MutableSet?| = R|/c|) { + when (lval : R|java/util/HashSet| = R|/c|) { ==($subj$, Null(null)) -> { throw R|kotlin/KotlinNullPointerException.KotlinNullPointerException|() } diff --git a/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithCfgTestGenerated.java b/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithCfgTestGenerated.java index 747aff3857d..c8f076e7027 100644 --- a/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithCfgTestGenerated.java +++ b/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithCfgTestGenerated.java @@ -133,6 +133,11 @@ public class FirDiagnosticsWithCfgTestGenerated extends AbstractFirDiagnosticsWi runTest("compiler/fir/resolve/testData/resolve/smartcasts/dataFlowInfoFromWhileCondition.kt"); } + @TestMetadata("delayedAssignment.kt") + public void testDelayedAssignment() throws Exception { + runTest("compiler/fir/resolve/testData/resolve/smartcasts/delayedAssignment.kt"); + } + @TestMetadata("elvis.kt") public void testElvis() throws Exception { runTest("compiler/fir/resolve/testData/resolve/smartcasts/elvis.kt"); @@ -183,6 +188,11 @@ public class FirDiagnosticsWithCfgTestGenerated extends AbstractFirDiagnosticsWi runTest("compiler/fir/resolve/testData/resolve/smartcasts/simpleIf.kt"); } + @TestMetadata("smartcastAfterReassignment.kt") + public void testSmartcastAfterReassignment() throws Exception { + runTest("compiler/fir/resolve/testData/resolve/smartcasts/smartcastAfterReassignment.kt"); + } + @TestMetadata("when.kt") public void testWhen() throws Exception { runTest("compiler/fir/resolve/testData/resolve/smartcasts/when.kt"); diff --git a/compiler/testData/ir/irText/declarations/localDelegatedProperties.fir.txt b/compiler/testData/ir/irText/declarations/localDelegatedProperties.fir.txt index c900977761d..66c88876266 100644 --- a/compiler/testData/ir/irText/declarations/localDelegatedProperties.fir.txt +++ b/compiler/testData/ir/irText/declarations/localDelegatedProperties.fir.txt @@ -9,11 +9,13 @@ FILE fqName: fileName:/localDelegatedProperties.kt VAR name:x type:IrErrorType [var] SET_VAR 'var x: IrErrorType [var] declared in .test2' type=IrErrorType origin=null CONST Int type=kotlin.Int value=0 - VAR IR_TEMPORARY_VARIABLE name:tmp_0 type:IrErrorType [val] - GET_VAR 'var x: IrErrorType [var] declared in .test2' type=IrErrorType origin=null + VAR IR_TEMPORARY_VARIABLE name:tmp_0 type:kotlin.Int [val] + GET_VAR 'var x: IrErrorType [var] declared in .test2' type=kotlin.Int origin=null SET_VAR 'var x: IrErrorType [var] declared in .test2' type=IrErrorType origin=null - ERROR_CALL 'Unresolved reference: #' type=IrErrorType - GET_VAR 'val tmp_0: IrErrorType [val] declared in .test2' type=IrErrorType origin=null - CALL 'public final fun plusAssign (element: T of ): kotlin.Unit [inline] declared in kotlin.collections' type=kotlin.Unit origin=null - $receiver: GET_VAR 'var x: IrErrorType [var] declared in .test2' type=IrErrorType origin=null - element: CONST Int type=kotlin.Int value=1 + CALL 'public final fun inc (): kotlin.Int declared in kotlin.Int' type=kotlin.Int origin=null + $this: GET_VAR 'val tmp_0: kotlin.Int [val] declared in .test2' type=kotlin.Int origin=null + GET_VAR 'val tmp_0: kotlin.Int [val] declared in .test2' type=kotlin.Int origin=null + SET_VAR 'var x: IrErrorType [var] declared in .test2' type=IrErrorType origin=null + CALL 'public final fun plus (other: kotlin.Int): kotlin.Int declared in kotlin.Int' type=kotlin.Int origin=null + $this: GET_VAR 'var x: IrErrorType [var] declared in .test2' type=kotlin.Int origin=null + other: CONST Int type=kotlin.Int value=1