Skip to main content

php_ast/
visitor.rs

1use std::ops::ControlFlow;
2
3use crate::ast::*;
4
5/// Visitor trait for immutable AST traversal.
6///
7/// All methods return `ControlFlow<()>`:
8/// - `ControlFlow::Continue(())` — keep walking.
9/// - `ControlFlow::Break(())` — stop the entire traversal immediately.
10///
11/// Default implementations recursively walk child nodes, so implementors
12/// only need to override the node types they care about.
13///
14/// To **skip** a subtree, override the method and return `Continue(())`
15/// without calling the corresponding `walk_*` function.
16///
17/// # Example
18///
19/// ```
20/// use php_ast::visitor::{Visitor, walk_expr};
21/// use php_ast::ast::*;
22/// use std::ops::ControlFlow;
23///
24/// struct VarCounter { count: usize }
25///
26/// impl<'arena, 'src> Visitor<'arena, 'src> for VarCounter {
27///     fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
28///         if matches!(&expr.kind, ExprKind::Variable(_)) {
29///             self.count += 1;
30///         }
31///         walk_expr(self, expr)
32///     }
33/// }
34/// ```
35pub trait Visitor<'arena, 'src> {
36    /// Visits the root program node.
37    fn visit_program(&mut self, program: &Program<'arena, 'src>) -> ControlFlow<()> {
38        walk_program(self, program)
39    }
40
41    /// Visits a statement.
42    fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<()> {
43        walk_stmt(self, stmt)
44    }
45
46    /// Visits a block of statements.
47    fn visit_block(&mut self, block: &Block<'arena, 'src>) -> ControlFlow<()> {
48        walk_block(self, block)
49    }
50
51    /// Visits an expression.
52    fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
53        walk_expr(self, expr)
54    }
55
56    /// Visits a function parameter.
57    fn visit_param(&mut self, param: &Param<'arena, 'src>) -> ControlFlow<()> {
58        walk_param(self, param)
59    }
60
61    /// Visits a call argument.
62    fn visit_arg(&mut self, arg: &Arg<'arena, 'src>) -> ControlFlow<()> {
63        walk_arg(self, arg)
64    }
65
66    /// Visits a class-like member.
67    fn visit_class_member(&mut self, member: &ClassMember<'arena, 'src>) -> ControlFlow<()> {
68        walk_class_member(self, member)
69    }
70
71    /// Visits an enum member.
72    fn visit_enum_member(&mut self, member: &EnumMember<'arena, 'src>) -> ControlFlow<()> {
73        walk_enum_member(self, member)
74    }
75
76    /// Visits a property hook (`get`/`set`).
77    fn visit_property_hook(&mut self, hook: &PropertyHook<'arena, 'src>) -> ControlFlow<()> {
78        walk_property_hook(self, hook)
79    }
80
81    /// Visits a type hint.
82    fn visit_type_hint(&mut self, type_hint: &TypeHint<'arena, 'src>) -> ControlFlow<()> {
83        walk_type_hint(self, type_hint)
84    }
85
86    /// Visits an attribute.
87    fn visit_attribute(&mut self, attribute: &Attribute<'arena, 'src>) -> ControlFlow<()> {
88        walk_attribute(self, attribute)
89    }
90
91    /// Visits a `catch` clause.
92    fn visit_catch_clause(&mut self, catch: &CatchClause<'arena, 'src>) -> ControlFlow<()> {
93        walk_catch_clause(self, catch)
94    }
95
96    /// Visits a `match` arm.
97    fn visit_match_arm(&mut self, arm: &MatchArm<'arena, 'src>) -> ControlFlow<()> {
98        walk_match_arm(self, arm)
99    }
100
101    /// Visits a closure `use` variable.
102    fn visit_closure_use_var(&mut self, _var: &ClosureUseVar<'src>) -> ControlFlow<()> {
103        ControlFlow::Continue(())
104    }
105
106    /// Visits a `use Trait;` declaration.
107    fn visit_trait_use(&mut self, trait_use: &TraitUseDecl<'arena, 'src>) -> ControlFlow<()> {
108        walk_trait_use(self, trait_use)
109    }
110
111    /// Visits a trait `insteadof`/`as` adaptation.
112    fn visit_trait_adaptation(
113        &mut self,
114        _adaptation: &TraitAdaptation<'arena, 'src>,
115    ) -> ControlFlow<()> {
116        ControlFlow::Continue(())
117    }
118
119    /// Visits a name reference.
120    fn visit_name(&mut self, _name: &Name<'arena, 'src>) -> ControlFlow<()> {
121        ControlFlow::Continue(())
122    }
123
124    /// Called for each comment when driven by [`walk_comments`].
125    ///
126    /// Comments live in `ParseResult::comments`
127    /// separately from AST nodes. Use [`walk_comments`] to drive this hook.
128    fn visit_comment(&mut self, _comment: &Comment<'src>) -> ControlFlow<()> {
129        ControlFlow::Continue(())
130    }
131}
132
133// =============================================================================
134// Walk functions
135// =============================================================================
136
137/// Calls [`Visitor::visit_name`] on `name`.
138///
139/// The default [`Visitor::visit_name`] is a leaf — it does nothing and returns
140/// `Continue(())`. Override it if you need to inspect name nodes.
141pub fn walk_name<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
142    visitor: &mut V,
143    name: &Name<'arena, 'src>,
144) -> ControlFlow<()> {
145    visitor.visit_name(name)
146}
147
148/// Calls [`Visitor::visit_comment`] for each comment in `comments`.
149///
150/// Comments are stored separately from the AST in
151/// `ParseResult::comments` — this function is the bridge.
152/// Call it alongside [`walk_program`] to cover the full source:
153///
154/// ```ignore
155/// walk_comments(&mut visitor, &result.comments)?;
156/// visitor.visit_program(&result.program)?;
157/// ```
158///
159/// Order (before vs. after the program walk) is up to the caller.
160pub fn walk_comments<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
161    visitor: &mut V,
162    comments: &[Comment<'src>],
163) -> ControlFlow<()> {
164    for comment in comments {
165        visitor.visit_comment(comment)?;
166    }
167    ControlFlow::Continue(())
168}
169
170/// Visits every top-level statement in `program` by calling [`Visitor::visit_stmt`].
171///
172/// This is the entry point for a full-file traversal. Call it from
173/// [`Visitor::visit_program`] (which is the default) or drive it directly:
174///
175/// ```
176/// # use php_ast::visitor::{Visitor, walk_program};
177/// # use php_ast::ast::*;
178/// # use std::ops::ControlFlow;
179/// # struct V;
180/// # impl<'a, 'b> Visitor<'a, 'b> for V {}
181/// # fn example<'a, 'b>(v: &mut V, program: &Program<'a, 'b>) {
182/// walk_program(v, program);
183/// # }
184/// ```
185pub fn walk_program<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
186    visitor: &mut V,
187    program: &Program<'arena, 'src>,
188) -> ControlFlow<()> {
189    for stmt in program.stmts.iter() {
190        visitor.visit_stmt(stmt)?;
191    }
192    ControlFlow::Continue(())
193}
194
195/// Visits each statement in a block.
196pub fn walk_block<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
197    visitor: &mut V,
198    block: &Block<'arena, 'src>,
199) -> ControlFlow<()> {
200    for stmt in block.stmts.iter() {
201        visitor.visit_stmt(stmt)?;
202    }
203    ControlFlow::Continue(())
204}
205
206/// Dispatches `stmt` to the appropriate child visitors based on its [`StmtKind`].
207///
208/// Call this from [`Visitor::visit_stmt`] to recurse into a statement's children.
209/// Omit the call to skip the subtree entirely.
210pub fn walk_stmt<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
211    visitor: &mut V,
212    stmt: &Stmt<'arena, 'src>,
213) -> ControlFlow<()> {
214    match &stmt.kind {
215        StmtKind::Expression(expr) => {
216            visitor.visit_expr(expr)?;
217        }
218        StmtKind::Echo(exprs) => {
219            for expr in exprs.iter() {
220                visitor.visit_expr(expr)?;
221            }
222        }
223        StmtKind::Return(expr) => {
224            if let Some(expr) = expr {
225                visitor.visit_expr(expr)?;
226            }
227        }
228        StmtKind::Block(block) => {
229            visitor.visit_block(block)?;
230        }
231        StmtKind::If(if_stmt) => {
232            visitor.visit_expr(&if_stmt.condition)?;
233            visitor.visit_stmt(if_stmt.then_branch)?;
234            for elseif in if_stmt.elseif_branches.iter() {
235                visitor.visit_expr(&elseif.condition)?;
236                visitor.visit_stmt(&elseif.body)?;
237            }
238            if let Some(else_branch) = &if_stmt.else_branch {
239                visitor.visit_stmt(else_branch)?;
240            }
241        }
242        StmtKind::While(while_stmt) => {
243            visitor.visit_expr(&while_stmt.condition)?;
244            visitor.visit_stmt(while_stmt.body)?;
245        }
246        StmtKind::For(for_stmt) => {
247            for expr in for_stmt.init.iter() {
248                visitor.visit_expr(expr)?;
249            }
250            for expr in for_stmt.condition.iter() {
251                visitor.visit_expr(expr)?;
252            }
253            for expr in for_stmt.update.iter() {
254                visitor.visit_expr(expr)?;
255            }
256            visitor.visit_stmt(for_stmt.body)?;
257        }
258        StmtKind::Foreach(foreach_stmt) => {
259            visitor.visit_expr(&foreach_stmt.expr)?;
260            if let Some(key) = &foreach_stmt.key {
261                visitor.visit_expr(key)?;
262            }
263            visitor.visit_expr(&foreach_stmt.value)?;
264            visitor.visit_stmt(foreach_stmt.body)?;
265        }
266        StmtKind::DoWhile(do_while) => {
267            visitor.visit_stmt(do_while.body)?;
268            visitor.visit_expr(&do_while.condition)?;
269        }
270        StmtKind::Function(func) => {
271            walk_function_like(visitor, &func.attributes, &func.params, &func.return_type)?;
272            visitor.visit_block(func.body)?;
273        }
274        StmtKind::Break(expr) | StmtKind::Continue(expr) => {
275            if let Some(expr) = expr {
276                visitor.visit_expr(expr)?;
277            }
278        }
279        StmtKind::Switch(switch_stmt) => {
280            visitor.visit_expr(&switch_stmt.expr)?;
281            for case in switch_stmt.body.cases.iter() {
282                if let Some(value) = &case.value {
283                    visitor.visit_expr(value)?;
284                }
285                for stmt in case.body.iter() {
286                    visitor.visit_stmt(stmt)?;
287                }
288            }
289        }
290        StmtKind::Throw(expr) => {
291            visitor.visit_expr(expr)?;
292        }
293        StmtKind::TryCatch(tc) => {
294            visitor.visit_block(tc.body)?;
295            for catch in tc.catches.iter() {
296                visitor.visit_catch_clause(catch)?;
297            }
298            if let Some(finally) = tc.finally {
299                visitor.visit_block(finally)?;
300            }
301        }
302        StmtKind::Declare(decl) => {
303            for (_, expr) in decl.directives.iter() {
304                visitor.visit_expr(expr)?;
305            }
306            if let Some(body) = decl.body {
307                visitor.visit_stmt(body)?;
308            }
309        }
310        StmtKind::Unset(exprs) | StmtKind::Global(exprs) => {
311            for expr in exprs.iter() {
312                visitor.visit_expr(expr)?;
313            }
314        }
315        StmtKind::Class(class) => {
316            walk_attributes(visitor, &class.attributes)?;
317            if let Some(extends) = &class.extends {
318                visitor.visit_name(extends)?;
319            }
320            for name in class.implements.iter() {
321                visitor.visit_name(name)?;
322            }
323            for member in class.body.members.iter() {
324                visitor.visit_class_member(member)?;
325            }
326        }
327        StmtKind::Interface(iface) => {
328            walk_attributes(visitor, &iface.attributes)?;
329            for name in iface.extends.iter() {
330                visitor.visit_name(name)?;
331            }
332            for member in iface.body.members.iter() {
333                visitor.visit_class_member(member)?;
334            }
335        }
336        StmtKind::Trait(trait_decl) => {
337            walk_attributes(visitor, &trait_decl.attributes)?;
338            for member in trait_decl.body.members.iter() {
339                visitor.visit_class_member(member)?;
340            }
341        }
342        StmtKind::Enum(enum_decl) => {
343            walk_attributes(visitor, &enum_decl.attributes)?;
344            if let Some(scalar_type) = &enum_decl.scalar_type {
345                visitor.visit_name(scalar_type)?;
346            }
347            for name in enum_decl.implements.iter() {
348                visitor.visit_name(name)?;
349            }
350            for member in enum_decl.body.members.iter() {
351                visitor.visit_enum_member(member)?;
352            }
353        }
354        StmtKind::Namespace(ns) => {
355            if let NamespaceBody::Braced(block) = &ns.body {
356                visitor.visit_block(block)?;
357            }
358        }
359        StmtKind::Const(items) => {
360            for item in items.iter() {
361                walk_attributes(visitor, &item.attributes)?;
362                visitor.visit_expr(&item.value)?;
363            }
364        }
365        StmtKind::StaticVar(vars) => {
366            for var in vars.iter() {
367                if let Some(default) = &var.default {
368                    visitor.visit_expr(default)?;
369                }
370            }
371        }
372        StmtKind::Use(decl) => {
373            for item in decl.uses.iter() {
374                visitor.visit_name(&item.name)?;
375            }
376        }
377        StmtKind::Goto(_)
378        | StmtKind::Label(_)
379        | StmtKind::Nop
380        | StmtKind::InlineHtml(_)
381        | StmtKind::HaltCompiler(_)
382        | StmtKind::Error => {}
383    }
384    ControlFlow::Continue(())
385}
386
387/// Dispatches `expr` to the appropriate child visitors based on its [`ExprKind`].
388///
389/// Call this from [`Visitor::visit_expr`] to recurse into an expression's children.
390/// Omit the call to skip the subtree entirely.
391pub fn walk_expr<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
392    visitor: &mut V,
393    expr: &Expr<'arena, 'src>,
394) -> ControlFlow<()> {
395    match &expr.kind {
396        ExprKind::Assign(assign) => {
397            visitor.visit_expr(assign.target)?;
398            visitor.visit_expr(assign.value)?;
399        }
400        ExprKind::Binary(binary) => {
401            visitor.visit_expr(binary.left)?;
402            visitor.visit_expr(binary.right)?;
403        }
404        ExprKind::UnaryPrefix(unary) => {
405            visitor.visit_expr(unary.operand)?;
406        }
407        ExprKind::UnaryPostfix(unary) => {
408            visitor.visit_expr(unary.operand)?;
409        }
410        ExprKind::Ternary(ternary) => {
411            visitor.visit_expr(ternary.condition)?;
412            if let Some(then_expr) = &ternary.then_expr {
413                visitor.visit_expr(then_expr)?;
414            }
415            visitor.visit_expr(ternary.else_expr)?;
416        }
417        ExprKind::NullCoalesce(nc) => {
418            visitor.visit_expr(nc.left)?;
419            visitor.visit_expr(nc.right)?;
420        }
421        ExprKind::FunctionCall(call) => {
422            visitor.visit_expr(call.name)?;
423            for arg in call.args.iter() {
424                visitor.visit_arg(arg)?;
425            }
426        }
427        ExprKind::Array(elements) => {
428            for elem in elements.iter() {
429                if let Some(key) = &elem.key {
430                    visitor.visit_expr(key)?;
431                }
432                visitor.visit_expr(&elem.value)?;
433            }
434        }
435        ExprKind::ArrayAccess(access) => {
436            visitor.visit_expr(access.array)?;
437            if let Some(index) = &access.index {
438                visitor.visit_expr(index)?;
439            }
440        }
441        ExprKind::Print(expr) => {
442            visitor.visit_expr(expr)?;
443        }
444        ExprKind::Parenthesized(expr) => {
445            visitor.visit_expr(expr)?;
446        }
447        ExprKind::Cast(_, expr) => {
448            visitor.visit_expr(expr)?;
449        }
450        ExprKind::ErrorSuppress(expr) => {
451            visitor.visit_expr(expr)?;
452        }
453        ExprKind::Isset(exprs) => {
454            for expr in exprs.iter() {
455                visitor.visit_expr(expr)?;
456            }
457        }
458        ExprKind::Empty(expr) => {
459            visitor.visit_expr(expr)?;
460        }
461        ExprKind::Include(_, expr) => {
462            visitor.visit_expr(expr)?;
463        }
464        ExprKind::Eval(expr) => {
465            visitor.visit_expr(expr)?;
466        }
467        ExprKind::Exit(expr) => {
468            if let Some(expr) = expr {
469                visitor.visit_expr(expr)?;
470            }
471        }
472        ExprKind::Clone(expr) => {
473            visitor.visit_expr(expr)?;
474        }
475        ExprKind::CloneWith(object, overrides) => {
476            visitor.visit_expr(object)?;
477            visitor.visit_expr(overrides)?;
478        }
479        ExprKind::New(new_expr) => {
480            visitor.visit_expr(new_expr.class)?;
481            for arg in new_expr.args.iter() {
482                visitor.visit_arg(arg)?;
483            }
484        }
485        ExprKind::PropertyAccess(access) | ExprKind::NullsafePropertyAccess(access) => {
486            visitor.visit_expr(access.object)?;
487            visitor.visit_expr(access.property)?;
488        }
489        ExprKind::MethodCall(call) | ExprKind::NullsafeMethodCall(call) => {
490            visitor.visit_expr(call.object)?;
491            visitor.visit_expr(call.method)?;
492            for arg in call.args.iter() {
493                visitor.visit_arg(arg)?;
494            }
495        }
496        ExprKind::StaticPropertyAccess(access) | ExprKind::ClassConstAccess(access) => {
497            visitor.visit_expr(access.class)?;
498            visitor.visit_expr(access.member)?;
499        }
500        ExprKind::ClassConstAccessDynamic { class, member }
501        | ExprKind::StaticPropertyAccessDynamic { class, member } => {
502            visitor.visit_expr(class)?;
503            visitor.visit_expr(member)?;
504        }
505        ExprKind::StaticMethodCall(call) => {
506            visitor.visit_expr(call.class)?;
507            visitor.visit_expr(call.method)?;
508            for arg in call.args.iter() {
509                visitor.visit_arg(arg)?;
510            }
511        }
512        ExprKind::StaticDynMethodCall(call) => {
513            visitor.visit_expr(call.class)?;
514            visitor.visit_expr(call.method)?;
515            for arg in call.args.iter() {
516                visitor.visit_arg(arg)?;
517            }
518        }
519        ExprKind::Closure(closure) => {
520            walk_function_like(
521                visitor,
522                &closure.attributes,
523                &closure.params,
524                &closure.return_type,
525            )?;
526            for use_var in closure.use_vars.iter() {
527                visitor.visit_closure_use_var(use_var)?;
528            }
529            visitor.visit_block(closure.body)?;
530        }
531        ExprKind::ArrowFunction(arrow) => {
532            walk_function_like(
533                visitor,
534                &arrow.attributes,
535                &arrow.params,
536                &arrow.return_type,
537            )?;
538            visitor.visit_expr(arrow.body)?;
539        }
540        ExprKind::Match(match_expr) => {
541            visitor.visit_expr(match_expr.subject)?;
542            for arm in match_expr.arms.iter() {
543                visitor.visit_match_arm(arm)?;
544            }
545        }
546        ExprKind::ThrowExpr(expr) => {
547            visitor.visit_expr(expr)?;
548        }
549        ExprKind::Yield(yield_expr) => {
550            if let Some(key) = &yield_expr.key {
551                visitor.visit_expr(key)?;
552            }
553            if let Some(value) = &yield_expr.value {
554                visitor.visit_expr(value)?;
555            }
556        }
557        ExprKind::AnonymousClass(class) => {
558            walk_attributes(visitor, &class.attributes)?;
559            for member in class.body.members.iter() {
560                visitor.visit_class_member(member)?;
561            }
562        }
563        ExprKind::InterpolatedString(parts)
564        | ExprKind::Heredoc { parts, .. }
565        | ExprKind::ShellExec(parts) => {
566            for part in parts.iter() {
567                if let StringPart::Expr(e) = part {
568                    visitor.visit_expr(e)?;
569                }
570            }
571        }
572        ExprKind::VariableVariable(inner) => {
573            visitor.visit_expr(inner)?;
574        }
575        ExprKind::CallableCreate(cc) => match &cc.kind {
576            CallableCreateKind::Function(name) => visitor.visit_expr(name)?,
577            CallableCreateKind::Method { object, method }
578            | CallableCreateKind::NullsafeMethod { object, method } => {
579                visitor.visit_expr(object)?;
580                visitor.visit_expr(method)?;
581            }
582            CallableCreateKind::StaticMethod { class, method } => {
583                visitor.visit_expr(class)?;
584                visitor.visit_expr(method)?;
585            }
586        },
587        ExprKind::Int(_)
588        | ExprKind::Float(_)
589        | ExprKind::String(_)
590        | ExprKind::Bool(_)
591        | ExprKind::Null
592        | ExprKind::Omit
593        | ExprKind::Variable(_)
594        | ExprKind::Identifier(_)
595        | ExprKind::MagicConst(_)
596        | ExprKind::Nowdoc { .. }
597        | ExprKind::Error => {}
598    }
599    ControlFlow::Continue(())
600}
601
602/// Visits a function/method parameter's attributes, type hint, default expression, and property hooks.
603pub fn walk_param<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
604    visitor: &mut V,
605    param: &Param<'arena, 'src>,
606) -> ControlFlow<()> {
607    walk_attributes(visitor, &param.attributes)?;
608    if let Some(type_hint) = &param.type_hint {
609        visitor.visit_type_hint(type_hint)?;
610    }
611    if let Some(default) = &param.default {
612        visitor.visit_expr(default)?;
613    }
614    for hook in param.hooks.iter() {
615        visitor.visit_property_hook(hook)?;
616    }
617    ControlFlow::Continue(())
618}
619
620/// Visits the value expression of a call argument, skipping PHP 8.6 placeholders.
621pub fn walk_arg<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
622    visitor: &mut V,
623    arg: &Arg<'arena, 'src>,
624) -> ControlFlow<()> {
625    match &arg.value {
626        Some(value) => visitor.visit_expr(value),
627        None => ControlFlow::Continue(()),
628    }
629}
630
631/// Dispatches a class member (property, method, constant, or trait use) to its child visitors.
632pub fn walk_class_member<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
633    visitor: &mut V,
634    member: &ClassMember<'arena, 'src>,
635) -> ControlFlow<()> {
636    match &member.kind {
637        ClassMemberKind::Property(prop) => {
638            walk_property_decl(visitor, prop)?;
639        }
640        ClassMemberKind::Method(method) => {
641            walk_method_decl(visitor, method)?;
642        }
643        ClassMemberKind::ClassConst(cc) => {
644            walk_class_const_decl(visitor, cc)?;
645        }
646        ClassMemberKind::TraitUse(trait_use) => {
647            visitor.visit_trait_use(trait_use)?;
648        }
649    }
650    ControlFlow::Continue(())
651}
652
653/// Visits a property hook's attributes, parameters, and body statements or expression.
654pub fn walk_property_hook<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
655    visitor: &mut V,
656    hook: &PropertyHook<'arena, 'src>,
657) -> ControlFlow<()> {
658    walk_attributes(visitor, &hook.attributes)?;
659    for param in hook.params.iter() {
660        visitor.visit_param(param)?;
661    }
662    match &hook.body {
663        PropertyHookBody::Block(block) => {
664            visitor.visit_block(block)?;
665        }
666        PropertyHookBody::Expression(expr) => {
667            visitor.visit_expr(expr)?;
668        }
669        PropertyHookBody::Abstract => {}
670    }
671    ControlFlow::Continue(())
672}
673
674/// Dispatches an enum member (case, method, constant, or trait use) to its child visitors.
675pub fn walk_enum_member<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
676    visitor: &mut V,
677    member: &EnumMember<'arena, 'src>,
678) -> ControlFlow<()> {
679    match &member.kind {
680        EnumMemberKind::Case(case) => {
681            walk_attributes(visitor, &case.attributes)?;
682            if let Some(value) = &case.value {
683                visitor.visit_expr(value)?;
684            }
685        }
686        EnumMemberKind::Method(method) => {
687            walk_method_decl(visitor, method)?;
688        }
689        EnumMemberKind::ClassConst(cc) => {
690            walk_class_const_decl(visitor, cc)?;
691        }
692        EnumMemberKind::TraitUse(trait_use) => {
693            visitor.visit_trait_use(trait_use)?;
694        }
695    }
696    ControlFlow::Continue(())
697}
698
699/// Visits the inner types of a type hint (recursing into nullable, union, and intersection).
700pub fn walk_type_hint<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
701    visitor: &mut V,
702    type_hint: &TypeHint<'arena, 'src>,
703) -> ControlFlow<()> {
704    match &type_hint.kind {
705        TypeHintKind::Nullable(inner) => {
706            visitor.visit_type_hint(inner)?;
707        }
708        TypeHintKind::Union(types) | TypeHintKind::Intersection(types) => {
709            for ty in types.iter() {
710                visitor.visit_type_hint(ty)?;
711            }
712        }
713        TypeHintKind::Named(name) => {
714            visitor.visit_name(name)?;
715        }
716        TypeHintKind::Keyword(_, _) => {}
717    }
718    ControlFlow::Continue(())
719}
720
721/// Visits an attribute's name and argument expressions.
722pub fn walk_attribute<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
723    visitor: &mut V,
724    attribute: &Attribute<'arena, 'src>,
725) -> ControlFlow<()> {
726    visitor.visit_name(&attribute.name)?;
727    for arg in attribute.args.iter() {
728        visitor.visit_arg(arg)?;
729    }
730    ControlFlow::Continue(())
731}
732
733/// Visits a catch clause's caught type names and body statements.
734pub fn walk_catch_clause<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
735    visitor: &mut V,
736    catch: &CatchClause<'arena, 'src>,
737) -> ControlFlow<()> {
738    for ty in catch.types.iter() {
739        visitor.visit_name(ty)?;
740    }
741    visitor.visit_block(catch.body)?;
742    ControlFlow::Continue(())
743}
744
745/// Visits a match arm's condition expressions (if any) and body expression.
746pub fn walk_match_arm<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
747    visitor: &mut V,
748    arm: &MatchArm<'arena, 'src>,
749) -> ControlFlow<()> {
750    if let Some(conditions) = &arm.conditions {
751        for cond in conditions.iter() {
752            visitor.visit_expr(cond)?;
753        }
754    }
755    visitor.visit_expr(&arm.body)
756}
757
758/// Visits a trait use declaration's trait names and adaptations (`insteadof`, `as`).
759pub fn walk_trait_use<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
760    visitor: &mut V,
761    trait_use: &TraitUseDecl<'arena, 'src>,
762) -> ControlFlow<()> {
763    for name in trait_use.traits.iter() {
764        visitor.visit_name(name)?;
765    }
766    for adaptation in trait_use.adaptations.iter() {
767        visitor.visit_trait_adaptation(adaptation)?;
768    }
769    ControlFlow::Continue(())
770}
771
772// =============================================================================
773// Internal helpers — shared walking logic to avoid duplication
774// =============================================================================
775
776/// Walks the common parts of any function-like construct:
777/// attributes → params → optional return type.
778fn walk_function_like<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
779    visitor: &mut V,
780    attributes: &[Attribute<'arena, 'src>],
781    params: &[Param<'arena, 'src>],
782    return_type: &Option<TypeHint<'arena, 'src>>,
783) -> ControlFlow<()> {
784    walk_attributes(visitor, attributes)?;
785    for param in params.iter() {
786        visitor.visit_param(param)?;
787    }
788    if let Some(ret) = return_type {
789        visitor.visit_type_hint(ret)?;
790    }
791    ControlFlow::Continue(())
792}
793
794/// Walks a method declaration (shared by ClassMember and EnumMember).
795fn walk_method_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
796    visitor: &mut V,
797    method: &MethodDecl<'arena, 'src>,
798) -> ControlFlow<()> {
799    walk_function_like(
800        visitor,
801        &method.attributes,
802        &method.params,
803        &method.return_type,
804    )?;
805    if let Some(body) = method.body {
806        visitor.visit_block(body)?;
807    }
808    ControlFlow::Continue(())
809}
810
811/// Walks a class constant declaration (shared by ClassMember and EnumMember).
812fn walk_class_const_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
813    visitor: &mut V,
814    cc: &ClassConstDecl<'arena, 'src>,
815) -> ControlFlow<()> {
816    walk_attributes(visitor, &cc.attributes)?;
817    if let Some(type_hint) = &cc.type_hint {
818        visitor.visit_type_hint(type_hint)?;
819    }
820    visitor.visit_expr(&cc.value)
821}
822
823/// Walks a property declaration.
824fn walk_property_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
825    visitor: &mut V,
826    prop: &PropertyDecl<'arena, 'src>,
827) -> ControlFlow<()> {
828    walk_attributes(visitor, &prop.attributes)?;
829    if let Some(type_hint) = &prop.type_hint {
830        visitor.visit_type_hint(type_hint)?;
831    }
832    if let Some(default) = &prop.default {
833        visitor.visit_expr(default)?;
834    }
835    for hook in prop.hooks.iter() {
836        visitor.visit_property_hook(hook)?;
837    }
838    ControlFlow::Continue(())
839}
840
841fn walk_attributes<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
842    visitor: &mut V,
843    attributes: &[Attribute<'arena, 'src>],
844) -> ControlFlow<()> {
845    for attr in attributes.iter() {
846        visitor.visit_attribute(attr)?;
847    }
848    ControlFlow::Continue(())
849}
850
851// =============================================================================
852// ScopeVisitor — scope-aware traversal
853// =============================================================================
854
855/// Lexical scope context passed to each [`ScopeVisitor`] method.
856///
857/// Represents the immediately enclosing namespace, class-like definition,
858/// and named function or method at the point a node is visited.
859/// All fields are `None` when the node is at the global top level.
860///
861/// **Namespace** is set when inside a braced or simple `namespace` declaration.
862/// **`class_name`** is set inside `class`, `interface`, `trait`, and `enum`
863/// declarations; it is `None` for anonymous classes.
864/// **`function_name`** is set inside named functions and methods; it is `None`
865/// inside closures and arrow functions.
866#[derive(Debug, Clone, Copy, Default)]
867#[non_exhaustive]
868pub struct Scope<'src> {
869    /// Current namespace, or `None` for the global namespace.
870    ///
871    /// This is a borrowed slice of the original source string, so copying or
872    /// cloning the scope is always allocation-free.
873    pub namespace: Option<&'src str>,
874    /// Name of the immediately enclosing class-like declaration, or `None`.
875    pub class_name: Option<&'src str>,
876    /// Name of the immediately enclosing named function or method, or `None`.
877    pub function_name: Option<&'src str>,
878}
879
880/// A scope-aware variant of [`Visitor`].
881///
882/// Every visit method receives a [`Scope`] describing the lexical context at
883/// that node — the current namespace, enclosing class-like declaration, and
884/// enclosing named function or method.  All methods have no-op default
885/// implementations, so implementors override only what they need.
886///
887/// Drive traversal with [`ScopeWalker`], which maintains the scope
888/// automatically and calls these methods with the current context.
889///
890/// # Example
891///
892/// ```
893/// use php_ast::visitor::{ScopeVisitor, ScopeWalker, Scope};
894/// use php_ast::ast::*;
895/// use std::ops::ControlFlow;
896///
897/// struct MethodCollector { methods: Vec<String> }
898///
899/// impl<'arena, 'src> ScopeVisitor<'arena, 'src> for MethodCollector {
900///     fn visit_class_member(
901///         &mut self,
902///         member: &ClassMember<'arena, 'src>,
903///         scope: &Scope<'src>,
904///     ) -> ControlFlow<()> {
905///         if let ClassMemberKind::Method(m) = &member.kind {
906///             self.methods.push(format!(
907///                 "{}::{}",
908///                 scope.class_name.unwrap_or("<anon>"),
909///                 m.name
910///             ));
911///         }
912///         ControlFlow::Continue(())
913///     }
914/// }
915/// ```
916pub trait ScopeVisitor<'arena, 'src> {
917    /// Visits the root program node with the enclosing scope.
918    fn visit_program(
919        &mut self,
920        _program: &Program<'arena, 'src>,
921        _scope: &Scope<'src>,
922    ) -> ControlFlow<()> {
923        ControlFlow::Continue(())
924    }
925    /// Visits a statement with the enclosing scope.
926    fn visit_stmt(&mut self, _stmt: &Stmt<'arena, 'src>, _scope: &Scope<'src>) -> ControlFlow<()> {
927        ControlFlow::Continue(())
928    }
929    /// Visits an expression with the enclosing scope.
930    fn visit_expr(&mut self, _expr: &Expr<'arena, 'src>, _scope: &Scope<'src>) -> ControlFlow<()> {
931        ControlFlow::Continue(())
932    }
933    /// Visits a function parameter with the enclosing scope.
934    fn visit_param(
935        &mut self,
936        _param: &Param<'arena, 'src>,
937        _scope: &Scope<'src>,
938    ) -> ControlFlow<()> {
939        ControlFlow::Continue(())
940    }
941    /// Visits a call argument with the enclosing scope.
942    fn visit_arg(&mut self, _arg: &Arg<'arena, 'src>, _scope: &Scope<'src>) -> ControlFlow<()> {
943        ControlFlow::Continue(())
944    }
945    /// Visits a class-like member with the enclosing scope.
946    fn visit_class_member(
947        &mut self,
948        _member: &ClassMember<'arena, 'src>,
949        _scope: &Scope<'src>,
950    ) -> ControlFlow<()> {
951        ControlFlow::Continue(())
952    }
953    /// Visits an enum member with the enclosing scope.
954    fn visit_enum_member(
955        &mut self,
956        _member: &EnumMember<'arena, 'src>,
957        _scope: &Scope<'src>,
958    ) -> ControlFlow<()> {
959        ControlFlow::Continue(())
960    }
961    /// Visits a property hook (`get`/`set`) with the enclosing scope.
962    fn visit_property_hook(
963        &mut self,
964        _hook: &PropertyHook<'arena, 'src>,
965        _scope: &Scope<'src>,
966    ) -> ControlFlow<()> {
967        ControlFlow::Continue(())
968    }
969    /// Visits a type hint with the enclosing scope.
970    fn visit_type_hint(
971        &mut self,
972        _type_hint: &TypeHint<'arena, 'src>,
973        _scope: &Scope<'src>,
974    ) -> ControlFlow<()> {
975        ControlFlow::Continue(())
976    }
977    /// Visits an attribute with the enclosing scope.
978    fn visit_attribute(
979        &mut self,
980        _attribute: &Attribute<'arena, 'src>,
981        _scope: &Scope<'src>,
982    ) -> ControlFlow<()> {
983        ControlFlow::Continue(())
984    }
985    /// Visits a `catch` clause with the enclosing scope.
986    fn visit_catch_clause(
987        &mut self,
988        _catch: &CatchClause<'arena, 'src>,
989        _scope: &Scope<'src>,
990    ) -> ControlFlow<()> {
991        ControlFlow::Continue(())
992    }
993    /// Visits a `match` arm with the enclosing scope.
994    fn visit_match_arm(
995        &mut self,
996        _arm: &MatchArm<'arena, 'src>,
997        _scope: &Scope<'src>,
998    ) -> ControlFlow<()> {
999        ControlFlow::Continue(())
1000    }
1001    /// Visits a closure `use` variable with the enclosing scope.
1002    fn visit_closure_use_var(
1003        &mut self,
1004        _var: &ClosureUseVar<'src>,
1005        _scope: &Scope<'src>,
1006    ) -> ControlFlow<()> {
1007        ControlFlow::Continue(())
1008    }
1009
1010    /// Visits a `use Trait;` declaration with the enclosing scope.
1011    fn visit_trait_use(
1012        &mut self,
1013        _trait_use: &TraitUseDecl<'arena, 'src>,
1014        _scope: &Scope<'src>,
1015    ) -> ControlFlow<()> {
1016        ControlFlow::Continue(())
1017    }
1018
1019    /// Visits a trait `insteadof`/`as` adaptation with the enclosing scope.
1020    fn visit_trait_adaptation(
1021        &mut self,
1022        _adaptation: &TraitAdaptation<'arena, 'src>,
1023        _scope: &Scope<'src>,
1024    ) -> ControlFlow<()> {
1025        ControlFlow::Continue(())
1026    }
1027
1028    /// Visits a comment with the enclosing scope.
1029    fn visit_comment(&mut self, _comment: &Comment<'src>, _scope: &Scope<'src>) -> ControlFlow<()> {
1030        ControlFlow::Continue(())
1031    }
1032}
1033
1034/// Drives a [`ScopeVisitor`] over an AST, maintaining [`Scope`] automatically.
1035///
1036/// `ScopeWalker` wraps a [`ScopeVisitor`] and tracks the lexical scope as it
1037/// descends the tree, updating scope before visiting children and restoring it
1038/// on exit from scope-defining nodes (functions, classes, namespaces).
1039///
1040/// # Usage
1041///
1042/// ```no_run
1043/// # use php_ast::visitor::{ScopeWalker, ScopeVisitor, Scope};
1044/// # use php_ast::ast::*;
1045/// # use std::ops::ControlFlow;
1046/// # struct MyVisitor;
1047/// # impl<'a, 'b> ScopeVisitor<'a, 'b> for MyVisitor {}
1048/// # fn parse<'a, 'b>(_: &'a bumpalo::Bump, _: &'b str) -> Program<'a, 'b> { unimplemented!() }
1049/// let arena = bumpalo::Bump::new();
1050/// let src = "<?php class Foo { public function bar() {} }";
1051/// let program = parse(&arena, src);
1052/// let mut walker = ScopeWalker::new(src, MyVisitor);
1053/// walker.walk(&program);
1054/// let _my_visitor = walker.into_inner();
1055/// ```
1056pub struct ScopeWalker<'src, V> {
1057    inner: V,
1058    scope: Scope<'src>,
1059    src: &'src str,
1060}
1061
1062impl<'src, V> ScopeWalker<'src, V> {
1063    /// Creates a new `ScopeWalker` wrapping `inner`.
1064    ///
1065    /// `src` must be the same source string that was passed to the parser that
1066    /// produced the [`Program`] you will walk.  It is used to derive
1067    /// zero-allocation [`Scope::namespace`] slices for qualified namespace
1068    /// names (e.g. `Foo\Bar`).
1069    pub fn new(src: &'src str, inner: V) -> Self {
1070        Self {
1071            inner,
1072            scope: Scope::default(),
1073            src,
1074        }
1075    }
1076
1077    /// Consumes the walker and returns the inner visitor.
1078    pub fn into_inner(self) -> V {
1079        self.inner
1080    }
1081
1082    /// Returns a reference to the inner visitor.
1083    pub fn inner(&self) -> &V {
1084        &self.inner
1085    }
1086
1087    /// Returns a mutable reference to the inner visitor.
1088    pub fn inner_mut(&mut self) -> &mut V {
1089        &mut self.inner
1090    }
1091}
1092
1093impl<'arena, 'src, V: ScopeVisitor<'arena, 'src>> ScopeWalker<'src, V> {
1094    /// Walks `program`, calling [`ScopeVisitor`] methods with scope context.
1095    pub fn walk(&mut self, program: &Program<'arena, 'src>) -> ControlFlow<()> {
1096        self.visit_program(program)
1097    }
1098}
1099
1100impl<'arena, 'src, V: ScopeVisitor<'arena, 'src>> Visitor<'arena, 'src> for ScopeWalker<'src, V> {
1101    fn visit_program(&mut self, program: &Program<'arena, 'src>) -> ControlFlow<()> {
1102        self.inner.visit_program(program, &self.scope)?;
1103        walk_program(self, program)
1104    }
1105
1106    fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<()> {
1107        self.inner.visit_stmt(stmt, &self.scope)?;
1108        match &stmt.kind {
1109            StmtKind::Function(func) => {
1110                let prev_fn = std::mem::replace(&mut self.scope.function_name, func.name.as_str());
1111                walk_stmt(self, stmt)?;
1112                self.scope.function_name = prev_fn;
1113            }
1114            StmtKind::Class(class) => {
1115                let prev_class = self.scope.class_name;
1116                let prev_fn = self.scope.function_name.take();
1117                self.scope.class_name = class.name.and_then(|n| n.as_str());
1118                walk_stmt(self, stmt)?;
1119                self.scope.class_name = prev_class;
1120                self.scope.function_name = prev_fn;
1121            }
1122            StmtKind::Interface(iface) => {
1123                let prev_class = std::mem::replace(&mut self.scope.class_name, iface.name.as_str());
1124                let prev_fn = self.scope.function_name.take();
1125                walk_stmt(self, stmt)?;
1126                self.scope.class_name = prev_class;
1127                self.scope.function_name = prev_fn;
1128            }
1129            StmtKind::Trait(trait_decl) => {
1130                let prev_class =
1131                    std::mem::replace(&mut self.scope.class_name, trait_decl.name.as_str());
1132                let prev_fn = self.scope.function_name.take();
1133                walk_stmt(self, stmt)?;
1134                self.scope.class_name = prev_class;
1135                self.scope.function_name = prev_fn;
1136            }
1137            StmtKind::Enum(enum_decl) => {
1138                let prev_class =
1139                    std::mem::replace(&mut self.scope.class_name, enum_decl.name.as_str());
1140                let prev_fn = self.scope.function_name.take();
1141                walk_stmt(self, stmt)?;
1142                self.scope.class_name = prev_class;
1143                self.scope.function_name = prev_fn;
1144            }
1145            StmtKind::Namespace(ns) => {
1146                let ns_str = ns.name.as_ref().map(|n| n.src_repr(self.src));
1147                match ns.body {
1148                    NamespaceBody::Braced(_) => {
1149                        let prev_ns = self.scope.namespace;
1150                        let prev_class = self.scope.class_name.take();
1151                        let prev_fn = self.scope.function_name.take();
1152                        self.scope.namespace = ns_str;
1153                        walk_stmt(self, stmt)?;
1154                        self.scope.namespace = prev_ns;
1155                        self.scope.class_name = prev_class;
1156                        self.scope.function_name = prev_fn;
1157                    }
1158                    NamespaceBody::Simple => {
1159                        // Simple namespace: update scope and leave it set for
1160                        // the remainder of the file (no push/pop).
1161                        self.scope.namespace = ns_str;
1162                        self.scope.class_name = None;
1163                        self.scope.function_name = None;
1164                    }
1165                }
1166            }
1167            _ => {
1168                walk_stmt(self, stmt)?;
1169            }
1170        }
1171        ControlFlow::Continue(())
1172    }
1173
1174    fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1175        self.inner.visit_expr(expr, &self.scope)?;
1176        match &expr.kind {
1177            ExprKind::Closure(_) | ExprKind::ArrowFunction(_) => {
1178                let prev_fn = self.scope.function_name.take();
1179                walk_expr(self, expr)?;
1180                self.scope.function_name = prev_fn;
1181            }
1182            ExprKind::AnonymousClass(_) => {
1183                let prev_class = self.scope.class_name.take();
1184                let prev_fn = self.scope.function_name.take();
1185                walk_expr(self, expr)?;
1186                self.scope.class_name = prev_class;
1187                self.scope.function_name = prev_fn;
1188            }
1189            _ => {
1190                walk_expr(self, expr)?;
1191            }
1192        }
1193        ControlFlow::Continue(())
1194    }
1195
1196    fn visit_class_member(&mut self, member: &ClassMember<'arena, 'src>) -> ControlFlow<()> {
1197        self.inner.visit_class_member(member, &self.scope)?;
1198        if let ClassMemberKind::Method(method) = &member.kind {
1199            let prev_fn = std::mem::replace(&mut self.scope.function_name, method.name.as_str());
1200            walk_class_member(self, member)?;
1201            self.scope.function_name = prev_fn;
1202        } else {
1203            walk_class_member(self, member)?;
1204        }
1205        ControlFlow::Continue(())
1206    }
1207
1208    fn visit_enum_member(&mut self, member: &EnumMember<'arena, 'src>) -> ControlFlow<()> {
1209        self.inner.visit_enum_member(member, &self.scope)?;
1210        if let EnumMemberKind::Method(method) = &member.kind {
1211            let prev_fn = std::mem::replace(&mut self.scope.function_name, method.name.as_str());
1212            walk_enum_member(self, member)?;
1213            self.scope.function_name = prev_fn;
1214        } else {
1215            walk_enum_member(self, member)?;
1216        }
1217        ControlFlow::Continue(())
1218    }
1219
1220    fn visit_param(&mut self, param: &Param<'arena, 'src>) -> ControlFlow<()> {
1221        self.inner.visit_param(param, &self.scope)?;
1222        walk_param(self, param)
1223    }
1224
1225    fn visit_arg(&mut self, arg: &Arg<'arena, 'src>) -> ControlFlow<()> {
1226        self.inner.visit_arg(arg, &self.scope)?;
1227        walk_arg(self, arg)
1228    }
1229
1230    fn visit_property_hook(&mut self, hook: &PropertyHook<'arena, 'src>) -> ControlFlow<()> {
1231        self.inner.visit_property_hook(hook, &self.scope)?;
1232        walk_property_hook(self, hook)
1233    }
1234
1235    fn visit_type_hint(&mut self, type_hint: &TypeHint<'arena, 'src>) -> ControlFlow<()> {
1236        self.inner.visit_type_hint(type_hint, &self.scope)?;
1237        walk_type_hint(self, type_hint)
1238    }
1239
1240    fn visit_attribute(&mut self, attribute: &Attribute<'arena, 'src>) -> ControlFlow<()> {
1241        self.inner.visit_attribute(attribute, &self.scope)?;
1242        walk_attribute(self, attribute)
1243    }
1244
1245    fn visit_catch_clause(&mut self, catch: &CatchClause<'arena, 'src>) -> ControlFlow<()> {
1246        self.inner.visit_catch_clause(catch, &self.scope)?;
1247        walk_catch_clause(self, catch)
1248    }
1249
1250    fn visit_match_arm(&mut self, arm: &MatchArm<'arena, 'src>) -> ControlFlow<()> {
1251        self.inner.visit_match_arm(arm, &self.scope)?;
1252        walk_match_arm(self, arm)
1253    }
1254
1255    fn visit_closure_use_var(&mut self, var: &ClosureUseVar<'src>) -> ControlFlow<()> {
1256        self.inner.visit_closure_use_var(var, &self.scope)
1257    }
1258
1259    fn visit_trait_use(&mut self, trait_use: &TraitUseDecl<'arena, 'src>) -> ControlFlow<()> {
1260        self.inner.visit_trait_use(trait_use, &self.scope)?;
1261        walk_trait_use(self, trait_use)
1262    }
1263
1264    fn visit_trait_adaptation(
1265        &mut self,
1266        adaptation: &TraitAdaptation<'arena, 'src>,
1267    ) -> ControlFlow<()> {
1268        self.inner.visit_trait_adaptation(adaptation, &self.scope)
1269    }
1270
1271    fn visit_comment(&mut self, comment: &Comment<'src>) -> ControlFlow<()> {
1272        self.inner.visit_comment(comment, &self.scope)
1273    }
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278    use super::*;
1279    use crate::Span;
1280    // =========================================================================
1281    // Unit tests with hand-built ASTs
1282    // =========================================================================
1283
1284    struct VarCounter {
1285        count: usize,
1286    }
1287
1288    impl<'arena, 'src> Visitor<'arena, 'src> for VarCounter {
1289        fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1290            if matches!(&expr.kind, ExprKind::Variable(_)) {
1291                self.count += 1;
1292            }
1293            walk_expr(self, expr)
1294        }
1295    }
1296
1297    #[test]
1298    fn counts_variables() {
1299        let arena = bumpalo::Bump::new();
1300        let var_x = arena.alloc(Expr {
1301            kind: ExprKind::Variable(NameStr::__src("x")),
1302            span: Span::DUMMY,
1303        });
1304        let var_y = arena.alloc(Expr {
1305            kind: ExprKind::Variable(NameStr::__src("y")),
1306            span: Span::DUMMY,
1307        });
1308        let var_z = arena.alloc(Expr {
1309            kind: ExprKind::Variable(NameStr::__src("z")),
1310            span: Span::DUMMY,
1311        });
1312        let binary = arena.alloc(Expr {
1313            kind: ExprKind::Binary(BinaryExpr {
1314                left: var_y,
1315                op: BinaryOp::Add,
1316                right: var_z,
1317            }),
1318            span: Span::DUMMY,
1319        });
1320        let assign = arena.alloc(Expr {
1321            kind: ExprKind::Assign(AssignExpr {
1322                target: var_x,
1323                op: AssignOp::Assign,
1324                value: binary,
1325                by_ref: false,
1326            }),
1327            span: Span::DUMMY,
1328        });
1329        let mut stmts = ArenaVec::new_in(&arena);
1330        stmts.push(Stmt {
1331            kind: StmtKind::Expression(assign),
1332            span: Span::DUMMY,
1333            doc_comment: None,
1334        });
1335        let program = Program {
1336            stmts,
1337            span: Span::DUMMY,
1338        };
1339
1340        let mut v = VarCounter { count: 0 };
1341        let _ = v.visit_program(&program);
1342        assert_eq!(v.count, 3);
1343    }
1344
1345    #[test]
1346    fn early_termination() {
1347        let arena = bumpalo::Bump::new();
1348        let var_a = arena.alloc(Expr {
1349            kind: ExprKind::Variable(NameStr::__src("a")),
1350            span: Span::DUMMY,
1351        });
1352        let var_b = arena.alloc(Expr {
1353            kind: ExprKind::Variable(NameStr::__src("b")),
1354            span: Span::DUMMY,
1355        });
1356        let binary = arena.alloc(Expr {
1357            kind: ExprKind::Binary(BinaryExpr {
1358                left: var_a,
1359                op: BinaryOp::Add,
1360                right: var_b,
1361            }),
1362            span: Span::DUMMY,
1363        });
1364        let mut stmts = ArenaVec::new_in(&arena);
1365        stmts.push(Stmt {
1366            kind: StmtKind::Expression(binary),
1367            span: Span::DUMMY,
1368            doc_comment: None,
1369        });
1370        let program = Program {
1371            stmts,
1372            span: Span::DUMMY,
1373        };
1374
1375        struct FindFirst {
1376            found: Option<String>,
1377        }
1378        impl<'arena, 'src> Visitor<'arena, 'src> for FindFirst {
1379            fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1380                if let ExprKind::Variable(name) = &expr.kind {
1381                    self.found = Some(name.to_string());
1382                    return ControlFlow::Break(());
1383                }
1384                walk_expr(self, expr)
1385            }
1386        }
1387
1388        let mut finder = FindFirst { found: None };
1389        let result = finder.visit_program(&program);
1390        assert!(result.is_break());
1391        assert_eq!(finder.found.as_deref(), Some("a"));
1392    }
1393
1394    #[test]
1395    fn skip_subtree() {
1396        let arena = bumpalo::Bump::new();
1397        // 1 + 2; function foo() { 3 + 4; }
1398        let one = arena.alloc(Expr {
1399            kind: ExprKind::Int(1),
1400            span: Span::DUMMY,
1401        });
1402        let two = arena.alloc(Expr {
1403            kind: ExprKind::Int(2),
1404            span: Span::DUMMY,
1405        });
1406        let top = arena.alloc(Expr {
1407            kind: ExprKind::Binary(BinaryExpr {
1408                left: one,
1409                op: BinaryOp::Add,
1410                right: two,
1411            }),
1412            span: Span::DUMMY,
1413        });
1414        let three = arena.alloc(Expr {
1415            kind: ExprKind::Int(3),
1416            span: Span::DUMMY,
1417        });
1418        let four = arena.alloc(Expr {
1419            kind: ExprKind::Int(4),
1420            span: Span::DUMMY,
1421        });
1422        let inner = arena.alloc(Expr {
1423            kind: ExprKind::Binary(BinaryExpr {
1424                left: three,
1425                op: BinaryOp::Add,
1426                right: four,
1427            }),
1428            span: Span::DUMMY,
1429        });
1430        let mut func_body_stmts = ArenaVec::new_in(&arena);
1431        func_body_stmts.push(Stmt {
1432            kind: StmtKind::Expression(inner),
1433            span: Span::DUMMY,
1434            doc_comment: None,
1435        });
1436        let func_body = arena.alloc(Block {
1437            stmts: func_body_stmts,
1438            span: Span::DUMMY,
1439        });
1440        let func = arena.alloc(FunctionDecl {
1441            name: Ident::name("foo"),
1442            params: ArenaVec::new_in(&arena),
1443            body: func_body,
1444            return_type: None,
1445            by_ref: false,
1446            attributes: ArenaVec::new_in(&arena),
1447            doc_comment: None,
1448        });
1449        let mut stmts = ArenaVec::new_in(&arena);
1450        stmts.push(Stmt {
1451            kind: StmtKind::Expression(top),
1452            span: Span::DUMMY,
1453            doc_comment: None,
1454        });
1455        stmts.push(Stmt {
1456            kind: StmtKind::Function(func),
1457            span: Span::DUMMY,
1458            doc_comment: None,
1459        });
1460        let program = Program {
1461            stmts,
1462            span: Span::DUMMY,
1463        };
1464
1465        struct SkipFunctions {
1466            expr_count: usize,
1467        }
1468        impl<'arena, 'src> Visitor<'arena, 'src> for SkipFunctions {
1469            fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1470                self.expr_count += 1;
1471                walk_expr(self, expr)
1472            }
1473            fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<()> {
1474                if matches!(&stmt.kind, StmtKind::Function(_)) {
1475                    return ControlFlow::Continue(());
1476                }
1477                walk_stmt(self, stmt)
1478            }
1479        }
1480
1481        let mut v = SkipFunctions { expr_count: 0 };
1482        let _ = v.visit_program(&program);
1483        // Only top-level: binary(1, 2) = 3 exprs
1484        assert_eq!(v.expr_count, 3);
1485    }
1486}