1use crate::analysis::{prefix_effects_readable, subtree_contains};
8use crate::effect::Effect;
9use crate::kind::{BinOp, LitRef, NodeKind, UnOp};
10use crate::pattern::{matches, Pat};
11use crate::rule::{Edit, RewriteCtx, Rule};
12use crate::lang::{Lang, ReassocOutcome};
13use crate::walk::Walk;
14
15pub struct ParenRemoval;
20
21impl<L: Lang> Rule<L> for ParenRemoval {
22 fn name(&self) -> &'static str {
23 "paren_removal"
24 }
25 fn kinds(&self) -> &'static [NodeKind] {
26 &[NodeKind::Paren]
27 }
28 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
29 let RewriteCtx { lang, walk: _ } = ctx;
30 if lang.kind(id) != NodeKind::Paren {
31 return None;
32 }
33 let inner = *lang.children(id).first()?;
34 Some(Edit::Replace {
35 target: id,
36 with: inner,
37 })
38 }
39}
40
41fn self_is_terminal<L: Lang>(lang: &L, n: L::Id) -> bool {
50 matches!(
51 lang.kind(n),
52 NodeKind::Return | NodeKind::Throw | NodeKind::Break | NodeKind::Continue | NodeKind::Yield
53 )
54}
55
56fn contains_yield<L: Lang>(lang: &L, n: L::Id) -> bool {
59 subtree_contains(lang, n, |x| lang.kind(x) == NodeKind::Yield)
60}
61
62pub struct ConstCondition;
63
64impl<L: Lang> Rule<L> for ConstCondition {
65 fn name(&self) -> &'static str {
66 "const_condition"
67 }
68 fn kinds(&self) -> &'static [NodeKind] {
69 &[NodeKind::If]
70 }
71 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
72 let RewriteCtx { lang, walk } = ctx;
73 if lang.kind(id) != NodeKind::If {
74 return None;
75 }
76 let ch = lang.children(id);
77 let cond = *ch.first()?;
78 match lang.literal(cond) {
79 Some(LitRef::Bool(true)) => {
80 let then = ch.get(1).copied()?;
86 if let Some(&els) = ch.get(2) {
89 if contains_yield(&*lang, els) {
90 return None;
91 }
92 }
93 let insert: Vec<L::Id> = if lang.kind(then) == NodeKind::Block {
94 lang.children(then).to_vec()
95 } else {
96 vec![then]
97 };
98 let terminates = insert.iter().any(|&s| self_is_terminal(lang, s));
99 if let (Some(parent), Some(index)) = (walk.parent(id), walk.index(id)) {
100 if lang.kind(parent) == NodeKind::Block {
101 let siblings = lang.children(parent).to_vec();
102 let remove = if terminates && index + 1 < siblings.len() {
103 siblings.len() - index
104 } else {
105 1
106 };
107 if remove > 1
110 && siblings[index + 1..]
111 .iter()
112 .any(|&s| contains_yield(&*lang, s))
113 {
114 return None;
115 }
116 return Some(Edit::Splice {
117 node: parent,
118 index,
119 remove,
120 insert,
121 });
122 }
123 if lang.kind(parent) == NodeKind::Case && terminates {
132 let siblings = lang.children(parent).to_vec();
133 if index + 1 < siblings.len() {
134 return None;
135 }
136 }
137 }
138 Some(Edit::Replace {
139 target: id,
140 with: then,
141 })
142 }
143 Some(LitRef::Bool(false)) => {
144 let then = *ch.get(1)?;
149 if contains_yield(&*lang, then) {
150 return None;
151 }
152 let els = ch.get(2).copied();
153 if let (Some(parent), Some(index)) = (walk.parent(id), walk.index(id)) {
154 if lang.kind(parent) == NodeKind::Block {
155 let insert: Vec<L::Id> = match els {
156 Some(e) => {
157 if lang.kind(e) == NodeKind::Block {
158 lang.children(e).to_vec()
159 } else {
160 vec![e]
161 }
162 }
163 None => Vec::new(),
164 };
165 return Some(if insert.is_empty() {
166 Edit::Delete { node: id }
167 } else {
168 Edit::Splice {
169 node: parent,
170 index,
171 remove: 1,
172 insert,
173 }
174 });
175 }
176 }
177 match els {
178 Some(e) => Some(Edit::Replace {
179 target: id,
180 with: e,
181 }),
182 None => Some(Edit::Delete { node: id }),
183 }
184 }
185 _ => None,
186 }
187 }
188}
189
190static P_IF_RET_TF: Pat = Pat::Kind(
197 NodeKind::If,
198 &[Pat::Bind("cond"), Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)]), Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])],
199);
200static P_IF_RET_FT: Pat = Pat::Kind(
201 NodeKind::If,
202 &[Pat::Bind("cond"), Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)]), Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])],
203);
204static P_IF_BRET_TF: Pat = Pat::Kind(
206 NodeKind::If,
207 &[
208 Pat::Bind("cond"),
209 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])]),
210 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])]),
211 ],
212);
213static P_IF_BRET_FT: Pat = Pat::Kind(
214 NodeKind::If,
215 &[
216 Pat::Bind("cond"),
217 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])]),
218 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])]),
219 ],
220);
221
222pub struct BooleanReturn;
223
224impl<L: Lang> Rule<L> for BooleanReturn {
225 fn name(&self) -> &'static str {
226 "boolean_return"
227 }
228 fn kinds(&self) -> &'static [NodeKind] {
229 &[NodeKind::If]
230 }
231 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
232 let RewriteCtx { lang, walk: _ } = ctx;
233 if lang.kind(id) != NodeKind::If {
234 return None;
235 }
236 let negated = |cond: L::Id, lang: &mut L| {
238 let nc = lang.build_unary(UnOp::Not, cond);
239 lang.build_return(Some(nc))
240 };
241 for (pat, invert) in [
242 (&P_IF_RET_TF, false),
243 (&P_IF_BRET_TF, false),
244 (&P_IF_RET_FT, true),
245 (&P_IF_BRET_FT, true),
246 ] {
247 if let Some(b) = matches(&*lang, id, pat) {
248 let cond = lookup(&b, "cond")?;
249 if !lang.is_bool(cond) {
250 return None;
251 }
252 let ret = if invert {
253 negated(cond, lang)
254 } else {
255 lang.build_return(Some(cond))
256 };
257 return Some(Edit::Replace {
258 target: id,
259 with: ret,
260 });
261 }
262 }
263 None
264 }
265}
266
267fn lookup<Id: Copy>(binds: &[(&'static str, Id)], name: &str) -> Option<Id> {
268 binds.iter().find(|(n, _)| *n == name).map(|(_, id)| *id)
269}
270
271pub struct IfElseEmpty;
277
278impl<L: Lang> Rule<L> for IfElseEmpty {
279 fn name(&self) -> &'static str {
280 "if_else_empty"
281 }
282 fn kinds(&self) -> &'static [NodeKind] {
283 &[NodeKind::If]
284 }
285 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
286 let RewriteCtx { lang, walk } = ctx;
287 if lang.kind(id) != NodeKind::If {
288 return None;
289 }
290 let ch = lang.children(id);
291 let cond = *ch.first()?;
292 let is_empty_block = |n: L::Id| -> bool {
293 lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
294 };
295 match ch.len() {
296 3 => {
297 let (then, els) = (ch[1], ch[2]);
298 if is_empty_block(then) {
299 if is_empty_block(els) {
300 return Some(Edit::Splice {
302 node: id,
303 index: 0,
304 remove: 3,
305 insert: vec![cond, then],
306 });
307 }
308 let nc = lang.build_unary(UnOp::Not, cond);
310 return Some(Edit::Splice {
311 node: id,
312 index: 0,
313 remove: 3,
314 insert: vec![nc, els],
315 });
316 }
317 if is_empty_block(els) {
318 return Some(Edit::Splice {
319 node: id,
320 index: 0,
321 remove: 3,
322 insert: vec![cond, then],
323 });
324 }
325 None
326 }
327 2 => {
328 let then = ch[1];
329 let parent_is_block = walk
336 .parent(id)
337 .map(|p| lang.kind(p) == NodeKind::Block)
338 .unwrap_or(false);
339 if is_empty_block(then)
340 && lang.effect(cond) <= Effect::MayRead
341 && parent_is_block
342 {
343 return Some(Edit::Delete { node: id });
345 }
346 None
347 }
348 _ => None,
349 }
350 }
351}
352
353pub struct BoolCompare;
359
360impl<L: Lang> Rule<L> for BoolCompare {
361 fn name(&self) -> &'static str {
362 "bool_compare"
363 }
364 fn kinds(&self) -> &'static [NodeKind] {
365 &[NodeKind::Binary]
366 }
367 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
368 let RewriteCtx { lang, walk } = ctx;
369 if lang.kind(id) != NodeKind::Binary {
370 return None;
371 }
372 let op = lang.bin_op(id)?;
373 if !matches!(op, BinOp::Eq | BinOp::Ne) {
374 return None;
375 }
376 let ch = lang.children(id);
377 let (l, r) = (*ch.first()?, *ch.get(1)?);
378 let lit_bool = |n: L::Id| -> Option<bool> {
379 match lang.literal(n) {
380 Some(LitRef::Bool(b)) => Some(b),
381 _ => None,
382 }
383 };
384 let (x, lit) = match (lit_bool(l), lit_bool(r)) {
386 (None, Some(b)) => (l, b),
387 (Some(b), None) => (r, b),
388 _ => return None,
389 };
390 if !lang.is_bool(x) {
391 let in_cond = walk.parent(id).is_some_and(|p| {
396 matches!(lang.kind(p), NodeKind::If | NodeKind::While) && lang.children(p).first() == Some(&id)
397 });
398 if !in_cond {
399 return None;
400 }
401 }
402 let neg = match (op, lit) {
404 (BinOp::Eq, true) | (BinOp::Ne, false) => false,
405 (BinOp::Eq, false) | (BinOp::Ne, true) => true,
406 _ => return None,
407 };
408 let with = if neg {
409 lang.build_unary(UnOp::Not, x)
410 } else {
411 x
412 };
413 Some(Edit::Replace {
414 target: id,
415 with,
416 })
417 }
418}
419
420pub struct DoubleNot;
426
427impl<L: Lang> Rule<L> for DoubleNot {
428 fn name(&self) -> &'static str {
429 "double_not"
430 }
431 fn kinds(&self) -> &'static [NodeKind] {
432 &[NodeKind::Unary]
433 }
434 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
435 let RewriteCtx { lang, walk: _ } = ctx;
436 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
437 return None;
438 }
439 let inner = *lang.children(id).first()?;
440 if lang.kind(inner) != NodeKind::Unary || lang.un_op(inner) != Some(UnOp::Not) {
441 return None;
442 }
443 let x = *lang.children(inner).first()?;
444 Some(Edit::Replace {
445 target: id,
446 with: x,
447 })
448 }
449}
450
451pub struct BoolNotFold;
457
458impl<L: Lang> Rule<L> for BoolNotFold {
459 fn name(&self) -> &'static str {
460 "bool_not_fold"
461 }
462 fn kinds(&self) -> &'static [NodeKind] {
463 &[NodeKind::Unary]
464 }
465 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
466 let RewriteCtx { lang, walk: _ } = ctx;
467 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
468 return None;
469 }
470 let operand = *lang.children(id).first()?;
471 match lang.literal(operand) {
472 Some(LitRef::Bool(b)) => {
473 let folded = lang.build_bool(!b);
474 Some(Edit::Replace {
475 target: id,
476 with: folded,
477 })
478 }
479 _ => None,
480 }
481 }
482}
483
484pub struct SelfAssign;
489
490impl<L: Lang> Rule<L> for SelfAssign {
491 fn name(&self) -> &'static str {
492 "self_assign"
493 }
494 fn kinds(&self) -> &'static [NodeKind] {
495 &[NodeKind::Assign]
496 }
497 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
498 let RewriteCtx { lang, walk: _ } = ctx;
499 if lang.kind(id) != NodeKind::Assign {
500 return None;
501 }
502 if lang.assign_op(id).is_some() {
503 return None; }
505 let ch = lang.children(id);
506 let (t, v) = (*ch.first()?, *ch.get(1)?);
507 if lang.kind(t) != NodeKind::VarRef || lang.kind(v) != NodeKind::VarRef {
508 return None;
509 }
510 if lang.var_name(t) != lang.var_name(v) || !lang.is_local_var(t) {
511 return None;
512 }
513 Some(Edit::Delete { node: id })
514 }
515}
516
517pub struct DeadDecl;
531
532impl<L: Lang> Rule<L> for DeadDecl {
533 fn name(&self) -> &'static str {
534 "dead_decl"
535 }
536 fn kinds(&self) -> &'static [NodeKind] {
537 &[NodeKind::VarDecl]
538 }
539 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
540 let RewriteCtx { lang, walk } = ctx;
541 if lang.kind(id) != NodeKind::VarDecl {
542 return None;
543 }
544 let ch = lang.children(id).to_vec();
545 if let Some(&v) = ch.first() {
548 if lang.effect(v) > Effect::MayRead {
549 return None;
550 }
551 }
552 let parent = walk.parent(id)?;
553 if lang.kind(parent) != NodeKind::Block {
554 return None;
555 }
556 let idx = walk.index(id)?;
557 let stmts = lang.children(parent).to_vec();
558 let name_str = lang.var_name(id)?.to_string();
564 let referenced = stmts[idx + 1..].iter().any(|&s| {
565 subtree_contains(&*lang, s, |n| {
566 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name_str.as_str())
567 })
568 });
569 if referenced {
570 return None;
571 }
572 let name_key = lang.var_key(id)?;
574 let mut w = Watch::<L>::new(name_key, &[]);
575 for &s in &stmts[idx + 1..] {
576 scan_region(&*lang, s, &mut w);
577 }
578 if w.opaque || w.shadowed {
579 return None;
580 }
581 Some(Edit::Delete { node: id })
582 }
583}
584
585pub struct AssignBackFold;
600
601impl<L: Lang> Rule<L> for AssignBackFold {
602 fn name(&self) -> &'static str {
603 "assign_back_fold"
604 }
605 fn kinds(&self) -> &'static [NodeKind] {
606 &[NodeKind::VarDecl]
607 }
608 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
609 let RewriteCtx { lang, walk } = ctx;
610 if lang.kind(id) != NodeKind::VarDecl {
611 return None;
612 }
613 let ch = lang.children(id).to_vec();
614 let (&init, has_init) = (ch.first()?, !ch.is_empty());
615 if !has_init {
616 return None; }
618 let name_key = lang.var_key(id)?;
619 if !lang.is_wide_decl(id) {
623 return None;
624 }
625 let parent = walk.parent(id)?;
626 if lang.kind(parent) != NodeKind::Block {
627 return None;
628 }
629 let idx = walk.index(id)?;
630 let stmts = lang.children(parent).to_vec();
631
632 let name_str = lang.var_name(id)?.to_string();
637 for &s in &stmts[idx + 1..] {
638 let mut w = Watch::<L>::new(name_key, &[]);
639 scan_region(&*lang, s, &mut w);
640 let referenced = subtree_contains(&*lang, s, |n| {
641 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name_str.as_str())
642 });
643 let touched = referenced || w.opaque || w.shadowed;
644 if !touched {
645 continue; }
647 let assign = match lang.kind(s) {
649 NodeKind::Assign => s,
650 NodeKind::ExprStmt => {
651 let c = lang.children(s);
652 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
653 c[0]
654 } else {
655 return None;
656 }
657 }
658 _ => return None, };
660 let ach = lang.children(assign).to_vec();
661 if ach.len() != 2 {
662 return None;
663 }
664 let (target, rhs) = (ach[0], ach[1]);
665 if lang.kind(target) != NodeKind::VarRef || lang.var_key(target) != Some(name_key) {
666 return None;
667 }
668 let (op, k) = match lang.assign_op(assign) {
671 Some(op) => (op, lang.literal(rhs).and_then(|x| x.as_int())?),
672 None => {
673 if lang.kind(rhs) != NodeKind::Binary {
674 return None;
675 }
676 let op = lang.bin_op(rhs)?;
677 let rc = lang.children(rhs).to_vec();
678 if rc.len() != 2 || lang.kind(rc[0]) != NodeKind::VarRef {
679 return None;
680 }
681 if lang.var_key(rc[0]) != Some(name_key) {
683 return None;
684 }
685 (op, lang.literal(rc[1]).and_then(|x| x.as_int())?)
686 }
687 };
688 if op.is_short_circuit() || op.is_comparison() {
689 return None;
690 }
691 if matches!(op, BinOp::Div | BinOp::Rem) && k == 0 {
693 return None;
694 }
695 let lit = if lang.is_long_decl(id) {
700 lang.build_int(k, true)
701 } else {
702 lang.build_int(k, false)
703 };
704 let init = if lang.is_long_decl(id) {
705 match lang.literal(init) {
706 Some(crate::kind::LitRef::Int(v)) => lang.build_int(v, true),
707 _ => init,
708 }
709 } else {
710 init
711 };
712 let merged = lang.build_bin(op, init, lit);
713 return Some(Edit::Multi(vec![
715 Edit::Delete { node: s },
716 Edit::Splice { node: id, index: 0, remove: ch.len(), insert: vec![merged] },
717 ]));
718 }
719 None
720 }
721}
722
723pub struct ArithIdentity;
724
725impl<L: Lang> Rule<L> for ArithIdentity {
726 fn name(&self) -> &'static str {
727 "arith_identity"
728 }
729 fn kinds(&self) -> &'static [NodeKind] {
730 &[NodeKind::Binary]
731 }
732 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
733 let RewriteCtx { lang, walk: _ } = ctx;
734 if lang.kind(id) != NodeKind::Binary {
735 return None;
736 }
737 let op = lang.bin_op(id)?;
738 let ch = lang.children(id);
739 let (l, r) = (*ch.first()?, *ch.get(1)?);
740 let int_lit = |n: L::Id, v: i64| -> bool {
741 matches!(lang.literal(n), Some(l) if l.as_int() == Some(v))
742 };
743 let x = match op {
744 BinOp::Add if int_lit(r, 0) => l,
745 BinOp::Add if int_lit(l, 0) => r,
746 BinOp::Sub if int_lit(r, 0) => l,
747 BinOp::Mul if int_lit(r, 1) => l,
748 BinOp::Mul if int_lit(l, 1) => r,
749 BinOp::Div if int_lit(r, 1) => l,
750 _ => return None,
751 };
752 if !lang.is_exact_int(x) {
753 return None;
754 }
755 Some(Edit::Replace {
756 target: id,
757 with: x,
758 })
759 }
760}
761
762fn count_name_refs<L: Lang>(lang: &L, stmts: &[L::Id], name: &str) -> usize {
781 let mut n: usize = 0;
782 for &s in stmts {
783 count_var_refs(lang, s, name, &mut n);
784 }
785 n
786}
787
788fn count_var_refs<L: Lang>(lang: &L, node: L::Id, name: &str, n: &mut usize) {
789 if lang.kind(node) == NodeKind::VarRef && lang.var_name(node) == Some(name) {
790 *n += 1;
791 }
792 for &c in lang.children(node) {
793 count_var_refs(lang, c, name, n);
794 }
795}
796
797pub struct LocalPropagation;
798
799impl<L: Lang> Rule<L> for LocalPropagation {
800 fn name(&self) -> &'static str {
801 "local_propagation"
802 }
803 fn kinds(&self) -> &'static [NodeKind] {
804 &[NodeKind::VarDecl]
805 }
806 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
807 let RewriteCtx { lang, walk } = ctx;
808 if lang.kind(id) != NodeKind::VarDecl {
809 return None;
810 }
811 let name = lang.var_name(id)?.to_string();
812 let ch = lang.children(id);
813 if ch.len() != 1 {
814 return None; }
816 let value = ch[0];
817 if lang.kind(value) == NodeKind::ArrayLit {
821 return None;
822 }
823 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
827 return None;
828 }
829 if subtree_contains(&*lang, value, |n| {
831 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
832 }) {
833 return None;
834 }
835 let parent = walk.parent(id)?;
836 if lang.kind(parent) != NodeKind::Block {
837 return None;
838 }
839 let index = walk.index(id)?;
840 let stmts: Vec<L::Id> = lang.children(parent).to_vec();
841 if index + 1 > stmts.len() {
842 return None;
843 }
844
845 let mut value = value;
851 if lang.is_char_decl(id) {
852 match lang.literal(value) {
853 Some(LitRef::Char(_)) => {}
854 Some(LitRef::Int(v)) => {
855 let c = u32::try_from(v)
858 .ok()
859 .filter(|&u| (0..=0xFFFF).contains(&u) && !(0xD800..=0xDFFF).contains(&u))
860 .and_then(char::from_u32)?;
861 value = lang.build_char(c);
862 }
863 _ => return None,
864 }
865 }
866 if lang.is_long_decl(id) {
871 if let Some(LitRef::Int(v)) = lang.literal(value) {
872 value = lang.build_int(v, true);
873 }
874 }
875 if !lang.is_wide_decl(id) && !lang.is_char_decl(id) {
880 return None;
881 }
882
883 let name_str = lang.var_name(id)?.to_string();
888 {
893 let mut captured = false;
894 for &st in &stmts[index + 1..] {
895 if subtree_contains(&*lang, st, |n| {
898 if lang.kind(n) == NodeKind::Lambda {
899 return subtree_contains(&*lang, n, |m| {
900 lang.kind(m) == NodeKind::VarRef
901 && lang.var_name(m) == Some(name_str.as_str())
902 });
903 }
904 false
905 }) {
906 captured = true;
907 break;
908 }
909 }
910 if captured {
911 return None;
912 }
913 }
914 let syntactic_refs = count_name_refs(&*lang, &stmts[index + 1..], &name_str);
915 if syntactic_refs != 1 {
916 return None;
917 }
918 let name_key = lang.var_key(id)?;
919 let mut wa = Watch::<L>::new(name_key, &[]);
920 for &s in &stmts[index + 1..] {
921 scan_region(&*lang, s, &mut wa);
922 }
923 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
924 return None;
925 }
926 let use_id = wa.uses[0];
927
928 if let Some(&(p, _)) = walk.parents.get(&use_id) {
933 if lang.kind(p) == NodeKind::Assign && lang.assign_op(p).is_none() {
934 let pch = lang.children(p);
935 if pch.len() == 2
936 && lang.kind(pch[0]) == NodeKind::VarRef
937 && lang.var_key(pch[0]) == Some(name_key)
938 && pch[1] == use_id
939 {
940 return None;
941 }
942 }
943 }
944 {
949 let (up, head) = match walk.parents.get(&use_id) {
950 Some(&(p, _)) => {
951 (Some(p), lang.children(p).first() == Some(&use_id))
952 }
953 None => (None, false),
954 };
955 if lang.receiver_propagation_unsound(id, value, up, head) {
956 return None;
957 }
958 }
959 if matches!(
966 lang.literal(value),
967 Some(
968 LitRef::Null
969 | LitRef::Int(_)
970 | LitRef::Long(_)
971 | LitRef::Float(_)
972 | LitRef::Double(_)
973 | LitRef::Char(_)
974 | LitRef::Bool(_)
975 )
976 ) {
977 let mut cur = use_id;
978 while let Some(&(p, _)) = walk.parents.get(&cur) {
979 let at_head = lang.children(p).first() == Some(&cur);
980 match lang.kind(p) {
981 NodeKind::Member | NodeKind::Index if at_head => return None,
983 NodeKind::Call if at_head => return None,
985 NodeKind::InstanceOf if at_head => return None,
989 NodeKind::ForEach => {
991 let ch = lang.children(p);
992 if ch.first() == Some(&cur) {
993 return None;
994 }
995 }
996 NodeKind::Synchronized => {
998 let ch = lang.children(p);
999 if ch.len() == 1 && ch[0] == cur {
1000 return None;
1001 }
1002 }
1003 _ => {}
1004 }
1005 cur = p;
1006 }
1007 }
1008 let mut watch_keys: Vec<L::NameKey> = Vec::new();
1010 collect_read_keys(&*lang, value, &mut watch_keys);
1011 watch_keys.push(name_key);
1012 let mut wb = Watch::<L>::new(name_key, &watch_keys);
1016 let mut use_stmt_idx = None;
1017 for (off, &s) in stmts[index + 1..].iter().enumerate() {
1018 let uses_before = wb.uses.len();
1019 scan_region(&*lang, s, &mut wb);
1020 if wb.uses.len() > uses_before {
1021 use_stmt_idx = Some(index + 1 + off);
1022 break; }
1024 }
1025 if subtree_contains(&*lang, value, |n| {
1032 if lang.kind(n) != NodeKind::VarRef || lang.is_local_var(n) {
1033 return false;
1034 }
1035 if !lang.var_name(n).is_some_and(|nm| lang.is_field_name(nm)) {
1038 return false;
1039 }
1040 let is_callee = match walk.parent(n) {
1041 Some(p) => {
1042 matches!(lang.kind(p), NodeKind::Call) && lang.children(p).first() == Some(&n)
1043 }
1044 None => false,
1045 };
1046 !is_callee
1047 }) {
1048 if let Some(ui) = use_stmt_idx {
1049 for &s in &stmts[index + 1..=ui] {
1050 if lang.effect(s) != Effect::Pure {
1051 return None;
1052 }
1053 }
1054 }
1055 }
1056 if let Some(ui) = use_stmt_idx {
1060 if count_name_refs(&*lang, &stmts[ui + 1..], &name_str) != 0 {
1062 return None;
1063 }
1064 let mut wc = Watch::<L>::new(name_key, &[name_key]);
1065 for &s in &stmts[ui + 1..] {
1066 scan_region(&*lang, s, &mut wc);
1067 if wc.opaque || !wc.uses.is_empty() || wc.wrote(name_key) || wc.shadowed {
1068 return None;
1069 }
1070 }
1071 }
1072 let ve = lang.effect(value);
1073
1074 if ve <= Effect::MayRead {
1075 if let Some(target_key) =
1078 use_stmt_target_write_key(&*lang, &stmts[index + 1..], use_id)
1079 {
1080 wb.clear_wrote(target_key);
1081 }
1082 if wb.opaque || wb.any_write() {
1083 return None;
1084 }
1085 return Some(Edit::Multi(vec![
1086 Edit::Replace {
1087 target: use_id,
1088 with: value,
1089 },
1090 Edit::Delete { node: id },
1091 ]));
1092 }
1093
1094 if index + 1 >= stmts.len() {
1096 return None;
1097 }
1098 let stmt = stmts[index + 1];
1099 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
1100 return None;
1101 }
1102 match lang.kind(stmt) {
1103 NodeKind::Return
1106 | NodeKind::ExprStmt
1107 | NodeKind::Assign
1108 | NodeKind::Throw
1109 | NodeKind::VarDecl => {}
1110 _ => return None,
1111 }
1112 if !straight_path(&*lang, stmt, use_id) {
1113 return None;
1114 }
1115 if !prefix_effects_readable(&*lang, stmt, use_id) {
1116 return None;
1117 }
1118 Some(Edit::Multi(vec![
1119 Edit::Replace {
1120 target: use_id,
1121 with: value,
1122 },
1123 Edit::Delete { node: id },
1124 ]))
1125 }
1126}
1127
1128pub(crate) struct Watch<L: Lang> {
1134 opaque: bool,
1136 name: L::NameKey,
1138 watch: Vec<L::NameKey>,
1140 writes_hit: Vec<bool>,
1142 shadowed: bool,
1143 uses: Vec<L::Id>,
1145}
1146
1147impl<L: Lang> Watch<L> {
1148 pub(crate) fn new(name: L::NameKey, watch: &[L::NameKey]) -> Self {
1149 Watch {
1150 opaque: false,
1151 name,
1152 watch: watch.to_vec(),
1153 writes_hit: vec![false; watch.len()],
1154 shadowed: false,
1155 uses: Vec::new(),
1156 }
1157 }
1158 pub(crate) fn wrote(&self, k: L::NameKey) -> bool {
1160 self.watch
1161 .iter()
1162 .zip(&self.writes_hit)
1163 .any(|(w, h)| *h && *w == k)
1164 }
1165 pub(crate) fn any_write(&self) -> bool {
1167 self.writes_hit.iter().any(|h| *h)
1168 }
1169 pub(crate) fn clear_wrote(&mut self, k: L::NameKey) {
1171 for (w, h) in self.watch.iter().zip(self.writes_hit.iter_mut()) {
1172 if *w == k {
1173 *h = false;
1174 }
1175 }
1176 }
1177}
1178
1179fn scan_region<L: Lang>(lang: &L, node: L::Id, w: &mut Watch<L>) {
1181 lang.debug_verify_events(node);
1182 if let Some(events) = lang.region_events(node) {
1183 let lo = events.partition_point(|e| e.key < w.name);
1185 let hi = events.partition_point(|e| e.key <= w.name);
1186 for e in &events[lo..hi] {
1187 match e.kind {
1188 crate::kind::EventKind::Use => w.uses.push(e.node),
1189 crate::kind::EventKind::Shadow => w.shadowed = true,
1190 crate::kind::EventKind::Write => {
1191 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1192 if *wn == w.name {
1193 *hit = true;
1194 }
1195 }
1196 }
1197 crate::kind::EventKind::Opaque => w.opaque = true,
1200 }
1201 }
1202 for (i, wk) in w.watch.iter().enumerate() {
1204 if *wk == w.name {
1205 continue; }
1207 let lo = events.partition_point(|e| e.key < *wk);
1208 let hi = events.partition_point(|e| e.key <= *wk);
1209 for e in &events[lo..hi] {
1210 if e.kind == crate::kind::EventKind::Write {
1211 w.writes_hit[i] = true;
1212 break; }
1214 }
1215 }
1216 return;
1217 }
1218 if lang.is_opaque(node) {
1220 w.opaque = true;
1221 return;
1222 }
1223 match lang.kind(node) {
1225 NodeKind::Assign => {
1226 let ch = lang.children(node);
1227 if let Some(&t) = ch.first() {
1228 if lang.kind(t) == NodeKind::VarRef {
1229 if let Some(k) = lang.var_key(t) {
1230 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1231 if *wn == k {
1232 *hit = true;
1233 }
1234 }
1235 }
1236 }
1237 }
1238 if let Some(&v) = ch.get(1) {
1239 scan_region(lang, v, w);
1240 }
1241 }
1242 NodeKind::Unary if lang.un_op(node).is_some_and(|o| o.is_incdec()) => {
1243 let ch = lang.children(node);
1244 if let Some(&t) = ch.first() {
1245 if lang.kind(t) == NodeKind::VarRef {
1246 if let Some(k) = lang.var_key(t) {
1247 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1248 if *wn == k {
1249 *hit = true;
1250 }
1251 }
1252 }
1253 }
1254 }
1255 }
1256 NodeKind::VarDecl => {
1257 if let Some(k) = lang.var_key(node) {
1258 if k == w.name {
1259 w.shadowed = true;
1260 }
1261 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1262 if *wn == k {
1263 *hit = true;
1264 }
1265 }
1266 }
1267 for &c in lang.children(node) {
1268 scan_region(lang, c, w);
1269 }
1270 }
1271 NodeKind::ForEach | NodeKind::Catch => {
1274 if let Some(k) = lang.var_key(node) {
1275 if k == w.name {
1276 w.shadowed = true;
1277 }
1278 if lang.kind(node) == NodeKind::ForEach {
1279 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1280 if *wn == k {
1281 *hit = true;
1282 }
1283 }
1284 }
1285 }
1286 for &c in lang.children(node) {
1287 scan_region(lang, c, w);
1288 }
1289 }
1290 NodeKind::VarRef => {
1291 if lang.var_key(node) == Some(w.name) {
1292 w.uses.push(node);
1293 }
1294 }
1295 NodeKind::Raw => {
1296 w.opaque = true;
1298 }
1299 _ => {
1300 for &c in lang.children(node) {
1301 scan_region(lang, c, w);
1302 }
1303 }
1304 }
1305}
1306
1307fn straight_path<L: Lang>(lang: &L, root: L::Id, target: L::Id) -> bool {
1309 fn go<L: Lang>(lang: &L, node: L::Id, target: L::Id) -> bool {
1310 if node == target {
1311 return true;
1312 }
1313 match lang.kind(node) {
1314 NodeKind::Ternary | NodeKind::If => return false,
1315 NodeKind::Binary if lang.bin_op(node).is_some_and(|o| o.is_short_circuit()) => {
1316 return false
1317 }
1318 _ => {}
1319 }
1320 for &c in lang.children(node) {
1321 if subtree_contains(lang, c, |n| n == target) {
1322 return go(lang, c, target);
1323 }
1324 }
1325 false
1326 }
1327 go(lang, root, target)
1328}
1329
1330
1331pub struct BoolShortCircuit;
1339
1340impl<L: Lang> Rule<L> for BoolShortCircuit {
1341 fn name(&self) -> &'static str {
1342 "bool_short_circuit"
1343 }
1344 fn kinds(&self) -> &'static [NodeKind] {
1345 &[NodeKind::Binary]
1346 }
1347 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1348 let RewriteCtx { lang, walk: _ } = ctx;
1349 if lang.kind(id) != NodeKind::Binary {
1350 return None;
1351 }
1352 let op = lang.bin_op(id)?;
1353 if !op.is_short_circuit() {
1354 return None;
1355 }
1356 let ch = lang.children(id);
1357 let (l, r) = (*ch.first()?, *ch.get(1)?);
1358 let is_lit = |n: L::Id, v: bool| matches!(lang.literal(n), Some(LitRef::Bool(b)) if b == v);
1359 let with = match (op, is_lit(l, true), is_lit(l, false)) {
1361 (BinOp::And, _, true) => l, (BinOp::Or, true, _) => l, (BinOp::And, true, _) => r, (BinOp::Or, _, true) => r, _ => {
1366 match (op, is_lit(r, true), is_lit(r, false)) {
1369 (BinOp::And, true, _) => l,
1370 (BinOp::Or, _, true) => l,
1371 (BinOp::And, _, true) | (BinOp::Or, true, _)
1372 if lang.effect(l) <= Effect::MayRead => {
1373 r
1374 }
1375 _ => return None,
1376 }
1377 }
1378 };
1379 Some(Edit::Replace {
1380 target: id,
1381 with,
1382 })
1383 }
1384}
1385
1386pub struct NotCompare;
1392
1393impl<L: Lang> Rule<L> for NotCompare {
1394 fn name(&self) -> &'static str {
1395 "not_compare"
1396 }
1397 fn kinds(&self) -> &'static [NodeKind] {
1398 &[NodeKind::Unary]
1399 }
1400 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1401 let RewriteCtx { lang, walk: _ } = ctx;
1402 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
1403 return None;
1404 }
1405 let inner = *lang.children(id).first()?;
1406 if lang.kind(inner) != NodeKind::Binary {
1407 return None;
1408 }
1409 let op = lang.bin_op(inner)?;
1410 let neg = match op {
1411 BinOp::Eq => BinOp::Ne,
1412 BinOp::Ne => BinOp::Eq,
1413 BinOp::Lt => BinOp::Ge,
1414 BinOp::Le => BinOp::Gt,
1415 BinOp::Gt => BinOp::Le,
1416 BinOp::Ge => BinOp::Lt,
1417 _ => return None,
1418 };
1419 if op.is_short_circuit() {
1420 return None;
1421 }
1422 if matches!(op, BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) {
1424 let ch = lang.children(inner);
1425 if !lang.is_exact_int(*ch.first()?) || !lang.is_exact_int(*ch.get(1)?) {
1426 return None;
1427 }
1428 }
1429 let ch = lang.children(inner).to_vec();
1430 let rebuilt = lang.build_bin(neg, ch[0], ch[1]);
1431 Some(Edit::Replace {
1432 target: id,
1433 with: rebuilt,
1434 })
1435 }
1436}
1437
1438fn in_inferred_var_init<L: Lang>(lang: &L, walk: &Walk<L>, mut id: L::Id) -> bool {
1448 while let Some(&(p, _)) = walk.parents.get(&id) {
1449 match lang.kind(p) {
1450 NodeKind::Paren | NodeKind::Ternary => id = p,
1451 NodeKind::VarDecl => return lang.is_inferred_decl(p),
1452 _ => return false,
1453 }
1454 }
1455 false
1456}
1457
1458pub struct TernaryFold;
1459
1460impl<L: Lang> Rule<L> for TernaryFold {
1461 fn name(&self) -> &'static str {
1462 "ternary_fold"
1463 }
1464 fn kinds(&self) -> &'static [NodeKind] {
1465 &[NodeKind::Ternary]
1466 }
1467 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1468 let RewriteCtx { lang, walk } = ctx;
1469 if lang.kind(id) != NodeKind::Ternary {
1470 return None;
1471 }
1472 let ch = lang.children(id);
1473 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1474 match lang.literal(c) {
1475 Some(LitRef::Bool(true)) => {
1481 if !in_inferred_var_init(&*lang, walk, id) {
1482 return Some(Edit::Replace { target: id, with: a });
1483 }
1484 }
1485 Some(LitRef::Bool(false))
1486 if !in_inferred_var_init(&*lang, walk, id) => {
1487 return Some(Edit::Replace { target: id, with: b });
1488 }
1489 _ => {}
1490 }
1491 if crate::analysis::structurally_equal(&*lang, a, b) && lang.effect(c) <= Effect::MayRead {
1494 return Some(Edit::Replace { target: id, with: a });
1495 }
1496 None
1497 }
1498}
1499
1500pub struct TernaryBool;
1505
1506impl<L: Lang> Rule<L> for TernaryBool {
1507 fn name(&self) -> &'static str {
1508 "ternary_bool"
1509 }
1510 fn kinds(&self) -> &'static [NodeKind] {
1511 &[NodeKind::Ternary]
1512 }
1513 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1514 let RewriteCtx { lang, walk: _ } = ctx;
1515 if lang.kind(id) != NodeKind::Ternary {
1516 return None;
1517 }
1518 let ch = lang.children(id);
1519 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1520 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
1521 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
1522 if !lang.is_bool(c) {
1523 return None;
1524 }
1525 let with = if lb(a) && is_false(b) {
1526 c
1527 } else if is_false(a) && lb(b) {
1528 lang.build_unary(UnOp::Not, c)
1529 } else {
1530 return None;
1531 };
1532 Some(Edit::Replace {
1533 target: id,
1534 with,
1535 })
1536 }
1537}
1538
1539pub struct ConstFoldBin;
1546
1547impl<L: Lang> Rule<L> for ConstFoldBin {
1548 fn name(&self) -> &'static str {
1549 "const_fold_bin"
1550 }
1551 fn kinds(&self) -> &'static [NodeKind] {
1552 &[NodeKind::Binary]
1553 }
1554 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1555 let RewriteCtx { lang, walk: _ } = ctx;
1556 if lang.kind(id) != NodeKind::Binary {
1557 return None;
1558 }
1559 let op = lang.bin_op(id)?;
1560 if op.is_short_circuit() || op.is_comparison() {
1561 return None;
1562 }
1563 let ch = lang.children(id);
1564 let (l, r) = (*ch.first()?, *ch.get(1)?);
1565 lang.literal(l)?;
1567 lang.literal(r)?;
1568 let with = lang.fold_lit_bin(op, l, r)?;
1569 Some(Edit::Replace {
1570 target: id,
1571 with,
1572 })
1573 }
1574}
1575
1576pub struct ArithZero;
1585
1586impl<L: Lang> Rule<L> for ArithZero {
1587 fn name(&self) -> &'static str {
1588 "arith_zero"
1589 }
1590 fn kinds(&self) -> &'static [NodeKind] {
1591 &[NodeKind::Binary]
1592 }
1593 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1594 let RewriteCtx { lang, walk: _ } = ctx;
1595 if lang.kind(id) != NodeKind::Binary {
1596 return None;
1597 }
1598 let op = lang.bin_op(id)?;
1599 let ch = lang.children(id);
1600 let (l, r) = (*ch.first()?, *ch.get(1)?);
1601 let int_lit = |n: L::Id, v: i64| lang.literal(n).and_then(|x| x.as_int()) == Some(v);
1602
1603 if matches!(op, BinOp::Shl | BinOp::Shr | BinOp::UShr) && int_lit(r, 0) {
1605 return Some(Edit::Replace {
1606 target: id,
1607 with: l,
1608 });
1609 }
1610 if op == BinOp::Mul {
1612 let (zero, other) = if int_lit(l, 0) {
1613 (l, r)
1614 } else if int_lit(r, 0) {
1615 (r, l)
1616 } else {
1617 return None;
1618 };
1619 if !lang.is_exact_int(other) || lang.effect(other) > Effect::MayRead {
1620 return None;
1621 }
1622 return Some(Edit::Replace {
1623 target: id,
1624 with: zero,
1625 });
1626 }
1627 if op == BinOp::Sub
1629 && lang.kind(l) == NodeKind::VarRef
1630 && lang.kind(r) == NodeKind::VarRef
1631 && lang.var_name(l) == lang.var_name(r)
1632 && lang.is_local_var(l)
1633 && lang.is_exact_int(l)
1634 {
1635 let with = lang.build_int(0, false);
1636 return Some(Edit::Replace {
1637 target: id,
1638 with,
1639 });
1640 }
1641 None
1642 }
1643}
1644
1645pub struct InverseAssignPair;
1668
1669impl<L: Lang> Rule<L> for InverseAssignPair {
1670 fn name(&self) -> &'static str {
1671 "inverse_assign_pair"
1672 }
1673 fn kinds(&self) -> &'static [NodeKind] {
1674 &[NodeKind::Block]
1675 }
1676 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1677 let RewriteCtx { lang, .. } = ctx;
1678 let ch = lang.children(id).to_vec();
1679 if ch.len() < 2 {
1680 return None;
1681 }
1682 for w in 0..ch.len() - 1 {
1683 let (s1, s2) = (ch[w], ch[w + 1]);
1684 let Some(a1) = norm_assign(lang, s1) else { continue };
1686 let Some(a2) = norm_assign(lang, s2) else { continue };
1687 if a1.key != a2.key || !a1.local || !a2.local {
1689 continue;
1690 }
1691 if !a1.exact_int || !a2.exact_int {
1693 continue;
1694 }
1695 if a1.k != a2.k {
1697 continue;
1698 }
1699 let inverse = match (a1.op, a2.op) {
1700 (BinOp::Add, BinOp::Sub) | (BinOp::Sub, BinOp::Add) => true,
1701 (BinOp::BitXor, BinOp::BitXor) => true,
1702 _ => false,
1703 };
1704 if !inverse {
1705 continue;
1706 }
1707 return Some(Edit::Multi(vec![
1709 Edit::Delete { node: s2 },
1710 Edit::Delete { node: s1 },
1711 ]));
1712 }
1713 None
1714 }
1715}
1716
1717struct NormAssign<K> {
1721 key: K,
1722 op: BinOp,
1723 k: i64,
1724 local: bool,
1725 exact_int: bool,
1727}
1728
1729fn norm_assign<L: Lang>(lang: &L, stmt: L::Id) -> Option<NormAssign<L::NameKey>> {
1730 let a = match lang.kind(stmt) {
1732 NodeKind::ExprStmt => {
1733 let c = lang.children(stmt);
1734 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
1735 c[0]
1736 } else {
1737 return None;
1738 }
1739 }
1740 NodeKind::Assign => stmt,
1741 _ => return None,
1742 };
1743 let ch = lang.children(a).to_vec();
1744 if ch.len() != 2 {
1745 return None;
1746 }
1747 let (target, rhs) = (ch[0], ch[1]);
1748 if lang.kind(target) != NodeKind::VarRef {
1749 return None;
1750 }
1751 let key = lang.var_key(target)?;
1752 let local = lang.is_local_var(target);
1753 let exact_int = lang.is_exact_int(target);
1754 match lang.assign_op(a) {
1758 Some(op) => {
1760 let k = lang.literal(rhs).and_then(|x| x.as_int())?;
1761 Some(NormAssign { key, op, k, local, exact_int })
1762 }
1763 None => {
1765 if lang.kind(rhs) != NodeKind::Binary {
1766 return None;
1767 }
1768 let op = lang.bin_op(rhs)?;
1769 let rc = lang.children(rhs).to_vec();
1770 if rc.len() != 2 {
1771 return None;
1772 }
1773 if lang.kind(rc[0]) != NodeKind::VarRef || lang.var_key(rc[0]) != Some(key) {
1775 return None;
1776 }
1777 let k = lang.literal(rc[1]).and_then(|x| x.as_int())?;
1778 Some(NormAssign { key, op, k, local, exact_int })
1779 }
1780 }
1781}
1782
1783pub struct DeadStore;
1784
1785impl<L: Lang> Rule<L> for DeadStore {
1786 fn name(&self) -> &'static str {
1787 "dead_store"
1788 }
1789 fn kinds(&self) -> &'static [NodeKind] {
1790 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
1791 }
1792 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1793 let RewriteCtx { lang, walk } = ctx;
1794 let (decl_form, first_assign) = match lang.kind(id) {
1796 NodeKind::VarDecl => {
1797 if lang.children(id).is_empty() {
1798 return None; }
1800 (true, None)
1801 }
1802 NodeKind::Assign => (false, Some(id)),
1803 NodeKind::ExprStmt => {
1804 let ch = lang.children(id);
1805 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
1806 (false, Some(ch[0]))
1807 } else {
1808 return None;
1809 }
1810 }
1811 _ => return None,
1812 };
1813 let first_value = if decl_form {
1814 *lang.children(id).first()?
1815 } else {
1816 let a = first_assign?;
1817 if lang.assign_op(a).is_some() {
1818 return None; }
1820 *lang.children(a).get(1)?
1821 };
1822
1823 let parent = walk.parent(id)?;
1825 if lang.kind(parent) != NodeKind::Block {
1826 return None;
1827 }
1828 let idx = walk.index(id)?;
1829 let stmts = lang.children(parent).to_vec();
1830 let next_stmt = *stmts.get(idx + 1)?;
1831
1832 let second = match lang.kind(next_stmt) {
1835 NodeKind::Assign => Some(next_stmt),
1836 NodeKind::ExprStmt => {
1837 let ch = lang.children(next_stmt);
1838 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
1839 Some(ch[0])
1840 } else {
1841 None
1842 }
1843 }
1844 _ => None,
1845 };
1846 let Some(second) = second else {
1847 let (name_node, target_local) = if decl_form {
1856 (id, true)
1857 } else {
1858 let t = *lang.children(first_assign?).first()?;
1859 if lang.kind(t) != NodeKind::VarRef {
1860 return None;
1861 }
1862 (t, lang.is_local_var(t))
1863 };
1864 if !target_local {
1866 return None;
1867 }
1868 let name = lang.var_name(name_node)?.to_string();
1869 let declared_in_this_block = if decl_form {
1870 true } else if parent == walk.root {
1872 true
1875 } else {
1876 stmts[..idx].iter().any(|&s| {
1879 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
1880 })
1881 };
1882 if !declared_in_this_block {
1883 return None;
1884 }
1885 let name_key0 = lang.var_key(name_node)?;
1886 let mut w0 = Watch::<L>::new(name_key0, &[name_key0]);
1887 for &s in &stmts[idx + 1..] {
1888 scan_region(&*lang, s, &mut w0);
1889 }
1890 if w0.opaque || !w0.uses.is_empty() || w0.wrote(name_key0) || w0.shadowed {
1892 return None;
1893 }
1894 for &s in &stmts[idx + 1..] {
1898 if subtree_contains(&*lang, s, |n| {
1899 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
1900 }) {
1901 return None;
1902 }
1903 }
1904 if lang.effect(first_value) > Effect::MayRead {
1905 return None;
1906 }
1907 return Some(Edit::Delete { node: id });
1908 };
1909 if lang.assign_op(second).is_some() {
1910 return None;
1911 }
1912 let nch = lang.children(second);
1913 let (target, value) = (*nch.first()?, *nch.get(1)?);
1914 if lang.kind(target) != NodeKind::VarRef {
1915 return None;
1916 }
1917
1918 let name = if decl_form {
1920 if lang.var_name(id) != lang.var_name(target) {
1921 return None;
1922 }
1923 lang.var_name(id).map(|s| s.to_string())
1924 } else {
1925 let a = first_assign?;
1926 let t = *lang.children(a).first()?;
1927 if lang.kind(t) != NodeKind::VarRef {
1928 return None;
1929 }
1930 if lang.var_name(t) != lang.var_name(target) || !lang.is_local_var(t) {
1931 return None;
1932 }
1933 lang.var_name(t).map(|s| s.to_string())
1934 }?;
1935 if subtree_contains(&*lang, value, |n| {
1937 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
1938 }) {
1939 return None;
1940 }
1941 if lang.effect(first_value) > Effect::MayRead {
1943 return None;
1944 }
1945
1946 if decl_form {
1947 Some(Edit::Multi(vec![
1949 Edit::Replace {
1950 target: first_value,
1951 with: value,
1952 },
1953 Edit::Delete {
1954 node: next_stmt,
1955 },
1956 ]))
1957 } else {
1958 Some(Edit::Delete { node: id })
1960 }
1961 }
1962}
1963
1964pub struct BlockFlatten;
1973
1974impl<L: Lang> Rule<L> for BlockFlatten {
1975 fn name(&self) -> &'static str {
1976 "block_flatten"
1977 }
1978 fn kinds(&self) -> &'static [NodeKind] {
1979 &[NodeKind::Block]
1980 }
1981 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1982 let RewriteCtx { lang, walk } = ctx;
1983 let parent = walk.parent(id)?;
1984 if lang.kind(parent) != NodeKind::Block {
1985 return None;
1986 }
1987 let inner = lang.children(id).to_vec();
1989 let declares = |n: L::Id| -> bool {
1990 matches!(
1991 lang.kind(n),
1992 NodeKind::VarDecl | NodeKind::ForEach | NodeKind::Catch
1993 )
1994 };
1995 if inner.iter().any(|&s| declares(s)) {
1996 return None;
1997 }
1998 if lang.has_pattern_bindings(id) {
2004 return None;
2005 }
2006 let idx = walk.index(id)?;
2007 Some(Edit::Splice {
2008 node: parent,
2009 index: idx,
2010 remove: 1,
2011 insert: inner,
2012 })
2013 }
2014}
2015
2016pub struct DoubleNegFold;
2019
2020impl<L: Lang> Rule<L> for DoubleNegFold {
2021 fn name(&self) -> &'static str {
2022 "double_neg_fold"
2023 }
2024 fn kinds(&self) -> &'static [NodeKind] {
2025 &[NodeKind::Unary]
2026 }
2027 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2028 let RewriteCtx { lang, .. } = ctx;
2029 if lang.un_op(id) != Some(UnOp::Neg) {
2030 return None;
2031 }
2032 let inner = *lang.children(id).first()?;
2033 if lang.un_op(inner) == Some(UnOp::Neg) {
2034 let x = *lang.children(inner).first()?;
2035 return Some(Edit::Replace {
2036 target: id,
2037 with: x,
2038 });
2039 }
2040 if let Some((v, wide)) = lang.literal(inner).and_then(|l| match l {
2043 LitRef::Int(v) => Some((v, false)),
2044 LitRef::Long(v) => Some((v, true)),
2045 _ => None,
2046 }) {
2047 if v < 0 {
2048 let n = lang.fold_lit_neg(v, wide)?;
2049 let with = lang.build_int(n, wide);
2050 return Some(Edit::Replace {
2051 target: id,
2052 with,
2053 });
2054 }
2055 }
2056 None
2057 }
2058}
2059
2060pub struct UnreachableAfterTerminal;
2061
2062impl<L: Lang> Rule<L> for UnreachableAfterTerminal {
2063 fn name(&self) -> &'static str {
2064 "unreachable_after_terminal"
2065 }
2066 fn kinds(&self) -> &'static [NodeKind] {
2067 &[NodeKind::Block]
2068 }
2069 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2070 let RewriteCtx { lang, walk: _ } = ctx;
2071 if lang.kind(id) != NodeKind::Block {
2072 return None;
2073 }
2074 let children = lang.children(id).to_vec();
2075 let terminal = |n: L::Id| -> bool {
2076 matches!(
2077 lang.kind(n),
2078 NodeKind::Return | NodeKind::Throw | NodeKind::Break | NodeKind::Continue
2079 )
2080 };
2081 let mut cut: Option<(usize, usize)> = None;
2082 for (i, &s) in children.iter().enumerate() {
2083 if terminal(s) && i + 1 < children.len() {
2084 cut = Some((i + 1, children.len() - i - 1));
2085 break;
2086 }
2087 }
2088 let (index, remove) = cut?;
2089 Some(Edit::Splice {
2090 node: id,
2091 index,
2092 remove,
2093 insert: Vec::new(),
2094 })
2095 }
2096}
2097
2098
2099pub struct CmpConstFold;
2106
2107impl<L: Lang> Rule<L> for CmpConstFold {
2108 fn name(&self) -> &'static str {
2109 "cmp_const_fold"
2110 }
2111 fn kinds(&self) -> &'static [NodeKind] {
2112 &[NodeKind::Binary]
2113 }
2114 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2115 let RewriteCtx { lang, walk: _ } = ctx;
2116 if lang.kind(id) != NodeKind::Binary {
2117 return None;
2118 }
2119 let op = lang.bin_op(id)?;
2120 if !op.is_comparison() {
2121 return None;
2122 }
2123 let ch = lang.children(id);
2124 let (l, r) = (*ch.first()?, *ch.get(1)?);
2125 lang.literal(l)?;
2128 lang.literal(r)?;
2129 let v = lang.fold_lit_cmp(op, l, r)?;
2130 let with = lang.build_bool(v);
2131 Some(Edit::Replace {
2132 target: id,
2133 with,
2134 })
2135 }
2136}
2137
2138pub struct BitIdentity;
2146
2147impl<L: Lang> Rule<L> for BitIdentity {
2148 fn name(&self) -> &'static str {
2149 "bit_identity"
2150 }
2151 fn kinds(&self) -> &'static [NodeKind] {
2152 &[NodeKind::Binary]
2153 }
2154 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2155 let RewriteCtx { lang, walk: _ } = ctx;
2156 if lang.kind(id) != NodeKind::Binary {
2157 return None;
2158 }
2159 let op = lang.bin_op(id)?;
2160 if !matches!(op, BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor) {
2161 return None;
2162 }
2163 let ch = lang.children(id);
2164 let (l, r) = (*ch.first()?, *ch.get(1)?);
2165 let int_lit = |n: L::Id| lang.literal(n).and_then(|x| x.as_int());
2166
2167 if op == BinOp::BitXor
2169 && lang.kind(l) == NodeKind::VarRef
2170 && lang.kind(r) == NodeKind::VarRef
2171 && lang.var_name(l) == lang.var_name(r)
2172 && lang.is_local_var(l)
2173 && lang.is_exact_int(l)
2174 {
2175 let with = lang.build_int(0, false);
2176 return Some(Edit::Replace {
2177 target: id,
2178 with,
2179 });
2180 }
2181
2182 for (x, k) in [(l, r), (r, l)] {
2184 let Some(kv) = int_lit(k) else {
2185 continue;
2186 };
2187 if !lang.is_exact_int(x) {
2188 continue;
2189 }
2190 let identity = match (op, kv) {
2191 (BinOp::BitXor, 0) => Some(x), (BinOp::BitOr, 0) => Some(x), (BinOp::BitAnd, -1) => Some(x), (BinOp::BitAnd, 0) | (BinOp::BitOr, -1) => {
2195 if lang.effect(x) > Effect::MayRead {
2197 None
2198 } else {
2199 Some(k)
2200 }
2201 }
2202 _ => None,
2203 };
2204 if let Some(with) = identity {
2205 return Some(Edit::Replace {
2206 target: id,
2207 with,
2208 });
2209 }
2210 }
2211 None
2212 }
2213}
2214
2215pub struct ArithReassoc;
2221
2222impl<L: Lang> Rule<L> for ArithReassoc {
2223 fn name(&self) -> &'static str {
2224 "arith_reassoc"
2225 }
2226 fn kinds(&self) -> &'static [NodeKind] {
2227 &[NodeKind::Binary]
2228 }
2229 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2230 let RewriteCtx { lang, walk: _ } = ctx;
2231 if lang.kind(id) != NodeKind::Binary {
2232 return None;
2233 }
2234 let op2 = lang.bin_op(id)?;
2235 if !matches!(op2, BinOp::Add | BinOp::Sub | BinOp::BitXor) {
2236 return None;
2237 }
2238 let och = lang.children(id).to_vec();
2239 let inner = och[0];
2240 if lang.kind(inner) != NodeKind::Binary {
2241 return None;
2242 }
2243 let op1 = lang.bin_op(inner)?;
2244 let ich = lang.children(inner).to_vec();
2245 let x = ich[0];
2246
2247 if op1 == BinOp::Add && op2 == BinOp::Add {
2253 if let (Some(LitRef::Str(k1)), Some(LitRef::Str(k2))) =
2254 (lang.literal(ich[1]), lang.literal(och[1]))
2255 {
2256 let mut chain: Vec<L::Id> = vec![id];
2258 let mut cur = id;
2259 loop {
2260 let cch = lang.children(cur).to_vec();
2261 let next = cch[0];
2262 let deeper = lang.kind(next) == NodeKind::Binary
2263 && lang.bin_op(next) == Some(BinOp::Add)
2264 && matches!(lang.literal(lang.children(next).to_vec()[1]),
2265 Some(LitRef::Str(_)));
2266 if deeper {
2267 chain.push(next);
2268 cur = next;
2269 } else {
2270 break;
2271 }
2272 }
2273 let innermost = *chain.last().unwrap();
2275 let ich2 = lang.children(innermost).to_vec();
2276 let base = ich2[0];
2277 let mut joined = String::new();
2278 for &n in chain.iter().rev() {
2279 if let Some(LitRef::Str(k)) =
2280 lang.literal(lang.children(n).to_vec()[1])
2281 {
2282 joined.push_str(k);
2283 }
2284 }
2285 let _ = (k1, k2); let lit = lang.build_str(&joined);
2287 let with = lang.build_bin(BinOp::Add, base, lit);
2288 return Some(Edit::Replace {
2289 target: id,
2290 with,
2291 });
2292 }
2293 }
2294
2295 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2299 let k2 = lang.literal(och[1]).and_then(|x| x.as_int())?;
2300 if !lang.is_exact_int(x) {
2301 return None;
2302 }
2303 let is_long = matches!(lang.literal(ich[1]), Some(LitRef::Long(_)))
2304 || matches!(lang.literal(och[1]), Some(LitRef::Long(_)));
2305 let with = match lang.reassoc_delta(op1, k1, op2, k2, is_long)? {
2306 ReassocOutcome::Neutral => x,
2307 ReassocOutcome::Lit(lit, op) => lang.build_bin(op, x, lit),
2308 };
2309 Some(Edit::Replace {
2310 target: id,
2311 with,
2312 })
2313 }
2314}
2315
2316pub struct IfToTernary;
2325
2326impl<L: Lang> Rule<L> for IfToTernary {
2327 fn name(&self) -> &'static str {
2328 "if_to_ternary"
2329 }
2330 fn kinds(&self) -> &'static [NodeKind] {
2331 &[NodeKind::If]
2332 }
2333 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2334 let RewriteCtx { lang, walk } = ctx;
2335 if lang.kind(id) != NodeKind::If {
2336 return None;
2337 }
2338 let ch = lang.children(id).to_vec();
2339 let c = ch[0];
2340
2341 let single_ret = |n: L::Id| -> Option<Option<L::Id>> {
2343 match lang.kind(n) {
2344 NodeKind::Return => Some(lang.children(n).first().copied()),
2345 NodeKind::Block if lang.children(n).len() == 1 => {
2346 let inner = lang.children(n)[0];
2347 if lang.kind(inner) == NodeKind::Return {
2348 Some(lang.children(inner).first().copied())
2349 } else {
2350 None
2351 }
2352 }
2353 _ => None,
2354 }
2355 };
2356
2357 if ch.len() == 3 {
2358 let a = single_ret(ch[1])?;
2360 let b = single_ret(ch[2])?;
2361 let with = match (a, b) {
2362 (Some(va), Some(vb)) => {
2363 let t = lang.build_ternary(c, va, vb);
2364 lang.build_return(Some(t))
2365 }
2366 (None, None) => lang.build_return(None),
2367 _ => return None,
2368 };
2369 return Some(Edit::Replace {
2370 target: id,
2371 with,
2372 });
2373 }
2374
2375 if ch.len() != 2 {
2377 return None;
2378 }
2379 let a = single_ret(ch[1])?;
2380 let parent = walk.parent(id)?;
2381 if lang.kind(parent) != NodeKind::Block {
2382 return None;
2383 }
2384 let idx = walk.index(id)?;
2385 let stmts = lang.children(parent).to_vec();
2386 if idx + 2 != stmts.len() {
2387 return None; }
2389 let tail = stmts[idx + 1];
2390 if lang.kind(tail) != NodeKind::Return {
2391 return None;
2392 }
2393 let b = lang.children(tail).first().copied();
2394 let with = match (a, b) {
2395 (Some(va), Some(vb)) => {
2396 let t = lang.build_ternary(c, va, vb);
2397 lang.build_return(Some(t))
2398 }
2399 (None, None) => lang.build_return(None),
2400 _ => return None,
2401 };
2402 Some(Edit::Splice {
2403 node: parent,
2404 index: idx,
2405 remove: 2,
2406 insert: vec![with],
2407 })
2408 }
2409}
2410
2411pub struct IfAssignTernary;
2418
2419impl<L: Lang> Rule<L> for IfAssignTernary {
2420 fn name(&self) -> &'static str {
2421 "if_assign_ternary"
2422 }
2423 fn kinds(&self) -> &'static [NodeKind] {
2424 &[NodeKind::If]
2425 }
2426 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2427 let RewriteCtx { lang, walk: _ } = ctx;
2428 if lang.kind(id) != NodeKind::If {
2429 return None;
2430 }
2431 let ch = lang.children(id).to_vec();
2432 if ch.len() != 3 {
2433 return None;
2434 }
2435 let c = ch[0];
2436 let single_assign = |n: L::Id| -> Option<(L::Id, L::Id)> {
2438 let unwrap = |i: L::Id| -> L::Id {
2439 if lang.kind(i) == NodeKind::ExprStmt {
2440 let ch = lang.children(i);
2441 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2442 return ch[0];
2443 }
2444 }
2445 i
2446 };
2447 let inner = match lang.kind(n) {
2448 NodeKind::Assign => n,
2449 NodeKind::ExprStmt => unwrap(n),
2450 NodeKind::Block if lang.children(n).len() == 1 => unwrap(lang.children(n)[0]),
2451 _ => return None,
2452 };
2453 if lang.kind(inner) != NodeKind::Assign {
2454 return None;
2455 }
2456 if lang.assign_op(inner).is_some() {
2457 return None;
2458 }
2459 let a = lang.children(inner).to_vec();
2460 if lang.kind(a[0]) != NodeKind::VarRef {
2461 return None;
2462 }
2463 Some((a[0], a[1]))
2464 };
2465 let (t1, va) = single_assign(ch[1])?;
2466 let (t2, vb) = single_assign(ch[2])?;
2467 if lang.var_name(t1) != lang.var_name(t2) || lang.var_name(t1).is_none() {
2468 return None;
2469 }
2470 let t = lang.build_ternary(c, va, vb);
2471 let with = lang.build_assign(t1, t);
2472 Some(Edit::Replace {
2473 target: id,
2474 with,
2475 })
2476 }
2477}
2478
2479
2480pub struct DeclAssignMerge;
2488
2489impl<L: Lang> Rule<L> for DeclAssignMerge {
2490 fn name(&self) -> &'static str {
2491 "decl_assign_merge"
2492 }
2493 fn kinds(&self) -> &'static [NodeKind] {
2494 &[NodeKind::VarDecl]
2495 }
2496 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2497 let RewriteCtx { lang, walk } = ctx;
2498 if lang.kind(id) != NodeKind::VarDecl || !lang.children(id).is_empty() {
2499 return None; }
2501 let name = lang.var_name(id)?.to_string();
2502 let parent = walk.parent(id)?;
2503 if lang.kind(parent) != NodeKind::Block {
2504 return None;
2505 }
2506 let idx = walk.index(id)?;
2507 let stmts = lang.children(parent).to_vec();
2508 let next = *stmts.get(idx + 1)?;
2509 let assign = match lang.kind(next) {
2511 NodeKind::Assign => next,
2512 NodeKind::ExprStmt => {
2513 let ch = lang.children(next);
2514 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2515 ch[0]
2516 } else {
2517 return None;
2518 }
2519 }
2520 _ => return None,
2521 };
2522 if lang.assign_op(assign).is_some() {
2523 return None;
2524 }
2525 let ach = lang.children(assign).to_vec();
2526 let (target, value) = (ach[0], ach[1]);
2527 if lang.kind(target) != NodeKind::VarRef || lang.var_name(target) != Some(name.as_str()) {
2528 return None;
2529 }
2530 if subtree_contains(&*lang, value, |n| {
2532 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2533 }) {
2534 return None;
2535 }
2536 Some(Edit::Multi(vec![
2537 Edit::Splice {
2538 node: id,
2539 index: 0,
2540 remove: 0,
2541 insert: vec![value],
2542 },
2543 Edit::Delete { node: next },
2544 ]))
2545 }
2546}
2547
2548pub struct AssignPropagation;
2557
2558impl<L: Lang> Rule<L> for AssignPropagation {
2559 fn name(&self) -> &'static str {
2560 "assign_propagation"
2561 }
2562 fn kinds(&self) -> &'static [NodeKind] {
2563 &[NodeKind::Assign, NodeKind::ExprStmt]
2564 }
2565 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2566 let RewriteCtx { lang, walk } = ctx;
2567 let (assign, stmt_node) = match lang.kind(id) {
2569 NodeKind::Assign => (id, id),
2570 NodeKind::ExprStmt => {
2571 let ch = lang.children(id);
2572 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2573 (ch[0], id)
2574 } else {
2575 return None;
2576 }
2577 }
2578 _ => return None,
2579 };
2580 if lang.assign_op(assign).is_some() {
2581 return None;
2582 }
2583 let ach = lang.children(assign).to_vec();
2584 let (target, value) = (ach[0], ach[1]);
2585 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
2586 return None;
2587 }
2588 if lang.kind(value) == NodeKind::ArrayLit {
2590 return None;
2591 }
2592 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
2594 return None;
2595 }
2596 let name = lang.var_name(target)?.to_string();
2597
2598 let parent = walk.parent(stmt_node)?;
2599 if lang.kind(parent) != NodeKind::Block {
2600 return None;
2601 }
2602 let idx = walk.index(stmt_node)?;
2603 let stmts = lang.children(parent).to_vec();
2604
2605 let anchored = stmts[..idx].iter().any(|&s| {
2607 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2608 });
2609 if !anchored {
2610 return None;
2611 }
2612
2613 if subtree_contains(&*lang, value, |n| {
2615 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2616 }) {
2617 return None;
2618 }
2619
2620 let name_key = lang.var_key(target)?;
2623 if count_name_refs(&*lang, &stmts[idx + 1..], &name) != 1 {
2624 return None;
2625 }
2626 let mut wa = Watch::<L>::new(name_key, &[]);
2627 for &s in &stmts[idx + 1..] {
2628 scan_region(&*lang, s, &mut wa);
2629 }
2630 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
2631 return None;
2632 }
2633 let use_id = wa.uses[0];
2634 if matches!(lang.literal(value), Some(LitRef::Null)) {
2636 if let Some(&(p, _)) = walk.parents.get(&use_id) {
2639 let at_head = lang.children(p).first() == Some(&use_id);
2640 if at_head && matches!(lang.kind(p), NodeKind::Member | NodeKind::Call) {
2641 return None;
2642 }
2643 }
2644 }
2645 let mut watch_keys: Vec<L::NameKey> = Vec::new();
2647 collect_read_keys(&*lang, value, &mut watch_keys);
2648 watch_keys.push(name_key);
2649 let mut wb = Watch::<L>::new(name_key, &watch_keys);
2650 for &s in &stmts[idx + 1..] {
2651 let uses_before = wb.uses.len();
2652 scan_region(&*lang, s, &mut wb);
2653 if uses_before < wb.uses.len() {
2654 break; }
2656 }
2657 if wb.wrote(name_key) {
2659 return None;
2660 }
2661 let ve = lang.effect(value);
2662
2663 if ve <= Effect::MayRead {
2664 if let Some(target_key) = use_stmt_target_write_key(&*lang, &stmts[idx + 1..], use_id) {
2665 wb.clear_wrote(target_key);
2666 }
2667 if wb.any_write() {
2668 return None;
2669 }
2670 return Some(Edit::Multi(vec![
2671 Edit::Replace {
2672 target: use_id,
2673 with: value,
2674 },
2675 Edit::Delete { node: stmt_node },
2676 ]));
2677 }
2678
2679 if idx + 1 >= stmts.len() {
2681 return None;
2682 }
2683 let stmt = stmts[idx + 1];
2684 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
2685 return None;
2686 }
2687 match lang.kind(stmt) {
2688 NodeKind::Return
2689 | NodeKind::ExprStmt
2690 | NodeKind::Assign
2691 | NodeKind::Throw
2692 | NodeKind::VarDecl => {}
2693 _ => return None,
2694 }
2695 if !straight_path(&*lang, stmt, use_id) {
2696 return None;
2697 }
2698 if !prefix_effects_readable(&*lang, stmt, use_id) {
2699 return None;
2700 }
2701 Some(Edit::Multi(vec![
2702 Edit::Replace {
2703 target: use_id,
2704 with: value,
2705 },
2706 Edit::Delete { node: stmt_node },
2707 ]))
2708 }
2709}
2710
2711
2712fn use_stmt_target_write_key<L: Lang>(
2718 lang: &L,
2719 stmts_after: &[L::Id],
2720 use_id: L::Id,
2721) -> Option<L::NameKey> {
2722 for &s in stmts_after {
2723 if subtree_contains(lang, s, |n| n == use_id) {
2724 let assign = match lang.kind(s) {
2725 NodeKind::Assign => s,
2726 NodeKind::ExprStmt => {
2727 let ch = lang.children(s);
2728 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2729 ch[0]
2730 } else {
2731 return None;
2732 }
2733 }
2734 _ => return None,
2735 };
2736 if lang.assign_op(assign).is_some() {
2737 return None;
2738 }
2739 let ch = lang.children(assign);
2740 if ch.len() == 2 && ch[1] == use_id && lang.kind(ch[0]) == NodeKind::VarRef {
2741 return lang.var_key(ch[0]);
2742 }
2743 return None;
2744 }
2745 }
2746 None
2747}
2748
2749fn collect_read_keys<L: Lang>(lang: &L, id: L::Id, out: &mut Vec<L::NameKey>) {
2752 let mut stack = vec![id];
2753 while let Some(n) = stack.pop() {
2754 if lang.kind(n) == NodeKind::VarRef {
2755 if let Some(k) = lang.var_key(n) {
2756 if !out.contains(&k) {
2757 out.push(k);
2758 }
2759 }
2760 }
2761 for &c in lang.children(n) {
2762 stack.push(c);
2763 }
2764 }
2765}
2766
2767
2768pub struct TrailingContinue;
2775
2776impl<L: Lang> Rule<L> for TrailingContinue {
2777 fn name(&self) -> &'static str {
2778 "trailing_continue"
2779 }
2780 fn kinds(&self) -> &'static [NodeKind] {
2781 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
2782 }
2783 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2784 let RewriteCtx { lang, walk: _ } = ctx;
2785 let body = match lang.kind(id) {
2787 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
2788 *lang.children(id).last()?
2789 }
2790 _ => return None,
2791 };
2792 if lang.kind(body) != NodeKind::Block {
2793 return None;
2794 }
2795 let ch = lang.children(body).to_vec();
2796 let last = *ch.last()?;
2797 if lang.kind(last) != NodeKind::Continue || !lang.children(last).is_empty() {
2798 if lang.kind(last) != NodeKind::Continue {
2801 return None;
2802 }
2803 }
2804 Some(Edit::Delete { node: last })
2805 }
2806}
2807
2808pub struct MultiUseCopyPropagation;
2816
2817impl<L: Lang> Rule<L> for MultiUseCopyPropagation {
2818 fn name(&self) -> &'static str {
2819 "multi_use_copy"
2820 }
2821 fn kinds(&self) -> &'static [NodeKind] {
2822 &[NodeKind::Assign, NodeKind::ExprStmt]
2823 }
2824 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2825 let RewriteCtx { lang, walk } = ctx;
2826 let (assign, stmt_node) = match lang.kind(id) {
2828 NodeKind::Assign => (id, id),
2829 NodeKind::ExprStmt => {
2830 let ch = lang.children(id);
2831 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2832 (ch[0], id)
2833 } else {
2834 return None;
2835 }
2836 }
2837 _ => return None,
2838 };
2839 if lang.assign_op(assign).is_some() {
2840 return None;
2841 }
2842 let ach = lang.children(assign).to_vec();
2843 let (target, value) = (ach[0], ach[1]);
2844 if lang.kind(target) != NodeKind::VarRef || lang.kind(value) != NodeKind::VarRef {
2845 return None;
2846 }
2847 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
2849 return None;
2850 }
2851 if !lang.is_local_var(target) {
2852 return None;
2853 }
2854 let name = lang.var_name(target)?.to_string();
2855 let src = lang.var_name(value)?.to_string();
2856 if name == src {
2857 return None;
2858 }
2859
2860 let parent = walk.parent(stmt_node)?;
2861 if lang.kind(parent) != NodeKind::Block {
2862 return None;
2863 }
2864 let idx = walk.index(stmt_node)?;
2865 let stmts = lang.children(parent).to_vec();
2866
2867 let anchored = stmts[..idx].iter().any(|&s| {
2869 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2870 });
2871 if !anchored {
2872 return None;
2873 }
2874
2875 let name_key = lang.var_key(target)?;
2877 let src_key = lang.var_key(value)?;
2878 let mut w1 = Watch::<L>::new(name_key, &[name_key, src_key]);
2879 for &s in &stmts[idx + 1..] {
2880 scan_region(&*lang, s, &mut w1);
2881 }
2882 if w1.opaque || w1.shadowed || w1.uses.is_empty() {
2883 return None;
2884 }
2885 if w1.wrote(name_key) || w1.wrote(src_key) {
2886 return None;
2887 }
2888 let synth = count_name_refs(&*lang, &stmts[idx + 1..], &name);
2894 if synth != w1.uses.len() {
2895 return None;
2896 }
2897 let mut edits: Vec<Edit<L>> = w1.uses
2901 .iter()
2902 .map(|&u| Edit::Replace {
2903 target: u,
2904 with: value,
2905 })
2906 .collect();
2907 edits.push(Edit::Delete { node: stmt_node });
2908 Some(Edit::Multi(edits))
2909 }
2910}
2911
2912
2913pub struct TrailingReturn;
2920
2921impl<L: Lang> Rule<L> for TrailingReturn {
2922 fn name(&self) -> &'static str {
2923 "trailing_return"
2924 }
2925 fn kinds(&self) -> &'static [NodeKind] {
2926 &[NodeKind::Return]
2927 }
2928 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2929 let root = ctx.root();
2930 let parent = ctx.parent(id)?;
2931 let lang = ctx.lang;
2932 if lang.kind(id) != NodeKind::Return || !lang.children(id).is_empty() {
2933 return None;
2934 }
2935 if parent != root || lang.kind(parent) != NodeKind::Block {
2936 return None;
2937 }
2938 if lang.children(parent).last() != Some(&id) {
2939 return None;
2940 }
2941 Some(Edit::Delete { node: id })
2942 }
2943}
2944
2945
2946pub struct TernaryBoolOp;
2953
2954impl<L: Lang> Rule<L> for TernaryBoolOp {
2955 fn name(&self) -> &'static str {
2956 "ternary_bool_op"
2957 }
2958 fn kinds(&self) -> &'static [NodeKind] {
2959 &[NodeKind::Ternary]
2960 }
2961 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2962 let RewriteCtx { lang, walk: _ } = ctx;
2963 if lang.kind(id) != NodeKind::Ternary {
2964 return None;
2965 }
2966 let ch = lang.children(id);
2967 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
2968 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
2969 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
2970 let with = if is_false(b) && lang.is_bool(a) {
2971 lang.build_bin(BinOp::And, c, a)
2972 } else if lb(b) && lang.is_bool(a) {
2973 return None;
2975 } else if lb(a) && lang.is_bool(b) {
2976 lang.build_bin(BinOp::Or, c, b)
2978 } else if is_false(a) && lang.is_bool(b) {
2979 return None;
2981 } else {
2982 return None;
2983 };
2984 Some(Edit::Replace {
2985 target: id,
2986 with,
2987 })
2988 }
2989}
2990
2991
2992pub struct StoreKill;
3001
3002enum KillEvent<L: Lang> {
3006 Read,
3008 DomWrite(L::Id),
3010 Shadow,
3012}
3013
3014fn scan_kill_event<L: Lang>(
3018 lang: &L,
3019 node: L::Id,
3020 name: &str,
3021 dominating: bool,
3022) -> Option<KillEvent<L>> {
3023 match lang.kind(node) {
3024 NodeKind::VarRef => {
3025 if lang.var_name(node) == Some(name) {
3026 Some(KillEvent::Read)
3027 } else {
3028 None
3029 }
3030 }
3031 NodeKind::VarDecl => {
3032 if lang.var_name(node) == Some(name) {
3033 Some(KillEvent::Shadow)
3034 } else {
3035 for &c in lang.children(node) {
3037 if let Some(e) = scan_kill_event(lang, c, name, dominating) {
3038 return Some(e);
3039 }
3040 }
3041 None
3042 }
3043 }
3044 NodeKind::Assign => {
3045 let ch = lang.children(node);
3046 if let Some(&val) = ch.get(1) {
3048 if let Some(e) = scan_kill_event(lang, val, name, dominating) {
3049 if matches!(e, KillEvent::Read | KillEvent::Shadow) {
3050 return Some(e);
3051 }
3052 }
3053 }
3054 if lang.assign_op(node).is_none() {
3055 if let Some(&t) = ch.first() {
3056 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3057 if dominating {
3058 return Some(KillEvent::DomWrite(node));
3059 }
3060 return None; }
3062 }
3063 }
3064 None
3065 }
3066 NodeKind::If | NodeKind::While | NodeKind::Ternary => {
3067 let ch = lang.children(node);
3070 if let Some(&c) = ch.first() {
3071 if let Some(e) = scan_kill_event(lang, c, name, true) {
3072 return Some(e);
3073 }
3074 }
3075 for &c in &ch[1..] {
3076 if let Some(e) = scan_kill_event(lang, c, name, false) {
3077 if matches!(e, KillEvent::Read | KillEvent::Shadow) {
3078 return Some(e);
3079 }
3080 }
3081 }
3082 None
3083 }
3084 NodeKind::Binary => {
3085 let op = lang.bin_op(node)?;
3086 let ch = lang.children(node);
3087 if op.is_short_circuit() {
3088 if let Some(&l) = ch.first() {
3090 if let Some(e) = scan_kill_event(lang, l, name, true) {
3091 return Some(e);
3092 }
3093 }
3094 if let Some(&r) = ch.get(1) {
3095 if let Some(e) = scan_kill_event(lang, r, name, false) {
3096 if matches!(e, KillEvent::Read | KillEvent::Shadow) {
3097 return Some(e);
3098 }
3099 }
3100 }
3101 None
3102 } else {
3103 for &c in ch {
3104 if let Some(e) = scan_kill_event(lang, c, name, dominating) {
3105 return Some(e);
3106 }
3107 }
3108 None
3109 }
3110 }
3111 NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
3112 let ch = lang.children(node);
3116 let dom_len = match lang.kind(node) {
3117 NodeKind::For => ch.len().saturating_sub(2),
3118 NodeKind::ForEach | NodeKind::Switch => 1,
3119 _ => 0,
3120 };
3121 for &c in &ch[..dom_len] {
3122 if let Some(e) = scan_kill_event(lang, c, name, true) {
3123 return Some(e);
3124 }
3125 }
3126 for &c in &ch[dom_len..] {
3127 if let Some(e) = scan_kill_event(lang, c, name, false) {
3128 if matches!(e, KillEvent::Read | KillEvent::Shadow) {
3129 return Some(e);
3130 }
3131 }
3132 }
3133 None
3134 }
3135 _ => {
3136 for &c in lang.children(node) {
3137 if let Some(e) = scan_kill_event(lang, c, name, dominating) {
3138 return Some(e);
3139 }
3140 }
3141 None
3142 }
3143 }
3144}
3145
3146impl<L: Lang> Rule<L> for StoreKill {
3147 fn name(&self) -> &'static str {
3148 "store_kill"
3149 }
3150 fn kinds(&self) -> &'static [NodeKind] {
3151 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
3152 }
3153 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3154 let RewriteCtx { lang, walk } = ctx;
3155 let (is_decl, stmt_node, first_value) = match lang.kind(id) {
3157 NodeKind::VarDecl => {
3158 if lang.children(id).is_empty() {
3159 return None;
3160 }
3161 (true, id, *lang.children(id).first()?)
3162 }
3163 NodeKind::Assign => {
3164 if lang.assign_op(id).is_some() {
3165 return None;
3166 }
3167 (false, id, *lang.children(id).get(1)?)
3168 }
3169 NodeKind::ExprStmt => {
3170 let ch = lang.children(id);
3171 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3172 let a = ch[0];
3173 if lang.assign_op(a).is_some() {
3174 return None;
3175 }
3176 (false, id, *lang.children(a).get(1)?)
3177 } else {
3178 return None;
3179 }
3180 }
3181 _ => return None,
3182 };
3183 let name = if is_decl {
3184 lang.var_name(id)?.to_string()
3185 } else {
3186 let a = match lang.kind(id) {
3187 NodeKind::Assign => id,
3188 _ => lang.children(id)[0],
3189 };
3190 let t = *lang.children(a).first()?;
3191 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
3192 return None;
3193 }
3194 lang.var_name(t)?.to_string()
3195 };
3196
3197 let parent = walk.parent(stmt_node)?;
3198 if lang.kind(parent) != NodeKind::Block {
3199 return None;
3200 }
3201 let idx = walk.index(stmt_node)?;
3202 let stmts = lang.children(parent).to_vec();
3203
3204 let mut killed_by: Option<(usize, L::Id)> = None;
3206 for (si, &s) in stmts.iter().enumerate().skip(idx + 1) {
3207 match scan_kill_event(&*lang, s, &name, true) {
3208 Some(KillEvent::Read) | Some(KillEvent::Shadow) => return None,
3209 Some(KillEvent::DomWrite(assign)) => {
3210 killed_by = Some((si, assign));
3211 break;
3212 }
3213 None => continue,
3214 }
3215 }
3216 let (si, kill_assign) = killed_by?;
3217 {
3225 let kill_container = match walk.parents.get(&kill_assign) {
3226 Some(&(p, _)) if lang.kind(p) == NodeKind::ExprStmt => p,
3227 _ => kill_assign,
3228 };
3229 if kill_container != stmts[si] {
3230 return None;
3231 }
3232 }
3233 for &s in &stmts[idx + 1..si] {
3239 if lang.effect(s) > Effect::MayRead {
3240 return None;
3241 }
3242 }
3243
3244 if is_decl {
3245 if lang.effect(first_value) > Effect::MayRead {
3248 return None;
3249 }
3250 let value = *lang.children(kill_assign).get(1)?;
3251 if is_literal(lang, value) {
3252 let assign_stmt = stmts[si];
3253 return Some(Edit::Multi(vec![
3254 Edit::Splice {
3255 node: id,
3256 index: 0,
3257 remove: 1,
3258 insert: vec![value],
3259 },
3260 Edit::Delete { node: assign_stmt },
3261 ]));
3262 }
3263 if lang.is_inferred_decl(id) {
3267 return None;
3268 }
3269 if lang.effect(first_value) > Effect::MayRead {
3270 return None;
3271 }
3272 return Some(Edit::Splice {
3273 node: id,
3274 index: 0,
3275 remove: 1,
3276 insert: Vec::new(),
3277 });
3278 }
3279 if lang.effect(first_value) > Effect::MayRead {
3281 return None;
3282 }
3283 Some(Edit::Delete { node: stmt_node })
3284 }
3285}
3286
3287fn is_literal<L: Lang>(lang: &L, n: L::Id) -> bool {
3289 matches!(lang.kind(n), NodeKind::Literal)
3290}
3291
3292pub fn default_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3298 vec![
3299 Box::new(DoubleNegFold),
3300 Box::new(BlockFlatten),
3301 Box::new(ParenRemoval),
3302 Box::new(ConstCondition),
3303 Box::new(BooleanReturn),
3304 Box::new(IfToTernary),
3305 Box::new(IfAssignTernary),
3306 Box::new(IfElseEmpty),
3307 Box::new(BoolCompare),
3308 Box::new(DoubleNot),
3309 Box::new(BoolNotFold),
3310 Box::new(BoolShortCircuit),
3311 Box::new(NotCompare),
3312 Box::new(TernaryFold),
3313 Box::new(TernaryBool),
3314 Box::new(TernaryBoolOp),
3315 Box::new(ConstFoldBin),
3316 Box::new(CmpConstFold),
3317 Box::new(BitIdentity),
3318 Box::new(ArithReassoc),
3319 Box::new(SelfAssign),
3320 Box::new(ArithIdentity),
3321 Box::new(ArithZero),
3322 Box::new(DeclAssignMerge),
3323 Box::new(LocalPropagation),
3324 Box::new(AssignPropagation),
3325 Box::new(MultiUseCopyPropagation),
3326 Box::new(TrailingReturn),
3327 Box::new(InverseAssignPair),
3328 Box::new(DeadDecl),
3329 Box::new(AssignBackFold),
3330 Box::new(DeadStore),
3331 Box::new(StoreKill),
3332 ]
3333}
3334
3335pub fn all_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3337 let mut rules = default_rules();
3338 rules.push(Box::new(UnreachableAfterTerminal));
3339 rules
3340}