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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use super::{Simplify, dedup_operands, has_complement};
use std::mem;
use toasty_core::stmt;
impl Simplify<'_> {
/// Heavyweight OR rewrites. Cheap canonicalization (flatten, true
/// short-circuit, drop false, null propagation) runs in `fold::expr_or`
/// before this is reached.
pub(super) fn simplify_expr_or(&mut self, expr: &mut stmt::ExprOr) -> Option<stmt::Expr> {
dedup_operands(&mut expr.operands);
// Absorption law, `x or (x and y)` → `x`
// If an operand is an AND that contains another operand of the OR, remove the AND.
let non_and_operands: Vec<_> = expr
.operands
.iter()
.filter(|op| !matches!(op, stmt::Expr::And(_)))
.cloned()
.collect();
expr.operands.retain(|operand| {
if let stmt::Expr::And(and_expr) = operand {
// Remove this AND if any of its operands appears as a direct operand of the OR
!and_expr
.operands
.iter()
.any(|op| non_and_operands.iter().any(|e| e.is_equivalent_to(op)))
} else {
true
}
});
// Factoring, `(a and b) or (a and c)` → `a and (b or c)`
// Find common factors across all AND operands and factor them out.
if let Some(factored) = self.try_factor_or(expr) {
return Some(factored);
}
// Complement law, `a or not(a)` → `true` (only if `a` is non-nullable)
if has_complement(&expr.operands) {
return Some(true.into());
}
// The variant-tautology rewrite (`is_variant(x, 0) or is_variant(x, 1)`
// covering all variants → `true`) fires in the pre-lowering
// `LowerStatement::visit_expr_mut` `Expr::Or` arm, not here.
// OR-to-IN conversion, `a = 1 or a = 2 or a = 3` → `a in (1, 2, 3)`
if let Some(in_list) = self.try_or_to_in_list(expr) {
return Some(in_list);
}
if expr.operands.is_empty() {
Some(false.into())
} else if expr.operands.len() == 1 {
Some(expr.operands.remove(0))
} else {
None
}
}
/// Attempts to factor common terms from AND expressions within an OR.
/// `(a and b) or (a and c)` → `a and (b or c)`
/// `(a and b and c) or (a and b and d)` → `a and b and (c or d)`
fn try_factor_or(&self, expr: &mut stmt::ExprOr) -> Option<stmt::Expr> {
// Need at least 2 operands, all must be ANDs
if expr.operands.len() < 2 {
return None;
}
if !expr
.operands
.iter()
.all(|op| matches!(op, stmt::Expr::And(_)))
{
return None;
}
// Find all common factors by checking which operands from the first AND
// appear in all other ANDs
let first_and = match &expr.operands[0] {
stmt::Expr::And(and) => and,
_ => unreachable!(),
};
let common: Vec<_> = first_and
.operands
.iter()
.filter(|op| {
expr.operands[1..].iter().all(|other| {
if let stmt::Expr::And(other_and) = other {
other_and.operands.iter().any(|e| e.is_equivalent_to(op))
} else {
false
}
})
})
.cloned()
.collect();
if common.is_empty() {
return None;
}
// Remove all common factors from each AND
for operand in &mut expr.operands {
if let stmt::Expr::And(and) = operand {
and.operands
.retain(|op| !common.iter().any(|c| c.is_equivalent_to(op)));
// If only one operand left, unwrap the AND
if and.operands.len() == 1 {
*operand = and.operands.pop().unwrap();
} else if and.operands.is_empty() {
*operand = true.into();
}
}
}
// Common factors AND (the modified OR)
let mut result = common;
let or_expr = stmt::ExprOr {
operands: mem::take(&mut expr.operands),
};
result.push(stmt::Expr::Or(or_expr));
Some(stmt::Expr::and_from_vec(result))
}
/// Converts disjunctive equality chains to IN lists.
///
/// `a = 1 or a = 2 or b = 3` → `a in (1, 2) or b = 3`
/// `a = 1 or a = 2 or b = 3 or b = 4` → `a in (1, 2) or b in (3, 4)`
///
/// Groups equality comparisons by their LHS and converts groups with 2+
/// values into IN lists. Non-equality operands are preserved.
fn try_or_to_in_list(&self, expr: &mut stmt::ExprOr) -> Option<stmt::Expr> {
let mut groups: Vec<(stmt::Expr, Vec<stmt::Value>)> = Vec::new();
let mut other_operands: Vec<stmt::Expr> = Vec::new();
for operand in mem::take(&mut expr.operands) {
if let stmt::Expr::BinaryOp(bin_op) = &operand
&& bin_op.op.is_eq()
&& let stmt::Expr::Value(value) = bin_op.rhs.as_ref()
{
// Keep non-deterministic expressions in separate groups so an
// IN list cannot collapse independent evaluations into one.
if let Some((_, values)) = groups
.iter_mut()
.find(|(lhs, _)| lhs.is_equivalent_to(&bin_op.lhs))
{
values.push(value.clone());
} else {
groups.push(((*bin_op.lhs).clone(), vec![value.clone()]));
}
continue;
}
// Non-equality or non-constant RHS - keep as is
other_operands.push(operand);
}
let has_conversion = groups.iter().any(|(_, values)| values.len() >= 2);
let mut result_operands = other_operands;
for (lhs, mut values) in groups {
let operand = if values.len() >= 2 {
stmt::Expr::in_list(lhs, stmt::Expr::list(values))
} else {
stmt::Expr::eq(lhs, values.pop().unwrap())
};
result_operands.push(operand);
}
if has_conversion && result_operands.len() == 1 {
Some(result_operands.remove(0))
} else {
expr.operands = result_operands;
None
}
}
}