Skip to main content

php_ast/
visitor.rs

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