ryo_mutations/idiom/
bool_simplify.rs1use ryo_source::pure::{PureBlock, PureExpr, PureStmt};
14use ryo_symbol::SymbolId;
15
16use crate::Mutation;
17
18#[derive(Debug, Clone, Default)]
30pub struct BoolSimplifyMutation {
31 pub target_fn: Option<SymbolId>,
33}
34
35impl BoolSimplifyMutation {
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 pub fn in_function(mut self, id: SymbolId) -> Self {
42 self.target_fn = Some(id);
43 self
44 }
45
46 fn is_bool_literal(expr: &PureExpr) -> Option<bool> {
48 match expr {
49 PureExpr::Lit(lit) => match lit.as_str() {
50 "true" => Some(true),
51 "false" => Some(false),
52 _ => None,
53 },
54 PureExpr::Path(path) => match path.as_str() {
55 "true" => Some(true),
56 "false" => Some(false),
57 _ => None,
58 },
59 _ => None,
60 }
61 }
62
63 fn transform_expr(&self, expr: &mut PureExpr) -> usize {
65 let mut changes = 0;
66
67 if let PureExpr::Binary { op, left, right } = expr {
69 let is_eq = op == "==";
70 let is_neq = op == "!=";
71
72 if is_eq || is_neq {
73 if let Some(bool_val) = Self::is_bool_literal(left) {
75 let other = std::mem::replace(
77 right.as_mut(),
78 PureExpr::Path("__placeholder".to_string()),
79 );
80 *expr = Self::simplify(other, bool_val, is_eq);
81 return 1;
82 }
83
84 if let Some(bool_val) = Self::is_bool_literal(right) {
86 let other = std::mem::replace(
88 left.as_mut(),
89 PureExpr::Path("__placeholder".to_string()),
90 );
91 *expr = Self::simplify(other, bool_val, is_eq);
92 return 1;
93 }
94 }
95 }
96
97 match expr {
99 PureExpr::Binary { left, right, .. } => {
100 changes += self.transform_expr(left);
101 changes += self.transform_expr(right);
102 }
103 PureExpr::Unary { expr: inner, .. } => {
104 changes += self.transform_expr(inner);
105 }
106 PureExpr::Call { func, args } => {
107 changes += self.transform_expr(func);
108 for arg in args {
109 changes += self.transform_expr(arg);
110 }
111 }
112 PureExpr::MethodCall { receiver, args, .. } => {
113 changes += self.transform_expr(receiver);
114 for arg in args {
115 changes += self.transform_expr(arg);
116 }
117 }
118 PureExpr::Field { expr: inner, .. } => {
119 changes += self.transform_expr(inner);
120 }
121 PureExpr::Index { expr: inner, index } => {
122 changes += self.transform_expr(inner);
123 changes += self.transform_expr(index);
124 }
125 PureExpr::Block { block, .. } => {
126 changes += self.transform_block(block);
127 }
128 PureExpr::If {
129 cond,
130 then_branch,
131 else_branch,
132 } => {
133 changes += self.transform_expr(cond);
134 changes += self.transform_block(then_branch);
135 if let Some(else_expr) = else_branch {
136 changes += self.transform_expr(else_expr);
137 }
138 }
139 PureExpr::Match { expr: e, arms } => {
140 changes += self.transform_expr(e);
141 for arm in arms {
142 changes += self.transform_expr(&mut arm.body);
143 }
144 }
145 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
146 changes += self.transform_block(block);
147 }
148 PureExpr::For {
149 expr: iter_expr,
150 body,
151 ..
152 } => {
153 changes += self.transform_expr(iter_expr);
154 changes += self.transform_block(body);
155 }
156 PureExpr::Closure { body, .. } => {
157 changes += self.transform_expr(body);
158 }
159 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
160 for e in exprs {
161 changes += self.transform_expr(e);
162 }
163 }
164 PureExpr::Struct { fields, .. } => {
165 for (_, e) in fields {
166 changes += self.transform_expr(e);
167 }
168 }
169 PureExpr::Ref { expr: inner, .. } => {
170 changes += self.transform_expr(inner);
171 }
172 PureExpr::Return(Some(inner)) => {
173 changes += self.transform_expr(inner);
174 }
175 PureExpr::Try(inner) | PureExpr::Await(inner) => {
176 changes += self.transform_expr(inner);
177 }
178 _ => {}
179 }
180
181 changes
182 }
183
184 fn simplify(expr: PureExpr, bool_val: bool, is_eq: bool) -> PureExpr {
186 let need_negate = (is_eq && !bool_val) || (!is_eq && bool_val);
191
192 if need_negate {
193 PureExpr::Unary {
194 op: "!".to_string(),
195 expr: Box::new(expr),
196 }
197 } else {
198 expr
199 }
200 }
201
202 pub fn transform_block(&self, block: &mut PureBlock) -> usize {
203 let mut changes = 0;
204 for stmt in &mut block.stmts {
205 changes += self.transform_stmt(stmt);
206 }
207 changes
208 }
209
210 fn transform_stmt(&self, stmt: &mut PureStmt) -> usize {
211 match stmt {
212 PureStmt::Local { init: Some(e), .. } => self.transform_expr(e),
213 PureStmt::Semi(e) | PureStmt::Expr(e) => self.transform_expr(e),
214 _ => 0,
215 }
216 }
217}
218
219impl Mutation for BoolSimplifyMutation {
220 fn describe(&self) -> String {
221 "Simplify boolean comparisons (x == true → x)".to_string()
222 }
223
224 fn mutation_type(&self) -> &'static str {
225 "BoolSimplify"
226 }
227
228 fn box_clone(&self) -> Box<dyn Mutation> {
229 Box::new(self.clone())
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_is_bool_literal() {
239 assert_eq!(
240 BoolSimplifyMutation::is_bool_literal(&PureExpr::Lit("true".to_string())),
241 Some(true)
242 );
243 assert_eq!(
244 BoolSimplifyMutation::is_bool_literal(&PureExpr::Lit("false".to_string())),
245 Some(false)
246 );
247 assert_eq!(
248 BoolSimplifyMutation::is_bool_literal(&PureExpr::Path("true".to_string())),
249 Some(true)
250 );
251 assert_eq!(
252 BoolSimplifyMutation::is_bool_literal(&PureExpr::Lit("42".to_string())),
253 None
254 );
255 }
256
257 #[test]
258 fn test_simplify_eq_true() {
259 let expr = PureExpr::Path("x".to_string());
261 let result = BoolSimplifyMutation::simplify(expr, true, true);
262 assert!(matches!(result, PureExpr::Path(s) if s == "x"));
263 }
264
265 #[test]
266 fn test_simplify_eq_false() {
267 let expr = PureExpr::Path("x".to_string());
269 let result = BoolSimplifyMutation::simplify(expr, false, true);
270 assert!(matches!(result, PureExpr::Unary { op, .. } if op == "!"));
271 }
272
273 #[test]
274 fn test_simplify_neq_true() {
275 let expr = PureExpr::Path("x".to_string());
277 let result = BoolSimplifyMutation::simplify(expr, true, false);
278 assert!(matches!(result, PureExpr::Unary { op, .. } if op == "!"));
279 }
280
281 #[test]
282 fn test_simplify_neq_false() {
283 let expr = PureExpr::Path("x".to_string());
285 let result = BoolSimplifyMutation::simplify(expr, false, false);
286 assert!(matches!(result, PureExpr::Path(s) if s == "x"));
287 }
288}