1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//! Cheap, schema-free, idempotent rewrites.
//!
//! `fold` is the canonicalization pass shared between `lower`, `simplify`, and
//! `exec_statement`. Each rule is local, O(n) per pass, and produces output
//! that satisfies `fold(fold(x)) == fold(x)`. Rules pattern-match on local
//! structure only; no schema lookups.
//!
//! See `docs/dev/design/lower-then-simplify.md` for the full taxonomy.
mod expr_and;
mod expr_binary_op;
mod expr_cast;
mod expr_in_list;
mod expr_intersects;
mod expr_is_null;
mod expr_is_superset;
mod expr_let;
mod expr_list;
mod expr_match;
mod expr_not;
mod expr_or;
mod expr_record;
#[cfg(test)]
mod tests;
use toasty_core::stmt::{self, Expr, Node, VisitMut};
/// Folds an expression tree in place: canonicalizes structure, evaluates
/// constants, propagates nulls, drops boolean units. Idempotent.
pub(crate) fn fold_stmt<T: Node>(stmt: &mut T) {
Fold.visit_mut(stmt);
}
/// Visitor that applies fold rules bottom-up.
struct Fold;
impl VisitMut for Fold {
fn visit_expr_mut(&mut self, i: &mut Expr) {
// Recurse into children first so parent rules see canonical input.
stmt::visit_mut::visit_expr_mut(self, i);
// A single rule's output may itself be fold-eligible (e.g.
// `not(eq(x, y))` becomes `ne(x, y)`, which then constant-folds
// to a literal when both sides are values, or De Morgan creates a
// new `Or` of `Not`s that need their own pass). Recurse on the
// replacement so the fixed point is reached in one traversal.
if let Some(mut expr) = fold_one(i) {
self.visit_expr_mut(&mut expr);
*i = expr;
}
}
}
/// Apply one round of fold rules to `i`. Returns `Some` if a rule fired and
/// produced a replacement expression; returns `None` if no rule applied or
/// the rule mutated `i` in place.
fn fold_one(i: &mut Expr) -> Option<Expr> {
match i {
Expr::And(expr) => expr_and::fold_expr_and(expr),
Expr::BinaryOp(expr) => {
expr_binary_op::fold_expr_binary_op(expr.op, &mut expr.lhs, &mut expr.rhs)
}
Expr::Cast(expr) => expr_cast::fold_expr_cast(expr),
Expr::InList(expr) => expr_in_list::fold_expr_in_list(expr),
Expr::Intersects(expr) => expr_intersects::fold_expr_intersects(expr),
Expr::IsNull(expr) => expr_is_null::fold_expr_is_null(expr),
Expr::IsSuperset(expr) => expr_is_superset::fold_expr_is_superset(expr),
Expr::Let(expr) => expr_let::fold_expr_let(expr),
Expr::List(expr) => expr_list::fold_expr_list(expr),
Expr::Match(expr) => expr_match::fold_expr_match(expr),
Expr::Not(expr) => expr_not::fold_expr_not(expr),
Expr::Or(expr) => expr_or::fold_expr_or(expr),
Expr::Record(expr) => expr_record::fold_expr_record(expr),
// No fold rules yet — leaves and not-yet-migrated variants pass
// through untouched. Listed explicitly so adding a new `Expr`
// variant forces a decision about whether it has cheap rewrites.
Expr::AllOp(_)
| Expr::Any(_)
| Expr::AnyOp(_)
| Expr::Arg(_)
| Expr::Between(_)
| Expr::Default
| Expr::Error(_)
| Expr::Exists(_)
| Expr::Func(_)
| Expr::Ident(_)
| Expr::InSubquery(_)
| Expr::Incoming(_)
| Expr::IsVariant(_)
| Expr::Length(_)
| Expr::Like(_)
| Expr::Map(_)
| Expr::Project(_)
| Expr::Reference(_)
| Expr::StartsWith(_)
| Expr::Static(_)
| Expr::Stmt(_)
| Expr::Value(_) => None,
}
}