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, trait use, or property) 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        EnumMemberKind::Property(prop) => {
721            walk_property_decl(visitor, prop)?;
722        }
723    }
724    ControlFlow::Continue(())
725}
726
727/// Visits the inner types of a type hint (recursing into nullable, union, and intersection).
728pub fn walk_type_hint<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
729    visitor: &mut V,
730    type_hint: &TypeHint<'arena, 'src>,
731) -> ControlFlow<V::Break> {
732    match &type_hint.kind {
733        TypeHintKind::Nullable(inner) => {
734            visitor.visit_type_hint(inner)?;
735        }
736        TypeHintKind::Union(types) | TypeHintKind::Intersection(types) => {
737            for ty in types.iter() {
738                visitor.visit_type_hint(ty)?;
739            }
740        }
741        TypeHintKind::Named(name) => {
742            visitor.visit_name(name)?;
743        }
744        TypeHintKind::Keyword(_, _) => {}
745    }
746    ControlFlow::Continue(())
747}
748
749/// Visits an attribute's name and argument expressions.
750pub fn walk_attribute<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
751    visitor: &mut V,
752    attribute: &Attribute<'arena, 'src>,
753) -> ControlFlow<V::Break> {
754    visitor.visit_name(&attribute.name)?;
755    for arg in attribute.args.iter() {
756        visitor.visit_arg(arg)?;
757    }
758    ControlFlow::Continue(())
759}
760
761/// Visits a catch clause's caught type names and body statements.
762pub fn walk_catch_clause<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
763    visitor: &mut V,
764    catch: &CatchClause<'arena, 'src>,
765) -> ControlFlow<V::Break> {
766    for ty in catch.types.iter() {
767        visitor.visit_name(ty)?;
768    }
769    visitor.visit_block(catch.body)?;
770    ControlFlow::Continue(())
771}
772
773/// Visits a match arm's condition expressions (if any) and body expression.
774pub fn walk_match_arm<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
775    visitor: &mut V,
776    arm: &MatchArm<'arena, 'src>,
777) -> ControlFlow<V::Break> {
778    if let Some(conditions) = &arm.conditions {
779        for cond in conditions.iter() {
780            visitor.visit_expr(cond)?;
781        }
782    }
783    visitor.visit_expr(&arm.body)
784}
785
786/// Visits a trait use declaration's trait names and adaptations (`insteadof`, `as`).
787pub fn walk_trait_use<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
788    visitor: &mut V,
789    trait_use: &TraitUseDecl<'arena, 'src>,
790) -> ControlFlow<V::Break> {
791    for name in trait_use.traits.iter() {
792        visitor.visit_name(name)?;
793    }
794    for adaptation in trait_use.adaptations.iter() {
795        visitor.visit_trait_adaptation(adaptation)?;
796    }
797    ControlFlow::Continue(())
798}
799
800/// Visits the trait and method names of an `insteadof`/`as` adaptation.
801pub fn walk_trait_adaptation<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
802    visitor: &mut V,
803    adaptation: &TraitAdaptation<'arena, 'src>,
804) -> ControlFlow<V::Break> {
805    match &adaptation.kind {
806        TraitAdaptationKind::Precedence {
807            trait_name,
808            method,
809            insteadof,
810        } => {
811            visitor.visit_name(trait_name)?;
812            visitor.visit_name(method)?;
813            for name in insteadof.iter() {
814                visitor.visit_name(name)?;
815            }
816        }
817        TraitAdaptationKind::Alias {
818            trait_name,
819            method,
820            new_name,
821            ..
822        } => {
823            if let Some(name) = trait_name {
824                visitor.visit_name(name)?;
825            }
826            visitor.visit_name(method)?;
827            if let Some(name) = new_name {
828                visitor.visit_name(name)?;
829            }
830        }
831    }
832    ControlFlow::Continue(())
833}
834
835// =============================================================================
836// Internal helpers — shared walking logic to avoid duplication
837// =============================================================================
838
839/// Walks the common parts of any function-like construct:
840/// attributes → params → optional return type.
841fn walk_function_like<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
842    visitor: &mut V,
843    attributes: &[Attribute<'arena, 'src>],
844    params: &[Param<'arena, 'src>],
845    return_type: &Option<TypeHint<'arena, 'src>>,
846) -> ControlFlow<V::Break> {
847    walk_attributes(visitor, attributes)?;
848    for param in params.iter() {
849        visitor.visit_param(param)?;
850    }
851    if let Some(ret) = return_type {
852        visitor.visit_type_hint(ret)?;
853    }
854    ControlFlow::Continue(())
855}
856
857/// Walks a method declaration (shared by ClassMember and EnumMember).
858fn walk_method_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
859    visitor: &mut V,
860    method: &MethodDecl<'arena, 'src>,
861) -> ControlFlow<V::Break> {
862    walk_function_like(
863        visitor,
864        &method.attributes,
865        &method.params,
866        &method.return_type,
867    )?;
868    if let Some(body) = method.body {
869        visitor.visit_block(body)?;
870    }
871    ControlFlow::Continue(())
872}
873
874/// Walks a class constant declaration (shared by ClassMember and EnumMember).
875fn walk_class_const_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
876    visitor: &mut V,
877    cc: &ClassConstDecl<'arena, 'src>,
878) -> ControlFlow<V::Break> {
879    walk_attributes(visitor, &cc.attributes)?;
880    if let Some(type_hint) = &cc.type_hint {
881        visitor.visit_type_hint(type_hint)?;
882    }
883    visitor.visit_expr(&cc.value)
884}
885
886/// Walks a property declaration.
887fn walk_property_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
888    visitor: &mut V,
889    prop: &PropertyDecl<'arena, 'src>,
890) -> ControlFlow<V::Break> {
891    walk_attributes(visitor, &prop.attributes)?;
892    if let Some(type_hint) = &prop.type_hint {
893        visitor.visit_type_hint(type_hint)?;
894    }
895    if let Some(default) = &prop.default {
896        visitor.visit_expr(default)?;
897    }
898    for hook in prop.hooks.iter() {
899        visitor.visit_property_hook(hook)?;
900    }
901    ControlFlow::Continue(())
902}
903
904fn walk_attributes<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
905    visitor: &mut V,
906    attributes: &[Attribute<'arena, 'src>],
907) -> ControlFlow<V::Break> {
908    for attr in attributes.iter() {
909        visitor.visit_attribute(attr)?;
910    }
911    ControlFlow::Continue(())
912}
913
914// =============================================================================
915// ScopeVisitor — scope-aware traversal
916// =============================================================================
917
918/// Lexical scope context passed to each [`ScopeVisitor`] method.
919///
920/// Represents the immediately enclosing namespace, class-like definition,
921/// and named function or method at the point a node is visited.
922/// All fields are `None` when the node is at the global top level.
923///
924/// **Namespace** is set when inside a braced or simple `namespace` declaration.
925/// **`class_name`** is set inside `class`, `interface`, `trait`, and `enum`
926/// declarations; it is `None` for anonymous classes.
927/// **`function_name`** is set inside named functions and methods; it is `None`
928/// inside closures and arrow functions.
929#[derive(Debug, Clone, Copy, Default)]
930#[non_exhaustive]
931pub struct Scope<'src> {
932    /// Current namespace, or `None` for the global namespace.
933    ///
934    /// This is a borrowed slice of the original source string, so copying or
935    /// cloning the scope is always allocation-free.
936    pub namespace: Option<&'src str>,
937    /// Name of the immediately enclosing class-like declaration, or `None`.
938    pub class_name: Option<&'src str>,
939    /// Name of the immediately enclosing named function or method, or `None`.
940    pub function_name: Option<&'src str>,
941}
942
943/// A scope-aware variant of [`Visitor`].
944///
945/// Every visit method receives a [`Scope`] describing the lexical context at
946/// that node — the current namespace, enclosing class-like declaration, and
947/// enclosing named function or method.  All methods have no-op default
948/// implementations, so implementors override only what they need.
949///
950/// Drive traversal with [`ScopeWalker`], which maintains the scope
951/// automatically and calls these methods with the current context.
952///
953/// # Example
954///
955/// ```
956/// use php_ast::visitor::{ScopeVisitor, ScopeWalker, Scope};
957/// use php_ast::ast::*;
958/// use std::ops::ControlFlow;
959///
960/// struct MethodCollector { methods: Vec<String> }
961///
962/// impl<'arena, 'src> ScopeVisitor<'arena, 'src> for MethodCollector {
963///     type Break = ();
964///     fn visit_class_member(
965///         &mut self,
966///         member: &ClassMember<'arena, 'src>,
967///         scope: &Scope<'src>,
968///     ) -> ControlFlow<()> {
969///         if let ClassMemberKind::Method(m) = &member.kind {
970///             self.methods.push(format!(
971///                 "{}::{}",
972///                 scope.class_name.unwrap_or("<anon>"),
973///                 m.name
974///             ));
975///         }
976///         ControlFlow::Continue(())
977///     }
978/// }
979/// ```
980pub trait ScopeVisitor<'arena, 'src> {
981    /// Value carried by [`ControlFlow::Break`] when a visit stops the walk early; use `()` to
982    /// stop without a result.
983    type Break;
984
985    /// Visits the root program node with the enclosing scope.
986    fn visit_program(
987        &mut self,
988        _program: &Program<'arena, 'src>,
989        _scope: &Scope<'src>,
990    ) -> ControlFlow<Self::Break> {
991        ControlFlow::Continue(())
992    }
993    /// Visits a statement with the enclosing scope.
994    fn visit_stmt(
995        &mut self,
996        _stmt: &Stmt<'arena, 'src>,
997        _scope: &Scope<'src>,
998    ) -> ControlFlow<Self::Break> {
999        ControlFlow::Continue(())
1000    }
1001    /// Visits an expression with the enclosing scope.
1002    fn visit_expr(
1003        &mut self,
1004        _expr: &Expr<'arena, 'src>,
1005        _scope: &Scope<'src>,
1006    ) -> ControlFlow<Self::Break> {
1007        ControlFlow::Continue(())
1008    }
1009    /// Visits a function parameter with the enclosing scope.
1010    fn visit_param(
1011        &mut self,
1012        _param: &Param<'arena, 'src>,
1013        _scope: &Scope<'src>,
1014    ) -> ControlFlow<Self::Break> {
1015        ControlFlow::Continue(())
1016    }
1017    /// Visits a call argument with the enclosing scope.
1018    fn visit_arg(
1019        &mut self,
1020        _arg: &Arg<'arena, 'src>,
1021        _scope: &Scope<'src>,
1022    ) -> ControlFlow<Self::Break> {
1023        ControlFlow::Continue(())
1024    }
1025    /// Visits a class-like member with the enclosing scope.
1026    fn visit_class_member(
1027        &mut self,
1028        _member: &ClassMember<'arena, 'src>,
1029        _scope: &Scope<'src>,
1030    ) -> ControlFlow<Self::Break> {
1031        ControlFlow::Continue(())
1032    }
1033    /// Visits an enum member with the enclosing scope.
1034    fn visit_enum_member(
1035        &mut self,
1036        _member: &EnumMember<'arena, 'src>,
1037        _scope: &Scope<'src>,
1038    ) -> ControlFlow<Self::Break> {
1039        ControlFlow::Continue(())
1040    }
1041    /// Visits a property hook (`get`/`set`) with the enclosing scope.
1042    fn visit_property_hook(
1043        &mut self,
1044        _hook: &PropertyHook<'arena, 'src>,
1045        _scope: &Scope<'src>,
1046    ) -> ControlFlow<Self::Break> {
1047        ControlFlow::Continue(())
1048    }
1049    /// Visits a type hint with the enclosing scope.
1050    fn visit_type_hint(
1051        &mut self,
1052        _type_hint: &TypeHint<'arena, 'src>,
1053        _scope: &Scope<'src>,
1054    ) -> ControlFlow<Self::Break> {
1055        ControlFlow::Continue(())
1056    }
1057    /// Visits an attribute with the enclosing scope.
1058    fn visit_attribute(
1059        &mut self,
1060        _attribute: &Attribute<'arena, 'src>,
1061        _scope: &Scope<'src>,
1062    ) -> ControlFlow<Self::Break> {
1063        ControlFlow::Continue(())
1064    }
1065    /// Visits a `catch` clause with the enclosing scope.
1066    fn visit_catch_clause(
1067        &mut self,
1068        _catch: &CatchClause<'arena, 'src>,
1069        _scope: &Scope<'src>,
1070    ) -> ControlFlow<Self::Break> {
1071        ControlFlow::Continue(())
1072    }
1073    /// Visits a `match` arm with the enclosing scope.
1074    fn visit_match_arm(
1075        &mut self,
1076        _arm: &MatchArm<'arena, 'src>,
1077        _scope: &Scope<'src>,
1078    ) -> ControlFlow<Self::Break> {
1079        ControlFlow::Continue(())
1080    }
1081    /// Visits a closure `use` variable with the enclosing scope.
1082    fn visit_closure_use_var(
1083        &mut self,
1084        _var: &ClosureUseVar<'src>,
1085        _scope: &Scope<'src>,
1086    ) -> ControlFlow<Self::Break> {
1087        ControlFlow::Continue(())
1088    }
1089
1090    /// Visits a `use Trait;` declaration with the enclosing scope.
1091    fn visit_trait_use(
1092        &mut self,
1093        _trait_use: &TraitUseDecl<'arena, 'src>,
1094        _scope: &Scope<'src>,
1095    ) -> ControlFlow<Self::Break> {
1096        ControlFlow::Continue(())
1097    }
1098
1099    /// Visits a trait `insteadof`/`as` adaptation with the enclosing scope.
1100    fn visit_trait_adaptation(
1101        &mut self,
1102        _adaptation: &TraitAdaptation<'arena, 'src>,
1103        _scope: &Scope<'src>,
1104    ) -> ControlFlow<Self::Break> {
1105        ControlFlow::Continue(())
1106    }
1107
1108    /// Visits a block with the enclosing scope.
1109    fn visit_block(
1110        &mut self,
1111        _block: &Block<'arena, 'src>,
1112        _scope: &Scope<'src>,
1113    ) -> ControlFlow<Self::Break> {
1114        ControlFlow::Continue(())
1115    }
1116
1117    /// Visits a name reference with the enclosing scope.
1118    fn visit_name(
1119        &mut self,
1120        _name: &Name<'arena, 'src>,
1121        _scope: &Scope<'src>,
1122    ) -> ControlFlow<Self::Break> {
1123        ControlFlow::Continue(())
1124    }
1125
1126    /// Visits a comment with the enclosing scope.
1127    fn visit_comment(
1128        &mut self,
1129        _comment: &Comment<'src>,
1130        _scope: &Scope<'src>,
1131    ) -> ControlFlow<Self::Break> {
1132        ControlFlow::Continue(())
1133    }
1134}
1135
1136/// Drives a [`ScopeVisitor`] over an AST, maintaining [`Scope`] automatically.
1137///
1138/// `ScopeWalker` wraps a [`ScopeVisitor`] and tracks the lexical scope as it
1139/// descends the tree, updating scope before visiting children and restoring it
1140/// on exit from scope-defining nodes (functions, classes, namespaces).
1141///
1142/// # Usage
1143///
1144/// ```no_run
1145/// # use php_ast::visitor::{ScopeWalker, ScopeVisitor, Scope};
1146/// # use php_ast::ast::*;
1147/// # use std::ops::ControlFlow;
1148/// # struct MyVisitor;
1149/// # impl<'a, 'b> ScopeVisitor<'a, 'b> for MyVisitor { type Break = (); }
1150/// # fn parse<'a, 'b>(_: &'a bumpalo::Bump, _: &'b str) -> Program<'a, 'b> { unimplemented!() }
1151/// let arena = bumpalo::Bump::new();
1152/// let src = "<?php class Foo { public function bar() {} }";
1153/// let program = parse(&arena, src);
1154/// let mut walker = ScopeWalker::new(src, MyVisitor);
1155/// walker.walk(&program);
1156/// let _my_visitor = walker.into_inner();
1157/// ```
1158pub struct ScopeWalker<'src, V> {
1159    inner: V,
1160    scope: Scope<'src>,
1161    src: &'src str,
1162}
1163
1164impl<'src, V> ScopeWalker<'src, V> {
1165    /// Creates a new `ScopeWalker` wrapping `inner`.
1166    ///
1167    /// `src` must be the same source string that was passed to the parser that
1168    /// produced the [`Program`] you will walk.  It is used to derive
1169    /// zero-allocation [`Scope::namespace`] slices for qualified namespace
1170    /// names (e.g. `Foo\Bar`).
1171    pub fn new(src: &'src str, inner: V) -> Self {
1172        Self {
1173            inner,
1174            scope: Scope::default(),
1175            src,
1176        }
1177    }
1178
1179    /// Consumes the walker and returns the inner visitor.
1180    pub fn into_inner(self) -> V {
1181        self.inner
1182    }
1183
1184    /// Returns a reference to the inner visitor.
1185    pub fn inner(&self) -> &V {
1186        &self.inner
1187    }
1188
1189    /// Returns a mutable reference to the inner visitor.
1190    pub fn inner_mut(&mut self) -> &mut V {
1191        &mut self.inner
1192    }
1193}
1194
1195impl<'arena, 'src, V: ScopeVisitor<'arena, 'src>> ScopeWalker<'src, V> {
1196    /// Walks `program`, calling [`ScopeVisitor`] methods with scope context.
1197    pub fn walk(&mut self, program: &Program<'arena, 'src>) -> ControlFlow<V::Break> {
1198        self.visit_program(program)
1199    }
1200}
1201
1202impl<'arena, 'src, V: ScopeVisitor<'arena, 'src>> Visitor<'arena, 'src> for ScopeWalker<'src, V> {
1203    type Break = V::Break;
1204
1205    fn visit_program(&mut self, program: &Program<'arena, 'src>) -> ControlFlow<V::Break> {
1206        // A previous walk may have ended inside a namespace or after a `Break`.
1207        self.scope = Scope::default();
1208        self.inner.visit_program(program, &self.scope)?;
1209        walk_program(self, program)
1210    }
1211
1212    fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<V::Break> {
1213        self.inner.visit_stmt(stmt, &self.scope)?;
1214        match &stmt.kind {
1215            StmtKind::Function(func) => {
1216                let prev_fn = std::mem::replace(&mut self.scope.function_name, func.name.as_str());
1217                walk_stmt(self, stmt)?;
1218                self.scope.function_name = prev_fn;
1219            }
1220            StmtKind::Class(class) => {
1221                let prev_class = self.scope.class_name;
1222                let prev_fn = self.scope.function_name.take();
1223                self.scope.class_name = class.name.and_then(|n| n.as_str());
1224                walk_stmt(self, stmt)?;
1225                self.scope.class_name = prev_class;
1226                self.scope.function_name = prev_fn;
1227            }
1228            StmtKind::Interface(iface) => {
1229                let prev_class = std::mem::replace(&mut self.scope.class_name, iface.name.as_str());
1230                let prev_fn = self.scope.function_name.take();
1231                walk_stmt(self, stmt)?;
1232                self.scope.class_name = prev_class;
1233                self.scope.function_name = prev_fn;
1234            }
1235            StmtKind::Trait(trait_decl) => {
1236                let prev_class =
1237                    std::mem::replace(&mut self.scope.class_name, trait_decl.name.as_str());
1238                let prev_fn = self.scope.function_name.take();
1239                walk_stmt(self, stmt)?;
1240                self.scope.class_name = prev_class;
1241                self.scope.function_name = prev_fn;
1242            }
1243            StmtKind::Enum(enum_decl) => {
1244                let prev_class =
1245                    std::mem::replace(&mut self.scope.class_name, enum_decl.name.as_str());
1246                let prev_fn = self.scope.function_name.take();
1247                walk_stmt(self, stmt)?;
1248                self.scope.class_name = prev_class;
1249                self.scope.function_name = prev_fn;
1250            }
1251            StmtKind::Namespace(ns) => {
1252                let ns_str = ns.name.as_ref().map(|n| n.src_repr(self.src));
1253                match ns.body {
1254                    NamespaceBody::Braced(_) => {
1255                        let prev_ns = self.scope.namespace;
1256                        let prev_class = self.scope.class_name.take();
1257                        let prev_fn = self.scope.function_name.take();
1258                        self.scope.namespace = ns_str;
1259                        walk_stmt(self, stmt)?;
1260                        self.scope.namespace = prev_ns;
1261                        self.scope.class_name = prev_class;
1262                        self.scope.function_name = prev_fn;
1263                    }
1264                    NamespaceBody::Simple => {
1265                        // Simple namespace: update scope and leave it set for
1266                        // the remainder of the file (no push/pop).
1267                        self.scope.namespace = ns_str;
1268                        self.scope.class_name = None;
1269                        self.scope.function_name = None;
1270                    }
1271                }
1272            }
1273            _ => {
1274                walk_stmt(self, stmt)?;
1275            }
1276        }
1277        ControlFlow::Continue(())
1278    }
1279
1280    fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<V::Break> {
1281        self.inner.visit_expr(expr, &self.scope)?;
1282        match &expr.kind {
1283            ExprKind::Closure(_) | ExprKind::ArrowFunction(_) => {
1284                let prev_fn = self.scope.function_name.take();
1285                walk_expr(self, expr)?;
1286                self.scope.function_name = prev_fn;
1287            }
1288            ExprKind::AnonymousClass(_) => {
1289                let prev_class = self.scope.class_name.take();
1290                let prev_fn = self.scope.function_name.take();
1291                walk_expr(self, expr)?;
1292                self.scope.class_name = prev_class;
1293                self.scope.function_name = prev_fn;
1294            }
1295            _ => {
1296                walk_expr(self, expr)?;
1297            }
1298        }
1299        ControlFlow::Continue(())
1300    }
1301
1302    fn visit_class_member(&mut self, member: &ClassMember<'arena, 'src>) -> ControlFlow<V::Break> {
1303        self.inner.visit_class_member(member, &self.scope)?;
1304        if let ClassMemberKind::Method(method) = &member.kind {
1305            let prev_fn = std::mem::replace(&mut self.scope.function_name, method.name.as_str());
1306            walk_class_member(self, member)?;
1307            self.scope.function_name = prev_fn;
1308        } else {
1309            walk_class_member(self, member)?;
1310        }
1311        ControlFlow::Continue(())
1312    }
1313
1314    fn visit_enum_member(&mut self, member: &EnumMember<'arena, 'src>) -> ControlFlow<V::Break> {
1315        self.inner.visit_enum_member(member, &self.scope)?;
1316        if let EnumMemberKind::Method(method) = &member.kind {
1317            let prev_fn = std::mem::replace(&mut self.scope.function_name, method.name.as_str());
1318            walk_enum_member(self, member)?;
1319            self.scope.function_name = prev_fn;
1320        } else {
1321            walk_enum_member(self, member)?;
1322        }
1323        ControlFlow::Continue(())
1324    }
1325
1326    fn visit_param(&mut self, param: &Param<'arena, 'src>) -> ControlFlow<V::Break> {
1327        self.inner.visit_param(param, &self.scope)?;
1328        walk_param(self, param)
1329    }
1330
1331    fn visit_arg(&mut self, arg: &Arg<'arena, 'src>) -> ControlFlow<V::Break> {
1332        self.inner.visit_arg(arg, &self.scope)?;
1333        walk_arg(self, arg)
1334    }
1335
1336    fn visit_property_hook(&mut self, hook: &PropertyHook<'arena, 'src>) -> ControlFlow<V::Break> {
1337        self.inner.visit_property_hook(hook, &self.scope)?;
1338        walk_property_hook(self, hook)
1339    }
1340
1341    fn visit_type_hint(&mut self, type_hint: &TypeHint<'arena, 'src>) -> ControlFlow<V::Break> {
1342        self.inner.visit_type_hint(type_hint, &self.scope)?;
1343        walk_type_hint(self, type_hint)
1344    }
1345
1346    fn visit_attribute(&mut self, attribute: &Attribute<'arena, 'src>) -> ControlFlow<V::Break> {
1347        self.inner.visit_attribute(attribute, &self.scope)?;
1348        walk_attribute(self, attribute)
1349    }
1350
1351    fn visit_catch_clause(&mut self, catch: &CatchClause<'arena, 'src>) -> ControlFlow<V::Break> {
1352        self.inner.visit_catch_clause(catch, &self.scope)?;
1353        walk_catch_clause(self, catch)
1354    }
1355
1356    fn visit_match_arm(&mut self, arm: &MatchArm<'arena, 'src>) -> ControlFlow<V::Break> {
1357        self.inner.visit_match_arm(arm, &self.scope)?;
1358        walk_match_arm(self, arm)
1359    }
1360
1361    fn visit_closure_use_var(&mut self, var: &ClosureUseVar<'src>) -> ControlFlow<V::Break> {
1362        self.inner.visit_closure_use_var(var, &self.scope)
1363    }
1364
1365    fn visit_trait_use(&mut self, trait_use: &TraitUseDecl<'arena, 'src>) -> ControlFlow<V::Break> {
1366        self.inner.visit_trait_use(trait_use, &self.scope)?;
1367        walk_trait_use(self, trait_use)
1368    }
1369
1370    fn visit_trait_adaptation(
1371        &mut self,
1372        adaptation: &TraitAdaptation<'arena, 'src>,
1373    ) -> ControlFlow<V::Break> {
1374        self.inner.visit_trait_adaptation(adaptation, &self.scope)?;
1375        walk_trait_adaptation(self, adaptation)
1376    }
1377
1378    fn visit_block(&mut self, block: &Block<'arena, 'src>) -> ControlFlow<V::Break> {
1379        self.inner.visit_block(block, &self.scope)?;
1380        walk_block(self, block)
1381    }
1382
1383    fn visit_name(&mut self, name: &Name<'arena, 'src>) -> ControlFlow<V::Break> {
1384        self.inner.visit_name(name, &self.scope)
1385    }
1386
1387    fn visit_comment(&mut self, comment: &Comment<'src>) -> ControlFlow<V::Break> {
1388        self.inner.visit_comment(comment, &self.scope)
1389    }
1390}
1391
1392#[cfg(test)]
1393mod tests {
1394    use super::*;
1395    use crate::Span;
1396    // =========================================================================
1397    // Unit tests with hand-built ASTs
1398    // =========================================================================
1399
1400    struct VarCounter {
1401        count: usize,
1402    }
1403
1404    impl<'arena, 'src> Visitor<'arena, 'src> for VarCounter {
1405        type Break = ();
1406        fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1407            if matches!(&expr.kind, ExprKind::Variable(_)) {
1408                self.count += 1;
1409            }
1410            walk_expr(self, expr)
1411        }
1412    }
1413
1414    #[test]
1415    fn counts_variables() {
1416        let arena = bumpalo::Bump::new();
1417        let var_x = arena.alloc(Expr {
1418            kind: ExprKind::Variable(NameStr::from_source("x")),
1419            span: Span::DUMMY,
1420        });
1421        let var_y = arena.alloc(Expr {
1422            kind: ExprKind::Variable(NameStr::from_source("y")),
1423            span: Span::DUMMY,
1424        });
1425        let var_z = arena.alloc(Expr {
1426            kind: ExprKind::Variable(NameStr::from_source("z")),
1427            span: Span::DUMMY,
1428        });
1429        let binary = arena.alloc(Expr {
1430            kind: ExprKind::Binary(BinaryExpr {
1431                left: var_y,
1432                op: BinaryOp::Add,
1433                right: var_z,
1434            }),
1435            span: Span::DUMMY,
1436        });
1437        let assign = arena.alloc(Expr {
1438            kind: ExprKind::Assign(AssignExpr {
1439                target: var_x,
1440                op: AssignOp::Assign,
1441                value: binary,
1442                by_ref: false,
1443            }),
1444            span: Span::DUMMY,
1445        });
1446        let mut stmts = ArenaVec::new_in(&arena);
1447        stmts.push(Stmt {
1448            kind: StmtKind::Expression(assign),
1449            span: Span::DUMMY,
1450            doc_comment: None,
1451        });
1452        let program = Program {
1453            stmts,
1454            span: Span::DUMMY,
1455        };
1456
1457        let mut v = VarCounter { count: 0 };
1458        let _ = v.visit_program(&program);
1459        assert_eq!(v.count, 3);
1460    }
1461
1462    #[test]
1463    fn early_termination() {
1464        let arena = bumpalo::Bump::new();
1465        let var_a = arena.alloc(Expr {
1466            kind: ExprKind::Variable(NameStr::from_source("a")),
1467            span: Span::DUMMY,
1468        });
1469        let var_b = arena.alloc(Expr {
1470            kind: ExprKind::Variable(NameStr::from_source("b")),
1471            span: Span::DUMMY,
1472        });
1473        let binary = arena.alloc(Expr {
1474            kind: ExprKind::Binary(BinaryExpr {
1475                left: var_a,
1476                op: BinaryOp::Add,
1477                right: var_b,
1478            }),
1479            span: Span::DUMMY,
1480        });
1481        let mut stmts = ArenaVec::new_in(&arena);
1482        stmts.push(Stmt {
1483            kind: StmtKind::Expression(binary),
1484            span: Span::DUMMY,
1485            doc_comment: None,
1486        });
1487        let program = Program {
1488            stmts,
1489            span: Span::DUMMY,
1490        };
1491
1492        struct FindFirst {
1493            found: Option<String>,
1494        }
1495        impl<'arena, 'src> Visitor<'arena, 'src> for FindFirst {
1496            type Break = ();
1497            fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1498                if let ExprKind::Variable(name) = &expr.kind {
1499                    self.found = Some(name.to_string());
1500                    return ControlFlow::Break(());
1501                }
1502                walk_expr(self, expr)
1503            }
1504        }
1505
1506        let mut finder = FindFirst { found: None };
1507        let result = finder.visit_program(&program);
1508        assert!(result.is_break());
1509        assert_eq!(finder.found.as_deref(), Some("a"));
1510    }
1511
1512    #[test]
1513    fn skip_subtree() {
1514        let arena = bumpalo::Bump::new();
1515        // 1 + 2; function foo() { 3 + 4; }
1516        let one = arena.alloc(Expr {
1517            kind: ExprKind::Int(1),
1518            span: Span::DUMMY,
1519        });
1520        let two = arena.alloc(Expr {
1521            kind: ExprKind::Int(2),
1522            span: Span::DUMMY,
1523        });
1524        let top = arena.alloc(Expr {
1525            kind: ExprKind::Binary(BinaryExpr {
1526                left: one,
1527                op: BinaryOp::Add,
1528                right: two,
1529            }),
1530            span: Span::DUMMY,
1531        });
1532        let three = arena.alloc(Expr {
1533            kind: ExprKind::Int(3),
1534            span: Span::DUMMY,
1535        });
1536        let four = arena.alloc(Expr {
1537            kind: ExprKind::Int(4),
1538            span: Span::DUMMY,
1539        });
1540        let inner = arena.alloc(Expr {
1541            kind: ExprKind::Binary(BinaryExpr {
1542                left: three,
1543                op: BinaryOp::Add,
1544                right: four,
1545            }),
1546            span: Span::DUMMY,
1547        });
1548        let mut func_body_stmts = ArenaVec::new_in(&arena);
1549        func_body_stmts.push(Stmt {
1550            kind: StmtKind::Expression(inner),
1551            span: Span::DUMMY,
1552            doc_comment: None,
1553        });
1554        let func_body = arena.alloc(Block {
1555            stmts: func_body_stmts,
1556            span: Span::DUMMY,
1557        });
1558        let func = arena.alloc(FunctionDecl {
1559            name: Ident::new("foo", Span::DUMMY),
1560            params: ArenaVec::new_in(&arena),
1561            missing_param_list: false,
1562            body: func_body,
1563            return_type: None,
1564            by_ref: false,
1565            attributes: ArenaVec::new_in(&arena),
1566            doc_comment: None,
1567        });
1568        let mut stmts = ArenaVec::new_in(&arena);
1569        stmts.push(Stmt {
1570            kind: StmtKind::Expression(top),
1571            span: Span::DUMMY,
1572            doc_comment: None,
1573        });
1574        stmts.push(Stmt {
1575            kind: StmtKind::Function(func),
1576            span: Span::DUMMY,
1577            doc_comment: None,
1578        });
1579        let program = Program {
1580            stmts,
1581            span: Span::DUMMY,
1582        };
1583
1584        struct SkipFunctions {
1585            expr_count: usize,
1586        }
1587        impl<'arena, 'src> Visitor<'arena, 'src> for SkipFunctions {
1588            type Break = ();
1589            fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1590                self.expr_count += 1;
1591                walk_expr(self, expr)
1592            }
1593            fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<()> {
1594                if matches!(&stmt.kind, StmtKind::Function(_)) {
1595                    return ControlFlow::Continue(());
1596                }
1597                walk_stmt(self, stmt)
1598            }
1599        }
1600
1601        let mut v = SkipFunctions { expr_count: 0 };
1602        let _ = v.visit_program(&program);
1603        // Only top-level: binary(1, 2) = 3 exprs
1604        assert_eq!(v.expr_count, 3);
1605    }
1606}