Skip to main content

php_ast/owned/
visitor.rs

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