Skip to main content

pine_ast/
visitor.rs

1//! AST traversal via the visitor pattern.
2//!
3//! The design mirrors `rustc`/`syn`: the [`Visitor`] trait provides `visit_*`
4//! methods whose **default implementations recurse** by delegating to the free
5//! `walk_*` functions. A visitor overrides only the node kinds it cares about,
6//! and calls the matching `walk_*` (or `walk_expr`/`walk_stmt`) to keep
7//! descending into children. The traversal shape lives here, written once, so
8//! individual lint passes never re-implement tree-walking.
9//!
10//! All methods take `&mut self` so a pass can accumulate state (e.g. collected
11//! diagnostics) as it walks. The `?Sized` bounds on the `walk_*` functions let
12//! them drive a `dyn Visitor`, which the lint driver relies on.
13
14use crate::{Argument, Expr, Program, Stmt};
15
16/// A read-only traversal over the AST.
17///
18/// Override the `visit_*` methods for the nodes you care about; call the
19/// corresponding `walk_*` free function (or [`walk_expr`]/[`walk_stmt`]) from
20/// your override to continue into child nodes. Omitting that call prunes the
21/// subtree — occasionally useful, but usually you want to recurse.
22pub trait Visitor {
23    fn visit_program(&mut self, program: &Program) {
24        walk_program(self, program);
25    }
26
27    fn visit_stmt(&mut self, stmt: &Stmt) {
28        walk_stmt(self, stmt);
29    }
30
31    fn visit_expr(&mut self, expr: &Expr) {
32        walk_expr(self, expr);
33    }
34}
35
36pub fn walk_program<V: Visitor + ?Sized>(v: &mut V, program: &Program) {
37    for stmt in &program.statements {
38        v.visit_stmt(stmt);
39    }
40}
41
42pub fn walk_block<V: Visitor + ?Sized>(v: &mut V, body: &[Stmt]) {
43    for stmt in body {
44        v.visit_stmt(stmt);
45    }
46}
47
48pub fn walk_stmt<V: Visitor + ?Sized>(v: &mut V, stmt: &Stmt) {
49    match stmt {
50        Stmt::VarDecl { initializer, .. } => {
51            if let Some(init) = initializer {
52                v.visit_expr(init);
53            }
54        }
55        Stmt::Assignment { target, value } => {
56            v.visit_expr(target);
57            v.visit_expr(value);
58        }
59        Stmt::TupleAssignment { value, .. } => {
60            v.visit_expr(value);
61        }
62        Stmt::Expression(expr) => {
63            v.visit_expr(expr);
64        }
65        Stmt::If {
66            condition,
67            then_branch,
68            else_if_branches,
69            else_branch,
70        } => {
71            v.visit_expr(condition);
72            walk_block(v, then_branch);
73            for (cond, body) in else_if_branches {
74                v.visit_expr(cond);
75                walk_block(v, body);
76            }
77            if let Some(body) = else_branch {
78                walk_block(v, body);
79            }
80        }
81        Stmt::For { from, to, body, .. } => {
82            v.visit_expr(from);
83            v.visit_expr(to);
84            walk_block(v, body);
85        }
86        Stmt::ForIn {
87            collection, body, ..
88        } => {
89            v.visit_expr(collection);
90            walk_block(v, body);
91        }
92        Stmt::While { condition, body } => {
93            v.visit_expr(condition);
94            walk_block(v, body);
95        }
96        Stmt::FunctionDecl { body, .. } | Stmt::MethodDecl { body, .. } => {
97            walk_block(v, body);
98        }
99        // Leaf / declaration statements with no child expressions to walk.
100        Stmt::Break
101        | Stmt::Continue
102        | Stmt::TypeDecl { .. }
103        | Stmt::EnumDecl { .. }
104        | Stmt::Export { .. }
105        | Stmt::Import { .. } => {}
106    }
107}
108
109pub fn walk_expr<V: Visitor + ?Sized>(v: &mut V, expr: &Expr) {
110    match expr {
111        Expr::Binary { left, right, .. } => {
112            v.visit_expr(left);
113            v.visit_expr(right);
114        }
115        Expr::Unary { expr, .. } => {
116            v.visit_expr(expr);
117        }
118        Expr::Call { callee, args, .. } => {
119            v.visit_expr(callee);
120            for arg in args {
121                match arg {
122                    Argument::Positional(e) => v.visit_expr(e),
123                    Argument::Named { value, .. } => v.visit_expr(value),
124                }
125            }
126        }
127        Expr::Index { expr, index } => {
128            v.visit_expr(expr);
129            v.visit_expr(index);
130        }
131        Expr::MemberAccess { object, .. } => {
132            v.visit_expr(object);
133        }
134        Expr::Ternary {
135            condition,
136            then_expr,
137            else_expr,
138        } => {
139            v.visit_expr(condition);
140            v.visit_expr(then_expr);
141            v.visit_expr(else_expr);
142        }
143        Expr::Function { body, .. } => {
144            walk_block(v, body);
145        }
146        Expr::Array(elements) => {
147            for e in elements {
148                v.visit_expr(e);
149            }
150        }
151        Expr::Switch { value, cases } => {
152            v.visit_expr(value);
153            for (pattern, result) in cases {
154                v.visit_expr(pattern);
155                v.visit_expr(result);
156            }
157        }
158        Expr::IfExpr {
159            condition,
160            then_expr,
161            else_if_branches,
162            else_expr,
163        } => {
164            v.visit_expr(condition);
165            v.visit_expr(then_expr);
166            for (cond, e) in else_if_branches {
167                v.visit_expr(cond);
168                v.visit_expr(e);
169            }
170            if let Some(e) = else_expr {
171                v.visit_expr(e);
172            }
173        }
174        // Leaf expressions.
175        Expr::Literal(_) | Expr::Variable(_) => {}
176    }
177}