Skip to main content

emmylua_code_analysis/db_index/flow/
flow_node.rs

1use emmylua_parser::{
2    LuaAssignStat, LuaAstNode, LuaAstPtr, LuaChunk, LuaClosureExpr, LuaDocTagCast, LuaExpr,
3    LuaForStat, LuaFuncStat, LuaSyntaxKind, LuaSyntaxNode,
4};
5use internment::ArcIntern;
6use rowan::{TextRange, TextSize};
7use smol_str::SmolStr;
8
9/// Unique identifier for flow nodes
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
11pub struct FlowId(pub u32);
12
13/// Represents how flow nodes are connected
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum FlowAntecedent {
16    /// Single predecessor node
17    Single(FlowId),
18    /// Multiple predecessor nodes (stored externally by index)
19    Multiple(u32),
20}
21
22/// Main flow node structure containing all flow analysis information
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct FlowNode {
25    pub id: FlowId,
26    pub kind: FlowNodeKind,
27    pub antecedent: Option<FlowAntecedent>,
28}
29
30/// Different types of flow nodes in the control flow graph
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum FlowNodeKind {
33    /// Entry point of the flow
34    Start,
35    /// Unreachable code
36    Unreachable,
37    /// Label for branching (if/else, switch cases)
38    BranchLabel,
39    /// Label for loops (while, for, repeat)
40    LoopLabel,
41    /// Named label (goto target)
42    NamedLabel(ArcIntern<SmolStr>),
43    /// Declaration position
44    DeclPosition(TextSize),
45    /// Variable assignment
46    Assignment(LuaAstPtr<LuaAssignStat>),
47    /// Conditional flow (type guards, existence checks)
48    TrueCondition(LuaAstPtr<LuaExpr>),
49    /// Conditional flow (type guards, existence checks)
50    FalseCondition(LuaAstPtr<LuaExpr>),
51    /// impl function
52    ImplFunc(LuaAstPtr<LuaFuncStat>),
53    /// For loop initialization
54    ForIStat(LuaAstPtr<LuaForStat>),
55    /// Tag cast comment
56    TagCast(LuaAstPtr<LuaDocTagCast>),
57    /// Break statement
58    Break,
59    /// Return statement
60    Return,
61}
62
63#[allow(unused)]
64impl FlowNodeKind {
65    pub fn is_branch_label(&self) -> bool {
66        matches!(self, FlowNodeKind::BranchLabel)
67    }
68
69    pub fn is_loop_label(&self) -> bool {
70        matches!(self, FlowNodeKind::LoopLabel)
71    }
72
73    pub fn is_named_label(&self) -> bool {
74        matches!(self, FlowNodeKind::NamedLabel(_))
75    }
76
77    pub fn is_change_flow(&self) -> bool {
78        matches!(self, FlowNodeKind::Break | FlowNodeKind::Return)
79    }
80
81    pub fn is_assignment(&self) -> bool {
82        matches!(self, FlowNodeKind::Assignment(_))
83    }
84
85    pub fn is_conditional(&self) -> bool {
86        matches!(
87            self,
88            FlowNodeKind::TrueCondition(_) | FlowNodeKind::FalseCondition(_)
89        )
90    }
91
92    pub fn is_unreachable(&self) -> bool {
93        matches!(self, FlowNodeKind::Unreachable)
94    }
95}
96
97#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
98pub struct LuaClosureId(TextRange);
99
100impl LuaClosureId {
101    pub fn from_closure(closure_expr: LuaClosureExpr) -> Self {
102        Self(closure_expr.get_range())
103    }
104
105    pub fn from_chunk(chunk: LuaChunk) -> Self {
106        Self(chunk.get_range())
107    }
108
109    pub fn from_node(node: &LuaSyntaxNode) -> Self {
110        let flow_id = node.ancestors().find_map(|node| match node.kind().into() {
111            LuaSyntaxKind::ClosureExpr => {
112                LuaClosureExpr::cast(node).map(LuaClosureId::from_closure)
113            }
114            LuaSyntaxKind::Chunk => LuaChunk::cast(node).map(LuaClosureId::from_chunk),
115            _ => None,
116        });
117
118        flow_id.unwrap_or_else(|| LuaClosureId(TextRange::default()))
119    }
120
121    pub fn get_position(&self) -> TextSize {
122        self.0.start()
123    }
124
125    pub fn get_range(&self) -> TextRange {
126        self.0
127    }
128}