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