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