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