Skip to main content

safe_migrate/analysis/
expr_visitor.rs

1// FILE: src/analysis/expr_visitor.rs
2use crate::analysis::expr_ir::ExprIr;
3use squawk_syntax::ast::{AstNode, Expr};
4
5pub struct ExprVisitor;
6
7impl ExprVisitor {
8    pub fn convert(expr: Expr) -> ExprIr {
9        match expr {
10            Expr::Literal(lit) => Self::convert_literal(lit),
11            Expr::NameRef(nr) => Self::convert_name_ref(nr),
12            Expr::CallExpr(ce) => Self::convert_call_expr(ce),
13            Expr::BinExpr(be) => Self::convert_bin_expr(be),
14            Expr::CastExpr(ce) => Self::convert_cast_expr(ce),
15            Expr::PrefixExpr(pe) => pe
16                .expr()
17                .map(Self::convert)
18                .unwrap_or(ExprIr::Literal("<prefix>".into())),
19            Expr::ParenExpr(pe) => pe
20                .expr()
21                .map(Self::convert)
22                .unwrap_or(ExprIr::Literal("<paren>".into())),
23            Expr::CaseExpr(ce) => Self::convert_case_expr(ce),
24            Expr::ArrayExpr(ae) => Self::convert_array_expr(ae),
25            Expr::BetweenExpr(be) => Self::convert_between_expr(be),
26            Expr::IndexExpr(ie) => Self::convert_index_expr(ie),
27            Expr::SliceExpr(se) => Self::convert_slice_expr(se),
28            Expr::FieldExpr(fe) => Self::convert_field_expr(fe),
29            Expr::PostfixExpr(pe) => Self::convert_postfix_expr(pe),
30            Expr::Collate(ce) => {
31                let left = ce
32                    .expr()
33                    .map(Self::convert)
34                    .unwrap_or(ExprIr::Literal("<lhs>".into()));
35                let right = ce
36                    .collation_ref()
37                    .map(|c| c.syntax().text().to_string())
38                    .unwrap_or_else(|| "<collation>".into());
39                ExprIr::BinaryOp {
40                    left: Box::new(left),
41                    op: "COLLATE".to_string(),
42                    right: Box::new(ExprIr::Literal(right)),
43                }
44            }
45            _ => ExprIr::Literal("<complex>".into()),
46        }
47    }
48
49    fn convert_literal(lit: squawk_syntax::ast::Literal) -> ExprIr {
50        ExprIr::Literal(lit.syntax().text().to_string())
51    }
52
53    fn convert_name_ref(nr: squawk_syntax::ast::NameRef) -> ExprIr {
54        let name = nr.text().to_string();
55        ExprIr::ColumnRef(name)
56    }
57
58    fn convert_call_expr(ce: squawk_syntax::ast::CallExpr) -> ExprIr {
59        let name = ce
60            .expr()
61            .map(|e| match e {
62                Expr::NameRef(nr) => nr.text().to_string(),
63                other => other.syntax().text().to_string(),
64            })
65            .unwrap_or_else(|| "<fn>".into());
66
67        // FIX for squawk_syntax >= 2.58.0: filter_map through the new `Arg` wrapper
68        let args = ce
69            .arg_list()
70            .map(|al| {
71                al.args()
72                    .filter_map(|arg| arg.expr())
73                    .map(Self::convert)
74                    .collect()
75            })
76            .unwrap_or_default();
77
78        ExprIr::FunctionCall { name, args }
79    }
80
81    fn convert_bin_expr(be: squawk_syntax::ast::BinExpr) -> ExprIr {
82        let left = be
83            .lhs()
84            .map(Self::convert)
85            .unwrap_or(ExprIr::Literal("<lhs>".into()));
86        let right = be
87            .rhs()
88            .map(Self::convert)
89            .unwrap_or(ExprIr::Literal("<rhs>".into()));
90
91        use squawk_syntax::ast::BinOp;
92        let op = be
93            .op()
94            .map(|o| match o {
95                BinOp::And(t) => t.text().to_string(),
96                BinOp::Caret(t) => t.text().to_string(),
97                BinOp::ColonEq(t) => t.text().to_string(),
98                BinOp::Eq(t) => t.text().to_string(),
99                BinOp::FatArrow(t) => t.text().to_string(),
100                BinOp::Gteq(t) => t.text().to_string(),
101                BinOp::Ilike(t) => t.text().to_string(),
102                BinOp::In(t) => t.text().to_string(),
103                BinOp::Is(t) => t.text().to_string(),
104                BinOp::LAngle(t) => t.text().to_string(),
105                BinOp::Like(t) => t.text().to_string(),
106                BinOp::Lteq(t) => t.text().to_string(),
107                BinOp::Minus(t) => t.text().to_string(),
108                BinOp::Neq(t) => t.text().to_string(),
109                BinOp::Neqb(t) => t.text().to_string(),
110                BinOp::Or(t) => t.text().to_string(),
111                BinOp::Overlaps(t) => t.text().to_string(),
112                BinOp::Percent(t) => t.text().to_string(),
113                BinOp::Plus(t) => t.text().to_string(),
114                BinOp::RAngle(t) => t.text().to_string(),
115                BinOp::Slash(t) => t.text().to_string(),
116                BinOp::Star(t) => t.text().to_string(),
117                BinOp::AtTimeZone(n) => n.syntax().text().to_string(),
118                BinOp::ColonColon(n) => n.syntax().text().to_string(),
119                BinOp::CustomOp(n) => n.syntax().text().to_string(),
120                BinOp::IsDistinctFrom(n) => n.syntax().text().to_string(),
121                BinOp::IsNot(n) => n.syntax().text().to_string(),
122                BinOp::IsNotDistinctFrom(n) => n.syntax().text().to_string(),
123                BinOp::NotIlike(n) => n.syntax().text().to_string(),
124                BinOp::NotIn(n) => n.syntax().text().to_string(),
125                BinOp::NotLike(n) => n.syntax().text().to_string(),
126                BinOp::NotSimilarTo(n) => n.syntax().text().to_string(),
127                BinOp::OperatorCall(n) => n.syntax().text().to_string(),
128                BinOp::SimilarTo(n) => n.syntax().text().to_string(),
129                // FIX for squawk_syntax >= 2.58.0: New Escape operator coverage
130                BinOp::Escape(t) => t.text().to_string(),
131            })
132            .unwrap_or_else(|| "<op>".into());
133
134        ExprIr::BinaryOp {
135            left: Box::new(left),
136            op,
137            right: Box::new(right),
138        }
139    }
140
141    fn convert_cast_expr(ce: squawk_syntax::ast::CastExpr) -> ExprIr {
142        let inner = ce
143            .expr()
144            .map(Self::convert)
145            .unwrap_or(ExprIr::Literal("<cast_inner>".into()));
146        let target_type = ce
147            .ty()
148            .map(|t| t.syntax().text().to_string())
149            .unwrap_or_else(|| "<type>".into());
150        ExprIr::Cast {
151            expr: Box::new(inner),
152            target_type,
153        }
154    }
155
156    fn convert_case_expr(ce: squawk_syntax::ast::CaseExpr) -> ExprIr {
157        let mut branches: Vec<ExprIr> = Vec::new();
158
159        branches.push(ce.expr().map(Self::convert).unwrap_or(ExprIr::Omitted));
160
161        if let Some(wcl) = ce.when_clause_list() {
162            for when in wcl.when_clauses() {
163                branches.push(
164                    when.condition()
165                        .map(Self::convert)
166                        .unwrap_or(ExprIr::Omitted),
167                );
168                branches.push(when.then().map(Self::convert).unwrap_or(ExprIr::Omitted));
169            }
170        }
171
172        branches.push(
173            ce.else_clause()
174                .and_then(|ec| ec.expr())
175                .map(Self::convert)
176                .unwrap_or(ExprIr::Omitted),
177        );
178
179        ExprIr::FunctionCall {
180            name: "<case>".into(),
181            args: branches,
182        }
183    }
184
185    fn convert_array_expr(ae: squawk_syntax::ast::ArrayExpr) -> ExprIr {
186        let elements: Vec<ExprIr> = ae.exprs().map(Self::convert).collect();
187        ExprIr::FunctionCall {
188            name: "<array>".into(),
189            args: elements,
190        }
191    }
192
193    fn convert_between_expr(be: squawk_syntax::ast::BetweenExpr) -> ExprIr {
194        let args = vec![
195            be.target().map(Self::convert).unwrap_or(ExprIr::Omitted),
196            be.start().map(Self::convert).unwrap_or(ExprIr::Omitted),
197            be.end().map(Self::convert).unwrap_or(ExprIr::Omitted),
198        ];
199        ExprIr::FunctionCall {
200            name: "<between>".into(),
201            args,
202        }
203    }
204
205    fn convert_index_expr(ie: squawk_syntax::ast::IndexExpr) -> ExprIr {
206        let args = vec![
207            ie.base().map(Self::convert).unwrap_or(ExprIr::Omitted),
208            ie.index().map(Self::convert).unwrap_or(ExprIr::Omitted),
209        ];
210        ExprIr::FunctionCall {
211            name: "<index>".into(),
212            args,
213        }
214    }
215
216    fn convert_slice_expr(se: squawk_syntax::ast::SliceExpr) -> ExprIr {
217        let args = vec![
218            se.base().map(Self::convert).unwrap_or(ExprIr::Omitted),
219            se.start().map(Self::convert).unwrap_or(ExprIr::Omitted),
220            se.end().map(Self::convert).unwrap_or(ExprIr::Omitted),
221        ];
222        ExprIr::FunctionCall {
223            name: "<slice>".into(),
224            args,
225        }
226    }
227
228    fn convert_field_expr(fe: squawk_syntax::ast::FieldExpr) -> ExprIr {
229        let args = vec![
230            fe.base().map(Self::convert).unwrap_or(ExprIr::Omitted),
231            fe.field()
232                .map(|f| ExprIr::Literal(f.text().to_string()))
233                .unwrap_or(ExprIr::Omitted),
234        ];
235        ExprIr::FunctionCall {
236            name: "<field>".into(),
237            args,
238        }
239    }
240
241    fn convert_postfix_expr(pe: squawk_syntax::ast::PostfixExpr) -> ExprIr {
242        let mut args = Vec::new();
243        for child in pe.syntax().children() {
244            if let Some(expr) = Expr::cast(child) {
245                args.push(Self::convert(expr));
246            }
247        }
248        ExprIr::FunctionCall {
249            name: "<postfix>".into(),
250            args,
251        }
252    }
253}