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