Skip to main content

php_ast/
visitor.rs

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