@@ -184,6 +184,7 @@ pub(crate) enum Definition {
184
184
FunctionDef ( TypedNodeKey < ast:: StmtFunctionDef > ) ,
185
185
Assignment ( TypedNodeKey < ast:: StmtAssign > ) ,
186
186
AnnotatedAssignment ( TypedNodeKey < ast:: StmtAnnAssign > ) ,
187
+ None ,
187
188
// TODO with statements, except handlers, function args...
188
189
}
189
190
@@ -288,8 +289,8 @@ impl SymbolTable {
288
289
let flow_node_id = self . flow_graph . ast_to_flow [ & node_key] ;
289
290
ReachableDefinitionsIterator {
290
291
table : self ,
291
- flow_node_id,
292
292
symbol_id,
293
+ pending : vec ! [ flow_node_id] ,
293
294
}
294
295
}
295
296
@@ -545,25 +546,30 @@ where
545
546
#[ derive( Debug ) ]
546
547
pub ( crate ) struct ReachableDefinitionsIterator < ' a > {
547
548
table : & ' a SymbolTable ,
548
- flow_node_id : FlowNodeId ,
549
549
symbol_id : SymbolId ,
550
+ pending : Vec < FlowNodeId > ,
550
551
}
551
552
552
553
impl < ' a > Iterator for ReachableDefinitionsIterator < ' a > {
553
554
type Item = Definition ;
554
555
555
556
fn next ( & mut self ) -> Option < Self :: Item > {
556
557
loop {
557
- match & self . table . flow_graph . flow_nodes_by_id [ self . flow_node_id ] {
558
- FlowNode :: Start => return None ,
558
+ let flow_node_id = self . pending . pop ( ) ?;
559
+ match & self . table . flow_graph . flow_nodes_by_id [ flow_node_id] {
560
+ FlowNode :: Start => return Some ( Definition :: None ) ,
559
561
FlowNode :: Definition ( def_node) => {
560
562
if def_node. symbol_id == self . symbol_id {
561
- // we found a definition; previous definitions along this path are not
562
- // reachable
563
- self . flow_node_id = FlowGraph :: start ( ) ;
564
563
return Some ( def_node. definition . clone ( ) ) ;
565
564
}
566
- self . flow_node_id = def_node. predecessor ;
565
+ self . pending . push ( def_node. predecessor ) ;
566
+ }
567
+ FlowNode :: Branch ( branch_node) => {
568
+ self . pending . push ( branch_node. predecessor ) ;
569
+ }
570
+ FlowNode :: Phi ( phi_node) => {
571
+ self . pending . push ( phi_node. first_predecessor ) ;
572
+ self . pending . push ( phi_node. second_predecessor ) ;
567
573
}
568
574
}
569
575
}
@@ -579,15 +585,31 @@ struct FlowNodeId;
579
585
enum FlowNode {
580
586
Start ,
581
587
Definition ( DefinitionFlowNode ) ,
588
+ Branch ( BranchFlowNode ) ,
589
+ Phi ( PhiFlowNode ) ,
582
590
}
583
591
592
+ /// A Definition node represents a point in control flow where a symbol is defined
584
593
#[ derive( Debug ) ]
585
594
struct DefinitionFlowNode {
586
595
symbol_id : SymbolId ,
587
596
definition : Definition ,
588
597
predecessor : FlowNodeId ,
589
598
}
590
599
600
+ /// A Branch node represents a branch in control flow
601
+ #[ derive( Debug ) ]
602
+ struct BranchFlowNode {
603
+ predecessor : FlowNodeId ,
604
+ }
605
+
606
+ /// A Phi node represents a join point where control flow paths come together
607
+ #[ derive( Debug ) ]
608
+ struct PhiFlowNode {
609
+ first_predecessor : FlowNodeId ,
610
+ second_predecessor : FlowNodeId ,
611
+ }
612
+
591
613
#[ derive( Debug , Default ) ]
592
614
struct FlowGraph {
593
615
flow_nodes_by_id : IndexVec < FlowNodeId , FlowNode > ,
@@ -636,6 +658,10 @@ impl SymbolTableBuilder {
636
658
. add_or_update_symbol ( self . cur_scope ( ) , identifier, flags)
637
659
}
638
660
661
+ fn new_flow_node ( & mut self , node : FlowNode ) -> FlowNodeId {
662
+ self . table . flow_graph . flow_nodes_by_id . push ( node)
663
+ }
664
+
639
665
fn add_or_update_symbol_with_def (
640
666
& mut self ,
641
667
identifier : & str ,
@@ -647,15 +673,11 @@ impl SymbolTableBuilder {
647
673
. entry ( symbol_id)
648
674
. or_default ( )
649
675
. push ( definition. clone ( ) ) ;
650
- let new_flow_node_id = self
651
- . table
652
- . flow_graph
653
- . flow_nodes_by_id
654
- . push ( FlowNode :: Definition ( DefinitionFlowNode {
655
- definition,
656
- symbol_id,
657
- predecessor : self . current_flow_node ( ) ,
658
- } ) ) ;
676
+ let new_flow_node_id = self . new_flow_node ( FlowNode :: Definition ( DefinitionFlowNode {
677
+ definition,
678
+ symbol_id,
679
+ predecessor : self . current_flow_node ( ) ,
680
+ } ) ) ;
659
681
self . set_current_flow_node ( new_flow_node_id) ;
660
682
symbol_id
661
683
}
@@ -871,13 +893,127 @@ impl PreorderVisitor<'_> for SymbolTableBuilder {
871
893
ast:: visitor:: preorder:: walk_stmt ( self , stmt) ;
872
894
self . current_definition = None ;
873
895
}
896
+ ast:: Stmt :: If ( node) => {
897
+ // we visit the if "test" condition first regardless
898
+ self . visit_expr ( & node. test ) ;
899
+
900
+ // create branch node: does the if test pass or not?
901
+ let if_branch = self . new_flow_node ( FlowNode :: Branch ( BranchFlowNode {
902
+ predecessor : self . current_flow_node ( ) ,
903
+ } ) ) ;
904
+
905
+ // visit the body of the `if` clause
906
+ self . set_current_flow_node ( if_branch) ;
907
+ self . visit_body ( & node. body ) ;
908
+
909
+ // Flow node for the last if/elif condition branch; represents the "no branch
910
+ // taken yet" possibility (where "taking a branch" means that the condition in an
911
+ // if or elif evaluated to true and control flow went into that clause).
912
+ let mut prior_branch = if_branch;
913
+
914
+ // Flow node for the state after the prior if/elif/else clause; represents "we have
915
+ // taken one of the branches up to this point." Initially set to the post-if-clause
916
+ // state, later will be set to the phi node joining that possible path with the
917
+ // possibility that we took a later if/elif/else clause instead.
918
+ let mut post_prior_clause = self . current_flow_node ( ) ;
919
+
920
+ // Flag to mark if the final clause is an "else" -- if so, that means the "match no
921
+ // clauses" path is not possible, we have to go through one of the clauses.
922
+ let mut last_branch_is_else = false ;
923
+
924
+ for clause in & node. elif_else_clauses {
925
+ if clause. test . is_some ( ) {
926
+ // This is an elif clause. Create a new branch node. Its predecessor is the
927
+ // previous branch node, because we can only take one branch in an entire
928
+ // if/elif/else chain, so if we take this branch, it can only be because we
929
+ // didn't take the previous one.
930
+ prior_branch = self . new_flow_node ( FlowNode :: Branch ( BranchFlowNode {
931
+ predecessor : prior_branch,
932
+ } ) ) ;
933
+ self . set_current_flow_node ( prior_branch) ;
934
+ } else {
935
+ // This is an else clause. No need to create a branch node; there's no
936
+ // branch here, if we haven't taken any previous branch, we definitely go
937
+ // into the "else" clause.
938
+ self . set_current_flow_node ( prior_branch) ;
939
+ last_branch_is_else = true ;
940
+ }
941
+ self . visit_elif_else_clause ( clause) ;
942
+ // Update `post_prior_clause` to a new phi node joining the possibility that we
943
+ // took any of the previous branches with the possibility that we took the one
944
+ // just visited.
945
+ post_prior_clause = self . new_flow_node ( FlowNode :: Phi ( PhiFlowNode {
946
+ first_predecessor : self . current_flow_node ( ) ,
947
+ second_predecessor : post_prior_clause,
948
+ } ) ) ;
949
+ }
950
+
951
+ if !last_branch_is_else {
952
+ // Final branch was not an "else", which means it's possible we took zero
953
+ // branches in the entire if/elif chain, so we need one more phi node to join
954
+ // the "no branches taken" possibility.
955
+ post_prior_clause = self . new_flow_node ( FlowNode :: Phi ( PhiFlowNode {
956
+ first_predecessor : post_prior_clause,
957
+ second_predecessor : prior_branch,
958
+ } ) ) ;
959
+ }
960
+
961
+ // Onward, with current flow node set to our final Phi node.
962
+ self . set_current_flow_node ( post_prior_clause) ;
963
+ }
874
964
_ => {
875
965
ast:: visitor:: preorder:: walk_stmt ( self , stmt) ;
876
966
}
877
967
}
878
968
}
879
969
}
880
970
971
+ impl std:: fmt:: Display for FlowGraph {
972
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
973
+ writeln ! ( f, "flowchart TD" ) ?;
974
+ for ( id, node) in self . flow_nodes_by_id . iter_enumerated ( ) {
975
+ write ! ( f, " id{}" , id. as_u32( ) ) ?;
976
+ match node {
977
+ FlowNode :: Start => writeln ! ( f, r"[\Start/]" ) ?,
978
+ FlowNode :: Definition ( def_node) => {
979
+ writeln ! ( f, r"(Define symbol {})" , def_node. symbol_id. as_u32( ) ) ?;
980
+ writeln ! (
981
+ f,
982
+ r" id{}-->id{}" ,
983
+ def_node. predecessor. as_u32( ) ,
984
+ id. as_u32( )
985
+ ) ?;
986
+ }
987
+ FlowNode :: Branch ( branch_node) => {
988
+ writeln ! ( f, r"{{Branch}}" ) ?;
989
+ writeln ! (
990
+ f,
991
+ r" id{}-->id{}" ,
992
+ branch_node. predecessor. as_u32( ) ,
993
+ id. as_u32( )
994
+ ) ?;
995
+ }
996
+ FlowNode :: Phi ( phi_node) => {
997
+ writeln ! ( f, r"((Phi))" ) ?;
998
+ writeln ! (
999
+ f,
1000
+ r" id{}-->id{}" ,
1001
+ phi_node. second_predecessor. as_u32( ) ,
1002
+ id. as_u32( )
1003
+ ) ?;
1004
+ writeln ! (
1005
+ f,
1006
+ r" id{}-->id{}" ,
1007
+ phi_node. first_predecessor. as_u32( ) ,
1008
+ id. as_u32( )
1009
+ ) ?;
1010
+ }
1011
+ }
1012
+ }
1013
+ Ok ( ( ) )
1014
+ }
1015
+ }
1016
+
881
1017
#[ derive( Debug , Default ) ]
882
1018
pub struct SymbolTablesStorage ( KeyValueCache < FileId , Arc < SymbolTable > > ) ;
883
1019
0 commit comments