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