1use crate::ast::{BindingPattern, DictEntry, MatchArm, Node, SNode, SelectCase, TypedParam};
25
26pub fn walk_program(program: &[SNode], visitor: &mut impl FnMut(&SNode)) {
29 let mut stack = Vec::with_capacity(program.len());
30 push_nodes_reversed(program, &mut stack);
31 walk_stack(&mut stack, visitor);
32}
33
34pub fn contains_identifier_receiver_access(program: &[SNode]) -> bool {
40 let mut found = false;
41 walk_program(program, &mut |node| {
42 let object = match &node.node {
43 Node::PropertyAccess { object, .. }
44 | Node::OptionalPropertyAccess { object, .. }
45 | Node::MethodCall { object, .. }
46 | Node::OptionalMethodCall { object, .. } => object,
47 _ => return,
48 };
49 found |= matches!(&object.node, Node::Identifier(_));
50 });
51 found
52}
53
54pub fn contains_identifier_enum_pattern(program: &[SNode]) -> bool {
65 let mut found = false;
66 walk_program(program, &mut |node| {
67 let Node::MatchExpr { arms, .. } = &node.node else {
68 return;
69 };
70 found |= arms
71 .iter()
72 .any(|arm| contains_identifier_enum_pattern_node(&arm.pattern));
73 });
74 found
75}
76
77fn contains_identifier_enum_pattern_node(node: &SNode) -> bool {
78 match &node.node {
79 Node::PropertyAccess { object, .. } | Node::MethodCall { object, .. } => {
80 matches!(&object.node, Node::Identifier(_))
81 }
82 Node::OrPattern(patterns) => patterns.iter().any(contains_identifier_enum_pattern_node),
83 _ => false,
84 }
85}
86
87pub fn walk_node(node: &SNode, visitor: &mut impl FnMut(&SNode)) {
89 let mut stack = vec![node];
90 walk_stack(&mut stack, visitor);
91}
92
93pub fn walk_children(node: &SNode, visitor: &mut impl FnMut(&SNode)) {
97 let mut stack = Vec::new();
98 push_children_reversed(node, &mut stack);
99 walk_stack(&mut stack, visitor);
100}
101
102fn walk_stack(stack: &mut Vec<&SNode>, visitor: &mut impl FnMut(&SNode)) {
103 while let Some(node) = stack.pop() {
104 visitor(node);
105 push_children_reversed(node, stack);
106 }
107}
108
109fn push_children_reversed<'a>(node: &'a SNode, stack: &mut Vec<&'a SNode>) {
110 let mut children = Vec::new();
111 collect_children(node, &mut children);
112 stack.extend(children.into_iter().rev());
113}
114
115fn push_nodes_reversed<'a>(nodes: &'a [SNode], stack: &mut Vec<&'a SNode>) {
116 stack.extend(nodes.iter().rev());
117}
118
119pub fn immediate_children(node: &SNode) -> Vec<&SNode> {
123 let mut children = Vec::new();
124 collect_children(node, &mut children);
125 children
126}
127
128fn collect_children<'a>(node: &'a SNode, children: &mut Vec<&'a SNode>) {
129 match &node.node {
130 Node::AttributedDecl { attributes, inner } => {
131 for attr in attributes {
132 for arg in &attr.args {
133 children.push(&arg.value);
134 }
135 }
136 children.push(inner);
137 }
138 Node::Pipeline { body, .. } | Node::OverrideDecl { body, .. } => {
139 collect_nodes(body, children);
140 }
141 Node::LetBinding { pattern, value, .. } | Node::ConstBinding { pattern, value, .. } => {
142 collect_binding_pattern(pattern, children);
143 children.push(value);
144 }
145 Node::EnumDecl { variants, .. } => {
146 for variant in variants {
147 collect_typed_param_defaults(&variant.fields, children);
148 }
149 }
150 Node::StructDecl { .. }
151 | Node::ImportDecl { .. }
152 | Node::SelectiveImport { .. }
153 | Node::NamespaceImport { .. }
154 | Node::TypeDecl { .. }
155 | Node::BreakStmt
156 | Node::ContinueStmt => {}
157 Node::InterfaceDecl { methods, .. } => {
158 for method in methods {
159 collect_typed_param_defaults(&method.params, children);
160 }
161 }
162 Node::ImplBlock { methods, .. } => collect_nodes(methods, children),
163 Node::IfElse {
164 condition,
165 then_body,
166 else_body,
167 ..
168 } => {
169 children.push(condition);
170 collect_nodes(then_body, children);
171 if let Some(body) = else_body {
172 collect_nodes(body, children);
173 }
174 }
175 Node::ForIn {
176 pattern,
177 iterable,
178 body,
179 } => {
180 collect_binding_pattern(pattern, children);
181 children.push(iterable);
182 collect_nodes(body, children);
183 }
184 Node::MatchExpr { value, arms } => {
185 children.push(value);
186 for arm in arms {
187 collect_match_arm(arm, children);
188 }
189 }
190 Node::WhileLoop { condition, body } => {
191 children.push(condition);
192 collect_nodes(body, children);
193 }
194 Node::Retry { count, body } => {
195 children.push(count);
196 collect_nodes(body, children);
197 }
198 Node::CostRoute { options, body } => {
199 collect_option_values(options, children);
200 collect_nodes(body, children);
201 }
202 Node::ReturnStmt { value } | Node::YieldExpr { value } => {
203 if let Some(value) = value {
204 children.push(value);
205 }
206 }
207 Node::TryCatch {
208 has_catch: _,
209 body,
210 catch_body,
211 finally_body,
212 ..
213 } => {
214 collect_nodes(body, children);
215 collect_nodes(catch_body, children);
216 if let Some(body) = finally_body {
217 collect_nodes(body, children);
218 }
219 }
220 Node::TryExpr { body }
221 | Node::SpawnExpr { body }
222 | Node::ScopeBlock { body }
223 | Node::DeferStmt { body }
224 | Node::Block(body) => collect_nodes(body, children),
225 Node::Closure { params, body, .. } => {
226 collect_typed_param_defaults(params, children);
227 collect_nodes(body, children);
228 }
229 Node::MutexBlock { key, body } => {
230 if let Some(key) = key {
231 children.push(key);
232 }
233 collect_nodes(body, children);
234 }
235 Node::FnDecl { params, body, .. } | Node::ToolDecl { params, body, .. } => {
236 collect_typed_param_defaults(params, children);
237 collect_nodes(body, children);
238 }
239 Node::SkillDecl { fields, .. } => collect_field_values(fields, children),
240 Node::EvalPackDecl {
241 fields,
242 body,
243 summarize,
244 ..
245 } => {
246 collect_field_values(fields, children);
247 collect_nodes(body, children);
248 if let Some(body) = summarize {
249 collect_nodes(body, children);
250 }
251 }
252 Node::RangeExpr { start, end, .. } => {
253 children.push(start);
254 children.push(end);
255 }
256 Node::GuardStmt {
257 condition,
258 else_body,
259 } => {
260 children.push(condition);
261 collect_nodes(else_body, children);
262 }
263 Node::RequireStmt { condition, message } => {
264 children.push(condition);
265 if let Some(message) = message {
266 children.push(message);
267 }
268 }
269 Node::DeadlineBlock { duration, body } => {
270 children.push(duration);
271 collect_nodes(body, children);
272 }
273 Node::EmitExpr { value }
274 | Node::ThrowStmt { value }
275 | Node::Spread(value)
276 | Node::TryOperator { operand: value }
277 | Node::TryStar { operand: value }
278 | Node::NonNullAssert { operand: value }
279 | Node::UnaryOp { operand: value, .. } => children.push(value),
280 Node::HitlExpr { args, .. } => {
281 for arg in args {
282 children.push(&arg.value);
283 }
284 }
285 Node::Parallel {
286 expr,
287 body,
288 options,
289 ..
290 } => {
291 children.push(expr);
292 collect_option_values(options, children);
293 collect_nodes(body, children);
294 }
295 Node::SelectExpr {
296 cases,
297 timeout,
298 default_body,
299 } => {
300 for case in cases {
301 collect_select_case(case, children);
302 }
303 if let Some((duration, body)) = timeout {
304 children.push(duration);
305 collect_nodes(body, children);
306 }
307 if let Some(body) = default_body {
308 collect_nodes(body, children);
309 }
310 }
311 Node::FunctionCall { args, .. } | Node::EnumConstruct { args, .. } => {
312 collect_nodes(args, children);
313 }
314 Node::ValueCall { callee, args } => {
315 children.push(callee);
316 collect_nodes(args, children);
317 }
318 Node::MethodCall { object, args, .. } | Node::OptionalMethodCall { object, args, .. } => {
319 children.push(object);
320 collect_nodes(args, children);
321 }
322 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
323 children.push(object);
324 }
325 Node::SubscriptAccess { object, index }
326 | Node::OptionalSubscriptAccess { object, index } => {
327 children.push(object);
328 children.push(index);
329 }
330 Node::SliceAccess { object, start, end } => {
331 children.push(object);
332 if let Some(start) = start {
333 children.push(start);
334 }
335 if let Some(end) = end {
336 children.push(end);
337 }
338 }
339 Node::BinaryOp { left, right, .. } => {
340 children.push(left);
341 children.push(right);
342 }
343 Node::Ternary {
344 condition,
345 true_expr,
346 false_expr,
347 } => {
348 children.push(condition);
349 children.push(true_expr);
350 children.push(false_expr);
351 }
352 Node::Assignment { target, value, .. } => {
353 children.push(target);
354 children.push(value);
355 }
356 Node::StructConstruct { fields, .. } | Node::DictLiteral(fields) => {
357 collect_dict_entries(fields, children);
358 }
359 Node::ListLiteral(items) | Node::OrPattern(items) => collect_nodes(items, children),
360 Node::InterpolatedString(_)
361 | Node::StringLiteral(_)
362 | Node::RawStringLiteral(_)
363 | Node::IntLiteral(_)
364 | Node::FloatLiteral(_)
365 | Node::BoolLiteral(_)
366 | Node::NilLiteral
367 | Node::Identifier(_)
368 | Node::DurationLiteral(_) => {}
369 }
370}
371
372fn collect_nodes<'a>(nodes: &'a [SNode], children: &mut Vec<&'a SNode>) {
373 children.extend(nodes.iter());
374}
375
376fn collect_dict_entries<'a>(entries: &'a [DictEntry], children: &mut Vec<&'a SNode>) {
377 for entry in entries {
378 children.push(&entry.key);
379 children.push(&entry.value);
380 }
381}
382
383fn collect_field_values<'a>(fields: &'a [(String, SNode)], children: &mut Vec<&'a SNode>) {
384 for (_, value) in fields {
385 children.push(value);
386 }
387}
388
389fn collect_option_values<'a>(options: &'a [(String, SNode)], children: &mut Vec<&'a SNode>) {
390 for (_, value) in options {
391 children.push(value);
392 }
393}
394
395fn collect_typed_param_defaults<'a>(params: &'a [TypedParam], children: &mut Vec<&'a SNode>) {
396 for param in params {
397 if let Some(default) = ¶m.default_value {
398 children.push(default);
399 }
400 }
401}
402
403fn collect_match_arm<'a>(arm: &'a MatchArm, children: &mut Vec<&'a SNode>) {
404 children.push(&arm.pattern);
405 if let Some(guard) = &arm.guard {
406 children.push(guard);
407 }
408 collect_nodes(&arm.body, children);
409}
410
411fn collect_select_case<'a>(case: &'a SelectCase, children: &mut Vec<&'a SNode>) {
412 children.push(&case.channel);
413 collect_nodes(&case.body, children);
414}
415
416fn collect_binding_pattern<'a>(pattern: &'a BindingPattern, children: &mut Vec<&'a SNode>) {
417 match pattern {
418 BindingPattern::Identifier(_) | BindingPattern::Pair(_, _) => {}
419 BindingPattern::Dict(fields) => {
420 for field in fields {
421 if let Some(default) = &field.default_value {
422 children.push(default);
423 }
424 }
425 }
426 BindingPattern::List(items) => {
427 for item in items {
428 if let Some(default) = &item.default_value {
429 children.push(default);
430 }
431 }
432 }
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439 use crate::ast::{spanned, Node, TypedParam};
440 use harn_lexer::Span;
441
442 fn dummy(node: Node) -> SNode {
443 spanned(node, Span::dummy())
444 }
445
446 #[test]
447 fn walk_program_preserves_preorder() {
448 let program = vec![dummy(Node::LetBinding {
449 pattern: BindingPattern::Identifier("x".to_string()),
450 type_ann: None,
451 value: Box::new(dummy(Node::BinaryOp {
452 op: "+".to_string(),
453 left: Box::new(dummy(Node::IntLiteral(1))),
454 right: Box::new(dummy(Node::IntLiteral(2))),
455 })),
456 is_pub: false,
457 })];
458 let mut seen = Vec::new();
459
460 walk_program(&program, &mut |node| {
461 seen.push(match &node.node {
462 Node::LetBinding { .. } => "let",
463 Node::BinaryOp { .. } => "binary",
464 Node::IntLiteral(1) => "one",
465 Node::IntLiteral(2) => "two",
466 other => panic!("unexpected node {other:?}"),
467 });
468 });
469
470 assert_eq!(seen, vec!["let", "binary", "one", "two"]);
471 }
472
473 #[test]
474 fn identifier_receiver_access_predicate_ignores_function_calls() {
475 let plain = vec![dummy(Node::FunctionCall {
476 name: "helper".to_string(),
477 type_args: Vec::new(),
478 args: Vec::new(),
479 })];
480 assert!(!contains_identifier_receiver_access(&plain));
481
482 let qualified = vec![dummy(Node::PropertyAccess {
483 object: Box::new(dummy(Node::Identifier("Status".to_string()))),
484 property: "Ready".to_string(),
485 })];
486 assert!(contains_identifier_receiver_access(&qualified));
487 }
488
489 #[test]
490 fn enum_pattern_predicate_ignores_ordinary_property_access() {
491 let ordinary = vec![dummy(Node::PropertyAccess {
492 object: Box::new(dummy(Node::Identifier("record".to_string()))),
493 property: "field".to_string(),
494 })];
495 assert!(!contains_identifier_enum_pattern(&ordinary));
496
497 let pattern = dummy(Node::PropertyAccess {
498 object: Box::new(dummy(Node::Identifier("Status".to_string()))),
499 property: "Ready".to_string(),
500 });
501 let match_expr = dummy(Node::MatchExpr {
502 value: Box::new(dummy(Node::Identifier("value".to_string()))),
503 arms: vec![MatchArm {
504 pattern,
505 guard: None,
506 body: Vec::new(),
507 span: Span::dummy(),
508 }],
509 });
510 assert!(contains_identifier_enum_pattern(&[match_expr]));
511 }
512
513 #[test]
514 fn walk_node_handles_deep_unary_chain_iteratively() {
515 let mut node = dummy(Node::IntLiteral(0));
516 for _ in 0..10_000 {
517 node = dummy(Node::UnaryOp {
518 op: "!".to_string(),
519 operand: Box::new(node),
520 });
521 }
522
523 let mut count = 0usize;
524 walk_node(&node, &mut |_| count += 1);
525
526 assert_eq!(count, 10_001);
527 }
528
529 #[test]
530 fn walk_node_visits_typed_param_defaults() {
531 let default = dummy(Node::Identifier("fallback".to_string()));
532 let node = dummy(Node::FnDecl {
533 name: "load".to_string(),
534 type_params: Vec::new(),
535 params: vec![TypedParam {
536 name: "root".to_string(),
537 type_expr: None,
538 default_value: Some(Box::new(default)),
539 rest: false,
540 }],
541 return_type: None,
542 throws: None,
543 where_clauses: Vec::new(),
544 body: Vec::new(),
545 is_pub: false,
546 is_stream: false,
547 });
548 let mut seen = Vec::new();
549
550 walk_node(&node, &mut |node| {
551 if let Node::Identifier(name) = &node.node {
552 seen.push(name.clone());
553 }
554 });
555
556 assert_eq!(seen, vec!["fallback"]);
557 }
558}