fluidattacks_blends_domain/stack/
node.rs1use alloc::string::String;
2
3#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
5#[allow(
6 clippy::exhaustive_enums,
7 reason = "a fixed ontology: the lexical scope kinds the stack graph model distinguishes"
8)]
9pub enum ScopeType {
10 Block,
12 Class,
14 Declaration,
16 File,
18 Function,
20 Namespace,
22}
23
24#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
26#[non_exhaustive]
27pub struct StackScope {
28 pub scope_type: Option<ScopeType>,
30}
31
32#[derive(Clone, PartialEq, Eq, Debug)]
34#[non_exhaustive]
35pub struct SymbolPush {
36 pub symbol: String,
38 pub precedence: i64,
40 pub is_reference: bool,
42}
43
44#[derive(Clone, PartialEq, Eq, Debug, Default)]
46#[non_exhaustive]
47pub struct Definition {
48 pub is_private: bool,
50 pub visibility_guard: Option<String>,
52}
53
54#[derive(Clone, PartialEq, Eq, Debug)]
56#[non_exhaustive]
57pub struct SymbolPop {
58 pub symbol: String,
60 pub precedence: i64,
62 pub definition: Option<Definition>,
64 pub import: Option<ImportBinding>,
66}
67
68#[derive(Clone, PartialEq, Eq, Debug)]
70#[non_exhaustive]
71pub struct ImportBinding {
72 pub module_stem: String,
74 pub source_symbol: Option<String>,
76 pub relative_level: u32,
78 pub binds_module: bool,
80}
81
82#[derive(Clone, PartialEq, Eq, Debug)]
85#[allow(
86 clippy::exhaustive_enums,
87 reason = "a fixed ontology: the stack-graphs algorithm's node kinds"
88)]
89pub enum StackNode {
90 PopSymbol(SymbolPop),
92 PushSymbol(SymbolPush),
94 Root,
96 Scope(StackScope),
98}
99
100impl StackNode {
101 #[must_use]
103 pub fn symbol(&self) -> Option<&str> {
104 match self {
105 Self::PushSymbol(push) => Some(&push.symbol),
106 Self::PopSymbol(pop) => Some(&pop.symbol),
107 Self::Root | Self::Scope(_) => None,
108 }
109 }
110
111 #[must_use]
113 pub const fn precedence(&self) -> i64 {
114 match self {
115 Self::PushSymbol(push) => push.precedence,
116 Self::PopSymbol(pop) => pop.precedence,
117 Self::Root | Self::Scope(_) => 0,
118 }
119 }
120
121 #[must_use]
123 pub const fn is_reference(&self) -> bool {
124 match self {
125 Self::PushSymbol(push) => push.is_reference,
126 Self::PopSymbol(_) | Self::Root | Self::Scope(_) => false,
127 }
128 }
129
130 #[must_use]
132 pub const fn is_definition(&self) -> bool {
133 match self {
134 Self::PopSymbol(pop) => pop.definition.is_some(),
135 Self::PushSymbol(_) | Self::Root | Self::Scope(_) => false,
136 }
137 }
138
139 #[must_use]
141 pub const fn import_binding(&self) -> Option<&ImportBinding> {
142 match self {
143 Self::PopSymbol(pop) => pop.import.as_ref(),
144 Self::PushSymbol(_) | Self::Root | Self::Scope(_) => None,
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::{
152 Definition, ImportBinding, ScopeType, StackNode, StackScope, SymbolPop, SymbolPush,
153 };
154 use alloc::borrow::ToOwned;
155
156 fn push(symbol: &str) -> SymbolPush {
157 SymbolPush {
158 symbol: symbol.to_owned(),
159 precedence: 0,
160 is_reference: true,
161 }
162 }
163
164 fn pop(symbol: &str) -> SymbolPop {
165 SymbolPop {
166 symbol: symbol.to_owned(),
167 precedence: 1,
168 definition: Some(Definition::default()),
169 import: None,
170 }
171 }
172
173 fn file_scope() -> StackNode {
174 StackNode::Scope(StackScope {
175 scope_type: Some(ScopeType::File),
176 })
177 }
178
179 #[test]
180 fn symbol_nodes_carry_their_symbol() {
181 assert_eq!(StackNode::PushSymbol(push("send")).symbol(), Some("send"));
182 assert_eq!(StackNode::PopSymbol(pop("utils")).symbol(), Some("utils"));
183 assert_eq!(StackNode::Root.symbol(), None);
184 assert_eq!(file_scope().symbol(), None);
185 }
186
187 #[test]
188 fn nodes_without_a_symbol_take_no_precedence() {
189 assert_eq!(StackNode::PopSymbol(pop("send")).precedence(), 1);
190 assert_eq!(StackNode::PushSymbol(push("send")).precedence(), 0);
191 assert_eq!(StackNode::Root.precedence(), 0);
192 assert_eq!(file_scope().precedence(), 0);
193 }
194
195 #[test]
196 fn only_marked_push_nodes_are_references() {
197 let chain_link = SymbolPush {
198 is_reference: false,
199 ..push("utils")
200 };
201
202 assert!(StackNode::PushSymbol(push("send")).is_reference());
203 assert!(!StackNode::PushSymbol(chain_link).is_reference());
204 assert!(!StackNode::PopSymbol(pop("send")).is_reference());
205 assert!(!StackNode::Root.is_reference());
206 }
207
208 #[test]
209 fn only_marked_pop_nodes_are_definitions() {
210 let path_segment = SymbolPop {
211 definition: None,
212 ..pop("utils")
213 };
214
215 assert!(StackNode::PopSymbol(pop("send")).is_definition());
216 assert!(!StackNode::PopSymbol(path_segment).is_definition());
217 assert!(!StackNode::PushSymbol(push("send")).is_definition());
218 assert!(!file_scope().is_definition());
219 }
220
221 #[test]
222 fn only_pops_from_imports_bind_imports() {
223 let binding = ImportBinding {
224 module_stem: "utils".to_owned(),
225 source_symbol: Some("send".to_owned()),
226 relative_level: 1,
227 binds_module: false,
228 };
229 let import = StackNode::PopSymbol(SymbolPop {
230 import: Some(binding.clone()),
231 ..pop("send")
232 });
233
234 assert_eq!(import.import_binding(), Some(&binding));
235 assert!(import.is_definition());
236 assert_eq!(StackNode::PopSymbol(pop("send")).import_binding(), None);
237 assert_eq!(StackNode::PushSymbol(push("send")).import_binding(), None);
238 }
239}