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 let (parent, _) = walk.parents.get(&id)?;
520 match lang.kind(*parent) {
521 NodeKind::ExprStmt => Some(Edit::Delete { node: *parent }),
522 NodeKind::Block => Some(Edit::Delete { node: id }),
523 _ => None,
524 }
525 }
526}
527
528pub struct DeadDecl;
542
543impl<L: Lang> Rule<L> for DeadDecl {
544 fn name(&self) -> &'static str {
545 "dead_decl"
546 }
547 fn kinds(&self) -> &'static [NodeKind] {
548 &[NodeKind::VarDecl]
549 }
550 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
551 let RewriteCtx { lang, walk } = ctx;
552 if lang.kind(id) != NodeKind::VarDecl {
553 return None;
554 }
555 let ch = lang.children(id).to_vec();
556 if let Some(&v) = ch.first() {
559 if lang.effect(v) > Effect::MayRead {
560 return None;
561 }
562 }
563 let parent = walk.parent(id)?;
564 if lang.kind(parent) != NodeKind::Block {
565 return None;
566 }
567 let idx = walk.index(id)?;
568 let stmts = lang.children(parent).to_vec();
569 let name_str = lang.var_name(id)?.to_string();
575 let referenced = stmts[idx + 1..].iter().any(|&s| {
576 subtree_contains(&*lang, s, |n| {
577 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name_str.as_str())
578 })
579 });
580 if referenced {
581 return None;
582 }
583 let name_key = lang.var_key(id)?;
585 let mut w = Watch::<L>::new(name_key, &[]);
586 for &s in &stmts[idx + 1..] {
587 scan_region(&*lang, s, &mut w);
588 }
589 if w.opaque || w.shadowed {
590 return None;
591 }
592 Some(Edit::Delete { node: id })
593 }
594}
595
596pub struct AssignBackFold;
611
612impl<L: Lang> Rule<L> for AssignBackFold {
613 fn name(&self) -> &'static str {
614 "assign_back_fold"
615 }
616 fn kinds(&self) -> &'static [NodeKind] {
617 &[NodeKind::VarDecl]
618 }
619 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
620 let RewriteCtx { lang, walk } = ctx;
621 if lang.kind(id) != NodeKind::VarDecl {
622 return None;
623 }
624 let ch = lang.children(id).to_vec();
625 let (&init, has_init) = (ch.first()?, !ch.is_empty());
626 if !has_init {
627 return None; }
629 let name_key = lang.var_key(id)?;
630 if !lang.is_wide_decl(id) {
634 return None;
635 }
636 let parent = walk.parent(id)?;
637 if lang.kind(parent) != NodeKind::Block {
638 return None;
639 }
640 let idx = walk.index(id)?;
641 let stmts = lang.children(parent).to_vec();
642
643 let name_str = lang.var_name(id)?.to_string();
648 for &s in &stmts[idx + 1..] {
649 let mut w = Watch::<L>::new(name_key, &[]);
650 scan_region(&*lang, s, &mut w);
651 let referenced = subtree_contains(&*lang, s, |n| {
652 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name_str.as_str())
653 });
654 let touched = referenced || w.opaque || w.shadowed;
655 if !touched {
656 continue; }
658 let assign = match lang.kind(s) {
660 NodeKind::Assign => s,
661 NodeKind::ExprStmt => {
662 let c = lang.children(s);
663 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
664 c[0]
665 } else {
666 return None;
667 }
668 }
669 _ => return None, };
671 let ach = lang.children(assign).to_vec();
672 if ach.len() != 2 {
673 return None;
674 }
675 let (target, rhs) = (ach[0], ach[1]);
676 if lang.kind(target) != NodeKind::VarRef || lang.var_key(target) != Some(name_key) {
677 return None;
678 }
679 let (op, k) = match lang.assign_op(assign) {
682 Some(op) => (op, lang.literal(rhs).and_then(|x| x.as_int())?),
683 None => {
684 if lang.kind(rhs) != NodeKind::Binary {
685 return None;
686 }
687 let op = lang.bin_op(rhs)?;
688 let rc = lang.children(rhs).to_vec();
689 if rc.len() != 2 || lang.kind(rc[0]) != NodeKind::VarRef {
690 return None;
691 }
692 if lang.var_key(rc[0]) != Some(name_key) {
694 return None;
695 }
696 (op, lang.literal(rc[1]).and_then(|x| x.as_int())?)
697 }
698 };
699 if op.is_short_circuit() || op.is_comparison() {
700 return None;
701 }
702 if matches!(op, BinOp::Div | BinOp::Rem) && k == 0 {
704 return None;
705 }
706 let lit = if lang.is_long_decl(id) {
711 lang.build_int(k, true)
712 } else {
713 lang.build_int(k, false)
714 };
715 let init = if lang.is_long_decl(id) {
716 match lang.literal(init) {
717 Some(crate::kind::LitRef::Int(v)) => lang.build_int(v, true),
718 _ => init,
719 }
720 } else {
721 init
722 };
723 let merged = lang.build_bin(op, init, lit);
724 return Some(Edit::Multi(vec![
726 Edit::Delete { node: s },
727 Edit::Splice { node: id, index: 0, remove: ch.len(), insert: vec![merged] },
728 ]));
729 }
730 None
731 }
732}
733
734pub struct ArithIdentity;
735
736impl<L: Lang> Rule<L> for ArithIdentity {
737 fn name(&self) -> &'static str {
738 "arith_identity"
739 }
740 fn kinds(&self) -> &'static [NodeKind] {
741 &[NodeKind::Binary]
742 }
743 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
744 let RewriteCtx { lang, walk: _ } = ctx;
745 if lang.kind(id) != NodeKind::Binary {
746 return None;
747 }
748 let op = lang.bin_op(id)?;
749 let ch = lang.children(id);
750 let (l, r) = (*ch.first()?, *ch.get(1)?);
751 let int_lit = |n: L::Id, v: i64| -> bool {
752 matches!(lang.literal(n), Some(l) if l.as_int() == Some(v))
753 };
754 let x = match op {
755 BinOp::Add if int_lit(r, 0) => l,
756 BinOp::Add if int_lit(l, 0) => r,
757 BinOp::Sub if int_lit(r, 0) => l,
758 BinOp::Mul if int_lit(r, 1) => l,
759 BinOp::Mul if int_lit(l, 1) => r,
760 BinOp::Div if int_lit(r, 1) => l,
761 _ => return None,
762 };
763 if !lang.is_exact_int(x) {
764 return None;
765 }
766 Some(Edit::Replace {
767 target: id,
768 with: x,
769 })
770 }
771}
772
773fn count_name_refs<L: Lang>(lang: &L, stmts: &[L::Id], name: &str) -> usize {
792 let mut n: usize = 0;
793 for &s in stmts {
794 count_var_refs(lang, s, name, &mut n);
795 }
796 n
797}
798
799fn null_unsafe_position<L: Lang>(lang: &L, walk: &Walk<L>, use_id: L::Id) -> bool {
806 let mut cur = use_id;
807 while let Some(&(p, _)) = walk.parents.get(&cur) {
808 match lang.kind(p) {
809 NodeKind::Paren => cur = p,
810 NodeKind::Call
811 | NodeKind::New
812 | NodeKind::Member
813 | NodeKind::Index
814 | NodeKind::InstanceOf => return true,
815 _ => return false,
816 }
817 }
818 false
819}
820
821fn typed_position_unsafe<L: Lang>(lang: &L, walk: &Walk<L>, use_id: L::Id) -> bool {
829 let mut cur = use_id;
830 while let Some(&(p, _)) = walk.parents.get(&cur) {
831 match lang.kind(p) {
832 NodeKind::Paren => cur = p,
833 NodeKind::Cast | NodeKind::InstanceOf => return true,
834 _ => return false,
835 }
836 }
837 false
838}
839
840fn count_var_refs<L: Lang>(lang: &L, node: L::Id, name: &str, n: &mut usize) {
841 if lang.kind(node) == NodeKind::VarRef && lang.var_name(node) == Some(name) {
842 *n += 1;
843 }
844 for &c in lang.children(node) {
845 count_var_refs(lang, c, name, n);
846 }
847}
848
849pub struct LocalPropagation;
850
851impl<L: Lang> Rule<L> for LocalPropagation {
852 fn name(&self) -> &'static str {
853 "local_propagation"
854 }
855 fn kinds(&self) -> &'static [NodeKind] {
856 &[NodeKind::VarDecl]
857 }
858 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
859 let RewriteCtx { lang, walk } = ctx;
860 if lang.kind(id) != NodeKind::VarDecl {
861 return None;
862 }
863 let name = lang.var_name(id)?.to_string();
864 let ch = lang.children(id);
865 if ch.len() != 1 {
866 return None; }
868 let value = ch[0];
869 if lang.kind(value) == NodeKind::ArrayLit {
873 return None;
874 }
875 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
879 return None;
880 }
881 if subtree_contains(&*lang, value, |n| {
883 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
884 }) {
885 return None;
886 }
887 let parent = walk.parent(id)?;
888 if lang.kind(parent) != NodeKind::Block {
889 return None;
890 }
891 let index = walk.index(id)?;
892 let stmts: Vec<L::Id> = lang.children(parent).to_vec();
893 if index + 1 > stmts.len() {
894 return None;
895 }
896
897 let mut value = value;
903 if lang.is_char_decl(id) {
904 match lang.literal(value) {
905 Some(LitRef::Char(_)) => {}
906 Some(LitRef::Int(v)) => {
907 let c = u32::try_from(v)
910 .ok()
911 .filter(|&u| (0..=0xFFFF).contains(&u) && !(0xD800..=0xDFFF).contains(&u))
912 .and_then(char::from_u32)?;
913 value = lang.build_char(c);
914 }
915 _ => return None,
916 }
917 }
918 if lang.is_long_decl(id) {
923 if let Some(LitRef::Int(v)) = lang.literal(value) {
924 value = lang.build_int(v, true);
925 }
926 }
927 if !lang.is_wide_decl(id) && !lang.is_char_decl(id) {
932 return None;
933 }
934
935 let name_str = lang.var_name(id)?.to_string();
940 {
945 let mut captured = false;
946 for &st in &stmts[index + 1..] {
947 if subtree_contains(&*lang, st, |n| {
950 if lang.kind(n) == NodeKind::Lambda {
951 return subtree_contains(&*lang, n, |m| {
952 lang.kind(m) == NodeKind::VarRef
953 && lang.var_name(m) == Some(name_str.as_str())
954 });
955 }
956 false
957 }) {
958 captured = true;
959 break;
960 }
961 }
962 if captured {
963 return None;
964 }
965 }
966 let syntactic_refs = count_name_refs(&*lang, &stmts[index + 1..], &name_str);
967 if syntactic_refs != 1 {
968 return None;
969 }
970 let name_key = lang.var_key(id)?;
971 let mut wa = Watch::<L>::new(name_key, &[]);
972 for &s in &stmts[index + 1..] {
973 scan_region(&*lang, s, &mut wa);
974 }
975 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
976 return None;
977 }
978 let use_id = wa.uses[0];
979
980 if let Some(&(p, _)) = walk.parents.get(&use_id) {
985 if lang.kind(p) == NodeKind::Assign && lang.assign_op(p).is_none() {
986 let pch = lang.children(p);
987 if pch.len() == 2
988 && lang.kind(pch[0]) == NodeKind::VarRef
989 && lang.var_key(pch[0]) == Some(name_key)
990 && pch[1] == use_id
991 {
992 return None;
993 }
994 }
995 }
996 {
1001 let (up, head) = match walk.parents.get(&use_id) {
1002 Some(&(p, _)) => {
1003 (Some(p), lang.children(p).first() == Some(&use_id))
1004 }
1005 None => (None, false),
1006 };
1007 if lang.receiver_propagation_unsound(id, value, up, head) {
1008 return None;
1009 }
1010 }
1011 if matches!(
1018 lang.literal(value),
1019 Some(
1020 LitRef::Null
1021 | LitRef::Int(_)
1022 | LitRef::Long(_)
1023 | LitRef::Float(_)
1024 | LitRef::Double(_)
1025 | LitRef::Char(_)
1026 | LitRef::Bool(_)
1027 )
1028 ) {
1029 let mut cur = use_id;
1030 while let Some(&(p, _)) = walk.parents.get(&cur) {
1031 let at_head = lang.children(p).first() == Some(&cur);
1032 match lang.kind(p) {
1033 NodeKind::Member | NodeKind::Index if at_head => return None,
1035 NodeKind::Call if at_head => return None,
1037 NodeKind::InstanceOf if at_head => return None,
1041 NodeKind::ForEach => {
1043 let ch = lang.children(p);
1044 if ch.first() == Some(&cur) {
1045 return None;
1046 }
1047 }
1048 NodeKind::Synchronized => {
1050 let ch = lang.children(p);
1051 if ch.len() == 1 && ch[0] == cur {
1052 return None;
1053 }
1054 }
1055 _ => {}
1056 }
1057 cur = p;
1058 }
1059 }
1060 if matches!(lang.literal(value), Some(LitRef::Null))
1068 && null_unsafe_position(&*lang, walk, use_id)
1069 {
1070 return None;
1071 }
1072 if lang.kind(value) == NodeKind::VarRef
1075 && typed_position_unsafe(&*lang, walk, use_id)
1076 {
1077 return None;
1078 }
1079 let mut watch_keys: Vec<L::NameKey> = Vec::new();
1081 collect_read_keys(&*lang, value, &mut watch_keys);
1082 watch_keys.push(name_key);
1083 let mut wb = Watch::<L>::new(name_key, &watch_keys);
1087 let mut use_stmt_idx = None;
1088 for (off, &s) in stmts[index + 1..].iter().enumerate() {
1089 let uses_before = wb.uses.len();
1090 scan_region(&*lang, s, &mut wb);
1091 if wb.uses.len() > uses_before {
1092 use_stmt_idx = Some(index + 1 + off);
1093 break; }
1095 }
1096 if subtree_contains(&*lang, value, |n| {
1103 if lang.kind(n) != NodeKind::VarRef || lang.is_local_var(n) {
1104 return false;
1105 }
1106 if !lang.var_name(n).is_some_and(|nm| lang.is_field_name(nm)) {
1109 return false;
1110 }
1111 let is_callee = match walk.parent(n) {
1112 Some(p) => {
1113 matches!(lang.kind(p), NodeKind::Call) && lang.children(p).first() == Some(&n)
1114 }
1115 None => false,
1116 };
1117 !is_callee
1118 }) {
1119 if let Some(ui) = use_stmt_idx {
1120 for &s in &stmts[index + 1..=ui] {
1121 if lang.effect(s) != Effect::Pure {
1122 return None;
1123 }
1124 }
1125 }
1126 }
1127 if let Some(ui) = use_stmt_idx {
1131 if count_name_refs(&*lang, &stmts[ui + 1..], &name_str) != 0 {
1133 return None;
1134 }
1135 let mut wc = Watch::<L>::new(name_key, &[name_key]);
1136 for &s in &stmts[ui + 1..] {
1137 scan_region(&*lang, s, &mut wc);
1138 if wc.opaque || !wc.uses.is_empty() || wc.wrote(name_key) || wc.shadowed {
1139 return None;
1140 }
1141 }
1142 }
1143 let ve = lang.effect(value);
1144
1145 if ve <= Effect::MayRead {
1146 if let Some(target_key) =
1149 use_stmt_target_write_key(&*lang, &stmts[index + 1..], use_id)
1150 {
1151 wb.clear_wrote(target_key);
1152 }
1153 if wb.opaque || wb.any_write() {
1154 return None;
1155 }
1156 return Some(Edit::Multi(vec![
1157 Edit::Replace {
1158 target: use_id,
1159 with: value,
1160 },
1161 Edit::Delete { node: id },
1162 ]));
1163 }
1164
1165 if index + 1 >= stmts.len() {
1167 return None;
1168 }
1169 let stmt = stmts[index + 1];
1170 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
1171 return None;
1172 }
1173 match lang.kind(stmt) {
1174 NodeKind::Return
1177 | NodeKind::ExprStmt
1178 | NodeKind::Assign
1179 | NodeKind::Throw
1180 | NodeKind::VarDecl => {}
1181 _ => return None,
1182 }
1183 if !straight_path(&*lang, stmt, use_id) {
1184 return None;
1185 }
1186 if !prefix_effects_readable(&*lang, stmt, use_id) {
1187 return None;
1188 }
1189 Some(Edit::Multi(vec![
1190 Edit::Replace {
1191 target: use_id,
1192 with: value,
1193 },
1194 Edit::Delete { node: id },
1195 ]))
1196 }
1197}
1198
1199pub(crate) struct Watch<L: Lang> {
1205 opaque: bool,
1207 name: L::NameKey,
1209 watch: Vec<L::NameKey>,
1211 writes_hit: Vec<bool>,
1213 shadowed: bool,
1214 uses: Vec<L::Id>,
1216}
1217
1218impl<L: Lang> Watch<L> {
1219 pub(crate) fn new(name: L::NameKey, watch: &[L::NameKey]) -> Self {
1220 Watch {
1221 opaque: false,
1222 name,
1223 watch: watch.to_vec(),
1224 writes_hit: vec![false; watch.len()],
1225 shadowed: false,
1226 uses: Vec::new(),
1227 }
1228 }
1229 pub(crate) fn wrote(&self, k: L::NameKey) -> bool {
1231 self.watch
1232 .iter()
1233 .zip(&self.writes_hit)
1234 .any(|(w, h)| *h && *w == k)
1235 }
1236 pub(crate) fn any_write(&self) -> bool {
1238 self.writes_hit.iter().any(|h| *h)
1239 }
1240 pub(crate) fn clear_wrote(&mut self, k: L::NameKey) {
1242 for (w, h) in self.watch.iter().zip(self.writes_hit.iter_mut()) {
1243 if *w == k {
1244 *h = false;
1245 }
1246 }
1247 }
1248}
1249
1250fn scan_region<L: Lang>(lang: &L, node: L::Id, w: &mut Watch<L>) {
1252 lang.debug_verify_events(node);
1253 if let Some(events) = lang.region_events(node) {
1254 let lo = events.partition_point(|e| e.key < w.name);
1256 let hi = events.partition_point(|e| e.key <= w.name);
1257 for e in &events[lo..hi] {
1258 match e.kind {
1259 crate::kind::EventKind::Use => w.uses.push(e.node),
1260 crate::kind::EventKind::Shadow => w.shadowed = true,
1261 crate::kind::EventKind::Write => {
1262 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1263 if *wn == w.name {
1264 *hit = true;
1265 }
1266 }
1267 }
1268 crate::kind::EventKind::Opaque => w.opaque = true,
1271 }
1272 }
1273 for (i, wk) in w.watch.iter().enumerate() {
1275 if *wk == w.name {
1276 continue; }
1278 let lo = events.partition_point(|e| e.key < *wk);
1279 let hi = events.partition_point(|e| e.key <= *wk);
1280 for e in &events[lo..hi] {
1281 if e.kind == crate::kind::EventKind::Write {
1282 w.writes_hit[i] = true;
1283 break; }
1285 }
1286 }
1287 return;
1288 }
1289 if lang.is_opaque(node) {
1291 w.opaque = true;
1292 return;
1293 }
1294 match lang.kind(node) {
1296 NodeKind::Assign => {
1297 let ch = lang.children(node);
1298 if let Some(&t) = ch.first() {
1299 if lang.kind(t) == NodeKind::VarRef {
1300 if let Some(k) = lang.var_key(t) {
1301 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1302 if *wn == k {
1303 *hit = true;
1304 }
1305 }
1306 }
1307 }
1308 }
1309 if let Some(&v) = ch.get(1) {
1310 scan_region(lang, v, w);
1311 }
1312 }
1313 NodeKind::Unary if lang.un_op(node).is_some_and(|o| o.is_incdec()) => {
1314 let ch = lang.children(node);
1315 if let Some(&t) = ch.first() {
1316 if lang.kind(t) == NodeKind::VarRef {
1317 if let Some(k) = lang.var_key(t) {
1318 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1319 if *wn == k {
1320 *hit = true;
1321 }
1322 }
1323 }
1324 }
1325 }
1326 }
1327 NodeKind::VarDecl => {
1328 if let Some(k) = lang.var_key(node) {
1329 if k == w.name {
1330 w.shadowed = true;
1331 }
1332 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1333 if *wn == k {
1334 *hit = true;
1335 }
1336 }
1337 }
1338 for &c in lang.children(node) {
1339 scan_region(lang, c, w);
1340 }
1341 }
1342 NodeKind::ForEach | NodeKind::Catch => {
1345 if let Some(k) = lang.var_key(node) {
1346 if k == w.name {
1347 w.shadowed = true;
1348 }
1349 if lang.kind(node) == NodeKind::ForEach {
1350 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1351 if *wn == k {
1352 *hit = true;
1353 }
1354 }
1355 }
1356 }
1357 for &c in lang.children(node) {
1358 scan_region(lang, c, w);
1359 }
1360 }
1361 NodeKind::VarRef => {
1362 if lang.var_key(node) == Some(w.name) {
1363 w.uses.push(node);
1364 }
1365 }
1366 NodeKind::Raw => {
1367 w.opaque = true;
1369 }
1370 _ => {
1371 for &c in lang.children(node) {
1372 scan_region(lang, c, w);
1373 }
1374 }
1375 }
1376}
1377
1378fn straight_path<L: Lang>(lang: &L, root: L::Id, target: L::Id) -> bool {
1380 fn go<L: Lang>(lang: &L, node: L::Id, target: L::Id) -> bool {
1381 if node == target {
1382 return true;
1383 }
1384 match lang.kind(node) {
1385 NodeKind::Ternary | NodeKind::If => return false,
1386 NodeKind::Binary if lang.bin_op(node).is_some_and(|o| o.is_short_circuit()) => {
1387 return false
1388 }
1389 _ => {}
1390 }
1391 for &c in lang.children(node) {
1392 if subtree_contains(lang, c, |n| n == target) {
1393 return go(lang, c, target);
1394 }
1395 }
1396 false
1397 }
1398 go(lang, root, target)
1399}
1400
1401
1402pub struct BoolShortCircuit;
1410
1411impl<L: Lang> Rule<L> for BoolShortCircuit {
1412 fn name(&self) -> &'static str {
1413 "bool_short_circuit"
1414 }
1415 fn kinds(&self) -> &'static [NodeKind] {
1416 &[NodeKind::Binary]
1417 }
1418 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1419 let RewriteCtx { lang, walk: _ } = ctx;
1420 if lang.kind(id) != NodeKind::Binary {
1421 return None;
1422 }
1423 let op = lang.bin_op(id)?;
1424 if !op.is_short_circuit() {
1425 return None;
1426 }
1427 let ch = lang.children(id);
1428 let (l, r) = (*ch.first()?, *ch.get(1)?);
1429 let is_lit = |n: L::Id, v: bool| matches!(lang.literal(n), Some(LitRef::Bool(b)) if b == v);
1430 let with = match (op, is_lit(l, true), is_lit(l, false)) {
1432 (BinOp::And, _, true) => l, (BinOp::Or, true, _) => l, (BinOp::And, true, _) => r, (BinOp::Or, _, true) => r, _ => {
1437 match (op, is_lit(r, true), is_lit(r, false)) {
1440 (BinOp::And, true, _) => l,
1441 (BinOp::Or, _, true) => l,
1442 (BinOp::And, _, true) | (BinOp::Or, true, _)
1443 if lang.effect(l) <= Effect::MayRead => {
1444 r
1445 }
1446 _ => return None,
1447 }
1448 }
1449 };
1450 Some(Edit::Replace {
1451 target: id,
1452 with,
1453 })
1454 }
1455}
1456
1457pub struct NotCompare;
1463
1464impl<L: Lang> Rule<L> for NotCompare {
1465 fn name(&self) -> &'static str {
1466 "not_compare"
1467 }
1468 fn kinds(&self) -> &'static [NodeKind] {
1469 &[NodeKind::Unary]
1470 }
1471 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1472 let RewriteCtx { lang, walk: _ } = ctx;
1473 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
1474 return None;
1475 }
1476 let inner = *lang.children(id).first()?;
1477 if lang.kind(inner) != NodeKind::Binary {
1478 return None;
1479 }
1480 let op = lang.bin_op(inner)?;
1481 let neg = match op {
1482 BinOp::Eq => BinOp::Ne,
1483 BinOp::Ne => BinOp::Eq,
1484 BinOp::Lt => BinOp::Ge,
1485 BinOp::Le => BinOp::Gt,
1486 BinOp::Gt => BinOp::Le,
1487 BinOp::Ge => BinOp::Lt,
1488 _ => return None,
1489 };
1490 if op.is_short_circuit() {
1491 return None;
1492 }
1493 if matches!(op, BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) {
1495 let ch = lang.children(inner);
1496 if !lang.is_exact_int(*ch.first()?) || !lang.is_exact_int(*ch.get(1)?) {
1497 return None;
1498 }
1499 }
1500 let ch = lang.children(inner).to_vec();
1501 let rebuilt = lang.build_bin(neg, ch[0], ch[1]);
1502 Some(Edit::Replace {
1503 target: id,
1504 with: rebuilt,
1505 })
1506 }
1507}
1508
1509fn in_inferred_var_init<L: Lang>(lang: &L, walk: &Walk<L>, mut id: L::Id) -> bool {
1519 while let Some(&(p, _)) = walk.parents.get(&id) {
1520 match lang.kind(p) {
1521 NodeKind::Paren | NodeKind::Ternary => id = p,
1522 NodeKind::VarDecl => return lang.is_inferred_decl(p),
1523 _ => return false,
1524 }
1525 }
1526 false
1527}
1528
1529pub struct TernaryFold;
1530
1531impl<L: Lang> Rule<L> for TernaryFold {
1532 fn name(&self) -> &'static str {
1533 "ternary_fold"
1534 }
1535 fn kinds(&self) -> &'static [NodeKind] {
1536 &[NodeKind::Ternary]
1537 }
1538 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1539 let RewriteCtx { lang, walk } = ctx;
1540 if lang.kind(id) != NodeKind::Ternary {
1541 return None;
1542 }
1543 let ch = lang.children(id);
1544 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1545 match lang.literal(c) {
1546 Some(LitRef::Bool(true)) => {
1552 if !in_inferred_var_init(&*lang, walk, id) {
1553 return Some(Edit::Replace { target: id, with: a });
1554 }
1555 }
1556 Some(LitRef::Bool(false))
1557 if !in_inferred_var_init(&*lang, walk, id) => {
1558 return Some(Edit::Replace { target: id, with: b });
1559 }
1560 _ => {}
1561 }
1562 if crate::analysis::structurally_equal(&*lang, a, b) && lang.effect(c) <= Effect::MayRead {
1565 return Some(Edit::Replace { target: id, with: a });
1566 }
1567 None
1568 }
1569}
1570
1571pub struct TernaryBool;
1576
1577impl<L: Lang> Rule<L> for TernaryBool {
1578 fn name(&self) -> &'static str {
1579 "ternary_bool"
1580 }
1581 fn kinds(&self) -> &'static [NodeKind] {
1582 &[NodeKind::Ternary]
1583 }
1584 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1585 let RewriteCtx { lang, walk: _ } = ctx;
1586 if lang.kind(id) != NodeKind::Ternary {
1587 return None;
1588 }
1589 let ch = lang.children(id);
1590 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1591 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
1592 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
1593 if !lang.is_bool(c) {
1594 return None;
1595 }
1596 let with = if lb(a) && is_false(b) {
1597 c
1598 } else if is_false(a) && lb(b) {
1599 lang.build_unary(UnOp::Not, c)
1600 } else {
1601 return None;
1602 };
1603 Some(Edit::Replace {
1604 target: id,
1605 with,
1606 })
1607 }
1608}
1609
1610pub struct ConstFoldBin;
1617
1618impl<L: Lang> Rule<L> for ConstFoldBin {
1619 fn name(&self) -> &'static str {
1620 "const_fold_bin"
1621 }
1622 fn kinds(&self) -> &'static [NodeKind] {
1623 &[NodeKind::Binary]
1624 }
1625 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1626 let RewriteCtx { lang, walk: _ } = ctx;
1627 if lang.kind(id) != NodeKind::Binary {
1628 return None;
1629 }
1630 let op = lang.bin_op(id)?;
1631 if op.is_short_circuit() || op.is_comparison() {
1632 return None;
1633 }
1634 let ch = lang.children(id);
1635 let (l, r) = (*ch.first()?, *ch.get(1)?);
1636 lang.literal(l)?;
1638 lang.literal(r)?;
1639 let with = lang.fold_lit_bin(op, l, r)?;
1640 Some(Edit::Replace {
1641 target: id,
1642 with,
1643 })
1644 }
1645}
1646
1647pub struct ArithZero;
1656
1657impl<L: Lang> Rule<L> for ArithZero {
1658 fn name(&self) -> &'static str {
1659 "arith_zero"
1660 }
1661 fn kinds(&self) -> &'static [NodeKind] {
1662 &[NodeKind::Binary]
1663 }
1664 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1665 let RewriteCtx { lang, walk: _ } = ctx;
1666 if lang.kind(id) != NodeKind::Binary {
1667 return None;
1668 }
1669 let op = lang.bin_op(id)?;
1670 let ch = lang.children(id);
1671 let (l, r) = (*ch.first()?, *ch.get(1)?);
1672 let int_lit = |n: L::Id, v: i64| lang.literal(n).and_then(|x| x.as_int()) == Some(v);
1673
1674 if matches!(op, BinOp::Shl | BinOp::Shr | BinOp::UShr) && int_lit(r, 0) {
1676 return Some(Edit::Replace {
1677 target: id,
1678 with: l,
1679 });
1680 }
1681 if op == BinOp::Mul {
1683 let (zero, other) = if int_lit(l, 0) {
1684 (l, r)
1685 } else if int_lit(r, 0) {
1686 (r, l)
1687 } else {
1688 return None;
1689 };
1690 if !lang.is_exact_int(other) || lang.effect(other) > Effect::MayRead {
1691 return None;
1692 }
1693 return Some(Edit::Replace {
1694 target: id,
1695 with: zero,
1696 });
1697 }
1698 if op == BinOp::Sub
1700 && lang.kind(l) == NodeKind::VarRef
1701 && lang.kind(r) == NodeKind::VarRef
1702 && lang.var_name(l) == lang.var_name(r)
1703 && lang.is_local_var(l)
1704 && lang.is_exact_int(l)
1705 {
1706 let with = lang.build_int(0, false);
1707 return Some(Edit::Replace {
1708 target: id,
1709 with,
1710 });
1711 }
1712 None
1713 }
1714}
1715
1716pub struct InverseAssignPair;
1739
1740impl<L: Lang> Rule<L> for InverseAssignPair {
1741 fn name(&self) -> &'static str {
1742 "inverse_assign_pair"
1743 }
1744 fn kinds(&self) -> &'static [NodeKind] {
1745 &[NodeKind::Block]
1746 }
1747 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1748 let RewriteCtx { lang, .. } = ctx;
1749 let ch = lang.children(id).to_vec();
1750 if ch.len() < 2 {
1751 return None;
1752 }
1753 for w in 0..ch.len() - 1 {
1754 let (s1, s2) = (ch[w], ch[w + 1]);
1755 let Some(a1) = norm_assign(lang, s1) else { continue };
1757 let Some(a2) = norm_assign(lang, s2) else { continue };
1758 if a1.key != a2.key || !a1.local || !a2.local {
1760 continue;
1761 }
1762 if !a1.exact_int || !a2.exact_int {
1764 continue;
1765 }
1766 if a1.k != a2.k {
1768 continue;
1769 }
1770 let inverse = match (a1.op, a2.op) {
1771 (BinOp::Add, BinOp::Sub) | (BinOp::Sub, BinOp::Add) => true,
1772 (BinOp::BitXor, BinOp::BitXor) => true,
1773 _ => false,
1774 };
1775 if !inverse {
1776 continue;
1777 }
1778 return Some(Edit::Multi(vec![
1780 Edit::Delete { node: s2 },
1781 Edit::Delete { node: s1 },
1782 ]));
1783 }
1784 None
1785 }
1786}
1787
1788struct NormAssign<K> {
1792 key: K,
1793 op: BinOp,
1794 k: i64,
1795 local: bool,
1796 exact_int: bool,
1798}
1799
1800fn norm_assign<L: Lang>(lang: &L, stmt: L::Id) -> Option<NormAssign<L::NameKey>> {
1801 let a = match lang.kind(stmt) {
1803 NodeKind::ExprStmt => {
1804 let c = lang.children(stmt);
1805 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
1806 c[0]
1807 } else {
1808 return None;
1809 }
1810 }
1811 NodeKind::Assign => stmt,
1812 _ => return None,
1813 };
1814 let ch = lang.children(a).to_vec();
1815 if ch.len() != 2 {
1816 return None;
1817 }
1818 let (target, rhs) = (ch[0], ch[1]);
1819 if lang.kind(target) != NodeKind::VarRef {
1820 return None;
1821 }
1822 let key = lang.var_key(target)?;
1823 let local = lang.is_local_var(target);
1824 let exact_int = lang.is_exact_int(target);
1825 match lang.assign_op(a) {
1829 Some(op) => {
1831 let k = lang.literal(rhs).and_then(|x| x.as_int())?;
1832 Some(NormAssign { key, op, k, local, exact_int })
1833 }
1834 None => {
1836 if lang.kind(rhs) != NodeKind::Binary {
1837 return None;
1838 }
1839 let op = lang.bin_op(rhs)?;
1840 let rc = lang.children(rhs).to_vec();
1841 if rc.len() != 2 {
1842 return None;
1843 }
1844 if lang.kind(rc[0]) != NodeKind::VarRef || lang.var_key(rc[0]) != Some(key) {
1846 return None;
1847 }
1848 let k = lang.literal(rc[1]).and_then(|x| x.as_int())?;
1849 Some(NormAssign { key, op, k, local, exact_int })
1850 }
1851 }
1852}
1853
1854pub struct DeadStore;
1855
1856impl<L: Lang> Rule<L> for DeadStore {
1857 fn name(&self) -> &'static str {
1858 "dead_store"
1859 }
1860 fn kinds(&self) -> &'static [NodeKind] {
1861 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
1862 }
1863 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1864 let RewriteCtx { lang, walk } = ctx;
1865 let (decl_form, first_assign) = match lang.kind(id) {
1867 NodeKind::VarDecl => {
1868 if lang.children(id).is_empty() {
1869 return None; }
1871 (true, None)
1872 }
1873 NodeKind::Assign => (false, Some(id)),
1874 NodeKind::ExprStmt => {
1875 let ch = lang.children(id);
1876 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
1877 (false, Some(ch[0]))
1878 } else {
1879 return None;
1880 }
1881 }
1882 _ => return None,
1883 };
1884 let first_value = if decl_form {
1885 *lang.children(id).first()?
1886 } else {
1887 let a = first_assign?;
1888 if lang.assign_op(a).is_some() {
1889 return None; }
1891 *lang.children(a).get(1)?
1892 };
1893
1894 let parent = walk.parent(id)?;
1896 if lang.kind(parent) != NodeKind::Block {
1897 return None;
1898 }
1899 let idx = walk.index(id)?;
1900 let stmts = lang.children(parent).to_vec();
1901 let next_stmt = *stmts.get(idx + 1)?;
1902
1903 let second = match lang.kind(next_stmt) {
1906 NodeKind::Assign => Some(next_stmt),
1907 NodeKind::ExprStmt => {
1908 let ch = lang.children(next_stmt);
1909 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
1910 Some(ch[0])
1911 } else {
1912 None
1913 }
1914 }
1915 _ => None,
1916 };
1917 let Some(second) = second else {
1918 let (name_node, target_local) = if decl_form {
1927 (id, true)
1928 } else {
1929 let t = *lang.children(first_assign?).first()?;
1930 if lang.kind(t) != NodeKind::VarRef {
1931 return None;
1932 }
1933 (t, lang.is_local_var(t))
1934 };
1935 if !target_local {
1937 return None;
1938 }
1939 let name = lang.var_name(name_node)?.to_string();
1940 let declared_in_this_block = if decl_form {
1941 true } else if parent == walk.root {
1943 true
1946 } else {
1947 stmts[..idx].iter().any(|&s| {
1950 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
1951 })
1952 };
1953 if !declared_in_this_block {
1954 return None;
1955 }
1956 let name_key0 = lang.var_key(name_node)?;
1957 let mut w0 = Watch::<L>::new(name_key0, &[name_key0]);
1958 for &s in &stmts[idx + 1..] {
1959 scan_region(&*lang, s, &mut w0);
1960 }
1961 if w0.opaque || !w0.uses.is_empty() || w0.wrote(name_key0) || w0.shadowed {
1963 return None;
1964 }
1965 for &s in &stmts[idx + 1..] {
1969 if subtree_contains(&*lang, s, |n| {
1970 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
1971 }) {
1972 return None;
1973 }
1974 }
1975 if lang.effect(first_value) > Effect::MayRead {
1976 return None;
1977 }
1978 return Some(Edit::Delete { node: id });
1979 };
1980 if lang.assign_op(second).is_some() {
1981 return None;
1982 }
1983 let nch = lang.children(second);
1984 let (target, value) = (*nch.first()?, *nch.get(1)?);
1985 if lang.kind(target) != NodeKind::VarRef {
1986 return None;
1987 }
1988
1989 let name = if decl_form {
1991 if lang.var_name(id) != lang.var_name(target) {
1992 return None;
1993 }
1994 lang.var_name(id).map(|s| s.to_string())
1995 } else {
1996 let a = first_assign?;
1997 let t = *lang.children(a).first()?;
1998 if lang.kind(t) != NodeKind::VarRef {
1999 return None;
2000 }
2001 if lang.var_name(t) != lang.var_name(target) || !lang.is_local_var(t) {
2002 return None;
2003 }
2004 lang.var_name(t).map(|s| s.to_string())
2005 }?;
2006 if subtree_contains(&*lang, value, |n| {
2008 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2009 }) {
2010 return None;
2011 }
2012 if lang.effect(first_value) > Effect::MayRead {
2014 return None;
2015 }
2016
2017 if decl_form {
2018 Some(Edit::Multi(vec![
2020 Edit::Replace {
2021 target: first_value,
2022 with: value,
2023 },
2024 Edit::Delete {
2025 node: next_stmt,
2026 },
2027 ]))
2028 } else {
2029 Some(Edit::Delete { node: id })
2031 }
2032 }
2033}
2034
2035pub struct BlockFlatten;
2044
2045impl<L: Lang> Rule<L> for BlockFlatten {
2046 fn name(&self) -> &'static str {
2047 "block_flatten"
2048 }
2049 fn kinds(&self) -> &'static [NodeKind] {
2050 &[NodeKind::Block]
2051 }
2052 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2053 let RewriteCtx { lang, walk } = ctx;
2054 let parent = walk.parent(id)?;
2055 if lang.kind(parent) != NodeKind::Block {
2056 return None;
2057 }
2058 let inner = lang.children(id).to_vec();
2060 let declares = |n: L::Id| -> bool {
2061 matches!(
2062 lang.kind(n),
2063 NodeKind::VarDecl | NodeKind::ForEach | NodeKind::Catch
2064 )
2065 };
2066 if inner.iter().any(|&s| declares(s)) {
2067 return None;
2068 }
2069 if lang.has_pattern_bindings(id) {
2075 return None;
2076 }
2077 let idx = walk.index(id)?;
2078 Some(Edit::Splice {
2079 node: parent,
2080 index: idx,
2081 remove: 1,
2082 insert: inner,
2083 })
2084 }
2085}
2086
2087pub struct DoubleNegFold;
2090
2091impl<L: Lang> Rule<L> for DoubleNegFold {
2092 fn name(&self) -> &'static str {
2093 "double_neg_fold"
2094 }
2095 fn kinds(&self) -> &'static [NodeKind] {
2096 &[NodeKind::Unary]
2097 }
2098 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2099 let RewriteCtx { lang, .. } = ctx;
2100 if lang.un_op(id) != Some(UnOp::Neg) {
2101 return None;
2102 }
2103 let inner = *lang.children(id).first()?;
2104 if lang.un_op(inner) == Some(UnOp::Neg) {
2105 let x = *lang.children(inner).first()?;
2106 return Some(Edit::Replace {
2107 target: id,
2108 with: x,
2109 });
2110 }
2111 if let Some((v, wide)) = lang.literal(inner).and_then(|l| match l {
2114 LitRef::Int(v) => Some((v, false)),
2115 LitRef::Long(v) => Some((v, true)),
2116 _ => None,
2117 }) {
2118 if v < 0 {
2119 let n = lang.fold_lit_neg(v, wide)?;
2120 let with = lang.build_int(n, wide);
2121 return Some(Edit::Replace {
2122 target: id,
2123 with,
2124 });
2125 }
2126 }
2127 None
2128 }
2129}
2130
2131pub struct UnreachableAfterTerminal;
2132
2133impl<L: Lang> Rule<L> for UnreachableAfterTerminal {
2134 fn name(&self) -> &'static str {
2135 "unreachable_after_terminal"
2136 }
2137 fn kinds(&self) -> &'static [NodeKind] {
2138 &[NodeKind::Block]
2139 }
2140 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2141 let RewriteCtx { lang, walk: _ } = ctx;
2142 if lang.kind(id) != NodeKind::Block {
2143 return None;
2144 }
2145 let children = lang.children(id).to_vec();
2146 let terminal = |n: L::Id| -> bool {
2147 matches!(
2148 lang.kind(n),
2149 NodeKind::Return | NodeKind::Throw | NodeKind::Break | NodeKind::Continue
2150 )
2151 };
2152 let mut cut: Option<(usize, usize)> = None;
2153 for (i, &s) in children.iter().enumerate() {
2154 if terminal(s) && i + 1 < children.len() {
2155 cut = Some((i + 1, children.len() - i - 1));
2156 break;
2157 }
2158 }
2159 let (index, remove) = cut?;
2160 Some(Edit::Splice {
2161 node: id,
2162 index,
2163 remove,
2164 insert: Vec::new(),
2165 })
2166 }
2167}
2168
2169
2170pub struct CmpConstFold;
2177
2178impl<L: Lang> Rule<L> for CmpConstFold {
2179 fn name(&self) -> &'static str {
2180 "cmp_const_fold"
2181 }
2182 fn kinds(&self) -> &'static [NodeKind] {
2183 &[NodeKind::Binary]
2184 }
2185 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2186 let RewriteCtx { lang, walk: _ } = ctx;
2187 if lang.kind(id) != NodeKind::Binary {
2188 return None;
2189 }
2190 let op = lang.bin_op(id)?;
2191 if !op.is_comparison() {
2192 return None;
2193 }
2194 let ch = lang.children(id);
2195 let (l, r) = (*ch.first()?, *ch.get(1)?);
2196 lang.literal(l)?;
2199 lang.literal(r)?;
2200 let v = lang.fold_lit_cmp(op, l, r)?;
2201 let with = lang.build_bool(v);
2202 Some(Edit::Replace {
2203 target: id,
2204 with,
2205 })
2206 }
2207}
2208
2209pub struct BitIdentity;
2217
2218impl<L: Lang> Rule<L> for BitIdentity {
2219 fn name(&self) -> &'static str {
2220 "bit_identity"
2221 }
2222 fn kinds(&self) -> &'static [NodeKind] {
2223 &[NodeKind::Binary]
2224 }
2225 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2226 let RewriteCtx { lang, walk: _ } = ctx;
2227 if lang.kind(id) != NodeKind::Binary {
2228 return None;
2229 }
2230 let op = lang.bin_op(id)?;
2231 if !matches!(op, BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor) {
2232 return None;
2233 }
2234 let ch = lang.children(id);
2235 let (l, r) = (*ch.first()?, *ch.get(1)?);
2236 let int_lit = |n: L::Id| lang.literal(n).and_then(|x| x.as_int());
2237
2238 if op == BinOp::BitXor
2240 && lang.kind(l) == NodeKind::VarRef
2241 && lang.kind(r) == NodeKind::VarRef
2242 && lang.var_name(l) == lang.var_name(r)
2243 && lang.is_local_var(l)
2244 && lang.is_exact_int(l)
2245 {
2246 let with = lang.build_int(0, false);
2247 return Some(Edit::Replace {
2248 target: id,
2249 with,
2250 });
2251 }
2252
2253 for (x, k) in [(l, r), (r, l)] {
2255 let Some(kv) = int_lit(k) else {
2256 continue;
2257 };
2258 if !lang.is_exact_int(x) {
2259 continue;
2260 }
2261 let identity = match (op, kv) {
2262 (BinOp::BitXor, 0) => Some(x), (BinOp::BitOr, 0) => Some(x), (BinOp::BitAnd, -1) => Some(x), (BinOp::BitAnd, 0) | (BinOp::BitOr, -1) => {
2266 if lang.effect(x) > Effect::MayRead {
2268 None
2269 } else {
2270 Some(k)
2271 }
2272 }
2273 _ => None,
2274 };
2275 if let Some(with) = identity {
2276 return Some(Edit::Replace {
2277 target: id,
2278 with,
2279 });
2280 }
2281 }
2282 None
2283 }
2284}
2285
2286pub struct ArithReassoc;
2292
2293impl<L: Lang> Rule<L> for ArithReassoc {
2294 fn name(&self) -> &'static str {
2295 "arith_reassoc"
2296 }
2297 fn kinds(&self) -> &'static [NodeKind] {
2298 &[NodeKind::Binary]
2299 }
2300 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2301 let RewriteCtx { lang, walk: _ } = ctx;
2302 if lang.kind(id) != NodeKind::Binary {
2303 return None;
2304 }
2305 let op2 = lang.bin_op(id)?;
2306 if !matches!(op2, BinOp::Add | BinOp::Sub | BinOp::BitXor) {
2307 return None;
2308 }
2309 let och = lang.children(id).to_vec();
2310 let inner = och[0];
2311 if lang.kind(inner) != NodeKind::Binary {
2312 return None;
2313 }
2314 let op1 = lang.bin_op(inner)?;
2315 let ich = lang.children(inner).to_vec();
2316 let x = ich[0];
2317
2318 if op1 == BinOp::Add && op2 == BinOp::Add {
2324 if let (Some(LitRef::Str(k1)), Some(LitRef::Str(k2))) =
2325 (lang.literal(ich[1]), lang.literal(och[1]))
2326 {
2327 let mut chain: Vec<L::Id> = vec![id];
2329 let mut cur = id;
2330 loop {
2331 let cch = lang.children(cur).to_vec();
2332 let next = cch[0];
2333 let deeper = lang.kind(next) == NodeKind::Binary
2334 && lang.bin_op(next) == Some(BinOp::Add)
2335 && matches!(lang.literal(lang.children(next).to_vec()[1]),
2336 Some(LitRef::Str(_)));
2337 if deeper {
2338 chain.push(next);
2339 cur = next;
2340 } else {
2341 break;
2342 }
2343 }
2344 let innermost = *chain.last().unwrap();
2346 let ich2 = lang.children(innermost).to_vec();
2347 let base = ich2[0];
2348 let mut joined = String::new();
2349 for &n in chain.iter().rev() {
2350 if let Some(LitRef::Str(k)) =
2351 lang.literal(lang.children(n).to_vec()[1])
2352 {
2353 joined.push_str(k);
2354 }
2355 }
2356 let _ = (k1, k2); let lit = lang.build_str(&joined);
2358 let with = lang.build_bin(BinOp::Add, base, lit);
2359 return Some(Edit::Replace {
2360 target: id,
2361 with,
2362 });
2363 }
2364 }
2365
2366 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2370 let k2 = lang.literal(och[1]).and_then(|x| x.as_int())?;
2371 if !lang.is_exact_int(x) {
2372 return None;
2373 }
2374 let is_long = matches!(lang.literal(ich[1]), Some(LitRef::Long(_)))
2375 || matches!(lang.literal(och[1]), Some(LitRef::Long(_)));
2376 let with = match lang.reassoc_delta(op1, k1, op2, k2, is_long)? {
2377 ReassocOutcome::Neutral => x,
2378 ReassocOutcome::Lit(lit, op) => lang.build_bin(op, x, lit),
2379 };
2380 Some(Edit::Replace {
2381 target: id,
2382 with,
2383 })
2384 }
2385}
2386
2387pub struct IfToTernary;
2396
2397impl<L: Lang> Rule<L> for IfToTernary {
2398 fn name(&self) -> &'static str {
2399 "if_to_ternary"
2400 }
2401 fn kinds(&self) -> &'static [NodeKind] {
2402 &[NodeKind::If]
2403 }
2404 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2405 let RewriteCtx { lang, walk } = ctx;
2406 if lang.kind(id) != NodeKind::If {
2407 return None;
2408 }
2409 let ch = lang.children(id).to_vec();
2410 let c = ch[0];
2411
2412 let single_ret = |n: L::Id| -> Option<Option<L::Id>> {
2414 match lang.kind(n) {
2415 NodeKind::Return => Some(lang.children(n).first().copied()),
2416 NodeKind::Block if lang.children(n).len() == 1 => {
2417 let inner = lang.children(n)[0];
2418 if lang.kind(inner) == NodeKind::Return {
2419 Some(lang.children(inner).first().copied())
2420 } else {
2421 None
2422 }
2423 }
2424 _ => None,
2425 }
2426 };
2427
2428 if ch.len() == 3 {
2429 let a = single_ret(ch[1])?;
2431 let b = single_ret(ch[2])?;
2432 let with = match (a, b) {
2433 (Some(va), Some(vb)) => {
2434 let t = lang.build_ternary(c, va, vb);
2435 lang.build_return(Some(t))
2436 }
2437 (None, None) => lang.build_return(None),
2438 _ => return None,
2439 };
2440 return Some(Edit::Replace {
2441 target: id,
2442 with,
2443 });
2444 }
2445
2446 if ch.len() != 2 {
2448 return None;
2449 }
2450 let a = single_ret(ch[1])?;
2451 let parent = walk.parent(id)?;
2452 if lang.kind(parent) != NodeKind::Block {
2453 return None;
2454 }
2455 let idx = walk.index(id)?;
2456 let stmts = lang.children(parent).to_vec();
2457 if idx + 2 != stmts.len() {
2458 return None; }
2460 let tail = stmts[idx + 1];
2461 if lang.kind(tail) != NodeKind::Return {
2462 return None;
2463 }
2464 let b = lang.children(tail).first().copied();
2465 let with = match (a, b) {
2466 (Some(va), Some(vb)) => {
2467 let t = lang.build_ternary(c, va, vb);
2468 lang.build_return(Some(t))
2469 }
2470 (None, None) => lang.build_return(None),
2471 _ => return None,
2472 };
2473 Some(Edit::Splice {
2474 node: parent,
2475 index: idx,
2476 remove: 2,
2477 insert: vec![with],
2478 })
2479 }
2480}
2481
2482pub struct IfAssignTernary;
2489
2490impl<L: Lang> Rule<L> for IfAssignTernary {
2491 fn name(&self) -> &'static str {
2492 "if_assign_ternary"
2493 }
2494 fn kinds(&self) -> &'static [NodeKind] {
2495 &[NodeKind::If]
2496 }
2497 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2498 let RewriteCtx { lang, walk: _ } = ctx;
2499 if lang.kind(id) != NodeKind::If {
2500 return None;
2501 }
2502 let ch = lang.children(id).to_vec();
2503 if ch.len() != 3 {
2504 return None;
2505 }
2506 let c = ch[0];
2507 let single_assign = |n: L::Id| -> Option<(L::Id, L::Id)> {
2509 let unwrap = |i: L::Id| -> L::Id {
2510 if lang.kind(i) == NodeKind::ExprStmt {
2511 let ch = lang.children(i);
2512 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2513 return ch[0];
2514 }
2515 }
2516 i
2517 };
2518 let inner = match lang.kind(n) {
2519 NodeKind::Assign => n,
2520 NodeKind::ExprStmt => unwrap(n),
2521 NodeKind::Block if lang.children(n).len() == 1 => unwrap(lang.children(n)[0]),
2522 _ => return None,
2523 };
2524 if lang.kind(inner) != NodeKind::Assign {
2525 return None;
2526 }
2527 if lang.assign_op(inner).is_some() {
2528 return None;
2529 }
2530 let a = lang.children(inner).to_vec();
2531 if lang.kind(a[0]) != NodeKind::VarRef {
2532 return None;
2533 }
2534 Some((a[0], a[1]))
2535 };
2536 let (t1, va) = single_assign(ch[1])?;
2537 let (t2, vb) = single_assign(ch[2])?;
2538 if lang.var_name(t1) != lang.var_name(t2) || lang.var_name(t1).is_none() {
2539 return None;
2540 }
2541 let t = lang.build_ternary(c, va, vb);
2542 let with = lang.build_assign(t1, t);
2543 Some(Edit::Replace {
2544 target: id,
2545 with,
2546 })
2547 }
2548}
2549
2550
2551pub struct DeclAssignMerge;
2559
2560impl<L: Lang> Rule<L> for DeclAssignMerge {
2561 fn name(&self) -> &'static str {
2562 "decl_assign_merge"
2563 }
2564 fn kinds(&self) -> &'static [NodeKind] {
2565 &[NodeKind::VarDecl]
2566 }
2567 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2568 let RewriteCtx { lang, walk } = ctx;
2569 if lang.kind(id) != NodeKind::VarDecl || !lang.children(id).is_empty() {
2570 return None; }
2572 let name = lang.var_name(id)?.to_string();
2573 let parent = walk.parent(id)?;
2574 if lang.kind(parent) != NodeKind::Block {
2575 return None;
2576 }
2577 let idx = walk.index(id)?;
2578 let stmts = lang.children(parent).to_vec();
2579 let next = *stmts.get(idx + 1)?;
2580 let assign = match lang.kind(next) {
2582 NodeKind::Assign => next,
2583 NodeKind::ExprStmt => {
2584 let ch = lang.children(next);
2585 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2586 ch[0]
2587 } else {
2588 return None;
2589 }
2590 }
2591 _ => return None,
2592 };
2593 if lang.assign_op(assign).is_some() {
2594 return None;
2595 }
2596 let ach = lang.children(assign).to_vec();
2597 let (target, value) = (ach[0], ach[1]);
2598 if lang.kind(target) != NodeKind::VarRef || lang.var_name(target) != Some(name.as_str()) {
2599 return None;
2600 }
2601 if subtree_contains(&*lang, value, |n| {
2603 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2604 }) {
2605 return None;
2606 }
2607 Some(Edit::Multi(vec![
2608 Edit::Splice {
2609 node: id,
2610 index: 0,
2611 remove: 0,
2612 insert: vec![value],
2613 },
2614 Edit::Delete { node: next },
2615 ]))
2616 }
2617}
2618
2619pub struct AssignPropagation;
2628
2629impl<L: Lang> Rule<L> for AssignPropagation {
2630 fn name(&self) -> &'static str {
2631 "assign_propagation"
2632 }
2633 fn kinds(&self) -> &'static [NodeKind] {
2634 &[NodeKind::Assign, NodeKind::ExprStmt]
2635 }
2636 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2637 let RewriteCtx { lang, walk } = ctx;
2638 let (assign, stmt_node) = match lang.kind(id) {
2640 NodeKind::Assign => (id, id),
2641 NodeKind::ExprStmt => {
2642 let ch = lang.children(id);
2643 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2644 (ch[0], id)
2645 } else {
2646 return None;
2647 }
2648 }
2649 _ => return None,
2650 };
2651 if lang.assign_op(assign).is_some() {
2652 return None;
2653 }
2654 let ach = lang.children(assign).to_vec();
2655 let (target, value) = (ach[0], ach[1]);
2656 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
2657 return None;
2658 }
2659 if lang.kind(value) == NodeKind::ArrayLit {
2661 return None;
2662 }
2663 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
2665 return None;
2666 }
2667 let name = lang.var_name(target)?.to_string();
2668
2669 let parent = walk.parent(stmt_node)?;
2670 if lang.kind(parent) != NodeKind::Block {
2671 return None;
2672 }
2673 let idx = walk.index(stmt_node)?;
2674 let stmts = lang.children(parent).to_vec();
2675
2676 let anchored = stmts[..idx].iter().any(|&s| {
2678 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2679 });
2680 if !anchored {
2681 return None;
2682 }
2683
2684 if subtree_contains(&*lang, value, |n| {
2686 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2687 }) {
2688 return None;
2689 }
2690
2691 let name_key = lang.var_key(target)?;
2694 if count_name_refs(&*lang, &stmts[idx + 1..], &name) != 1 {
2695 return None;
2696 }
2697 let mut wa = Watch::<L>::new(name_key, &[]);
2698 for &s in &stmts[idx + 1..] {
2699 scan_region(&*lang, s, &mut wa);
2700 }
2701 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
2702 return None;
2703 }
2704 let use_id = wa.uses[0];
2705 if matches!(lang.literal(value), Some(LitRef::Null))
2712 && null_unsafe_position(&*lang, walk, use_id)
2713 {
2714 return None;
2715 }
2716 if lang.kind(value) == NodeKind::VarRef
2719 && typed_position_unsafe(&*lang, walk, use_id)
2720 {
2721 return None;
2722 }
2723 let mut watch_keys: Vec<L::NameKey> = Vec::new();
2725 collect_read_keys(&*lang, value, &mut watch_keys);
2726 watch_keys.push(name_key);
2727 let mut wb = Watch::<L>::new(name_key, &watch_keys);
2728 for &s in &stmts[idx + 1..] {
2729 let uses_before = wb.uses.len();
2730 scan_region(&*lang, s, &mut wb);
2731 if uses_before < wb.uses.len() {
2732 break; }
2734 }
2735 if wb.wrote(name_key) {
2737 return None;
2738 }
2739 let ve = lang.effect(value);
2740
2741 if ve <= Effect::MayRead {
2742 if let Some(target_key) = use_stmt_target_write_key(&*lang, &stmts[idx + 1..], use_id) {
2743 wb.clear_wrote(target_key);
2744 }
2745 if wb.any_write() {
2746 return None;
2747 }
2748 return Some(Edit::Multi(vec![
2749 Edit::Replace {
2750 target: use_id,
2751 with: value,
2752 },
2753 Edit::Delete { node: stmt_node },
2754 ]));
2755 }
2756
2757 if idx + 1 >= stmts.len() {
2759 return None;
2760 }
2761 let stmt = stmts[idx + 1];
2762 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
2763 return None;
2764 }
2765 match lang.kind(stmt) {
2766 NodeKind::Return
2767 | NodeKind::ExprStmt
2768 | NodeKind::Assign
2769 | NodeKind::Throw
2770 | NodeKind::VarDecl => {}
2771 _ => return None,
2772 }
2773 if !straight_path(&*lang, stmt, use_id) {
2774 return None;
2775 }
2776 if !prefix_effects_readable(&*lang, stmt, use_id) {
2777 return None;
2778 }
2779 Some(Edit::Multi(vec![
2780 Edit::Replace {
2781 target: use_id,
2782 with: value,
2783 },
2784 Edit::Delete { node: stmt_node },
2785 ]))
2786 }
2787}
2788
2789
2790fn use_stmt_target_write_key<L: Lang>(
2796 lang: &L,
2797 stmts_after: &[L::Id],
2798 use_id: L::Id,
2799) -> Option<L::NameKey> {
2800 for &s in stmts_after {
2801 if subtree_contains(lang, s, |n| n == use_id) {
2802 let assign = match lang.kind(s) {
2803 NodeKind::Assign => s,
2804 NodeKind::ExprStmt => {
2805 let ch = lang.children(s);
2806 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2807 ch[0]
2808 } else {
2809 return None;
2810 }
2811 }
2812 _ => return None,
2813 };
2814 if lang.assign_op(assign).is_some() {
2815 return None;
2816 }
2817 let ch = lang.children(assign);
2818 if ch.len() == 2 && ch[1] == use_id && lang.kind(ch[0]) == NodeKind::VarRef {
2819 return lang.var_key(ch[0]);
2820 }
2821 return None;
2822 }
2823 }
2824 None
2825}
2826
2827fn collect_read_keys<L: Lang>(lang: &L, id: L::Id, out: &mut Vec<L::NameKey>) {
2830 let mut stack = vec![id];
2831 while let Some(n) = stack.pop() {
2832 if lang.kind(n) == NodeKind::VarRef {
2833 if let Some(k) = lang.var_key(n) {
2834 if !out.contains(&k) {
2835 out.push(k);
2836 }
2837 }
2838 }
2839 for &c in lang.children(n) {
2840 stack.push(c);
2841 }
2842 }
2843}
2844
2845
2846pub struct TrailingContinue;
2853
2854impl<L: Lang> Rule<L> for TrailingContinue {
2855 fn name(&self) -> &'static str {
2856 "trailing_continue"
2857 }
2858 fn kinds(&self) -> &'static [NodeKind] {
2859 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
2860 }
2861 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2862 let RewriteCtx { lang, walk: _ } = ctx;
2863 let body = match lang.kind(id) {
2865 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
2866 *lang.children(id).last()?
2867 }
2868 _ => return None,
2869 };
2870 if lang.kind(body) != NodeKind::Block {
2871 return None;
2872 }
2873 let ch = lang.children(body).to_vec();
2874 let last = *ch.last()?;
2875 if lang.kind(last) != NodeKind::Continue || !lang.children(last).is_empty() {
2876 if lang.kind(last) != NodeKind::Continue {
2879 return None;
2880 }
2881 }
2882 Some(Edit::Delete { node: last })
2883 }
2884}
2885
2886pub struct MultiUseCopyPropagation;
2894
2895impl<L: Lang> Rule<L> for MultiUseCopyPropagation {
2896 fn name(&self) -> &'static str {
2897 "multi_use_copy"
2898 }
2899 fn kinds(&self) -> &'static [NodeKind] {
2900 &[NodeKind::Assign, NodeKind::ExprStmt]
2901 }
2902 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2903 let RewriteCtx { lang, walk } = ctx;
2904 let (assign, stmt_node) = match lang.kind(id) {
2906 NodeKind::Assign => (id, id),
2907 NodeKind::ExprStmt => {
2908 let ch = lang.children(id);
2909 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2910 (ch[0], id)
2911 } else {
2912 return None;
2913 }
2914 }
2915 _ => return None,
2916 };
2917 if lang.assign_op(assign).is_some() {
2918 return None;
2919 }
2920 let ach = lang.children(assign).to_vec();
2921 let (target, value) = (ach[0], ach[1]);
2922 if lang.kind(target) != NodeKind::VarRef || lang.kind(value) != NodeKind::VarRef {
2923 return None;
2924 }
2925 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
2927 return None;
2928 }
2929 if !lang.is_local_var(target) {
2930 return None;
2931 }
2932 let name = lang.var_name(target)?.to_string();
2933 let src = lang.var_name(value)?.to_string();
2934 if name == src {
2935 return None;
2936 }
2937
2938 let parent = walk.parent(stmt_node)?;
2939 if lang.kind(parent) != NodeKind::Block {
2940 return None;
2941 }
2942 let idx = walk.index(stmt_node)?;
2943 let stmts = lang.children(parent).to_vec();
2944
2945 let anchored = stmts[..idx].iter().any(|&s| {
2947 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2948 });
2949 if !anchored {
2950 return None;
2951 }
2952
2953 let name_key = lang.var_key(target)?;
2955 let src_key = lang.var_key(value)?;
2956 let mut w1 = Watch::<L>::new(name_key, &[name_key, src_key]);
2957 for &s in &stmts[idx + 1..] {
2958 scan_region(&*lang, s, &mut w1);
2959 }
2960 if w1.opaque || w1.shadowed || w1.uses.is_empty() {
2961 return None;
2962 }
2963 if w1.wrote(name_key) || w1.wrote(src_key) {
2964 return None;
2965 }
2966 let synth = count_name_refs(&*lang, &stmts[idx + 1..], &name);
2972 if synth != w1.uses.len() {
2973 return None;
2974 }
2975 if w1.uses
2984 .iter()
2985 .any(|&u| typed_position_unsafe(&*lang, walk, u))
2986 {
2987 return None;
2988 }
2989
2990 let mut edits: Vec<Edit<L>> = w1.uses
2991 .iter()
2992 .map(|&u| Edit::Replace {
2993 target: u,
2994 with: value,
2995 })
2996 .collect();
2997 edits.push(Edit::Delete { node: stmt_node });
2998 Some(Edit::Multi(edits))
2999 }
3000}
3001
3002
3003pub struct TrailingReturn;
3010
3011impl<L: Lang> Rule<L> for TrailingReturn {
3012 fn name(&self) -> &'static str {
3013 "trailing_return"
3014 }
3015 fn kinds(&self) -> &'static [NodeKind] {
3016 &[NodeKind::Return]
3017 }
3018 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3019 let root = ctx.root();
3020 let parent = ctx.parent(id)?;
3021 let lang = ctx.lang;
3022 if lang.kind(id) != NodeKind::Return || !lang.children(id).is_empty() {
3023 return None;
3024 }
3025 if parent != root || lang.kind(parent) != NodeKind::Block {
3026 return None;
3027 }
3028 if lang.children(parent).last() != Some(&id) {
3029 return None;
3030 }
3031 Some(Edit::Delete { node: id })
3032 }
3033}
3034
3035
3036pub struct TernaryBoolOp;
3043
3044impl<L: Lang> Rule<L> for TernaryBoolOp {
3045 fn name(&self) -> &'static str {
3046 "ternary_bool_op"
3047 }
3048 fn kinds(&self) -> &'static [NodeKind] {
3049 &[NodeKind::Ternary]
3050 }
3051 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3052 let RewriteCtx { lang, walk: _ } = ctx;
3053 if lang.kind(id) != NodeKind::Ternary {
3054 return None;
3055 }
3056 let ch = lang.children(id);
3057 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
3058 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
3059 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
3060 let with = if is_false(b) && lang.is_bool(a) {
3061 lang.build_bin(BinOp::And, c, a)
3062 } else if lb(b) && lang.is_bool(a) {
3063 return None;
3065 } else if lb(a) && lang.is_bool(b) {
3066 lang.build_bin(BinOp::Or, c, b)
3068 } else if is_false(a) && lang.is_bool(b) {
3069 return None;
3071 } else {
3072 return None;
3073 };
3074 Some(Edit::Replace {
3075 target: id,
3076 with,
3077 })
3078 }
3079}
3080
3081
3082pub struct StoreKill;
3091
3092enum KillEvent<L: Lang> {
3096 Read,
3098 DomWrite(L::Id),
3100 Shadow,
3102}
3103
3104fn scan_kill_event<L: Lang>(
3108 lang: &L,
3109 node: L::Id,
3110 name: &str,
3111 dominating: bool,
3112) -> Option<KillEvent<L>> {
3113 match lang.kind(node) {
3114 NodeKind::VarRef => {
3115 if lang.var_name(node) == Some(name) {
3116 Some(KillEvent::Read)
3117 } else {
3118 None
3119 }
3120 }
3121 NodeKind::VarDecl => {
3122 if lang.var_name(node) == Some(name) {
3123 Some(KillEvent::Shadow)
3124 } else {
3125 for &c in lang.children(node) {
3127 if let Some(e) = scan_kill_event(lang, c, name, dominating) {
3128 return Some(e);
3129 }
3130 }
3131 None
3132 }
3133 }
3134 NodeKind::Assign => {
3135 let ch = lang.children(node);
3136 if let Some(&val) = ch.get(1) {
3138 if let Some(e) = scan_kill_event(lang, val, name, dominating) {
3139 if matches!(e, KillEvent::Read | KillEvent::Shadow) {
3140 return Some(e);
3141 }
3142 }
3143 }
3144 if lang.assign_op(node).is_none() {
3145 if let Some(&t) = ch.first() {
3146 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3147 if dominating {
3148 return Some(KillEvent::DomWrite(node));
3149 }
3150 return None; }
3152 }
3153 }
3154 None
3155 }
3156 NodeKind::If | NodeKind::While | NodeKind::Ternary => {
3157 let ch = lang.children(node);
3160 if let Some(&c) = ch.first() {
3161 if let Some(e) = scan_kill_event(lang, c, name, true) {
3162 return Some(e);
3163 }
3164 }
3165 for &c in &ch[1..] {
3166 if let Some(e) = scan_kill_event(lang, c, name, false) {
3167 if matches!(e, KillEvent::Read | KillEvent::Shadow) {
3168 return Some(e);
3169 }
3170 }
3171 }
3172 None
3173 }
3174 NodeKind::Binary => {
3175 let op = lang.bin_op(node)?;
3176 let ch = lang.children(node);
3177 if op.is_short_circuit() {
3178 if let Some(&l) = ch.first() {
3180 if let Some(e) = scan_kill_event(lang, l, name, true) {
3181 return Some(e);
3182 }
3183 }
3184 if let Some(&r) = ch.get(1) {
3185 if let Some(e) = scan_kill_event(lang, r, name, false) {
3186 if matches!(e, KillEvent::Read | KillEvent::Shadow) {
3187 return Some(e);
3188 }
3189 }
3190 }
3191 None
3192 } else {
3193 for &c in ch {
3194 if let Some(e) = scan_kill_event(lang, c, name, dominating) {
3195 return Some(e);
3196 }
3197 }
3198 None
3199 }
3200 }
3201 NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
3202 let ch = lang.children(node);
3206 let dom_len = match lang.kind(node) {
3207 NodeKind::For => ch.len().saturating_sub(2),
3208 NodeKind::ForEach | NodeKind::Switch => 1,
3209 _ => 0,
3210 };
3211 for &c in &ch[..dom_len] {
3212 if let Some(e) = scan_kill_event(lang, c, name, true) {
3213 return Some(e);
3214 }
3215 }
3216 for &c in &ch[dom_len..] {
3217 if let Some(e) = scan_kill_event(lang, c, name, false) {
3218 if matches!(e, KillEvent::Read | KillEvent::Shadow) {
3219 return Some(e);
3220 }
3221 }
3222 }
3223 None
3224 }
3225 _ => {
3226 for &c in lang.children(node) {
3227 if let Some(e) = scan_kill_event(lang, c, name, dominating) {
3228 return Some(e);
3229 }
3230 }
3231 None
3232 }
3233 }
3234}
3235
3236impl<L: Lang> Rule<L> for StoreKill {
3237 fn name(&self) -> &'static str {
3238 "store_kill"
3239 }
3240 fn kinds(&self) -> &'static [NodeKind] {
3241 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
3242 }
3243 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3244 let RewriteCtx { lang, walk } = ctx;
3245 let (is_decl, stmt_node, first_value) = match lang.kind(id) {
3247 NodeKind::VarDecl => {
3248 if lang.children(id).is_empty() {
3249 return None;
3250 }
3251 (true, id, *lang.children(id).first()?)
3252 }
3253 NodeKind::Assign => {
3254 if lang.assign_op(id).is_some() {
3255 return None;
3256 }
3257 (false, id, *lang.children(id).get(1)?)
3258 }
3259 NodeKind::ExprStmt => {
3260 let ch = lang.children(id);
3261 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3262 let a = ch[0];
3263 if lang.assign_op(a).is_some() {
3264 return None;
3265 }
3266 (false, id, *lang.children(a).get(1)?)
3267 } else {
3268 return None;
3269 }
3270 }
3271 _ => return None,
3272 };
3273 let name = if is_decl {
3274 lang.var_name(id)?.to_string()
3275 } else {
3276 let a = match lang.kind(id) {
3277 NodeKind::Assign => id,
3278 _ => lang.children(id)[0],
3279 };
3280 let t = *lang.children(a).first()?;
3281 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
3282 return None;
3283 }
3284 lang.var_name(t)?.to_string()
3285 };
3286
3287 let parent = walk.parent(stmt_node)?;
3288 if lang.kind(parent) != NodeKind::Block {
3289 return None;
3290 }
3291 let idx = walk.index(stmt_node)?;
3292 let stmts = lang.children(parent).to_vec();
3293
3294 let mut killed_by: Option<(usize, L::Id)> = None;
3296 for (si, &s) in stmts.iter().enumerate().skip(idx + 1) {
3297 match scan_kill_event(&*lang, s, &name, true) {
3298 Some(KillEvent::Read) | Some(KillEvent::Shadow) => return None,
3299 Some(KillEvent::DomWrite(assign)) => {
3300 killed_by = Some((si, assign));
3301 break;
3302 }
3303 None => continue,
3304 }
3305 }
3306 let (si, kill_assign) = killed_by?;
3307 {
3315 let kill_container = match walk.parents.get(&kill_assign) {
3316 Some(&(p, _)) if lang.kind(p) == NodeKind::ExprStmt => p,
3317 _ => kill_assign,
3318 };
3319 if kill_container != stmts[si] {
3320 return None;
3321 }
3322 }
3323 for &s in &stmts[idx + 1..si] {
3329 if lang.effect(s) > Effect::MayRead {
3330 return None;
3331 }
3332 }
3333
3334 if is_decl {
3335 if lang.effect(first_value) > Effect::MayRead {
3338 return None;
3339 }
3340 let value = *lang.children(kill_assign).get(1)?;
3341 if is_literal(lang, value) {
3342 let assign_stmt = stmts[si];
3343 return Some(Edit::Multi(vec![
3344 Edit::Splice {
3345 node: id,
3346 index: 0,
3347 remove: 1,
3348 insert: vec![value],
3349 },
3350 Edit::Delete { node: assign_stmt },
3351 ]));
3352 }
3353 if lang.is_inferred_decl(id) {
3357 return None;
3358 }
3359 if lang.effect(first_value) > Effect::MayRead {
3360 return None;
3361 }
3362 return Some(Edit::Splice {
3363 node: id,
3364 index: 0,
3365 remove: 1,
3366 insert: Vec::new(),
3367 });
3368 }
3369 if lang.effect(first_value) > Effect::MayRead {
3371 return None;
3372 }
3373 Some(Edit::Delete { node: stmt_node })
3374 }
3375}
3376
3377fn is_literal<L: Lang>(lang: &L, n: L::Id) -> bool {
3379 matches!(lang.kind(n), NodeKind::Literal)
3380}
3381
3382pub fn default_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3388 vec![
3389 Box::new(DoubleNegFold),
3390 Box::new(BlockFlatten),
3391 Box::new(ParenRemoval),
3392 Box::new(ConstCondition),
3393 Box::new(BooleanReturn),
3394 Box::new(IfToTernary),
3395 Box::new(IfAssignTernary),
3396 Box::new(IfElseEmpty),
3397 Box::new(BoolCompare),
3398 Box::new(DoubleNot),
3399 Box::new(BoolNotFold),
3400 Box::new(BoolShortCircuit),
3401 Box::new(NotCompare),
3402 Box::new(TernaryFold),
3403 Box::new(TernaryBool),
3404 Box::new(TernaryBoolOp),
3405 Box::new(ConstFoldBin),
3406 Box::new(CmpConstFold),
3407 Box::new(BitIdentity),
3408 Box::new(ArithReassoc),
3409 Box::new(SelfAssign),
3410 Box::new(ArithIdentity),
3411 Box::new(ArithZero),
3412 Box::new(DeclAssignMerge),
3413 Box::new(LocalPropagation),
3414 Box::new(AssignPropagation),
3415 Box::new(MultiUseCopyPropagation),
3416 Box::new(TrailingReturn),
3417 Box::new(InverseAssignPair),
3418 Box::new(DeadDecl),
3419 Box::new(AssignBackFold),
3420 Box::new(DeadStore),
3421 Box::new(StoreKill),
3422 ]
3423}
3424
3425pub fn all_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3427 let mut rules = default_rules();
3428 rules.push(Box::new(UnreachableAfterTerminal));
3429 rules
3430}