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