1use std::ops::ControlFlow;
2
3use crate::ast::*;
4
5pub trait Visitor<'arena, 'src> {
38 type Break;
41
42 fn visit_program(&mut self, program: &Program<'arena, 'src>) -> ControlFlow<Self::Break> {
44 walk_program(self, program)
45 }
46
47 fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<Self::Break> {
49 walk_stmt(self, stmt)
50 }
51
52 fn visit_block(&mut self, block: &Block<'arena, 'src>) -> ControlFlow<Self::Break> {
54 walk_block(self, block)
55 }
56
57 fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<Self::Break> {
59 walk_expr(self, expr)
60 }
61
62 fn visit_param(&mut self, param: &Param<'arena, 'src>) -> ControlFlow<Self::Break> {
64 walk_param(self, param)
65 }
66
67 fn visit_arg(&mut self, arg: &Arg<'arena, 'src>) -> ControlFlow<Self::Break> {
69 walk_arg(self, arg)
70 }
71
72 fn visit_class_member(
74 &mut self,
75 member: &ClassMember<'arena, 'src>,
76 ) -> ControlFlow<Self::Break> {
77 walk_class_member(self, member)
78 }
79
80 fn visit_enum_member(&mut self, member: &EnumMember<'arena, 'src>) -> ControlFlow<Self::Break> {
82 walk_enum_member(self, member)
83 }
84
85 fn visit_property_hook(
87 &mut self,
88 hook: &PropertyHook<'arena, 'src>,
89 ) -> ControlFlow<Self::Break> {
90 walk_property_hook(self, hook)
91 }
92
93 fn visit_type_hint(&mut self, type_hint: &TypeHint<'arena, 'src>) -> ControlFlow<Self::Break> {
95 walk_type_hint(self, type_hint)
96 }
97
98 fn visit_attribute(&mut self, attribute: &Attribute<'arena, 'src>) -> ControlFlow<Self::Break> {
100 walk_attribute(self, attribute)
101 }
102
103 fn visit_catch_clause(
105 &mut self,
106 catch: &CatchClause<'arena, 'src>,
107 ) -> ControlFlow<Self::Break> {
108 walk_catch_clause(self, catch)
109 }
110
111 fn visit_match_arm(&mut self, arm: &MatchArm<'arena, 'src>) -> ControlFlow<Self::Break> {
113 walk_match_arm(self, arm)
114 }
115
116 fn visit_closure_use_var(&mut self, _var: &ClosureUseVar<'src>) -> ControlFlow<Self::Break> {
118 ControlFlow::Continue(())
119 }
120
121 fn visit_trait_use(
123 &mut self,
124 trait_use: &TraitUseDecl<'arena, 'src>,
125 ) -> ControlFlow<Self::Break> {
126 walk_trait_use(self, trait_use)
127 }
128
129 fn visit_trait_adaptation(
131 &mut self,
132 adaptation: &TraitAdaptation<'arena, 'src>,
133 ) -> ControlFlow<Self::Break> {
134 walk_trait_adaptation(self, adaptation)
135 }
136
137 fn visit_name(&mut self, _name: &Name<'arena, 'src>) -> ControlFlow<Self::Break> {
139 ControlFlow::Continue(())
140 }
141
142 fn visit_comment(&mut self, _comment: &Comment<'src>) -> ControlFlow<Self::Break> {
147 ControlFlow::Continue(())
148 }
149}
150
151pub fn walk_name<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
160 visitor: &mut V,
161 name: &Name<'arena, 'src>,
162) -> ControlFlow<V::Break> {
163 visitor.visit_name(name)
164}
165
166pub fn walk_comments<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
179 visitor: &mut V,
180 comments: &[Comment<'src>],
181) -> ControlFlow<V::Break> {
182 for comment in comments {
183 visitor.visit_comment(comment)?;
184 }
185 ControlFlow::Continue(())
186}
187
188pub fn walk_program<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
204 visitor: &mut V,
205 program: &Program<'arena, 'src>,
206) -> ControlFlow<V::Break> {
207 for stmt in program.stmts.iter() {
208 visitor.visit_stmt(stmt)?;
209 }
210 ControlFlow::Continue(())
211}
212
213pub fn walk_block<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
215 visitor: &mut V,
216 block: &Block<'arena, 'src>,
217) -> ControlFlow<V::Break> {
218 for stmt in block.stmts.iter() {
219 visitor.visit_stmt(stmt)?;
220 }
221 ControlFlow::Continue(())
222}
223
224pub fn walk_stmt<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
229 visitor: &mut V,
230 stmt: &Stmt<'arena, 'src>,
231) -> ControlFlow<V::Break> {
232 match &stmt.kind {
233 StmtKind::Expression(expr) => {
234 visitor.visit_expr(expr)?;
235 }
236 StmtKind::Echo(exprs) => {
237 for expr in exprs.iter() {
238 visitor.visit_expr(expr)?;
239 }
240 }
241 StmtKind::Return(expr) => {
242 if let Some(expr) = expr {
243 visitor.visit_expr(expr)?;
244 }
245 }
246 StmtKind::Block(block) => {
247 visitor.visit_block(block)?;
248 }
249 StmtKind::If(if_stmt) => {
250 visitor.visit_expr(&if_stmt.condition)?;
251 visitor.visit_stmt(if_stmt.then_branch)?;
252 for elseif in if_stmt.elseif_branches.iter() {
253 visitor.visit_expr(&elseif.condition)?;
254 visitor.visit_stmt(&elseif.body)?;
255 }
256 if let Some(else_branch) = &if_stmt.else_branch {
257 visitor.visit_stmt(else_branch)?;
258 }
259 }
260 StmtKind::While(while_stmt) => {
261 visitor.visit_expr(&while_stmt.condition)?;
262 visitor.visit_stmt(while_stmt.body)?;
263 }
264 StmtKind::For(for_stmt) => {
265 for expr in for_stmt.init.iter() {
266 visitor.visit_expr(expr)?;
267 }
268 for expr in for_stmt.condition.iter() {
269 visitor.visit_expr(expr)?;
270 }
271 for expr in for_stmt.update.iter() {
272 visitor.visit_expr(expr)?;
273 }
274 visitor.visit_stmt(for_stmt.body)?;
275 }
276 StmtKind::Foreach(foreach_stmt) => {
277 visitor.visit_expr(&foreach_stmt.expr)?;
278 if let Some(key) = &foreach_stmt.key {
279 visitor.visit_expr(key)?;
280 }
281 visitor.visit_expr(&foreach_stmt.value)?;
282 visitor.visit_stmt(foreach_stmt.body)?;
283 }
284 StmtKind::DoWhile(do_while) => {
285 visitor.visit_stmt(do_while.body)?;
286 visitor.visit_expr(&do_while.condition)?;
287 }
288 StmtKind::Function(func) => {
289 walk_function_like(visitor, &func.attributes, &func.params, &func.return_type)?;
290 visitor.visit_block(func.body)?;
291 }
292 StmtKind::Break(expr) | StmtKind::Continue(expr) => {
293 if let Some(expr) = expr {
294 visitor.visit_expr(expr)?;
295 }
296 }
297 StmtKind::Switch(switch_stmt) => {
298 visitor.visit_expr(&switch_stmt.expr)?;
299 for case in switch_stmt.body.cases.iter() {
300 if let Some(value) = &case.value {
301 visitor.visit_expr(value)?;
302 }
303 for stmt in case.body.iter() {
304 visitor.visit_stmt(stmt)?;
305 }
306 }
307 }
308 StmtKind::Throw(expr) => {
309 visitor.visit_expr(expr)?;
310 }
311 StmtKind::TryCatch(tc) => {
312 visitor.visit_block(tc.body)?;
313 for catch in tc.catches.iter() {
314 visitor.visit_catch_clause(catch)?;
315 }
316 if let Some(finally) = tc.finally {
317 visitor.visit_block(finally)?;
318 }
319 }
320 StmtKind::Declare(decl) => {
321 for (_, expr) in decl.directives.iter() {
322 visitor.visit_expr(expr)?;
323 }
324 if let Some(body) = decl.body {
325 visitor.visit_stmt(body)?;
326 }
327 }
328 StmtKind::Unset(exprs) | StmtKind::Global(exprs) => {
329 for expr in exprs.iter() {
330 visitor.visit_expr(expr)?;
331 }
332 }
333 StmtKind::Class(class) => {
334 walk_attributes(visitor, &class.attributes)?;
335 if let Some(extends) = &class.extends {
336 visitor.visit_name(extends)?;
337 }
338 for name in class.implements.iter() {
339 visitor.visit_name(name)?;
340 }
341 for member in class.body.members.iter() {
342 visitor.visit_class_member(member)?;
343 }
344 }
345 StmtKind::Interface(iface) => {
346 walk_attributes(visitor, &iface.attributes)?;
347 for name in iface.extends.iter() {
348 visitor.visit_name(name)?;
349 }
350 for member in iface.body.members.iter() {
351 visitor.visit_class_member(member)?;
352 }
353 }
354 StmtKind::Trait(trait_decl) => {
355 walk_attributes(visitor, &trait_decl.attributes)?;
356 for member in trait_decl.body.members.iter() {
357 visitor.visit_class_member(member)?;
358 }
359 }
360 StmtKind::Enum(enum_decl) => {
361 walk_attributes(visitor, &enum_decl.attributes)?;
362 if let Some(scalar_type) = &enum_decl.scalar_type {
363 visitor.visit_name(scalar_type)?;
364 }
365 for name in enum_decl.implements.iter() {
366 visitor.visit_name(name)?;
367 }
368 for member in enum_decl.body.members.iter() {
369 visitor.visit_enum_member(member)?;
370 }
371 }
372 StmtKind::Namespace(ns) => {
373 if let Some(name) = &ns.name {
374 visitor.visit_name(name)?;
375 }
376 if let NamespaceBody::Braced(block) = &ns.body {
377 visitor.visit_block(block)?;
378 }
379 }
380 StmtKind::Const(items) => {
381 for item in items.iter() {
382 walk_attributes(visitor, &item.attributes)?;
383 visitor.visit_expr(&item.value)?;
384 }
385 }
386 StmtKind::StaticVar(vars) => {
387 for var in vars.iter() {
388 if let Some(default) = &var.default {
389 visitor.visit_expr(default)?;
390 }
391 }
392 }
393 StmtKind::Use(decl) => {
394 for item in decl.uses.iter() {
395 visitor.visit_name(&item.name)?;
396 }
397 }
398 StmtKind::Goto(_)
399 | StmtKind::Label(_)
400 | StmtKind::Nop
401 | StmtKind::InlineHtml(_)
402 | StmtKind::HaltCompiler(_)
403 | StmtKind::Error => {}
404 }
405 ControlFlow::Continue(())
406}
407
408pub fn walk_expr<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
413 visitor: &mut V,
414 expr: &Expr<'arena, 'src>,
415) -> ControlFlow<V::Break> {
416 match &expr.kind {
417 ExprKind::Name(name) => visitor.visit_name(name)?,
418 ExprKind::Assign(assign) => {
419 visitor.visit_expr(assign.target)?;
420 visitor.visit_expr(assign.value)?;
421 }
422 ExprKind::Binary(binary) => {
423 visitor.visit_expr(binary.left)?;
424 visitor.visit_expr(binary.right)?;
425 }
426 ExprKind::UnaryPrefix(unary) => {
427 visitor.visit_expr(unary.operand)?;
428 }
429 ExprKind::UnaryPostfix(unary) => {
430 visitor.visit_expr(unary.operand)?;
431 }
432 ExprKind::Ternary(ternary) => {
433 visitor.visit_expr(ternary.condition)?;
434 if let Some(then_expr) = &ternary.then_expr {
435 visitor.visit_expr(then_expr)?;
436 }
437 visitor.visit_expr(ternary.else_expr)?;
438 }
439 ExprKind::NullCoalesce(nc) => {
440 visitor.visit_expr(nc.left)?;
441 visitor.visit_expr(nc.right)?;
442 }
443 ExprKind::FunctionCall(call) => {
444 visitor.visit_expr(call.name)?;
445 for arg in call.args.iter() {
446 visitor.visit_arg(arg)?;
447 }
448 }
449 ExprKind::Array(elements) => {
450 for elem in elements.iter() {
451 if let Some(key) = &elem.key {
452 visitor.visit_expr(key)?;
453 }
454 visitor.visit_expr(elem.value())?;
455 }
456 }
457 ExprKind::ArrayAccess(access) => {
458 visitor.visit_expr(access.array)?;
459 if let Some(index) = &access.index {
460 visitor.visit_expr(index)?;
461 }
462 }
463 ExprKind::Print(expr) => {
464 visitor.visit_expr(expr)?;
465 }
466 ExprKind::Parenthesized(expr) => {
467 visitor.visit_expr(expr)?;
468 }
469 ExprKind::Cast(_, expr) => {
470 visitor.visit_expr(expr)?;
471 }
472 ExprKind::ErrorSuppress(expr) => {
473 visitor.visit_expr(expr)?;
474 }
475 ExprKind::Isset(exprs) => {
476 for expr in exprs.iter() {
477 visitor.visit_expr(expr)?;
478 }
479 }
480 ExprKind::Empty(expr) => {
481 visitor.visit_expr(expr)?;
482 }
483 ExprKind::Include(_, expr) => {
484 visitor.visit_expr(expr)?;
485 }
486 ExprKind::Eval(expr) => {
487 visitor.visit_expr(expr)?;
488 }
489 ExprKind::Exit(expr) => {
490 if let Some(expr) = expr {
491 visitor.visit_expr(expr)?;
492 }
493 }
494 ExprKind::Clone(expr) => {
495 visitor.visit_expr(expr)?;
496 }
497 ExprKind::CloneWith(object, overrides) => {
498 visitor.visit_expr(object)?;
499 visitor.visit_expr(overrides)?;
500 }
501 ExprKind::New(new_expr) => {
502 visitor.visit_expr(new_expr.class)?;
503 for arg in new_expr.args.iter() {
504 visitor.visit_arg(arg)?;
505 }
506 }
507 ExprKind::PropertyAccess(access) | ExprKind::NullsafePropertyAccess(access) => {
508 visitor.visit_expr(access.object)?;
509 visitor.visit_expr(access.property)?;
510 }
511 ExprKind::MethodCall(call) | ExprKind::NullsafeMethodCall(call) => {
512 visitor.visit_expr(call.object)?;
513 visitor.visit_expr(call.method)?;
514 for arg in call.args.iter() {
515 visitor.visit_arg(arg)?;
516 }
517 }
518 ExprKind::StaticPropertyAccess(access) | ExprKind::ClassConstAccess(access) => {
519 visitor.visit_expr(access.class)?;
520 visitor.visit_expr(access.member)?;
521 }
522 ExprKind::ClassConstAccessDynamic { class, member }
523 | ExprKind::StaticPropertyAccessDynamic { class, member } => {
524 visitor.visit_expr(class)?;
525 visitor.visit_expr(member)?;
526 }
527 ExprKind::StaticMethodCall(call) => {
528 visitor.visit_expr(call.class)?;
529 visitor.visit_expr(call.method)?;
530 for arg in call.args.iter() {
531 visitor.visit_arg(arg)?;
532 }
533 }
534 ExprKind::StaticMethodCallDynamic(call) => {
535 visitor.visit_expr(call.class)?;
536 visitor.visit_expr(call.method)?;
537 for arg in call.args.iter() {
538 visitor.visit_arg(arg)?;
539 }
540 }
541 ExprKind::Closure(closure) => {
542 walk_function_like(
543 visitor,
544 &closure.attributes,
545 &closure.params,
546 &closure.return_type,
547 )?;
548 for use_var in closure.use_vars.iter() {
549 visitor.visit_closure_use_var(use_var)?;
550 }
551 visitor.visit_block(closure.body)?;
552 }
553 ExprKind::ArrowFunction(arrow) => {
554 walk_function_like(
555 visitor,
556 &arrow.attributes,
557 &arrow.params,
558 &arrow.return_type,
559 )?;
560 visitor.visit_expr(arrow.body)?;
561 }
562 ExprKind::Match(match_expr) => {
563 visitor.visit_expr(match_expr.subject)?;
564 for arm in match_expr.arms.iter() {
565 visitor.visit_match_arm(arm)?;
566 }
567 }
568 ExprKind::Throw(expr) => {
569 visitor.visit_expr(expr)?;
570 }
571 ExprKind::Yield(yield_expr) => {
572 if let Some(key) = &yield_expr.key {
573 visitor.visit_expr(key)?;
574 }
575 if let Some(value) = &yield_expr.value {
576 visitor.visit_expr(value)?;
577 }
578 }
579 ExprKind::AnonymousClass(class) => {
580 walk_attributes(visitor, &class.attributes)?;
581 for member in class.body.members.iter() {
582 visitor.visit_class_member(member)?;
583 }
584 }
585 ExprKind::InterpolatedString(parts)
586 | ExprKind::Heredoc { parts, .. }
587 | ExprKind::ShellExec(parts) => {
588 for part in parts.iter() {
589 if let StringPart::Expr(e) = part {
590 visitor.visit_expr(e)?;
591 }
592 }
593 }
594 ExprKind::VariableVariable(inner) => {
595 visitor.visit_expr(inner)?;
596 }
597 ExprKind::CallableCreate(cc) => match &cc.kind {
598 CallableCreateKind::Function(name) => visitor.visit_expr(name)?,
599 CallableCreateKind::Method { object, method }
600 | CallableCreateKind::NullsafeMethod { object, method } => {
601 visitor.visit_expr(object)?;
602 visitor.visit_expr(method)?;
603 }
604 CallableCreateKind::StaticMethod { class, method } => {
605 visitor.visit_expr(class)?;
606 visitor.visit_expr(method)?;
607 }
608 },
609 ExprKind::Int(_)
610 | ExprKind::Float(_)
611 | ExprKind::String(_)
612 | ExprKind::Bool(_)
613 | ExprKind::Null
614 | ExprKind::Omit
615 | ExprKind::Variable(_)
616 | ExprKind::Identifier(_)
617 | ExprKind::MagicConst(_)
618 | ExprKind::Nowdoc { .. }
619 | ExprKind::Error => {}
620 }
621 ControlFlow::Continue(())
622}
623
624pub fn walk_param<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
626 visitor: &mut V,
627 param: &Param<'arena, 'src>,
628) -> ControlFlow<V::Break> {
629 walk_attributes(visitor, ¶m.attributes)?;
630 if let Some(type_hint) = ¶m.type_hint {
631 visitor.visit_type_hint(type_hint)?;
632 }
633 if let Some(default) = ¶m.default {
634 visitor.visit_expr(default)?;
635 }
636 for hook in param.hooks.iter() {
637 visitor.visit_property_hook(hook)?;
638 }
639 ControlFlow::Continue(())
640}
641
642pub fn walk_arg<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
644 visitor: &mut V,
645 arg: &Arg<'arena, 'src>,
646) -> ControlFlow<V::Break> {
647 if let Some(name) = &arg.name {
648 visitor.visit_name(name)?;
649 }
650 match arg.value() {
651 Some(value) => visitor.visit_expr(value),
652 None => ControlFlow::Continue(()),
653 }
654}
655
656pub fn walk_class_member<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
658 visitor: &mut V,
659 member: &ClassMember<'arena, 'src>,
660) -> ControlFlow<V::Break> {
661 match &member.kind {
662 ClassMemberKind::Property(prop) => {
663 walk_property_decl(visitor, prop)?;
664 }
665 ClassMemberKind::Method(method) => {
666 walk_method_decl(visitor, method)?;
667 }
668 ClassMemberKind::ClassConst(cc) => {
669 walk_class_const_decl(visitor, cc)?;
670 }
671 ClassMemberKind::TraitUse(trait_use) => {
672 visitor.visit_trait_use(trait_use)?;
673 }
674 }
675 ControlFlow::Continue(())
676}
677
678pub fn walk_property_hook<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
680 visitor: &mut V,
681 hook: &PropertyHook<'arena, 'src>,
682) -> ControlFlow<V::Break> {
683 walk_attributes(visitor, &hook.attributes)?;
684 for param in hook.params.iter() {
685 visitor.visit_param(param)?;
686 }
687 match &hook.body {
688 PropertyHookBody::Block(block) => {
689 visitor.visit_block(block)?;
690 }
691 PropertyHookBody::Expression(expr) => {
692 visitor.visit_expr(expr)?;
693 }
694 PropertyHookBody::Abstract => {}
695 }
696 ControlFlow::Continue(())
697}
698
699pub fn walk_enum_member<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
701 visitor: &mut V,
702 member: &EnumMember<'arena, 'src>,
703) -> ControlFlow<V::Break> {
704 match &member.kind {
705 EnumMemberKind::Case(case) => {
706 walk_attributes(visitor, &case.attributes)?;
707 if let Some(value) = &case.value {
708 visitor.visit_expr(value)?;
709 }
710 }
711 EnumMemberKind::Method(method) => {
712 walk_method_decl(visitor, method)?;
713 }
714 EnumMemberKind::ClassConst(cc) => {
715 walk_class_const_decl(visitor, cc)?;
716 }
717 EnumMemberKind::TraitUse(trait_use) => {
718 visitor.visit_trait_use(trait_use)?;
719 }
720 EnumMemberKind::Property(prop) => {
721 walk_property_decl(visitor, prop)?;
722 }
723 }
724 ControlFlow::Continue(())
725}
726
727pub fn walk_type_hint<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
729 visitor: &mut V,
730 type_hint: &TypeHint<'arena, 'src>,
731) -> ControlFlow<V::Break> {
732 match &type_hint.kind {
733 TypeHintKind::Nullable(inner) => {
734 visitor.visit_type_hint(inner)?;
735 }
736 TypeHintKind::Union(types) | TypeHintKind::Intersection(types) => {
737 for ty in types.iter() {
738 visitor.visit_type_hint(ty)?;
739 }
740 }
741 TypeHintKind::Named(name) => {
742 visitor.visit_name(name)?;
743 }
744 TypeHintKind::Keyword(_, _) => {}
745 }
746 ControlFlow::Continue(())
747}
748
749pub fn walk_attribute<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
751 visitor: &mut V,
752 attribute: &Attribute<'arena, 'src>,
753) -> ControlFlow<V::Break> {
754 visitor.visit_name(&attribute.name)?;
755 for arg in attribute.args.iter() {
756 visitor.visit_arg(arg)?;
757 }
758 ControlFlow::Continue(())
759}
760
761pub fn walk_catch_clause<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
763 visitor: &mut V,
764 catch: &CatchClause<'arena, 'src>,
765) -> ControlFlow<V::Break> {
766 for ty in catch.types.iter() {
767 visitor.visit_name(ty)?;
768 }
769 visitor.visit_block(catch.body)?;
770 ControlFlow::Continue(())
771}
772
773pub fn walk_match_arm<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
775 visitor: &mut V,
776 arm: &MatchArm<'arena, 'src>,
777) -> ControlFlow<V::Break> {
778 if let Some(conditions) = &arm.conditions {
779 for cond in conditions.iter() {
780 visitor.visit_expr(cond)?;
781 }
782 }
783 visitor.visit_expr(&arm.body)
784}
785
786pub fn walk_trait_use<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
788 visitor: &mut V,
789 trait_use: &TraitUseDecl<'arena, 'src>,
790) -> ControlFlow<V::Break> {
791 for name in trait_use.traits.iter() {
792 visitor.visit_name(name)?;
793 }
794 for adaptation in trait_use.adaptations.iter() {
795 visitor.visit_trait_adaptation(adaptation)?;
796 }
797 ControlFlow::Continue(())
798}
799
800pub fn walk_trait_adaptation<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
802 visitor: &mut V,
803 adaptation: &TraitAdaptation<'arena, 'src>,
804) -> ControlFlow<V::Break> {
805 match &adaptation.kind {
806 TraitAdaptationKind::Precedence {
807 trait_name,
808 method,
809 insteadof,
810 } => {
811 visitor.visit_name(trait_name)?;
812 visitor.visit_name(method)?;
813 for name in insteadof.iter() {
814 visitor.visit_name(name)?;
815 }
816 }
817 TraitAdaptationKind::Alias {
818 trait_name,
819 method,
820 new_name,
821 ..
822 } => {
823 if let Some(name) = trait_name {
824 visitor.visit_name(name)?;
825 }
826 visitor.visit_name(method)?;
827 if let Some(name) = new_name {
828 visitor.visit_name(name)?;
829 }
830 }
831 }
832 ControlFlow::Continue(())
833}
834
835fn walk_function_like<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
842 visitor: &mut V,
843 attributes: &[Attribute<'arena, 'src>],
844 params: &[Param<'arena, 'src>],
845 return_type: &Option<TypeHint<'arena, 'src>>,
846) -> ControlFlow<V::Break> {
847 walk_attributes(visitor, attributes)?;
848 for param in params.iter() {
849 visitor.visit_param(param)?;
850 }
851 if let Some(ret) = return_type {
852 visitor.visit_type_hint(ret)?;
853 }
854 ControlFlow::Continue(())
855}
856
857fn walk_method_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
859 visitor: &mut V,
860 method: &MethodDecl<'arena, 'src>,
861) -> ControlFlow<V::Break> {
862 walk_function_like(
863 visitor,
864 &method.attributes,
865 &method.params,
866 &method.return_type,
867 )?;
868 if let Some(body) = method.body {
869 visitor.visit_block(body)?;
870 }
871 ControlFlow::Continue(())
872}
873
874fn walk_class_const_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
876 visitor: &mut V,
877 cc: &ClassConstDecl<'arena, 'src>,
878) -> ControlFlow<V::Break> {
879 walk_attributes(visitor, &cc.attributes)?;
880 if let Some(type_hint) = &cc.type_hint {
881 visitor.visit_type_hint(type_hint)?;
882 }
883 visitor.visit_expr(&cc.value)
884}
885
886fn walk_property_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
888 visitor: &mut V,
889 prop: &PropertyDecl<'arena, 'src>,
890) -> ControlFlow<V::Break> {
891 walk_attributes(visitor, &prop.attributes)?;
892 if let Some(type_hint) = &prop.type_hint {
893 visitor.visit_type_hint(type_hint)?;
894 }
895 if let Some(default) = &prop.default {
896 visitor.visit_expr(default)?;
897 }
898 for hook in prop.hooks.iter() {
899 visitor.visit_property_hook(hook)?;
900 }
901 ControlFlow::Continue(())
902}
903
904fn walk_attributes<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
905 visitor: &mut V,
906 attributes: &[Attribute<'arena, 'src>],
907) -> ControlFlow<V::Break> {
908 for attr in attributes.iter() {
909 visitor.visit_attribute(attr)?;
910 }
911 ControlFlow::Continue(())
912}
913
914#[derive(Debug, Clone, Copy, Default)]
930#[non_exhaustive]
931pub struct Scope<'src> {
932 pub namespace: Option<&'src str>,
937 pub class_name: Option<&'src str>,
939 pub function_name: Option<&'src str>,
941}
942
943pub trait ScopeVisitor<'arena, 'src> {
981 type Break;
984
985 fn visit_program(
987 &mut self,
988 _program: &Program<'arena, 'src>,
989 _scope: &Scope<'src>,
990 ) -> ControlFlow<Self::Break> {
991 ControlFlow::Continue(())
992 }
993 fn visit_stmt(
995 &mut self,
996 _stmt: &Stmt<'arena, 'src>,
997 _scope: &Scope<'src>,
998 ) -> ControlFlow<Self::Break> {
999 ControlFlow::Continue(())
1000 }
1001 fn visit_expr(
1003 &mut self,
1004 _expr: &Expr<'arena, 'src>,
1005 _scope: &Scope<'src>,
1006 ) -> ControlFlow<Self::Break> {
1007 ControlFlow::Continue(())
1008 }
1009 fn visit_param(
1011 &mut self,
1012 _param: &Param<'arena, 'src>,
1013 _scope: &Scope<'src>,
1014 ) -> ControlFlow<Self::Break> {
1015 ControlFlow::Continue(())
1016 }
1017 fn visit_arg(
1019 &mut self,
1020 _arg: &Arg<'arena, 'src>,
1021 _scope: &Scope<'src>,
1022 ) -> ControlFlow<Self::Break> {
1023 ControlFlow::Continue(())
1024 }
1025 fn visit_class_member(
1027 &mut self,
1028 _member: &ClassMember<'arena, 'src>,
1029 _scope: &Scope<'src>,
1030 ) -> ControlFlow<Self::Break> {
1031 ControlFlow::Continue(())
1032 }
1033 fn visit_enum_member(
1035 &mut self,
1036 _member: &EnumMember<'arena, 'src>,
1037 _scope: &Scope<'src>,
1038 ) -> ControlFlow<Self::Break> {
1039 ControlFlow::Continue(())
1040 }
1041 fn visit_property_hook(
1043 &mut self,
1044 _hook: &PropertyHook<'arena, 'src>,
1045 _scope: &Scope<'src>,
1046 ) -> ControlFlow<Self::Break> {
1047 ControlFlow::Continue(())
1048 }
1049 fn visit_type_hint(
1051 &mut self,
1052 _type_hint: &TypeHint<'arena, 'src>,
1053 _scope: &Scope<'src>,
1054 ) -> ControlFlow<Self::Break> {
1055 ControlFlow::Continue(())
1056 }
1057 fn visit_attribute(
1059 &mut self,
1060 _attribute: &Attribute<'arena, 'src>,
1061 _scope: &Scope<'src>,
1062 ) -> ControlFlow<Self::Break> {
1063 ControlFlow::Continue(())
1064 }
1065 fn visit_catch_clause(
1067 &mut self,
1068 _catch: &CatchClause<'arena, 'src>,
1069 _scope: &Scope<'src>,
1070 ) -> ControlFlow<Self::Break> {
1071 ControlFlow::Continue(())
1072 }
1073 fn visit_match_arm(
1075 &mut self,
1076 _arm: &MatchArm<'arena, 'src>,
1077 _scope: &Scope<'src>,
1078 ) -> ControlFlow<Self::Break> {
1079 ControlFlow::Continue(())
1080 }
1081 fn visit_closure_use_var(
1083 &mut self,
1084 _var: &ClosureUseVar<'src>,
1085 _scope: &Scope<'src>,
1086 ) -> ControlFlow<Self::Break> {
1087 ControlFlow::Continue(())
1088 }
1089
1090 fn visit_trait_use(
1092 &mut self,
1093 _trait_use: &TraitUseDecl<'arena, 'src>,
1094 _scope: &Scope<'src>,
1095 ) -> ControlFlow<Self::Break> {
1096 ControlFlow::Continue(())
1097 }
1098
1099 fn visit_trait_adaptation(
1101 &mut self,
1102 _adaptation: &TraitAdaptation<'arena, 'src>,
1103 _scope: &Scope<'src>,
1104 ) -> ControlFlow<Self::Break> {
1105 ControlFlow::Continue(())
1106 }
1107
1108 fn visit_block(
1110 &mut self,
1111 _block: &Block<'arena, 'src>,
1112 _scope: &Scope<'src>,
1113 ) -> ControlFlow<Self::Break> {
1114 ControlFlow::Continue(())
1115 }
1116
1117 fn visit_name(
1119 &mut self,
1120 _name: &Name<'arena, 'src>,
1121 _scope: &Scope<'src>,
1122 ) -> ControlFlow<Self::Break> {
1123 ControlFlow::Continue(())
1124 }
1125
1126 fn visit_comment(
1128 &mut self,
1129 _comment: &Comment<'src>,
1130 _scope: &Scope<'src>,
1131 ) -> ControlFlow<Self::Break> {
1132 ControlFlow::Continue(())
1133 }
1134}
1135
1136pub struct ScopeWalker<'src, V> {
1159 inner: V,
1160 scope: Scope<'src>,
1161 src: &'src str,
1162}
1163
1164impl<'src, V> ScopeWalker<'src, V> {
1165 pub fn new(src: &'src str, inner: V) -> Self {
1172 Self {
1173 inner,
1174 scope: Scope::default(),
1175 src,
1176 }
1177 }
1178
1179 pub fn into_inner(self) -> V {
1181 self.inner
1182 }
1183
1184 pub fn inner(&self) -> &V {
1186 &self.inner
1187 }
1188
1189 pub fn inner_mut(&mut self) -> &mut V {
1191 &mut self.inner
1192 }
1193}
1194
1195impl<'arena, 'src, V: ScopeVisitor<'arena, 'src>> ScopeWalker<'src, V> {
1196 pub fn walk(&mut self, program: &Program<'arena, 'src>) -> ControlFlow<V::Break> {
1198 self.visit_program(program)
1199 }
1200}
1201
1202impl<'arena, 'src, V: ScopeVisitor<'arena, 'src>> Visitor<'arena, 'src> for ScopeWalker<'src, V> {
1203 type Break = V::Break;
1204
1205 fn visit_program(&mut self, program: &Program<'arena, 'src>) -> ControlFlow<V::Break> {
1206 self.scope = Scope::default();
1208 self.inner.visit_program(program, &self.scope)?;
1209 walk_program(self, program)
1210 }
1211
1212 fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<V::Break> {
1213 self.inner.visit_stmt(stmt, &self.scope)?;
1214 match &stmt.kind {
1215 StmtKind::Function(func) => {
1216 let prev_fn = std::mem::replace(&mut self.scope.function_name, func.name.as_str());
1217 walk_stmt(self, stmt)?;
1218 self.scope.function_name = prev_fn;
1219 }
1220 StmtKind::Class(class) => {
1221 let prev_class = self.scope.class_name;
1222 let prev_fn = self.scope.function_name.take();
1223 self.scope.class_name = class.name.and_then(|n| n.as_str());
1224 walk_stmt(self, stmt)?;
1225 self.scope.class_name = prev_class;
1226 self.scope.function_name = prev_fn;
1227 }
1228 StmtKind::Interface(iface) => {
1229 let prev_class = std::mem::replace(&mut self.scope.class_name, iface.name.as_str());
1230 let prev_fn = self.scope.function_name.take();
1231 walk_stmt(self, stmt)?;
1232 self.scope.class_name = prev_class;
1233 self.scope.function_name = prev_fn;
1234 }
1235 StmtKind::Trait(trait_decl) => {
1236 let prev_class =
1237 std::mem::replace(&mut self.scope.class_name, trait_decl.name.as_str());
1238 let prev_fn = self.scope.function_name.take();
1239 walk_stmt(self, stmt)?;
1240 self.scope.class_name = prev_class;
1241 self.scope.function_name = prev_fn;
1242 }
1243 StmtKind::Enum(enum_decl) => {
1244 let prev_class =
1245 std::mem::replace(&mut self.scope.class_name, enum_decl.name.as_str());
1246 let prev_fn = self.scope.function_name.take();
1247 walk_stmt(self, stmt)?;
1248 self.scope.class_name = prev_class;
1249 self.scope.function_name = prev_fn;
1250 }
1251 StmtKind::Namespace(ns) => {
1252 let ns_str = ns.name.as_ref().map(|n| n.src_repr(self.src));
1253 match ns.body {
1254 NamespaceBody::Braced(_) => {
1255 let prev_ns = self.scope.namespace;
1256 let prev_class = self.scope.class_name.take();
1257 let prev_fn = self.scope.function_name.take();
1258 self.scope.namespace = ns_str;
1259 walk_stmt(self, stmt)?;
1260 self.scope.namespace = prev_ns;
1261 self.scope.class_name = prev_class;
1262 self.scope.function_name = prev_fn;
1263 }
1264 NamespaceBody::Simple => {
1265 self.scope.namespace = ns_str;
1268 self.scope.class_name = None;
1269 self.scope.function_name = None;
1270 }
1271 }
1272 }
1273 _ => {
1274 walk_stmt(self, stmt)?;
1275 }
1276 }
1277 ControlFlow::Continue(())
1278 }
1279
1280 fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<V::Break> {
1281 self.inner.visit_expr(expr, &self.scope)?;
1282 match &expr.kind {
1283 ExprKind::Closure(_) | ExprKind::ArrowFunction(_) => {
1284 let prev_fn = self.scope.function_name.take();
1285 walk_expr(self, expr)?;
1286 self.scope.function_name = prev_fn;
1287 }
1288 ExprKind::AnonymousClass(_) => {
1289 let prev_class = self.scope.class_name.take();
1290 let prev_fn = self.scope.function_name.take();
1291 walk_expr(self, expr)?;
1292 self.scope.class_name = prev_class;
1293 self.scope.function_name = prev_fn;
1294 }
1295 _ => {
1296 walk_expr(self, expr)?;
1297 }
1298 }
1299 ControlFlow::Continue(())
1300 }
1301
1302 fn visit_class_member(&mut self, member: &ClassMember<'arena, 'src>) -> ControlFlow<V::Break> {
1303 self.inner.visit_class_member(member, &self.scope)?;
1304 if let ClassMemberKind::Method(method) = &member.kind {
1305 let prev_fn = std::mem::replace(&mut self.scope.function_name, method.name.as_str());
1306 walk_class_member(self, member)?;
1307 self.scope.function_name = prev_fn;
1308 } else {
1309 walk_class_member(self, member)?;
1310 }
1311 ControlFlow::Continue(())
1312 }
1313
1314 fn visit_enum_member(&mut self, member: &EnumMember<'arena, 'src>) -> ControlFlow<V::Break> {
1315 self.inner.visit_enum_member(member, &self.scope)?;
1316 if let EnumMemberKind::Method(method) = &member.kind {
1317 let prev_fn = std::mem::replace(&mut self.scope.function_name, method.name.as_str());
1318 walk_enum_member(self, member)?;
1319 self.scope.function_name = prev_fn;
1320 } else {
1321 walk_enum_member(self, member)?;
1322 }
1323 ControlFlow::Continue(())
1324 }
1325
1326 fn visit_param(&mut self, param: &Param<'arena, 'src>) -> ControlFlow<V::Break> {
1327 self.inner.visit_param(param, &self.scope)?;
1328 walk_param(self, param)
1329 }
1330
1331 fn visit_arg(&mut self, arg: &Arg<'arena, 'src>) -> ControlFlow<V::Break> {
1332 self.inner.visit_arg(arg, &self.scope)?;
1333 walk_arg(self, arg)
1334 }
1335
1336 fn visit_property_hook(&mut self, hook: &PropertyHook<'arena, 'src>) -> ControlFlow<V::Break> {
1337 self.inner.visit_property_hook(hook, &self.scope)?;
1338 walk_property_hook(self, hook)
1339 }
1340
1341 fn visit_type_hint(&mut self, type_hint: &TypeHint<'arena, 'src>) -> ControlFlow<V::Break> {
1342 self.inner.visit_type_hint(type_hint, &self.scope)?;
1343 walk_type_hint(self, type_hint)
1344 }
1345
1346 fn visit_attribute(&mut self, attribute: &Attribute<'arena, 'src>) -> ControlFlow<V::Break> {
1347 self.inner.visit_attribute(attribute, &self.scope)?;
1348 walk_attribute(self, attribute)
1349 }
1350
1351 fn visit_catch_clause(&mut self, catch: &CatchClause<'arena, 'src>) -> ControlFlow<V::Break> {
1352 self.inner.visit_catch_clause(catch, &self.scope)?;
1353 walk_catch_clause(self, catch)
1354 }
1355
1356 fn visit_match_arm(&mut self, arm: &MatchArm<'arena, 'src>) -> ControlFlow<V::Break> {
1357 self.inner.visit_match_arm(arm, &self.scope)?;
1358 walk_match_arm(self, arm)
1359 }
1360
1361 fn visit_closure_use_var(&mut self, var: &ClosureUseVar<'src>) -> ControlFlow<V::Break> {
1362 self.inner.visit_closure_use_var(var, &self.scope)
1363 }
1364
1365 fn visit_trait_use(&mut self, trait_use: &TraitUseDecl<'arena, 'src>) -> ControlFlow<V::Break> {
1366 self.inner.visit_trait_use(trait_use, &self.scope)?;
1367 walk_trait_use(self, trait_use)
1368 }
1369
1370 fn visit_trait_adaptation(
1371 &mut self,
1372 adaptation: &TraitAdaptation<'arena, 'src>,
1373 ) -> ControlFlow<V::Break> {
1374 self.inner.visit_trait_adaptation(adaptation, &self.scope)?;
1375 walk_trait_adaptation(self, adaptation)
1376 }
1377
1378 fn visit_block(&mut self, block: &Block<'arena, 'src>) -> ControlFlow<V::Break> {
1379 self.inner.visit_block(block, &self.scope)?;
1380 walk_block(self, block)
1381 }
1382
1383 fn visit_name(&mut self, name: &Name<'arena, 'src>) -> ControlFlow<V::Break> {
1384 self.inner.visit_name(name, &self.scope)
1385 }
1386
1387 fn visit_comment(&mut self, comment: &Comment<'src>) -> ControlFlow<V::Break> {
1388 self.inner.visit_comment(comment, &self.scope)
1389 }
1390}
1391
1392#[cfg(test)]
1393mod tests {
1394 use super::*;
1395 use crate::Span;
1396 struct VarCounter {
1401 count: usize,
1402 }
1403
1404 impl<'arena, 'src> Visitor<'arena, 'src> for VarCounter {
1405 type Break = ();
1406 fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1407 if matches!(&expr.kind, ExprKind::Variable(_)) {
1408 self.count += 1;
1409 }
1410 walk_expr(self, expr)
1411 }
1412 }
1413
1414 #[test]
1415 fn counts_variables() {
1416 let arena = bumpalo::Bump::new();
1417 let var_x = arena.alloc(Expr {
1418 kind: ExprKind::Variable(NameStr::from_source("x")),
1419 span: Span::DUMMY,
1420 });
1421 let var_y = arena.alloc(Expr {
1422 kind: ExprKind::Variable(NameStr::from_source("y")),
1423 span: Span::DUMMY,
1424 });
1425 let var_z = arena.alloc(Expr {
1426 kind: ExprKind::Variable(NameStr::from_source("z")),
1427 span: Span::DUMMY,
1428 });
1429 let binary = arena.alloc(Expr {
1430 kind: ExprKind::Binary(BinaryExpr {
1431 left: var_y,
1432 op: BinaryOp::Add,
1433 right: var_z,
1434 }),
1435 span: Span::DUMMY,
1436 });
1437 let assign = arena.alloc(Expr {
1438 kind: ExprKind::Assign(AssignExpr {
1439 target: var_x,
1440 op: AssignOp::Assign,
1441 value: binary,
1442 by_ref: false,
1443 }),
1444 span: Span::DUMMY,
1445 });
1446 let mut stmts = ArenaVec::new_in(&arena);
1447 stmts.push(Stmt {
1448 kind: StmtKind::Expression(assign),
1449 span: Span::DUMMY,
1450 doc_comment: None,
1451 });
1452 let program = Program {
1453 stmts,
1454 span: Span::DUMMY,
1455 };
1456
1457 let mut v = VarCounter { count: 0 };
1458 let _ = v.visit_program(&program);
1459 assert_eq!(v.count, 3);
1460 }
1461
1462 #[test]
1463 fn early_termination() {
1464 let arena = bumpalo::Bump::new();
1465 let var_a = arena.alloc(Expr {
1466 kind: ExprKind::Variable(NameStr::from_source("a")),
1467 span: Span::DUMMY,
1468 });
1469 let var_b = arena.alloc(Expr {
1470 kind: ExprKind::Variable(NameStr::from_source("b")),
1471 span: Span::DUMMY,
1472 });
1473 let binary = arena.alloc(Expr {
1474 kind: ExprKind::Binary(BinaryExpr {
1475 left: var_a,
1476 op: BinaryOp::Add,
1477 right: var_b,
1478 }),
1479 span: Span::DUMMY,
1480 });
1481 let mut stmts = ArenaVec::new_in(&arena);
1482 stmts.push(Stmt {
1483 kind: StmtKind::Expression(binary),
1484 span: Span::DUMMY,
1485 doc_comment: None,
1486 });
1487 let program = Program {
1488 stmts,
1489 span: Span::DUMMY,
1490 };
1491
1492 struct FindFirst {
1493 found: Option<String>,
1494 }
1495 impl<'arena, 'src> Visitor<'arena, 'src> for FindFirst {
1496 type Break = ();
1497 fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1498 if let ExprKind::Variable(name) = &expr.kind {
1499 self.found = Some(name.to_string());
1500 return ControlFlow::Break(());
1501 }
1502 walk_expr(self, expr)
1503 }
1504 }
1505
1506 let mut finder = FindFirst { found: None };
1507 let result = finder.visit_program(&program);
1508 assert!(result.is_break());
1509 assert_eq!(finder.found.as_deref(), Some("a"));
1510 }
1511
1512 #[test]
1513 fn skip_subtree() {
1514 let arena = bumpalo::Bump::new();
1515 let one = arena.alloc(Expr {
1517 kind: ExprKind::Int(1),
1518 span: Span::DUMMY,
1519 });
1520 let two = arena.alloc(Expr {
1521 kind: ExprKind::Int(2),
1522 span: Span::DUMMY,
1523 });
1524 let top = arena.alloc(Expr {
1525 kind: ExprKind::Binary(BinaryExpr {
1526 left: one,
1527 op: BinaryOp::Add,
1528 right: two,
1529 }),
1530 span: Span::DUMMY,
1531 });
1532 let three = arena.alloc(Expr {
1533 kind: ExprKind::Int(3),
1534 span: Span::DUMMY,
1535 });
1536 let four = arena.alloc(Expr {
1537 kind: ExprKind::Int(4),
1538 span: Span::DUMMY,
1539 });
1540 let inner = arena.alloc(Expr {
1541 kind: ExprKind::Binary(BinaryExpr {
1542 left: three,
1543 op: BinaryOp::Add,
1544 right: four,
1545 }),
1546 span: Span::DUMMY,
1547 });
1548 let mut func_body_stmts = ArenaVec::new_in(&arena);
1549 func_body_stmts.push(Stmt {
1550 kind: StmtKind::Expression(inner),
1551 span: Span::DUMMY,
1552 doc_comment: None,
1553 });
1554 let func_body = arena.alloc(Block {
1555 stmts: func_body_stmts,
1556 span: Span::DUMMY,
1557 });
1558 let func = arena.alloc(FunctionDecl {
1559 name: Ident::new("foo", Span::DUMMY),
1560 params: ArenaVec::new_in(&arena),
1561 missing_param_list: false,
1562 body: func_body,
1563 return_type: None,
1564 by_ref: false,
1565 attributes: ArenaVec::new_in(&arena),
1566 doc_comment: None,
1567 });
1568 let mut stmts = ArenaVec::new_in(&arena);
1569 stmts.push(Stmt {
1570 kind: StmtKind::Expression(top),
1571 span: Span::DUMMY,
1572 doc_comment: None,
1573 });
1574 stmts.push(Stmt {
1575 kind: StmtKind::Function(func),
1576 span: Span::DUMMY,
1577 doc_comment: None,
1578 });
1579 let program = Program {
1580 stmts,
1581 span: Span::DUMMY,
1582 };
1583
1584 struct SkipFunctions {
1585 expr_count: usize,
1586 }
1587 impl<'arena, 'src> Visitor<'arena, 'src> for SkipFunctions {
1588 type Break = ();
1589 fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1590 self.expr_count += 1;
1591 walk_expr(self, expr)
1592 }
1593 fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<()> {
1594 if matches!(&stmt.kind, StmtKind::Function(_)) {
1595 return ControlFlow::Continue(());
1596 }
1597 walk_stmt(self, stmt)
1598 }
1599 }
1600
1601 let mut v = SkipFunctions { expr_count: 0 };
1602 let _ = v.visit_program(&program);
1603 assert_eq!(v.expr_count, 3);
1605 }
1606}