1use crate::ast::{BindingPattern, DictEntry, MatchArm, Node, SNode, SelectCase, TypedParam};
25
26pub fn walk_program<'a>(program: &'a [SNode], visitor: &mut impl FnMut(&'a 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 walk_program_unattributed<'a>(program: &'a [SNode], visitor: &mut impl FnMut(&'a SNode)) {
38 let mut stack = Vec::with_capacity(program.len());
39 push_nodes_reversed(program, &mut stack);
40 walk_projected_stack(&mut stack, visitor, |node| {
41 crate::ast::peel_attributes(node).1
42 });
43}
44
45pub fn walk_program_interpolated(
67 source: &str,
68 program: &[SNode],
69 visitor: &mut impl FnMut(&SNode),
70) {
71 walk_interpolated(source, program, visitor, |node| node);
72}
73
74pub fn walk_program_unattributed_interpolated(
76 source: &str,
77 program: &[SNode],
78 visitor: &mut impl FnMut(&SNode),
79) {
80 walk_interpolated(source, program, visitor, |node| {
81 crate::ast::peel_attributes(node).1
82 });
83}
84
85fn walk_interpolated(
86 source: &str,
87 program: &[SNode],
88 visitor: &mut impl FnMut(&SNode),
89 project: fn(&SNode) -> &SNode,
90) {
91 let mut holes = Vec::new();
92 let mut stack = Vec::with_capacity(program.len());
93 push_nodes_reversed(program, &mut stack);
94 walk_projected_stack(
95 &mut stack,
96 &mut |node| {
97 if let Node::InterpolatedString(segments) = &node.node {
98 for segment in segments {
99 if let harn_lexer::StringSegment::Expression(text, line, column) = segment {
100 holes.push((text.clone(), *line, *column));
101 }
102 }
103 }
104 visitor(node);
105 },
106 project,
107 );
108 while let Some((text, line, column)) = holes.pop() {
111 let Some(expression) =
112 crate::interpolation::parse_expression(Some(source), &text, line, column)
113 else {
114 continue;
115 };
116 walk_interpolated(source, std::slice::from_ref(&expression), visitor, project);
117 }
118}
119
120pub fn contains_identifier_receiver_access(program: &[SNode]) -> bool {
126 any_node(program, &mut |node| {
127 let object = match &node.node {
128 Node::PropertyAccess { object, .. }
129 | Node::OptionalPropertyAccess { object, .. }
130 | Node::MethodCall { object, .. }
131 | Node::OptionalMethodCall { object, .. } => object,
132 _ => return false,
133 };
134 matches!(&object.node, Node::Identifier(_))
135 })
136}
137
138pub fn contains_identifier_enum_pattern(program: &[SNode]) -> bool {
149 any_node(program, &mut |node| {
150 let Node::MatchExpr { arms, .. } = &node.node else {
151 return false;
152 };
153 arms.iter()
154 .any(|arm| contains_identifier_enum_pattern_node(&arm.pattern))
155 })
156}
157
158fn any_node(program: &[SNode], predicate: &mut impl FnMut(&SNode) -> bool) -> bool {
162 let mut stack = Vec::with_capacity(program.len());
163 push_nodes_reversed(program, &mut stack);
164 let mut scratch: Vec<&SNode> = Vec::new();
165 while let Some(node) = stack.pop() {
166 if predicate(node) {
167 return true;
168 }
169 scratch.clear();
170 collect_children(node, &mut |child| scratch.push(child));
171 stack.extend(scratch.iter().rev().copied());
172 }
173 false
174}
175
176fn contains_identifier_enum_pattern_node(node: &SNode) -> bool {
177 match &node.node {
178 Node::PropertyAccess { object, .. } | Node::MethodCall { object, .. } => {
179 matches!(&object.node, Node::Identifier(_))
180 }
181 Node::OrPattern(patterns) => patterns.iter().any(contains_identifier_enum_pattern_node),
182 _ => false,
183 }
184}
185
186pub fn walk_node<'a>(node: &'a SNode, visitor: &mut impl FnMut(&'a SNode)) {
188 let mut stack = vec![node];
189 walk_stack(&mut stack, visitor);
190}
191
192pub fn walk_children<'a>(node: &'a SNode, visitor: &mut impl FnMut(&'a SNode)) {
196 let mut stack = Vec::new();
197 collect_children(node, &mut |child| stack.push(child));
198 stack.reverse();
199 walk_stack(&mut stack, visitor);
200}
201
202fn walk_stack<'a>(stack: &mut Vec<&'a SNode>, visitor: &mut impl FnMut(&'a SNode)) {
203 walk_projected_stack(stack, visitor, |node| node);
204}
205
206fn walk_projected_stack<'a>(
207 stack: &mut Vec<&'a SNode>,
208 visitor: &mut impl FnMut(&'a SNode),
209 project: impl Fn(&'a SNode) -> &'a SNode,
210) {
211 let mut scratch: Vec<&'a SNode> = Vec::new();
215 while let Some(node) = stack.pop() {
216 let node = project(node);
217 visitor(node);
218 scratch.clear();
219 collect_children(node, &mut |child| scratch.push(child));
220 stack.extend(scratch.iter().rev().copied());
221 }
222}
223
224fn push_nodes_reversed<'a>(nodes: &'a [SNode], stack: &mut Vec<&'a SNode>) {
225 stack.extend(nodes.iter().rev());
226}
227
228pub fn immediate_children(node: &SNode) -> Vec<&SNode> {
232 let mut children = Vec::new();
233 collect_children(node, &mut |child| children.push(child));
234 children
235}
236
237pub fn for_each_immediate_child<'a>(node: &'a SNode, f: &mut impl FnMut(&'a SNode)) {
241 collect_children(node, f);
242}
243
244fn collect_children<'a>(node: &'a SNode, children: &mut impl FnMut(&'a SNode)) {
245 match &node.node {
246 Node::AttributedDecl { attributes, inner } => {
247 for attr in attributes {
248 for arg in &attr.args {
249 children(&arg.value);
250 }
251 }
252 children(inner);
253 }
254 Node::Pipeline { body, .. } | Node::OverrideDecl { body, .. } => {
255 collect_nodes(body, children);
256 }
257 Node::LetBinding { pattern, value, .. } | Node::ConstBinding { pattern, value, .. } => {
258 collect_binding_pattern(pattern, children);
259 children(value);
260 }
261 Node::EnumDecl { variants, .. } => {
262 for variant in variants {
263 collect_typed_param_defaults(&variant.fields, children);
264 }
265 }
266 Node::StructDecl { .. }
267 | Node::ImportDecl { .. }
268 | Node::SelectiveImport { .. }
269 | Node::NamespaceImport { .. }
270 | Node::TypeDecl { .. }
271 | Node::BreakStmt
272 | Node::ContinueStmt => {}
273 Node::InterfaceDecl { methods, .. } => {
274 for method in methods {
275 collect_typed_param_defaults(&method.params, children);
276 }
277 }
278 Node::ImplBlock { methods, .. } => collect_nodes(methods, children),
279 Node::IfElse {
280 condition,
281 then_body,
282 else_body,
283 ..
284 } => {
285 children(condition);
286 collect_nodes(then_body, children);
287 if let Some(body) = else_body {
288 collect_nodes(body, children);
289 }
290 }
291 Node::ForIn {
292 pattern,
293 iterable,
294 body,
295 } => {
296 collect_binding_pattern(pattern, children);
297 children(iterable);
298 collect_nodes(body, children);
299 }
300 Node::MatchExpr { value, arms } => {
301 children(value);
302 for arm in arms {
303 collect_match_arm(arm, children);
304 }
305 }
306 Node::WhileLoop { condition, body } => {
307 children(condition);
308 collect_nodes(body, children);
309 }
310 Node::Retry { count, body } => {
311 children(count);
312 collect_nodes(body, children);
313 }
314 Node::CostRoute { options, body } => {
315 collect_option_values(options, children);
316 collect_nodes(body, children);
317 }
318 Node::ReturnStmt { value } | Node::YieldExpr { value } => {
319 if let Some(value) = value {
320 children(value);
321 }
322 }
323 Node::TryCatch {
324 has_catch: _,
325 body,
326 catch_body,
327 finally_body,
328 ..
329 } => {
330 collect_nodes(body, children);
331 collect_nodes(catch_body, children);
332 if let Some(body) = finally_body {
333 collect_nodes(body, children);
334 }
335 }
336 Node::TryExpr { body }
337 | Node::SpawnExpr { body }
338 | Node::ScopeBlock { body }
339 | Node::DeferStmt { body }
340 | Node::Block(body) => collect_nodes(body, children),
341 Node::Closure { params, body, .. } => {
342 collect_typed_param_defaults(params, children);
343 collect_nodes(body, children);
344 }
345 Node::MutexBlock { key, body } => {
346 if let Some(key) = key {
347 children(key);
348 }
349 collect_nodes(body, children);
350 }
351 Node::FnDecl { params, body, .. } | Node::ToolDecl { params, body, .. } => {
352 collect_typed_param_defaults(params, children);
353 collect_nodes(body, children);
354 }
355 Node::SkillDecl { fields, .. } => collect_field_values(fields, children),
356 Node::EvalPackDecl {
357 fields,
358 body,
359 summarize,
360 ..
361 } => {
362 collect_field_values(fields, children);
363 collect_nodes(body, children);
364 if let Some(body) = summarize {
365 collect_nodes(body, children);
366 }
367 }
368 Node::RangeExpr { start, end, .. } => {
369 children(start);
370 children(end);
371 }
372 Node::GuardStmt {
373 condition,
374 else_body,
375 } => {
376 children(condition);
377 collect_nodes(else_body, children);
378 }
379 Node::RequireStmt { condition, message } => {
380 children(condition);
381 if let Some(message) = message {
382 children(message);
383 }
384 }
385 Node::DeadlineBlock { duration, body } => {
386 children(duration);
387 collect_nodes(body, children);
388 }
389 Node::EmitExpr { value }
390 | Node::ThrowStmt { value }
391 | Node::Spread(value)
392 | Node::TryOperator { operand: value }
393 | Node::TryStar { operand: value }
394 | Node::NonNullAssert { operand: value }
395 | Node::UnaryOp { operand: value, .. } => children(value),
396 Node::Parallel {
397 expr,
398 body,
399 options,
400 ..
401 } => {
402 children(expr);
403 collect_option_values(options, children);
404 collect_nodes(body, children);
405 }
406 Node::SelectExpr {
407 cases,
408 timeout,
409 default_body,
410 } => {
411 for case in cases {
412 collect_select_case(case, children);
413 }
414 if let Some((duration, body)) = timeout {
415 children(duration);
416 collect_nodes(body, children);
417 }
418 if let Some(body) = default_body {
419 collect_nodes(body, children);
420 }
421 }
422 Node::FunctionCall { args, .. } | Node::EnumConstruct { args, .. } => {
423 collect_nodes(args, children);
424 }
425 Node::ValueCall { callee, args } => {
426 children(callee);
427 collect_nodes(args, children);
428 }
429 Node::MethodCall { object, args, .. } | Node::OptionalMethodCall { object, args, .. } => {
430 children(object);
431 collect_nodes(args, children);
432 }
433 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
434 children(object);
435 }
436 Node::SubscriptAccess { object, index }
437 | Node::OptionalSubscriptAccess { object, index } => {
438 children(object);
439 children(index);
440 }
441 Node::SliceAccess { object, start, end } => {
442 children(object);
443 if let Some(start) = start {
444 children(start);
445 }
446 if let Some(end) = end {
447 children(end);
448 }
449 }
450 Node::BinaryOp { left, right, .. } => {
451 children(left);
452 children(right);
453 }
454 Node::Ternary {
455 condition,
456 true_expr,
457 false_expr,
458 } => {
459 children(condition);
460 children(true_expr);
461 children(false_expr);
462 }
463 Node::Assignment { target, value, .. } => {
464 children(target);
465 children(value);
466 }
467 Node::StructConstruct { fields, .. } | Node::DictLiteral(fields) => {
468 collect_dict_entries(fields, children);
469 }
470 Node::ListLiteral(items) | Node::OrPattern(items) => collect_nodes(items, children),
471 Node::InterpolatedString(_)
472 | Node::StringLiteral(_)
473 | Node::RawStringLiteral(_)
474 | Node::IntLiteral(_)
475 | Node::FloatLiteral(_)
476 | Node::BoolLiteral(_)
477 | Node::NilLiteral
478 | Node::Identifier(_)
479 | Node::DurationLiteral(_) => {}
480 }
481}
482
483fn collect_nodes<'a>(nodes: &'a [SNode], children: &mut impl FnMut(&'a SNode)) {
484 for node in nodes {
485 children(node);
486 }
487}
488
489fn collect_dict_entries<'a>(entries: &'a [DictEntry], children: &mut impl FnMut(&'a SNode)) {
490 for entry in entries {
491 children(&entry.key);
492 children(&entry.value);
493 }
494}
495
496fn collect_field_values<'a>(fields: &'a [(String, SNode)], children: &mut impl FnMut(&'a SNode)) {
497 for (_, value) in fields {
498 children(value);
499 }
500}
501
502fn collect_option_values<'a>(options: &'a [(String, SNode)], children: &mut impl FnMut(&'a SNode)) {
503 for (_, value) in options {
504 children(value);
505 }
506}
507
508fn collect_typed_param_defaults<'a>(
509 params: &'a [TypedParam],
510 children: &mut impl FnMut(&'a SNode),
511) {
512 for param in params {
513 if let Some(default) = ¶m.default_value {
514 children(default);
515 }
516 }
517}
518
519fn collect_match_arm<'a>(arm: &'a MatchArm, children: &mut impl FnMut(&'a SNode)) {
520 children(&arm.pattern);
521 if let Some(guard) = &arm.guard {
522 children(guard);
523 }
524 collect_nodes(&arm.body, children);
525}
526
527fn collect_select_case<'a>(case: &'a SelectCase, children: &mut impl FnMut(&'a SNode)) {
528 children(&case.channel);
529 collect_nodes(&case.body, children);
530}
531
532fn collect_binding_pattern<'a>(pattern: &'a BindingPattern, children: &mut impl FnMut(&'a SNode)) {
533 match pattern {
534 BindingPattern::Identifier(_) | BindingPattern::Pair(_, _) => {}
535 BindingPattern::Dict(fields) => {
536 for field in fields {
537 if let Some(default) = &field.default_value {
538 children(default);
539 }
540 }
541 }
542 BindingPattern::List(items) => {
543 for item in items {
544 if let Some(default) = &item.default_value {
545 children(default);
546 }
547 }
548 }
549 }
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555 use crate::ast::{spanned, BinaryOperator, Node, TypedParam, UnaryOperator};
556 use harn_lexer::Span;
557
558 fn dummy(node: Node) -> SNode {
559 spanned(node, Span::dummy())
560 }
561
562 #[test]
563 fn walk_program_preserves_preorder() {
564 let program = vec![dummy(Node::LetBinding {
565 pattern: BindingPattern::Identifier("x".to_string()),
566 type_ann: None,
567 value: Box::new(dummy(Node::BinaryOp {
568 op: BinaryOperator::Add,
569 left: Box::new(dummy(Node::IntLiteral(1))),
570 right: Box::new(dummy(Node::IntLiteral(2))),
571 })),
572 is_pub: false,
573 })];
574 let mut seen = Vec::new();
575
576 walk_program(&program, &mut |node| {
577 seen.push(match &node.node {
578 Node::LetBinding { .. } => "let",
579 Node::BinaryOp { .. } => "binary",
580 Node::IntLiteral(1) => "one",
581 Node::IntLiteral(2) => "two",
582 other => panic!("unexpected node {other:?}"),
583 });
584 });
585
586 assert_eq!(seen, vec!["let", "binary", "one", "two"]);
587 }
588
589 #[test]
590 fn identifier_receiver_access_predicate_ignores_function_calls() {
591 let plain = vec![dummy(Node::FunctionCall {
592 name: "helper".to_string(),
593 type_args: Vec::new(),
594 args: Vec::new(),
595 })];
596 assert!(!contains_identifier_receiver_access(&plain));
597
598 let qualified = vec![dummy(Node::PropertyAccess {
599 object: Box::new(dummy(Node::Identifier("Status".to_string()))),
600 property: "Ready".to_string(),
601 })];
602 assert!(contains_identifier_receiver_access(&qualified));
603 }
604
605 #[test]
606 fn enum_pattern_predicate_ignores_ordinary_property_access() {
607 let ordinary = vec![dummy(Node::PropertyAccess {
608 object: Box::new(dummy(Node::Identifier("record".to_string()))),
609 property: "field".to_string(),
610 })];
611 assert!(!contains_identifier_enum_pattern(&ordinary));
612
613 let pattern = dummy(Node::PropertyAccess {
614 object: Box::new(dummy(Node::Identifier("Status".to_string()))),
615 property: "Ready".to_string(),
616 });
617 let match_expr = dummy(Node::MatchExpr {
618 value: Box::new(dummy(Node::Identifier("value".to_string()))),
619 arms: vec![MatchArm {
620 pattern,
621 guard: None,
622 body: Vec::new(),
623 span: Span::dummy(),
624 }],
625 });
626 assert!(contains_identifier_enum_pattern(&[match_expr]));
627 }
628
629 #[test]
630 fn walk_node_handles_deep_unary_chain_iteratively() {
631 let mut node = dummy(Node::IntLiteral(0));
632 for _ in 0..10_000 {
633 node = dummy(Node::UnaryOp {
634 op: UnaryOperator::Not,
635 operand: Box::new(node),
636 });
637 }
638
639 let mut count = 0usize;
640 walk_node(&node, &mut |_| count += 1);
641
642 assert_eq!(count, 10_001);
643 }
644
645 #[test]
646 fn walk_node_visits_typed_param_defaults() {
647 let default = dummy(Node::Identifier("fallback".to_string()));
648 let node = dummy(Node::FnDecl {
649 name: "load".to_string(),
650 type_params: Vec::new(),
651 params: vec![TypedParam {
652 name: "root".to_string(),
653 type_expr: None,
654 default_value: Some(Box::new(default)),
655 rest: false,
656 span: harn_lexer::Span::dummy(),
657 }],
658 type_predicate: None,
659 return_type: None,
660 throws: None,
661 where_clauses: Vec::new(),
662 body: Vec::new(),
663 is_pub: false,
664 is_stream: false,
665 });
666 let mut seen = Vec::new();
667
668 walk_node(&node, &mut |node| {
669 if let Node::Identifier(name) = &node.node {
670 seen.push(name.clone());
671 }
672 });
673
674 assert_eq!(seen, vec!["fallback"]);
675 }
676}