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