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::Parallel {
351 expr,
352 body,
353 options,
354 ..
355 } => {
356 children(expr);
357 collect_option_values(options, children);
358 collect_nodes(body, children);
359 }
360 Node::SelectExpr {
361 cases,
362 timeout,
363 default_body,
364 } => {
365 for case in cases {
366 collect_select_case(case, children);
367 }
368 if let Some((duration, body)) = timeout {
369 children(duration);
370 collect_nodes(body, children);
371 }
372 if let Some(body) = default_body {
373 collect_nodes(body, children);
374 }
375 }
376 Node::FunctionCall { args, .. } | Node::EnumConstruct { args, .. } => {
377 collect_nodes(args, children);
378 }
379 Node::ValueCall { callee, args } => {
380 children(callee);
381 collect_nodes(args, children);
382 }
383 Node::MethodCall { object, args, .. } | Node::OptionalMethodCall { object, args, .. } => {
384 children(object);
385 collect_nodes(args, children);
386 }
387 Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
388 children(object);
389 }
390 Node::SubscriptAccess { object, index }
391 | Node::OptionalSubscriptAccess { object, index } => {
392 children(object);
393 children(index);
394 }
395 Node::SliceAccess { object, start, end } => {
396 children(object);
397 if let Some(start) = start {
398 children(start);
399 }
400 if let Some(end) = end {
401 children(end);
402 }
403 }
404 Node::BinaryOp { left, right, .. } => {
405 children(left);
406 children(right);
407 }
408 Node::Ternary {
409 condition,
410 true_expr,
411 false_expr,
412 } => {
413 children(condition);
414 children(true_expr);
415 children(false_expr);
416 }
417 Node::Assignment { target, value, .. } => {
418 children(target);
419 children(value);
420 }
421 Node::StructConstruct { fields, .. } | Node::DictLiteral(fields) => {
422 collect_dict_entries(fields, children);
423 }
424 Node::ListLiteral(items) | Node::OrPattern(items) => collect_nodes(items, children),
425 Node::InterpolatedString(_)
426 | Node::StringLiteral(_)
427 | Node::RawStringLiteral(_)
428 | Node::IntLiteral(_)
429 | Node::FloatLiteral(_)
430 | Node::BoolLiteral(_)
431 | Node::NilLiteral
432 | Node::Identifier(_)
433 | Node::DurationLiteral(_) => {}
434 }
435}
436
437fn collect_nodes<'a>(nodes: &'a [SNode], children: &mut impl FnMut(&'a SNode)) {
438 for node in nodes {
439 children(node);
440 }
441}
442
443fn collect_dict_entries<'a>(entries: &'a [DictEntry], children: &mut impl FnMut(&'a SNode)) {
444 for entry in entries {
445 children(&entry.key);
446 children(&entry.value);
447 }
448}
449
450fn collect_field_values<'a>(fields: &'a [(String, SNode)], children: &mut impl FnMut(&'a SNode)) {
451 for (_, value) in fields {
452 children(value);
453 }
454}
455
456fn collect_option_values<'a>(options: &'a [(String, SNode)], children: &mut impl FnMut(&'a SNode)) {
457 for (_, value) in options {
458 children(value);
459 }
460}
461
462fn collect_typed_param_defaults<'a>(
463 params: &'a [TypedParam],
464 children: &mut impl FnMut(&'a SNode),
465) {
466 for param in params {
467 if let Some(default) = ¶m.default_value {
468 children(default);
469 }
470 }
471}
472
473fn collect_match_arm<'a>(arm: &'a MatchArm, children: &mut impl FnMut(&'a SNode)) {
474 children(&arm.pattern);
475 if let Some(guard) = &arm.guard {
476 children(guard);
477 }
478 collect_nodes(&arm.body, children);
479}
480
481fn collect_select_case<'a>(case: &'a SelectCase, children: &mut impl FnMut(&'a SNode)) {
482 children(&case.channel);
483 collect_nodes(&case.body, children);
484}
485
486fn collect_binding_pattern<'a>(pattern: &'a BindingPattern, children: &mut impl FnMut(&'a SNode)) {
487 match pattern {
488 BindingPattern::Identifier(_) | BindingPattern::Pair(_, _) => {}
489 BindingPattern::Dict(fields) => {
490 for field in fields {
491 if let Some(default) = &field.default_value {
492 children(default);
493 }
494 }
495 }
496 BindingPattern::List(items) => {
497 for item in items {
498 if let Some(default) = &item.default_value {
499 children(default);
500 }
501 }
502 }
503 }
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509 use crate::ast::{spanned, Node, TypedParam};
510 use harn_lexer::Span;
511
512 fn dummy(node: Node) -> SNode {
513 spanned(node, Span::dummy())
514 }
515
516 #[test]
517 fn walk_program_preserves_preorder() {
518 let program = vec![dummy(Node::LetBinding {
519 pattern: BindingPattern::Identifier("x".to_string()),
520 type_ann: None,
521 value: Box::new(dummy(Node::BinaryOp {
522 op: "+".to_string(),
523 left: Box::new(dummy(Node::IntLiteral(1))),
524 right: Box::new(dummy(Node::IntLiteral(2))),
525 })),
526 is_pub: false,
527 })];
528 let mut seen = Vec::new();
529
530 walk_program(&program, &mut |node| {
531 seen.push(match &node.node {
532 Node::LetBinding { .. } => "let",
533 Node::BinaryOp { .. } => "binary",
534 Node::IntLiteral(1) => "one",
535 Node::IntLiteral(2) => "two",
536 other => panic!("unexpected node {other:?}"),
537 });
538 });
539
540 assert_eq!(seen, vec!["let", "binary", "one", "two"]);
541 }
542
543 #[test]
544 fn identifier_receiver_access_predicate_ignores_function_calls() {
545 let plain = vec![dummy(Node::FunctionCall {
546 name: "helper".to_string(),
547 type_args: Vec::new(),
548 args: Vec::new(),
549 })];
550 assert!(!contains_identifier_receiver_access(&plain));
551
552 let qualified = vec![dummy(Node::PropertyAccess {
553 object: Box::new(dummy(Node::Identifier("Status".to_string()))),
554 property: "Ready".to_string(),
555 })];
556 assert!(contains_identifier_receiver_access(&qualified));
557 }
558
559 #[test]
560 fn enum_pattern_predicate_ignores_ordinary_property_access() {
561 let ordinary = vec![dummy(Node::PropertyAccess {
562 object: Box::new(dummy(Node::Identifier("record".to_string()))),
563 property: "field".to_string(),
564 })];
565 assert!(!contains_identifier_enum_pattern(&ordinary));
566
567 let pattern = dummy(Node::PropertyAccess {
568 object: Box::new(dummy(Node::Identifier("Status".to_string()))),
569 property: "Ready".to_string(),
570 });
571 let match_expr = dummy(Node::MatchExpr {
572 value: Box::new(dummy(Node::Identifier("value".to_string()))),
573 arms: vec![MatchArm {
574 pattern,
575 guard: None,
576 body: Vec::new(),
577 span: Span::dummy(),
578 }],
579 });
580 assert!(contains_identifier_enum_pattern(&[match_expr]));
581 }
582
583 #[test]
584 fn walk_node_handles_deep_unary_chain_iteratively() {
585 let mut node = dummy(Node::IntLiteral(0));
586 for _ in 0..10_000 {
587 node = dummy(Node::UnaryOp {
588 op: "!".to_string(),
589 operand: Box::new(node),
590 });
591 }
592
593 let mut count = 0usize;
594 walk_node(&node, &mut |_| count += 1);
595
596 assert_eq!(count, 10_001);
597 }
598
599 #[test]
600 fn walk_node_visits_typed_param_defaults() {
601 let default = dummy(Node::Identifier("fallback".to_string()));
602 let node = dummy(Node::FnDecl {
603 name: "load".to_string(),
604 type_params: Vec::new(),
605 params: vec![TypedParam {
606 name: "root".to_string(),
607 type_expr: None,
608 default_value: Some(Box::new(default)),
609 rest: false,
610 span: harn_lexer::Span::dummy(),
611 }],
612 type_predicate: None,
613 return_type: None,
614 throws: None,
615 where_clauses: Vec::new(),
616 body: Vec::new(),
617 is_pub: false,
618 is_stream: false,
619 });
620 let mut seen = Vec::new();
621
622 walk_node(&node, &mut |node| {
623 if let Node::Identifier(name) = &node.node {
624 seen.push(name.clone());
625 }
626 });
627
628 assert_eq!(seen, vec!["fallback"]);
629 }
630}