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