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 }
721 ControlFlow::Continue(())
722}
723
724pub fn walk_type_hint<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
726 visitor: &mut V,
727 type_hint: &TypeHint<'arena, 'src>,
728) -> ControlFlow<V::Break> {
729 match &type_hint.kind {
730 TypeHintKind::Nullable(inner) => {
731 visitor.visit_type_hint(inner)?;
732 }
733 TypeHintKind::Union(types) | TypeHintKind::Intersection(types) => {
734 for ty in types.iter() {
735 visitor.visit_type_hint(ty)?;
736 }
737 }
738 TypeHintKind::Named(name) => {
739 visitor.visit_name(name)?;
740 }
741 TypeHintKind::Keyword(_, _) => {}
742 }
743 ControlFlow::Continue(())
744}
745
746pub fn walk_attribute<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
748 visitor: &mut V,
749 attribute: &Attribute<'arena, 'src>,
750) -> ControlFlow<V::Break> {
751 visitor.visit_name(&attribute.name)?;
752 for arg in attribute.args.iter() {
753 visitor.visit_arg(arg)?;
754 }
755 ControlFlow::Continue(())
756}
757
758pub fn walk_catch_clause<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
760 visitor: &mut V,
761 catch: &CatchClause<'arena, 'src>,
762) -> ControlFlow<V::Break> {
763 for ty in catch.types.iter() {
764 visitor.visit_name(ty)?;
765 }
766 visitor.visit_block(catch.body)?;
767 ControlFlow::Continue(())
768}
769
770pub fn walk_match_arm<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
772 visitor: &mut V,
773 arm: &MatchArm<'arena, 'src>,
774) -> ControlFlow<V::Break> {
775 if let Some(conditions) = &arm.conditions {
776 for cond in conditions.iter() {
777 visitor.visit_expr(cond)?;
778 }
779 }
780 visitor.visit_expr(&arm.body)
781}
782
783pub fn walk_trait_use<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
785 visitor: &mut V,
786 trait_use: &TraitUseDecl<'arena, 'src>,
787) -> ControlFlow<V::Break> {
788 for name in trait_use.traits.iter() {
789 visitor.visit_name(name)?;
790 }
791 for adaptation in trait_use.adaptations.iter() {
792 visitor.visit_trait_adaptation(adaptation)?;
793 }
794 ControlFlow::Continue(())
795}
796
797pub fn walk_trait_adaptation<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
799 visitor: &mut V,
800 adaptation: &TraitAdaptation<'arena, 'src>,
801) -> ControlFlow<V::Break> {
802 match &adaptation.kind {
803 TraitAdaptationKind::Precedence {
804 trait_name,
805 method,
806 insteadof,
807 } => {
808 visitor.visit_name(trait_name)?;
809 visitor.visit_name(method)?;
810 for name in insteadof.iter() {
811 visitor.visit_name(name)?;
812 }
813 }
814 TraitAdaptationKind::Alias {
815 trait_name,
816 method,
817 new_name,
818 ..
819 } => {
820 if let Some(name) = trait_name {
821 visitor.visit_name(name)?;
822 }
823 visitor.visit_name(method)?;
824 if let Some(name) = new_name {
825 visitor.visit_name(name)?;
826 }
827 }
828 }
829 ControlFlow::Continue(())
830}
831
832fn walk_function_like<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
839 visitor: &mut V,
840 attributes: &[Attribute<'arena, 'src>],
841 params: &[Param<'arena, 'src>],
842 return_type: &Option<TypeHint<'arena, 'src>>,
843) -> ControlFlow<V::Break> {
844 walk_attributes(visitor, attributes)?;
845 for param in params.iter() {
846 visitor.visit_param(param)?;
847 }
848 if let Some(ret) = return_type {
849 visitor.visit_type_hint(ret)?;
850 }
851 ControlFlow::Continue(())
852}
853
854fn walk_method_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
856 visitor: &mut V,
857 method: &MethodDecl<'arena, 'src>,
858) -> ControlFlow<V::Break> {
859 walk_function_like(
860 visitor,
861 &method.attributes,
862 &method.params,
863 &method.return_type,
864 )?;
865 if let Some(body) = method.body {
866 visitor.visit_block(body)?;
867 }
868 ControlFlow::Continue(())
869}
870
871fn walk_class_const_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
873 visitor: &mut V,
874 cc: &ClassConstDecl<'arena, 'src>,
875) -> ControlFlow<V::Break> {
876 walk_attributes(visitor, &cc.attributes)?;
877 if let Some(type_hint) = &cc.type_hint {
878 visitor.visit_type_hint(type_hint)?;
879 }
880 visitor.visit_expr(&cc.value)
881}
882
883fn walk_property_decl<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
885 visitor: &mut V,
886 prop: &PropertyDecl<'arena, 'src>,
887) -> ControlFlow<V::Break> {
888 walk_attributes(visitor, &prop.attributes)?;
889 if let Some(type_hint) = &prop.type_hint {
890 visitor.visit_type_hint(type_hint)?;
891 }
892 if let Some(default) = &prop.default {
893 visitor.visit_expr(default)?;
894 }
895 for hook in prop.hooks.iter() {
896 visitor.visit_property_hook(hook)?;
897 }
898 ControlFlow::Continue(())
899}
900
901fn walk_attributes<'arena, 'src, V: Visitor<'arena, 'src> + ?Sized>(
902 visitor: &mut V,
903 attributes: &[Attribute<'arena, 'src>],
904) -> ControlFlow<V::Break> {
905 for attr in attributes.iter() {
906 visitor.visit_attribute(attr)?;
907 }
908 ControlFlow::Continue(())
909}
910
911#[derive(Debug, Clone, Copy, Default)]
927#[non_exhaustive]
928pub struct Scope<'src> {
929 pub namespace: Option<&'src str>,
934 pub class_name: Option<&'src str>,
936 pub function_name: Option<&'src str>,
938}
939
940pub trait ScopeVisitor<'arena, 'src> {
978 type Break;
981
982 fn visit_program(
984 &mut self,
985 _program: &Program<'arena, 'src>,
986 _scope: &Scope<'src>,
987 ) -> ControlFlow<Self::Break> {
988 ControlFlow::Continue(())
989 }
990 fn visit_stmt(
992 &mut self,
993 _stmt: &Stmt<'arena, 'src>,
994 _scope: &Scope<'src>,
995 ) -> ControlFlow<Self::Break> {
996 ControlFlow::Continue(())
997 }
998 fn visit_expr(
1000 &mut self,
1001 _expr: &Expr<'arena, 'src>,
1002 _scope: &Scope<'src>,
1003 ) -> ControlFlow<Self::Break> {
1004 ControlFlow::Continue(())
1005 }
1006 fn visit_param(
1008 &mut self,
1009 _param: &Param<'arena, 'src>,
1010 _scope: &Scope<'src>,
1011 ) -> ControlFlow<Self::Break> {
1012 ControlFlow::Continue(())
1013 }
1014 fn visit_arg(
1016 &mut self,
1017 _arg: &Arg<'arena, 'src>,
1018 _scope: &Scope<'src>,
1019 ) -> ControlFlow<Self::Break> {
1020 ControlFlow::Continue(())
1021 }
1022 fn visit_class_member(
1024 &mut self,
1025 _member: &ClassMember<'arena, 'src>,
1026 _scope: &Scope<'src>,
1027 ) -> ControlFlow<Self::Break> {
1028 ControlFlow::Continue(())
1029 }
1030 fn visit_enum_member(
1032 &mut self,
1033 _member: &EnumMember<'arena, 'src>,
1034 _scope: &Scope<'src>,
1035 ) -> ControlFlow<Self::Break> {
1036 ControlFlow::Continue(())
1037 }
1038 fn visit_property_hook(
1040 &mut self,
1041 _hook: &PropertyHook<'arena, 'src>,
1042 _scope: &Scope<'src>,
1043 ) -> ControlFlow<Self::Break> {
1044 ControlFlow::Continue(())
1045 }
1046 fn visit_type_hint(
1048 &mut self,
1049 _type_hint: &TypeHint<'arena, 'src>,
1050 _scope: &Scope<'src>,
1051 ) -> ControlFlow<Self::Break> {
1052 ControlFlow::Continue(())
1053 }
1054 fn visit_attribute(
1056 &mut self,
1057 _attribute: &Attribute<'arena, 'src>,
1058 _scope: &Scope<'src>,
1059 ) -> ControlFlow<Self::Break> {
1060 ControlFlow::Continue(())
1061 }
1062 fn visit_catch_clause(
1064 &mut self,
1065 _catch: &CatchClause<'arena, 'src>,
1066 _scope: &Scope<'src>,
1067 ) -> ControlFlow<Self::Break> {
1068 ControlFlow::Continue(())
1069 }
1070 fn visit_match_arm(
1072 &mut self,
1073 _arm: &MatchArm<'arena, 'src>,
1074 _scope: &Scope<'src>,
1075 ) -> ControlFlow<Self::Break> {
1076 ControlFlow::Continue(())
1077 }
1078 fn visit_closure_use_var(
1080 &mut self,
1081 _var: &ClosureUseVar<'src>,
1082 _scope: &Scope<'src>,
1083 ) -> ControlFlow<Self::Break> {
1084 ControlFlow::Continue(())
1085 }
1086
1087 fn visit_trait_use(
1089 &mut self,
1090 _trait_use: &TraitUseDecl<'arena, 'src>,
1091 _scope: &Scope<'src>,
1092 ) -> ControlFlow<Self::Break> {
1093 ControlFlow::Continue(())
1094 }
1095
1096 fn visit_trait_adaptation(
1098 &mut self,
1099 _adaptation: &TraitAdaptation<'arena, 'src>,
1100 _scope: &Scope<'src>,
1101 ) -> ControlFlow<Self::Break> {
1102 ControlFlow::Continue(())
1103 }
1104
1105 fn visit_block(
1107 &mut self,
1108 _block: &Block<'arena, 'src>,
1109 _scope: &Scope<'src>,
1110 ) -> ControlFlow<Self::Break> {
1111 ControlFlow::Continue(())
1112 }
1113
1114 fn visit_name(
1116 &mut self,
1117 _name: &Name<'arena, 'src>,
1118 _scope: &Scope<'src>,
1119 ) -> ControlFlow<Self::Break> {
1120 ControlFlow::Continue(())
1121 }
1122
1123 fn visit_comment(
1125 &mut self,
1126 _comment: &Comment<'src>,
1127 _scope: &Scope<'src>,
1128 ) -> ControlFlow<Self::Break> {
1129 ControlFlow::Continue(())
1130 }
1131}
1132
1133pub struct ScopeWalker<'src, V> {
1156 inner: V,
1157 scope: Scope<'src>,
1158 src: &'src str,
1159}
1160
1161impl<'src, V> ScopeWalker<'src, V> {
1162 pub fn new(src: &'src str, inner: V) -> Self {
1169 Self {
1170 inner,
1171 scope: Scope::default(),
1172 src,
1173 }
1174 }
1175
1176 pub fn into_inner(self) -> V {
1178 self.inner
1179 }
1180
1181 pub fn inner(&self) -> &V {
1183 &self.inner
1184 }
1185
1186 pub fn inner_mut(&mut self) -> &mut V {
1188 &mut self.inner
1189 }
1190}
1191
1192impl<'arena, 'src, V: ScopeVisitor<'arena, 'src>> ScopeWalker<'src, V> {
1193 pub fn walk(&mut self, program: &Program<'arena, 'src>) -> ControlFlow<V::Break> {
1195 self.visit_program(program)
1196 }
1197}
1198
1199impl<'arena, 'src, V: ScopeVisitor<'arena, 'src>> Visitor<'arena, 'src> for ScopeWalker<'src, V> {
1200 type Break = V::Break;
1201
1202 fn visit_program(&mut self, program: &Program<'arena, 'src>) -> ControlFlow<V::Break> {
1203 self.scope = Scope::default();
1205 self.inner.visit_program(program, &self.scope)?;
1206 walk_program(self, program)
1207 }
1208
1209 fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<V::Break> {
1210 self.inner.visit_stmt(stmt, &self.scope)?;
1211 match &stmt.kind {
1212 StmtKind::Function(func) => {
1213 let prev_fn = std::mem::replace(&mut self.scope.function_name, func.name.as_str());
1214 walk_stmt(self, stmt)?;
1215 self.scope.function_name = prev_fn;
1216 }
1217 StmtKind::Class(class) => {
1218 let prev_class = self.scope.class_name;
1219 let prev_fn = self.scope.function_name.take();
1220 self.scope.class_name = class.name.and_then(|n| n.as_str());
1221 walk_stmt(self, stmt)?;
1222 self.scope.class_name = prev_class;
1223 self.scope.function_name = prev_fn;
1224 }
1225 StmtKind::Interface(iface) => {
1226 let prev_class = std::mem::replace(&mut self.scope.class_name, iface.name.as_str());
1227 let prev_fn = self.scope.function_name.take();
1228 walk_stmt(self, stmt)?;
1229 self.scope.class_name = prev_class;
1230 self.scope.function_name = prev_fn;
1231 }
1232 StmtKind::Trait(trait_decl) => {
1233 let prev_class =
1234 std::mem::replace(&mut self.scope.class_name, trait_decl.name.as_str());
1235 let prev_fn = self.scope.function_name.take();
1236 walk_stmt(self, stmt)?;
1237 self.scope.class_name = prev_class;
1238 self.scope.function_name = prev_fn;
1239 }
1240 StmtKind::Enum(enum_decl) => {
1241 let prev_class =
1242 std::mem::replace(&mut self.scope.class_name, enum_decl.name.as_str());
1243 let prev_fn = self.scope.function_name.take();
1244 walk_stmt(self, stmt)?;
1245 self.scope.class_name = prev_class;
1246 self.scope.function_name = prev_fn;
1247 }
1248 StmtKind::Namespace(ns) => {
1249 let ns_str = ns.name.as_ref().map(|n| n.src_repr(self.src));
1250 match ns.body {
1251 NamespaceBody::Braced(_) => {
1252 let prev_ns = self.scope.namespace;
1253 let prev_class = self.scope.class_name.take();
1254 let prev_fn = self.scope.function_name.take();
1255 self.scope.namespace = ns_str;
1256 walk_stmt(self, stmt)?;
1257 self.scope.namespace = prev_ns;
1258 self.scope.class_name = prev_class;
1259 self.scope.function_name = prev_fn;
1260 }
1261 NamespaceBody::Simple => {
1262 self.scope.namespace = ns_str;
1265 self.scope.class_name = None;
1266 self.scope.function_name = None;
1267 }
1268 }
1269 }
1270 _ => {
1271 walk_stmt(self, stmt)?;
1272 }
1273 }
1274 ControlFlow::Continue(())
1275 }
1276
1277 fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<V::Break> {
1278 self.inner.visit_expr(expr, &self.scope)?;
1279 match &expr.kind {
1280 ExprKind::Closure(_) | ExprKind::ArrowFunction(_) => {
1281 let prev_fn = self.scope.function_name.take();
1282 walk_expr(self, expr)?;
1283 self.scope.function_name = prev_fn;
1284 }
1285 ExprKind::AnonymousClass(_) => {
1286 let prev_class = self.scope.class_name.take();
1287 let prev_fn = self.scope.function_name.take();
1288 walk_expr(self, expr)?;
1289 self.scope.class_name = prev_class;
1290 self.scope.function_name = prev_fn;
1291 }
1292 _ => {
1293 walk_expr(self, expr)?;
1294 }
1295 }
1296 ControlFlow::Continue(())
1297 }
1298
1299 fn visit_class_member(&mut self, member: &ClassMember<'arena, 'src>) -> ControlFlow<V::Break> {
1300 self.inner.visit_class_member(member, &self.scope)?;
1301 if let ClassMemberKind::Method(method) = &member.kind {
1302 let prev_fn = std::mem::replace(&mut self.scope.function_name, method.name.as_str());
1303 walk_class_member(self, member)?;
1304 self.scope.function_name = prev_fn;
1305 } else {
1306 walk_class_member(self, member)?;
1307 }
1308 ControlFlow::Continue(())
1309 }
1310
1311 fn visit_enum_member(&mut self, member: &EnumMember<'arena, 'src>) -> ControlFlow<V::Break> {
1312 self.inner.visit_enum_member(member, &self.scope)?;
1313 if let EnumMemberKind::Method(method) = &member.kind {
1314 let prev_fn = std::mem::replace(&mut self.scope.function_name, method.name.as_str());
1315 walk_enum_member(self, member)?;
1316 self.scope.function_name = prev_fn;
1317 } else {
1318 walk_enum_member(self, member)?;
1319 }
1320 ControlFlow::Continue(())
1321 }
1322
1323 fn visit_param(&mut self, param: &Param<'arena, 'src>) -> ControlFlow<V::Break> {
1324 self.inner.visit_param(param, &self.scope)?;
1325 walk_param(self, param)
1326 }
1327
1328 fn visit_arg(&mut self, arg: &Arg<'arena, 'src>) -> ControlFlow<V::Break> {
1329 self.inner.visit_arg(arg, &self.scope)?;
1330 walk_arg(self, arg)
1331 }
1332
1333 fn visit_property_hook(&mut self, hook: &PropertyHook<'arena, 'src>) -> ControlFlow<V::Break> {
1334 self.inner.visit_property_hook(hook, &self.scope)?;
1335 walk_property_hook(self, hook)
1336 }
1337
1338 fn visit_type_hint(&mut self, type_hint: &TypeHint<'arena, 'src>) -> ControlFlow<V::Break> {
1339 self.inner.visit_type_hint(type_hint, &self.scope)?;
1340 walk_type_hint(self, type_hint)
1341 }
1342
1343 fn visit_attribute(&mut self, attribute: &Attribute<'arena, 'src>) -> ControlFlow<V::Break> {
1344 self.inner.visit_attribute(attribute, &self.scope)?;
1345 walk_attribute(self, attribute)
1346 }
1347
1348 fn visit_catch_clause(&mut self, catch: &CatchClause<'arena, 'src>) -> ControlFlow<V::Break> {
1349 self.inner.visit_catch_clause(catch, &self.scope)?;
1350 walk_catch_clause(self, catch)
1351 }
1352
1353 fn visit_match_arm(&mut self, arm: &MatchArm<'arena, 'src>) -> ControlFlow<V::Break> {
1354 self.inner.visit_match_arm(arm, &self.scope)?;
1355 walk_match_arm(self, arm)
1356 }
1357
1358 fn visit_closure_use_var(&mut self, var: &ClosureUseVar<'src>) -> ControlFlow<V::Break> {
1359 self.inner.visit_closure_use_var(var, &self.scope)
1360 }
1361
1362 fn visit_trait_use(&mut self, trait_use: &TraitUseDecl<'arena, 'src>) -> ControlFlow<V::Break> {
1363 self.inner.visit_trait_use(trait_use, &self.scope)?;
1364 walk_trait_use(self, trait_use)
1365 }
1366
1367 fn visit_trait_adaptation(
1368 &mut self,
1369 adaptation: &TraitAdaptation<'arena, 'src>,
1370 ) -> ControlFlow<V::Break> {
1371 self.inner.visit_trait_adaptation(adaptation, &self.scope)?;
1372 walk_trait_adaptation(self, adaptation)
1373 }
1374
1375 fn visit_block(&mut self, block: &Block<'arena, 'src>) -> ControlFlow<V::Break> {
1376 self.inner.visit_block(block, &self.scope)?;
1377 walk_block(self, block)
1378 }
1379
1380 fn visit_name(&mut self, name: &Name<'arena, 'src>) -> ControlFlow<V::Break> {
1381 self.inner.visit_name(name, &self.scope)
1382 }
1383
1384 fn visit_comment(&mut self, comment: &Comment<'src>) -> ControlFlow<V::Break> {
1385 self.inner.visit_comment(comment, &self.scope)
1386 }
1387}
1388
1389#[cfg(test)]
1390mod tests {
1391 use super::*;
1392 use crate::Span;
1393 struct VarCounter {
1398 count: usize,
1399 }
1400
1401 impl<'arena, 'src> Visitor<'arena, 'src> for VarCounter {
1402 type Break = ();
1403 fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1404 if matches!(&expr.kind, ExprKind::Variable(_)) {
1405 self.count += 1;
1406 }
1407 walk_expr(self, expr)
1408 }
1409 }
1410
1411 #[test]
1412 fn counts_variables() {
1413 let arena = bumpalo::Bump::new();
1414 let var_x = arena.alloc(Expr {
1415 kind: ExprKind::Variable(NameStr::from_source("x")),
1416 span: Span::DUMMY,
1417 });
1418 let var_y = arena.alloc(Expr {
1419 kind: ExprKind::Variable(NameStr::from_source("y")),
1420 span: Span::DUMMY,
1421 });
1422 let var_z = arena.alloc(Expr {
1423 kind: ExprKind::Variable(NameStr::from_source("z")),
1424 span: Span::DUMMY,
1425 });
1426 let binary = arena.alloc(Expr {
1427 kind: ExprKind::Binary(BinaryExpr {
1428 left: var_y,
1429 op: BinaryOp::Add,
1430 right: var_z,
1431 }),
1432 span: Span::DUMMY,
1433 });
1434 let assign = arena.alloc(Expr {
1435 kind: ExprKind::Assign(AssignExpr {
1436 target: var_x,
1437 op: AssignOp::Assign,
1438 value: binary,
1439 by_ref: false,
1440 }),
1441 span: Span::DUMMY,
1442 });
1443 let mut stmts = ArenaVec::new_in(&arena);
1444 stmts.push(Stmt {
1445 kind: StmtKind::Expression(assign),
1446 span: Span::DUMMY,
1447 doc_comment: None,
1448 });
1449 let program = Program {
1450 stmts,
1451 span: Span::DUMMY,
1452 };
1453
1454 let mut v = VarCounter { count: 0 };
1455 let _ = v.visit_program(&program);
1456 assert_eq!(v.count, 3);
1457 }
1458
1459 #[test]
1460 fn early_termination() {
1461 let arena = bumpalo::Bump::new();
1462 let var_a = arena.alloc(Expr {
1463 kind: ExprKind::Variable(NameStr::from_source("a")),
1464 span: Span::DUMMY,
1465 });
1466 let var_b = arena.alloc(Expr {
1467 kind: ExprKind::Variable(NameStr::from_source("b")),
1468 span: Span::DUMMY,
1469 });
1470 let binary = arena.alloc(Expr {
1471 kind: ExprKind::Binary(BinaryExpr {
1472 left: var_a,
1473 op: BinaryOp::Add,
1474 right: var_b,
1475 }),
1476 span: Span::DUMMY,
1477 });
1478 let mut stmts = ArenaVec::new_in(&arena);
1479 stmts.push(Stmt {
1480 kind: StmtKind::Expression(binary),
1481 span: Span::DUMMY,
1482 doc_comment: None,
1483 });
1484 let program = Program {
1485 stmts,
1486 span: Span::DUMMY,
1487 };
1488
1489 struct FindFirst {
1490 found: Option<String>,
1491 }
1492 impl<'arena, 'src> Visitor<'arena, 'src> for FindFirst {
1493 type Break = ();
1494 fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1495 if let ExprKind::Variable(name) = &expr.kind {
1496 self.found = Some(name.to_string());
1497 return ControlFlow::Break(());
1498 }
1499 walk_expr(self, expr)
1500 }
1501 }
1502
1503 let mut finder = FindFirst { found: None };
1504 let result = finder.visit_program(&program);
1505 assert!(result.is_break());
1506 assert_eq!(finder.found.as_deref(), Some("a"));
1507 }
1508
1509 #[test]
1510 fn skip_subtree() {
1511 let arena = bumpalo::Bump::new();
1512 let one = arena.alloc(Expr {
1514 kind: ExprKind::Int(1),
1515 span: Span::DUMMY,
1516 });
1517 let two = arena.alloc(Expr {
1518 kind: ExprKind::Int(2),
1519 span: Span::DUMMY,
1520 });
1521 let top = arena.alloc(Expr {
1522 kind: ExprKind::Binary(BinaryExpr {
1523 left: one,
1524 op: BinaryOp::Add,
1525 right: two,
1526 }),
1527 span: Span::DUMMY,
1528 });
1529 let three = arena.alloc(Expr {
1530 kind: ExprKind::Int(3),
1531 span: Span::DUMMY,
1532 });
1533 let four = arena.alloc(Expr {
1534 kind: ExprKind::Int(4),
1535 span: Span::DUMMY,
1536 });
1537 let inner = arena.alloc(Expr {
1538 kind: ExprKind::Binary(BinaryExpr {
1539 left: three,
1540 op: BinaryOp::Add,
1541 right: four,
1542 }),
1543 span: Span::DUMMY,
1544 });
1545 let mut func_body_stmts = ArenaVec::new_in(&arena);
1546 func_body_stmts.push(Stmt {
1547 kind: StmtKind::Expression(inner),
1548 span: Span::DUMMY,
1549 doc_comment: None,
1550 });
1551 let func_body = arena.alloc(Block {
1552 stmts: func_body_stmts,
1553 span: Span::DUMMY,
1554 });
1555 let func = arena.alloc(FunctionDecl {
1556 name: Ident::new("foo", Span::DUMMY),
1557 params: ArenaVec::new_in(&arena),
1558 body: func_body,
1559 return_type: None,
1560 by_ref: false,
1561 attributes: ArenaVec::new_in(&arena),
1562 doc_comment: None,
1563 });
1564 let mut stmts = ArenaVec::new_in(&arena);
1565 stmts.push(Stmt {
1566 kind: StmtKind::Expression(top),
1567 span: Span::DUMMY,
1568 doc_comment: None,
1569 });
1570 stmts.push(Stmt {
1571 kind: StmtKind::Function(func),
1572 span: Span::DUMMY,
1573 doc_comment: None,
1574 });
1575 let program = Program {
1576 stmts,
1577 span: Span::DUMMY,
1578 };
1579
1580 struct SkipFunctions {
1581 expr_count: usize,
1582 }
1583 impl<'arena, 'src> Visitor<'arena, 'src> for SkipFunctions {
1584 type Break = ();
1585 fn visit_expr(&mut self, expr: &Expr<'arena, 'src>) -> ControlFlow<()> {
1586 self.expr_count += 1;
1587 walk_expr(self, expr)
1588 }
1589 fn visit_stmt(&mut self, stmt: &Stmt<'arena, 'src>) -> ControlFlow<()> {
1590 if matches!(&stmt.kind, StmtKind::Function(_)) {
1591 return ControlFlow::Continue(());
1592 }
1593 walk_stmt(self, stmt)
1594 }
1595 }
1596
1597 let mut v = SkipFunctions { expr_count: 0 };
1598 let _ = v.visit_program(&program);
1599 assert_eq!(v.expr_count, 3);
1601 }
1602}