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, StmtKill};
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 name_key = lang.var_key(id)?;
576 for &s in &stmts[idx + 1..] {
577 match (lang.stmt_mentions(s, name_key), lang.stmt_opaque(s)) {
578 (Some(mentions), Some(opaque)) => {
579 if mentions || opaque {
582 return None;
583 }
584 }
585 _ => {
586 if subtree_contains(&*lang, s, |n| {
589 lang.kind(n) == NodeKind::VarRef
590 && lang.var_name(n) == Some(name_str.as_str())
591 }) {
592 return None;
593 }
594 let mut w = Watch::<L>::new(name_key, &[]);
595 scan_region(&*lang, s, &mut w);
596 if w.opaque || w.shadowed {
597 return None;
598 }
599 }
600 }
601 }
602 Some(Edit::Delete { node: id })
603 }
604}
605
606pub struct AssignBackFold;
621
622impl<L: Lang> Rule<L> for AssignBackFold {
623 fn name(&self) -> &'static str {
624 "assign_back_fold"
625 }
626 fn kinds(&self) -> &'static [NodeKind] {
627 &[NodeKind::VarDecl]
628 }
629 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
630 let RewriteCtx { lang, walk } = ctx;
631 if lang.kind(id) != NodeKind::VarDecl {
632 return None;
633 }
634 let ch = lang.children(id).to_vec();
635 let (&init, has_init) = (ch.first()?, !ch.is_empty());
636 if !has_init {
637 return None; }
639 let name_key = lang.var_key(id)?;
640 if !lang.is_wide_decl(id) {
644 return None;
645 }
646 let parent = walk.parent(id)?;
647 if lang.kind(parent) != NodeKind::Block {
648 return None;
649 }
650 let idx = walk.index(id)?;
651 let stmts = lang.children(parent).to_vec();
652
653 let name_str = lang.var_name(id)?.to_string();
658 for &s in &stmts[idx + 1..] {
659 let touched = match (lang.stmt_mentions(s, name_key), lang.stmt_opaque(s)) {
662 (Some(mentions), Some(opaque)) => mentions || opaque,
663 _ => {
664 let mut w = Watch::<L>::new(name_key, &[]);
665 scan_region(&*lang, s, &mut w);
666 let referenced = subtree_contains(&*lang, s, |n| {
667 lang.kind(n) == NodeKind::VarRef
668 && lang.var_name(n) == Some(name_str.as_str())
669 });
670 referenced || w.opaque || w.shadowed
671 }
672 };
673 if !touched {
674 continue; }
676 let assign = match lang.kind(s) {
678 NodeKind::Assign => s,
679 NodeKind::ExprStmt => {
680 let c = lang.children(s);
681 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
682 c[0]
683 } else {
684 return None;
685 }
686 }
687 _ => return None, };
689 let ach = lang.children(assign).to_vec();
690 if ach.len() != 2 {
691 return None;
692 }
693 let (target, rhs) = (ach[0], ach[1]);
694 if lang.kind(target) != NodeKind::VarRef || lang.var_key(target) != Some(name_key) {
695 return None;
696 }
697 let (op, k) = match lang.assign_op(assign) {
700 Some(op) => (op, lang.literal(rhs).and_then(|x| x.as_int())?),
701 None => {
702 if lang.kind(rhs) != NodeKind::Binary {
703 return None;
704 }
705 let op = lang.bin_op(rhs)?;
706 let rc = lang.children(rhs).to_vec();
707 if rc.len() != 2 || lang.kind(rc[0]) != NodeKind::VarRef {
708 return None;
709 }
710 if lang.var_key(rc[0]) != Some(name_key) {
712 return None;
713 }
714 (op, lang.literal(rc[1]).and_then(|x| x.as_int())?)
715 }
716 };
717 if op.is_short_circuit() || op.is_comparison() {
718 return None;
719 }
720 if matches!(op, BinOp::Div | BinOp::Rem) && k == 0 {
722 return None;
723 }
724 let lit = if lang.is_long_decl(id) {
729 lang.build_int(k, true)
730 } else {
731 lang.build_int(k, false)
732 };
733 let init = if lang.is_long_decl(id) {
734 match lang.literal(init) {
735 Some(crate::kind::LitRef::Int(v)) => lang.build_int(v, true),
736 _ => init,
737 }
738 } else {
739 init
740 };
741 let merged = lang.build_bin(op, init, lit);
742 return Some(Edit::Multi(vec![
744 Edit::Delete { node: s },
745 Edit::Splice { node: id, index: 0, remove: ch.len(), insert: vec![merged] },
746 ]));
747 }
748 None
749 }
750}
751
752pub struct ArithIdentity;
753
754impl<L: Lang> Rule<L> for ArithIdentity {
755 fn name(&self) -> &'static str {
756 "arith_identity"
757 }
758 fn kinds(&self) -> &'static [NodeKind] {
759 &[NodeKind::Binary]
760 }
761 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
762 let RewriteCtx { lang, walk: _ } = ctx;
763 if lang.kind(id) != NodeKind::Binary {
764 return None;
765 }
766 let op = lang.bin_op(id)?;
767 let ch = lang.children(id);
768 let (l, r) = (*ch.first()?, *ch.get(1)?);
769 let int_lit = |n: L::Id, v: i64| -> bool {
770 matches!(lang.literal(n), Some(l) if l.as_int() == Some(v))
771 };
772 let x = match op {
773 BinOp::Add if int_lit(r, 0) => l,
774 BinOp::Add if int_lit(l, 0) => r,
775 BinOp::Sub if int_lit(r, 0) => l,
776 BinOp::Mul if int_lit(r, 1) => l,
777 BinOp::Mul if int_lit(l, 1) => r,
778 BinOp::Div if int_lit(r, 1) => l,
779 _ => return None,
780 };
781 if !lang.is_exact_int(x) {
782 return None;
783 }
784 Some(Edit::Replace {
785 target: id,
786 with: x,
787 })
788 }
789}
790
791fn count_name_refs<L: Lang>(
812 lang: &L,
813 stmts: &[L::Id],
814 key: L::NameKey,
815 name: &str,
816) -> usize {
817 let mut n: usize = 0;
818 for &s in stmts {
819 match lang.stmt_var_ref_count(s, key) {
820 Some(c) => n += c as usize,
821 None => count_var_refs(lang, s, name, &mut n),
822 }
823 }
824 n
825}
826
827fn null_unsafe_position<L: Lang>(lang: &L, walk: &Walk<L>, use_id: L::Id) -> bool {
834 let mut cur = use_id;
835 while let Some(&(p, _)) = walk.parents.get(&cur) {
836 match lang.kind(p) {
837 NodeKind::Paren => cur = p,
838 NodeKind::Call
839 | NodeKind::New
840 | NodeKind::Member
841 | NodeKind::Index
842 | NodeKind::InstanceOf => return true,
843 _ => return false,
844 }
845 }
846 false
847}
848
849fn typed_position_unsafe<L: Lang>(lang: &L, walk: &Walk<L>, use_id: L::Id) -> bool {
857 let mut cur = use_id;
858 while let Some(&(p, _)) = walk.parents.get(&cur) {
859 match lang.kind(p) {
860 NodeKind::Paren => cur = p,
861 NodeKind::Cast | NodeKind::InstanceOf => return true,
862 _ => return false,
863 }
864 }
865 false
866}
867
868fn count_var_refs<L: Lang>(lang: &L, node: L::Id, name: &str, n: &mut usize) {
869 if lang.kind(node) == NodeKind::VarRef && lang.var_name(node) == Some(name) {
870 *n += 1;
871 }
872 for &c in lang.children(node) {
873 count_var_refs(lang, c, name, n);
874 }
875}
876
877pub struct LocalPropagation;
878
879impl<L: Lang> Rule<L> for LocalPropagation {
880 fn name(&self) -> &'static str {
881 "local_propagation"
882 }
883 fn kinds(&self) -> &'static [NodeKind] {
884 &[NodeKind::VarDecl]
885 }
886 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
887 let RewriteCtx { lang, walk } = ctx;
888 if lang.kind(id) != NodeKind::VarDecl {
889 return None;
890 }
891 let name = lang.var_name(id)?.to_string();
892 let ch = lang.children(id);
893 if ch.len() != 1 {
894 return None; }
896 let value = ch[0];
897 if lang.kind(value) == NodeKind::ArrayLit {
901 return None;
902 }
903 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
907 return None;
908 }
909 if subtree_contains(&*lang, value, |n| {
911 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
912 }) {
913 return None;
914 }
915 let parent = walk.parent(id)?;
916 if lang.kind(parent) != NodeKind::Block {
917 return None;
918 }
919 let index = walk.index(id)?;
920 let stmts: Vec<L::Id> = lang.children(parent).to_vec();
921 if index + 1 > stmts.len() {
922 return None;
923 }
924
925 let mut value = value;
931 if lang.is_char_decl(id) {
932 match lang.literal(value) {
933 Some(LitRef::Char(_)) => {}
934 Some(LitRef::Int(v)) => {
935 let c = u32::try_from(v)
938 .ok()
939 .filter(|&u| (0..=0xFFFF).contains(&u) && !(0xD800..=0xDFFF).contains(&u))
940 .and_then(char::from_u32)?;
941 value = lang.build_char(c);
942 }
943 _ => return None,
944 }
945 }
946 if lang.is_long_decl(id) {
951 if let Some(LitRef::Int(v)) = lang.literal(value) {
952 value = lang.build_int(v, true);
953 }
954 }
955 if !lang.is_wide_decl(id) && !lang.is_char_decl(id) {
960 return None;
961 }
962
963 let name_str = lang.var_name(id)?.to_string();
968 let name_key = lang.var_key(id)?;
969 let mut captured = false;
977 let mut syntactic_refs = 0usize;
978 for &st in &stmts[index + 1..] {
979 let (refs, cap) = match (
980 lang.stmt_var_ref_count(st, name_key),
981 lang.stmt_lambda_captures(st, name_key),
982 ) {
983 (Some(refs), Some(cap)) => (refs as usize, cap),
984 _ => {
985 let mut n = 0usize;
986 count_var_refs(&*lang, st, &name_str, &mut n);
987 let cap = subtree_contains(&*lang, st, |n| {
988 if lang.kind(n) == NodeKind::Lambda {
989 return subtree_contains(&*lang, n, |m| {
990 lang.kind(m) == NodeKind::VarRef
991 && lang.var_name(m) == Some(name_str.as_str())
992 });
993 }
994 false
995 });
996 (n, cap)
997 }
998 };
999 syntactic_refs += refs;
1000 captured |= cap;
1001 if captured || syntactic_refs > 1 {
1002 break;
1003 }
1004 }
1005 if captured || syntactic_refs != 1 {
1006 return None;
1007 }
1008 let mut wa = Watch::<L>::new(name_key, &[]);
1009 for &s in &stmts[index + 1..] {
1010 scan_region(&*lang, s, &mut wa);
1011 }
1012 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
1013 return None;
1014 }
1015 let use_id = wa.uses[0];
1016
1017 if let Some(&(p, _)) = walk.parents.get(&use_id) {
1022 if lang.kind(p) == NodeKind::Assign && lang.assign_op(p).is_none() {
1023 let pch = lang.children(p);
1024 if pch.len() == 2
1025 && lang.kind(pch[0]) == NodeKind::VarRef
1026 && lang.var_key(pch[0]) == Some(name_key)
1027 && pch[1] == use_id
1028 {
1029 return None;
1030 }
1031 }
1032 }
1033 {
1038 let (up, head) = match walk.parents.get(&use_id) {
1039 Some(&(p, _)) => {
1040 (Some(p), lang.children(p).first() == Some(&use_id))
1041 }
1042 None => (None, false),
1043 };
1044 if lang.receiver_propagation_unsound(id, value, up, head) {
1045 return None;
1046 }
1047 }
1048 if matches!(
1055 lang.literal(value),
1056 Some(
1057 LitRef::Null
1058 | LitRef::Int(_)
1059 | LitRef::Long(_)
1060 | LitRef::Float(_)
1061 | LitRef::Double(_)
1062 | LitRef::Char(_)
1063 | LitRef::Bool(_)
1064 )
1065 ) {
1066 let mut cur = use_id;
1067 while let Some(&(p, _)) = walk.parents.get(&cur) {
1068 let at_head = lang.children(p).first() == Some(&cur);
1069 match lang.kind(p) {
1070 NodeKind::Member | NodeKind::Index if at_head => return None,
1072 NodeKind::Call if at_head => return None,
1074 NodeKind::InstanceOf if at_head => return None,
1078 NodeKind::ForEach => {
1080 let ch = lang.children(p);
1081 if ch.first() == Some(&cur) {
1082 return None;
1083 }
1084 }
1085 NodeKind::Synchronized => {
1087 let ch = lang.children(p);
1088 if ch.len() == 1 && ch[0] == cur {
1089 return None;
1090 }
1091 }
1092 _ => {}
1093 }
1094 cur = p;
1095 }
1096 }
1097 if matches!(lang.literal(value), Some(LitRef::Null))
1105 && null_unsafe_position(&*lang, walk, use_id)
1106 {
1107 return None;
1108 }
1109 if lang.kind(value) == NodeKind::VarRef
1112 && typed_position_unsafe(&*lang, walk, use_id)
1113 {
1114 return None;
1115 }
1116 let mut watch_keys: Vec<L::NameKey> = Vec::new();
1118 collect_read_keys(&*lang, value, &mut watch_keys);
1119 watch_keys.push(name_key);
1120 let mut wb = Watch::<L>::new(name_key, &watch_keys);
1124 let mut use_stmt_idx = None;
1125 for (off, &s) in stmts[index + 1..].iter().enumerate() {
1126 let uses_before = wb.uses.len();
1127 scan_region(&*lang, s, &mut wb);
1128 if wb.uses.len() > uses_before {
1129 use_stmt_idx = Some(index + 1 + off);
1130 break; }
1132 }
1133 if subtree_contains(&*lang, value, |n| {
1140 if lang.kind(n) != NodeKind::VarRef || lang.is_local_var(n) {
1141 return false;
1142 }
1143 if !lang.var_name(n).is_some_and(|nm| lang.is_field_name(nm)) {
1146 return false;
1147 }
1148 let is_callee = match walk.parent(n) {
1149 Some(p) => {
1150 matches!(lang.kind(p), NodeKind::Call) && lang.children(p).first() == Some(&n)
1151 }
1152 None => false,
1153 };
1154 !is_callee
1155 }) {
1156 if let Some(ui) = use_stmt_idx {
1157 for &s in &stmts[index + 1..=ui] {
1158 if lang.effect(s) != Effect::Pure {
1159 return None;
1160 }
1161 }
1162 }
1163 }
1164 if let Some(ui) = use_stmt_idx {
1168 if count_name_refs(&*lang, &stmts[ui + 1..], name_key, &name_str) != 0 {
1170 return None;
1171 }
1172 let mut wc = Watch::<L>::new(name_key, &[name_key]);
1173 for &s in &stmts[ui + 1..] {
1174 scan_region(&*lang, s, &mut wc);
1175 if wc.opaque || !wc.uses.is_empty() || wc.wrote(name_key) || wc.shadowed {
1176 return None;
1177 }
1178 }
1179 }
1180 let ve = lang.effect(value);
1181
1182 if ve <= Effect::MayRead {
1183 if let Some(target_key) =
1186 use_stmt_target_write_key(&*lang, &stmts[index + 1..], use_id)
1187 {
1188 wb.clear_wrote(target_key);
1189 }
1190 if wb.opaque || wb.any_write() {
1191 return None;
1192 }
1193 return Some(Edit::Multi(vec![
1194 Edit::Replace {
1195 target: use_id,
1196 with: value,
1197 },
1198 Edit::Delete { node: id },
1199 ]));
1200 }
1201
1202 if index + 1 >= stmts.len() {
1204 return None;
1205 }
1206 let stmt = stmts[index + 1];
1207 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
1208 return None;
1209 }
1210 match lang.kind(stmt) {
1211 NodeKind::Return
1214 | NodeKind::ExprStmt
1215 | NodeKind::Assign
1216 | NodeKind::Throw
1217 | NodeKind::VarDecl => {}
1218 _ => return None,
1219 }
1220 if !straight_path(&*lang, stmt, use_id) {
1221 return None;
1222 }
1223 if !prefix_effects_readable(&*lang, stmt, use_id) {
1224 return None;
1225 }
1226 Some(Edit::Multi(vec![
1227 Edit::Replace {
1228 target: use_id,
1229 with: value,
1230 },
1231 Edit::Delete { node: id },
1232 ]))
1233 }
1234}
1235
1236pub(crate) struct Watch<L: Lang> {
1242 opaque: bool,
1244 name: L::NameKey,
1246 watch: Vec<L::NameKey>,
1248 writes_hit: Vec<bool>,
1250 shadowed: bool,
1251 uses: Vec<L::Id>,
1253}
1254
1255impl<L: Lang> Watch<L> {
1256 pub(crate) fn new(name: L::NameKey, watch: &[L::NameKey]) -> Self {
1257 Watch {
1258 opaque: false,
1259 name,
1260 watch: watch.to_vec(),
1261 writes_hit: vec![false; watch.len()],
1262 shadowed: false,
1263 uses: Vec::new(),
1264 }
1265 }
1266 pub(crate) fn wrote(&self, k: L::NameKey) -> bool {
1268 self.watch
1269 .iter()
1270 .zip(&self.writes_hit)
1271 .any(|(w, h)| *h && *w == k)
1272 }
1273 pub(crate) fn any_write(&self) -> bool {
1275 self.writes_hit.iter().any(|h| *h)
1276 }
1277 pub(crate) fn clear_wrote(&mut self, k: L::NameKey) {
1279 for (w, h) in self.watch.iter().zip(self.writes_hit.iter_mut()) {
1280 if *w == k {
1281 *h = false;
1282 }
1283 }
1284 }
1285}
1286
1287fn scan_region<L: Lang>(lang: &L, node: L::Id, w: &mut Watch<L>) {
1289 lang.debug_verify_events(node);
1290 if let Some(events) = lang.region_events(node) {
1291 let lo = events.partition_point(|e| e.key < w.name);
1293 let hi = events.partition_point(|e| e.key <= w.name);
1294 for e in &events[lo..hi] {
1295 match e.kind {
1296 crate::kind::EventKind::Use => w.uses.push(e.node),
1297 crate::kind::EventKind::Shadow => w.shadowed = true,
1298 crate::kind::EventKind::Write => {
1299 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1300 if *wn == w.name {
1301 *hit = true;
1302 }
1303 }
1304 }
1305 crate::kind::EventKind::Opaque => w.opaque = true,
1308 }
1309 }
1310 for (i, wk) in w.watch.iter().enumerate() {
1312 if *wk == w.name {
1313 continue; }
1315 let lo = events.partition_point(|e| e.key < *wk);
1316 let hi = events.partition_point(|e| e.key <= *wk);
1317 for e in &events[lo..hi] {
1318 if e.kind == crate::kind::EventKind::Write {
1319 w.writes_hit[i] = true;
1320 break; }
1322 }
1323 }
1324 return;
1325 }
1326 if lang.is_opaque(node) {
1328 w.opaque = true;
1329 return;
1330 }
1331 match lang.kind(node) {
1333 NodeKind::Assign => {
1334 let ch = lang.children(node);
1335 if let Some(&t) = ch.first() {
1336 if lang.kind(t) == NodeKind::VarRef {
1337 if let Some(k) = lang.var_key(t) {
1338 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1339 if *wn == k {
1340 *hit = true;
1341 }
1342 }
1343 }
1344 }
1345 }
1346 if let Some(&v) = ch.get(1) {
1347 scan_region(lang, v, w);
1348 }
1349 }
1350 NodeKind::Unary if lang.un_op(node).is_some_and(|o| o.is_incdec()) => {
1351 let ch = lang.children(node);
1352 if let Some(&t) = ch.first() {
1353 if lang.kind(t) == NodeKind::VarRef {
1354 if let Some(k) = lang.var_key(t) {
1355 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1356 if *wn == k {
1357 *hit = true;
1358 }
1359 }
1360 }
1361 }
1362 }
1363 }
1364 NodeKind::VarDecl => {
1365 if let Some(k) = lang.var_key(node) {
1366 if k == w.name {
1367 w.shadowed = true;
1368 }
1369 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1370 if *wn == k {
1371 *hit = true;
1372 }
1373 }
1374 }
1375 for &c in lang.children(node) {
1376 scan_region(lang, c, w);
1377 }
1378 }
1379 NodeKind::ForEach | NodeKind::Catch => {
1382 if let Some(k) = lang.var_key(node) {
1383 if k == w.name {
1384 w.shadowed = true;
1385 }
1386 if lang.kind(node) == NodeKind::ForEach {
1387 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1388 if *wn == k {
1389 *hit = true;
1390 }
1391 }
1392 }
1393 }
1394 for &c in lang.children(node) {
1395 scan_region(lang, c, w);
1396 }
1397 }
1398 NodeKind::VarRef => {
1399 if lang.var_key(node) == Some(w.name) {
1400 w.uses.push(node);
1401 }
1402 }
1403 NodeKind::Raw => {
1404 w.opaque = true;
1406 }
1407 _ => {
1408 for &c in lang.children(node) {
1409 scan_region(lang, c, w);
1410 }
1411 }
1412 }
1413}
1414
1415fn straight_path<L: Lang>(lang: &L, root: L::Id, target: L::Id) -> bool {
1417 fn go<L: Lang>(lang: &L, node: L::Id, target: L::Id) -> bool {
1418 if node == target {
1419 return true;
1420 }
1421 match lang.kind(node) {
1422 NodeKind::Ternary | NodeKind::If => return false,
1423 NodeKind::Binary if lang.bin_op(node).is_some_and(|o| o.is_short_circuit()) => {
1424 return false
1425 }
1426 _ => {}
1427 }
1428 for &c in lang.children(node) {
1429 if subtree_contains(lang, c, |n| n == target) {
1430 return go(lang, c, target);
1431 }
1432 }
1433 false
1434 }
1435 go(lang, root, target)
1436}
1437
1438
1439pub struct BoolShortCircuit;
1447
1448impl<L: Lang> Rule<L> for BoolShortCircuit {
1449 fn name(&self) -> &'static str {
1450 "bool_short_circuit"
1451 }
1452 fn kinds(&self) -> &'static [NodeKind] {
1453 &[NodeKind::Binary]
1454 }
1455 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1456 let RewriteCtx { lang, walk: _ } = ctx;
1457 if lang.kind(id) != NodeKind::Binary {
1458 return None;
1459 }
1460 let op = lang.bin_op(id)?;
1461 if !op.is_short_circuit() {
1462 return None;
1463 }
1464 let ch = lang.children(id);
1465 let (l, r) = (*ch.first()?, *ch.get(1)?);
1466 let is_lit = |n: L::Id, v: bool| matches!(lang.literal(n), Some(LitRef::Bool(b)) if b == v);
1467 let with = match (op, is_lit(l, true), is_lit(l, false)) {
1469 (BinOp::And, _, true) => l, (BinOp::Or, true, _) => l, (BinOp::And, true, _) => r, (BinOp::Or, _, true) => r, _ => {
1474 match (op, is_lit(r, true), is_lit(r, false)) {
1477 (BinOp::And, true, _) => l,
1478 (BinOp::Or, _, true) => l,
1479 (BinOp::And, _, true) | (BinOp::Or, true, _)
1480 if lang.effect(l) <= Effect::MayRead => {
1481 r
1482 }
1483 _ => return None,
1484 }
1485 }
1486 };
1487 Some(Edit::Replace {
1488 target: id,
1489 with,
1490 })
1491 }
1492}
1493
1494pub struct NotCompare;
1500
1501impl<L: Lang> Rule<L> for NotCompare {
1502 fn name(&self) -> &'static str {
1503 "not_compare"
1504 }
1505 fn kinds(&self) -> &'static [NodeKind] {
1506 &[NodeKind::Unary]
1507 }
1508 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1509 let RewriteCtx { lang, walk: _ } = ctx;
1510 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
1511 return None;
1512 }
1513 let inner = *lang.children(id).first()?;
1514 if lang.kind(inner) != NodeKind::Binary {
1515 return None;
1516 }
1517 let op = lang.bin_op(inner)?;
1518 let neg = match op {
1519 BinOp::Eq => BinOp::Ne,
1520 BinOp::Ne => BinOp::Eq,
1521 BinOp::Lt => BinOp::Ge,
1522 BinOp::Le => BinOp::Gt,
1523 BinOp::Gt => BinOp::Le,
1524 BinOp::Ge => BinOp::Lt,
1525 _ => return None,
1526 };
1527 if op.is_short_circuit() {
1528 return None;
1529 }
1530 if matches!(op, BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) {
1532 let ch = lang.children(inner);
1533 if !lang.is_exact_int(*ch.first()?) || !lang.is_exact_int(*ch.get(1)?) {
1534 return None;
1535 }
1536 }
1537 let ch = lang.children(inner).to_vec();
1538 let rebuilt = lang.build_bin(neg, ch[0], ch[1]);
1539 Some(Edit::Replace {
1540 target: id,
1541 with: rebuilt,
1542 })
1543 }
1544}
1545
1546fn in_inferred_var_init<L: Lang>(lang: &L, walk: &Walk<L>, mut id: L::Id) -> bool {
1556 while let Some(&(p, _)) = walk.parents.get(&id) {
1557 match lang.kind(p) {
1558 NodeKind::Paren | NodeKind::Ternary => id = p,
1559 NodeKind::VarDecl => return lang.is_inferred_decl(p),
1560 _ => return false,
1561 }
1562 }
1563 false
1564}
1565
1566pub struct TernaryFold;
1567
1568impl<L: Lang> Rule<L> for TernaryFold {
1569 fn name(&self) -> &'static str {
1570 "ternary_fold"
1571 }
1572 fn kinds(&self) -> &'static [NodeKind] {
1573 &[NodeKind::Ternary]
1574 }
1575 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1576 let RewriteCtx { lang, walk } = ctx;
1577 if lang.kind(id) != NodeKind::Ternary {
1578 return None;
1579 }
1580 let ch = lang.children(id);
1581 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1582 match lang.literal(c) {
1583 Some(LitRef::Bool(true)) => {
1589 if !in_inferred_var_init(&*lang, walk, id) {
1590 return Some(Edit::Replace { target: id, with: a });
1591 }
1592 }
1593 Some(LitRef::Bool(false))
1594 if !in_inferred_var_init(&*lang, walk, id) => {
1595 return Some(Edit::Replace { target: id, with: b });
1596 }
1597 _ => {}
1598 }
1599 if crate::analysis::structurally_equal(&*lang, a, b) && lang.effect(c) <= Effect::MayRead {
1602 return Some(Edit::Replace { target: id, with: a });
1603 }
1604 None
1605 }
1606}
1607
1608pub struct TernaryBool;
1613
1614impl<L: Lang> Rule<L> for TernaryBool {
1615 fn name(&self) -> &'static str {
1616 "ternary_bool"
1617 }
1618 fn kinds(&self) -> &'static [NodeKind] {
1619 &[NodeKind::Ternary]
1620 }
1621 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1622 let RewriteCtx { lang, walk: _ } = ctx;
1623 if lang.kind(id) != NodeKind::Ternary {
1624 return None;
1625 }
1626 let ch = lang.children(id);
1627 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1628 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
1629 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
1630 if !lang.is_bool(c) {
1631 return None;
1632 }
1633 let with = if lb(a) && is_false(b) {
1634 c
1635 } else if is_false(a) && lb(b) {
1636 lang.build_unary(UnOp::Not, c)
1637 } else {
1638 return None;
1639 };
1640 Some(Edit::Replace {
1641 target: id,
1642 with,
1643 })
1644 }
1645}
1646
1647pub struct ConstFoldBin;
1654
1655impl<L: Lang> Rule<L> for ConstFoldBin {
1656 fn name(&self) -> &'static str {
1657 "const_fold_bin"
1658 }
1659 fn kinds(&self) -> &'static [NodeKind] {
1660 &[NodeKind::Binary]
1661 }
1662 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1663 let RewriteCtx { lang, walk: _ } = ctx;
1664 if lang.kind(id) != NodeKind::Binary {
1665 return None;
1666 }
1667 let op = lang.bin_op(id)?;
1668 if op.is_short_circuit() || op.is_comparison() {
1669 return None;
1670 }
1671 let ch = lang.children(id);
1672 let (l, r) = (*ch.first()?, *ch.get(1)?);
1673 lang.literal(l)?;
1675 lang.literal(r)?;
1676 let with = lang.fold_lit_bin(op, l, r)?;
1677 Some(Edit::Replace {
1678 target: id,
1679 with,
1680 })
1681 }
1682}
1683
1684pub struct ArithZero;
1693
1694impl<L: Lang> Rule<L> for ArithZero {
1695 fn name(&self) -> &'static str {
1696 "arith_zero"
1697 }
1698 fn kinds(&self) -> &'static [NodeKind] {
1699 &[NodeKind::Binary]
1700 }
1701 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1702 let RewriteCtx { lang, walk: _ } = ctx;
1703 if lang.kind(id) != NodeKind::Binary {
1704 return None;
1705 }
1706 let op = lang.bin_op(id)?;
1707 let ch = lang.children(id);
1708 let (l, r) = (*ch.first()?, *ch.get(1)?);
1709 let int_lit = |n: L::Id, v: i64| lang.literal(n).and_then(|x| x.as_int()) == Some(v);
1710
1711 if matches!(op, BinOp::Shl | BinOp::Shr | BinOp::UShr) && int_lit(r, 0) {
1713 return Some(Edit::Replace {
1714 target: id,
1715 with: l,
1716 });
1717 }
1718 if op == BinOp::Mul {
1720 let (zero, other) = if int_lit(l, 0) {
1721 (l, r)
1722 } else if int_lit(r, 0) {
1723 (r, l)
1724 } else {
1725 return None;
1726 };
1727 if !lang.is_exact_int(other) || lang.effect(other) > Effect::MayRead {
1728 return None;
1729 }
1730 return Some(Edit::Replace {
1731 target: id,
1732 with: zero,
1733 });
1734 }
1735 if op == BinOp::Sub
1737 && lang.kind(l) == NodeKind::VarRef
1738 && lang.kind(r) == NodeKind::VarRef
1739 && lang.var_name(l) == lang.var_name(r)
1740 && lang.is_local_var(l)
1741 && lang.is_exact_int(l)
1742 {
1743 let with = lang.build_int(0, false);
1744 return Some(Edit::Replace {
1745 target: id,
1746 with,
1747 });
1748 }
1749 None
1750 }
1751}
1752
1753pub struct InverseAssignPair;
1776
1777impl<L: Lang> Rule<L> for InverseAssignPair {
1778 fn name(&self) -> &'static str {
1779 "inverse_assign_pair"
1780 }
1781 fn kinds(&self) -> &'static [NodeKind] {
1782 &[NodeKind::Block]
1783 }
1784 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1785 let RewriteCtx { lang, .. } = ctx;
1786 let ch = lang.children(id).to_vec();
1787 if ch.len() < 2 {
1788 return None;
1789 }
1790 for w in 0..ch.len() - 1 {
1791 let (s1, s2) = (ch[w], ch[w + 1]);
1792 let Some(a1) = norm_assign(lang, s1) else { continue };
1794 let Some(a2) = norm_assign(lang, s2) else { continue };
1795 if a1.key != a2.key || !a1.local || !a2.local {
1797 continue;
1798 }
1799 if !a1.exact_int || !a2.exact_int {
1801 continue;
1802 }
1803 if a1.k != a2.k {
1805 continue;
1806 }
1807 let inverse = match (a1.op, a2.op) {
1808 (BinOp::Add, BinOp::Sub) | (BinOp::Sub, BinOp::Add) => true,
1809 (BinOp::BitXor, BinOp::BitXor) => true,
1810 _ => false,
1811 };
1812 if !inverse {
1813 continue;
1814 }
1815 return Some(Edit::Multi(vec![
1817 Edit::Delete { node: s2 },
1818 Edit::Delete { node: s1 },
1819 ]));
1820 }
1821 None
1822 }
1823}
1824
1825struct NormAssign<K> {
1829 key: K,
1830 op: BinOp,
1831 k: i64,
1832 local: bool,
1833 exact_int: bool,
1835}
1836
1837fn norm_assign<L: Lang>(lang: &L, stmt: L::Id) -> Option<NormAssign<L::NameKey>> {
1838 let a = match lang.kind(stmt) {
1840 NodeKind::ExprStmt => {
1841 let c = lang.children(stmt);
1842 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
1843 c[0]
1844 } else {
1845 return None;
1846 }
1847 }
1848 NodeKind::Assign => stmt,
1849 _ => return None,
1850 };
1851 let ch = lang.children(a).to_vec();
1852 if ch.len() != 2 {
1853 return None;
1854 }
1855 let (target, rhs) = (ch[0], ch[1]);
1856 if lang.kind(target) != NodeKind::VarRef {
1857 return None;
1858 }
1859 let key = lang.var_key(target)?;
1860 let local = lang.is_local_var(target);
1861 let exact_int = lang.is_exact_int(target);
1862 match lang.assign_op(a) {
1866 Some(op) => {
1868 let k = lang.literal(rhs).and_then(|x| x.as_int())?;
1869 Some(NormAssign { key, op, k, local, exact_int })
1870 }
1871 None => {
1873 if lang.kind(rhs) != NodeKind::Binary {
1874 return None;
1875 }
1876 let op = lang.bin_op(rhs)?;
1877 let rc = lang.children(rhs).to_vec();
1878 if rc.len() != 2 {
1879 return None;
1880 }
1881 if lang.kind(rc[0]) != NodeKind::VarRef || lang.var_key(rc[0]) != Some(key) {
1883 return None;
1884 }
1885 let k = lang.literal(rc[1]).and_then(|x| x.as_int())?;
1886 Some(NormAssign { key, op, k, local, exact_int })
1887 }
1888 }
1889}
1890
1891pub struct DeadStore;
1892
1893impl<L: Lang> Rule<L> for DeadStore {
1894 fn name(&self) -> &'static str {
1895 "dead_store"
1896 }
1897 fn kinds(&self) -> &'static [NodeKind] {
1898 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
1899 }
1900 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1901 let RewriteCtx { lang, walk } = ctx;
1902 let (decl_form, first_assign) = match lang.kind(id) {
1904 NodeKind::VarDecl => {
1905 if lang.children(id).is_empty() {
1906 return None; }
1908 (true, None)
1909 }
1910 NodeKind::Assign => (false, Some(id)),
1911 NodeKind::ExprStmt => {
1912 let ch = lang.children(id);
1913 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
1914 (false, Some(ch[0]))
1915 } else {
1916 return None;
1917 }
1918 }
1919 _ => return None,
1920 };
1921 let first_value = if decl_form {
1922 *lang.children(id).first()?
1923 } else {
1924 let a = first_assign?;
1925 if lang.assign_op(a).is_some() {
1926 return None; }
1928 *lang.children(a).get(1)?
1929 };
1930
1931 let parent = walk.parent(id)?;
1933 if lang.kind(parent) != NodeKind::Block {
1934 return None;
1935 }
1936 let idx = walk.index(id)?;
1937 let stmts = lang.children(parent).to_vec();
1938 let next_stmt = *stmts.get(idx + 1)?;
1939
1940 let second = match lang.kind(next_stmt) {
1943 NodeKind::Assign => Some(next_stmt),
1944 NodeKind::ExprStmt => {
1945 let ch = lang.children(next_stmt);
1946 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
1947 Some(ch[0])
1948 } else {
1949 None
1950 }
1951 }
1952 _ => None,
1953 };
1954 let Some(second) = second else {
1955 let (name_node, target_local) = if decl_form {
1964 (id, true)
1965 } else {
1966 let t = *lang.children(first_assign?).first()?;
1967 if lang.kind(t) != NodeKind::VarRef {
1968 return None;
1969 }
1970 (t, lang.is_local_var(t))
1971 };
1972 if !target_local {
1974 return None;
1975 }
1976 let name = lang.var_name(name_node)?.to_string();
1977 let declared_in_this_block = if decl_form {
1978 true } else if parent == walk.root {
1980 true
1983 } else {
1984 stmts[..idx].iter().any(|&s| {
1987 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
1988 })
1989 };
1990 if !declared_in_this_block {
1991 return None;
1992 }
1993 let name_key0 = lang.var_key(name_node)?;
1994 for &s in &stmts[idx + 1..] {
2000 match (lang.stmt_mentions(s, name_key0), lang.stmt_opaque(s)) {
2001 (Some(mentions), Some(opaque)) => {
2002 if mentions || opaque {
2003 return None;
2004 }
2005 }
2006 _ => {
2007 let mut w0 = Watch::<L>::new(name_key0, &[name_key0]);
2008 scan_region(&*lang, s, &mut w0);
2009 if w0.opaque
2010 || !w0.uses.is_empty()
2011 || w0.wrote(name_key0)
2012 || w0.shadowed
2013 {
2014 return None;
2015 }
2016 if subtree_contains(&*lang, s, |n| {
2017 lang.kind(n) == NodeKind::VarRef
2018 && lang.var_name(n) == Some(name.as_str())
2019 }) {
2020 return None;
2021 }
2022 }
2023 }
2024 }
2025 if lang.effect(first_value) > Effect::MayRead {
2026 return None;
2027 }
2028 return Some(Edit::Delete { node: id });
2029 };
2030 if lang.assign_op(second).is_some() {
2031 return None;
2032 }
2033 let nch = lang.children(second);
2034 let (target, value) = (*nch.first()?, *nch.get(1)?);
2035 if lang.kind(target) != NodeKind::VarRef {
2036 return None;
2037 }
2038
2039 let name = if decl_form {
2041 if lang.var_name(id) != lang.var_name(target) {
2042 return None;
2043 }
2044 lang.var_name(id).map(|s| s.to_string())
2045 } else {
2046 let a = first_assign?;
2047 let t = *lang.children(a).first()?;
2048 if lang.kind(t) != NodeKind::VarRef {
2049 return None;
2050 }
2051 if lang.var_name(t) != lang.var_name(target) || !lang.is_local_var(t) {
2052 return None;
2053 }
2054 lang.var_name(t).map(|s| s.to_string())
2055 }?;
2056 if subtree_contains(&*lang, value, |n| {
2058 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2059 }) {
2060 return None;
2061 }
2062 if lang.effect(first_value) > Effect::MayRead {
2064 return None;
2065 }
2066
2067 if decl_form {
2068 Some(Edit::Multi(vec![
2070 Edit::Replace {
2071 target: first_value,
2072 with: value,
2073 },
2074 Edit::Delete {
2075 node: next_stmt,
2076 },
2077 ]))
2078 } else {
2079 Some(Edit::Delete { node: id })
2081 }
2082 }
2083}
2084
2085pub struct BlockFlatten;
2094
2095impl<L: Lang> Rule<L> for BlockFlatten {
2096 fn name(&self) -> &'static str {
2097 "block_flatten"
2098 }
2099 fn kinds(&self) -> &'static [NodeKind] {
2100 &[NodeKind::Block]
2101 }
2102 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2103 let RewriteCtx { lang, walk } = ctx;
2104 let parent = walk.parent(id)?;
2105 if lang.kind(parent) != NodeKind::Block {
2106 return None;
2107 }
2108 let inner = lang.children(id).to_vec();
2110 let declares = |n: L::Id| -> bool {
2111 matches!(
2112 lang.kind(n),
2113 NodeKind::VarDecl | NodeKind::ForEach | NodeKind::Catch
2114 )
2115 };
2116 if inner.iter().any(|&s| declares(s)) {
2117 return None;
2118 }
2119 if lang.has_pattern_bindings(id) {
2125 return None;
2126 }
2127 let idx = walk.index(id)?;
2128 Some(Edit::Splice {
2129 node: parent,
2130 index: idx,
2131 remove: 1,
2132 insert: inner,
2133 })
2134 }
2135}
2136
2137pub struct DoubleNegFold;
2140
2141impl<L: Lang> Rule<L> for DoubleNegFold {
2142 fn name(&self) -> &'static str {
2143 "double_neg_fold"
2144 }
2145 fn kinds(&self) -> &'static [NodeKind] {
2146 &[NodeKind::Unary]
2147 }
2148 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2149 let RewriteCtx { lang, .. } = ctx;
2150 if lang.un_op(id) != Some(UnOp::Neg) {
2151 return None;
2152 }
2153 let inner = *lang.children(id).first()?;
2154 if lang.un_op(inner) == Some(UnOp::Neg) {
2155 let x = *lang.children(inner).first()?;
2156 return Some(Edit::Replace {
2157 target: id,
2158 with: x,
2159 });
2160 }
2161 if let Some((v, wide)) = lang.literal(inner).and_then(|l| match l {
2164 LitRef::Int(v) => Some((v, false)),
2165 LitRef::Long(v) => Some((v, true)),
2166 _ => None,
2167 }) {
2168 if v < 0 {
2169 let n = lang.fold_lit_neg(v, wide)?;
2170 let with = lang.build_int(n, wide);
2171 return Some(Edit::Replace {
2172 target: id,
2173 with,
2174 });
2175 }
2176 }
2177 None
2178 }
2179}
2180
2181pub struct UnreachableAfterTerminal;
2182
2183impl<L: Lang> Rule<L> for UnreachableAfterTerminal {
2184 fn name(&self) -> &'static str {
2185 "unreachable_after_terminal"
2186 }
2187 fn kinds(&self) -> &'static [NodeKind] {
2188 &[NodeKind::Block]
2189 }
2190 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2191 let RewriteCtx { lang, walk: _ } = ctx;
2192 if lang.kind(id) != NodeKind::Block {
2193 return None;
2194 }
2195 let children = lang.children(id).to_vec();
2196 let terminal = |n: L::Id| -> bool {
2197 matches!(
2198 lang.kind(n),
2199 NodeKind::Return | NodeKind::Throw | NodeKind::Break | NodeKind::Continue
2200 )
2201 };
2202 let mut cut: Option<(usize, usize)> = None;
2203 for (i, &s) in children.iter().enumerate() {
2204 if terminal(s) && i + 1 < children.len() {
2205 cut = Some((i + 1, children.len() - i - 1));
2206 break;
2207 }
2208 }
2209 let (index, remove) = cut?;
2210 Some(Edit::Splice {
2211 node: id,
2212 index,
2213 remove,
2214 insert: Vec::new(),
2215 })
2216 }
2217}
2218
2219
2220pub struct CmpConstFold;
2227
2228impl<L: Lang> Rule<L> for CmpConstFold {
2229 fn name(&self) -> &'static str {
2230 "cmp_const_fold"
2231 }
2232 fn kinds(&self) -> &'static [NodeKind] {
2233 &[NodeKind::Binary]
2234 }
2235 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2236 let RewriteCtx { lang, walk: _ } = ctx;
2237 if lang.kind(id) != NodeKind::Binary {
2238 return None;
2239 }
2240 let op = lang.bin_op(id)?;
2241 if !op.is_comparison() {
2242 return None;
2243 }
2244 let ch = lang.children(id);
2245 let (l, r) = (*ch.first()?, *ch.get(1)?);
2246 lang.literal(l)?;
2249 lang.literal(r)?;
2250 let v = lang.fold_lit_cmp(op, l, r)?;
2251 let with = lang.build_bool(v);
2252 Some(Edit::Replace {
2253 target: id,
2254 with,
2255 })
2256 }
2257}
2258
2259pub struct BitIdentity;
2267
2268impl<L: Lang> Rule<L> for BitIdentity {
2269 fn name(&self) -> &'static str {
2270 "bit_identity"
2271 }
2272 fn kinds(&self) -> &'static [NodeKind] {
2273 &[NodeKind::Binary]
2274 }
2275 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2276 let RewriteCtx { lang, walk: _ } = ctx;
2277 if lang.kind(id) != NodeKind::Binary {
2278 return None;
2279 }
2280 let op = lang.bin_op(id)?;
2281 if !matches!(op, BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor) {
2282 return None;
2283 }
2284 let ch = lang.children(id);
2285 let (l, r) = (*ch.first()?, *ch.get(1)?);
2286 let int_lit = |n: L::Id| lang.literal(n).and_then(|x| x.as_int());
2287
2288 if op == BinOp::BitXor
2290 && lang.kind(l) == NodeKind::VarRef
2291 && lang.kind(r) == NodeKind::VarRef
2292 && lang.var_name(l) == lang.var_name(r)
2293 && lang.is_local_var(l)
2294 && lang.is_exact_int(l)
2295 {
2296 let with = lang.build_int(0, false);
2297 return Some(Edit::Replace {
2298 target: id,
2299 with,
2300 });
2301 }
2302
2303 for (x, k) in [(l, r), (r, l)] {
2305 let Some(kv) = int_lit(k) else {
2306 continue;
2307 };
2308 if !lang.is_exact_int(x) {
2309 continue;
2310 }
2311 let identity = match (op, kv) {
2312 (BinOp::BitXor, 0) => Some(x), (BinOp::BitOr, 0) => Some(x), (BinOp::BitAnd, -1) => Some(x), (BinOp::BitAnd, 0) | (BinOp::BitOr, -1) => {
2316 if lang.effect(x) > Effect::MayRead {
2318 None
2319 } else {
2320 Some(k)
2321 }
2322 }
2323 _ => None,
2324 };
2325 if let Some(with) = identity {
2326 return Some(Edit::Replace {
2327 target: id,
2328 with,
2329 });
2330 }
2331 }
2332 None
2333 }
2334}
2335
2336pub struct ArithReassoc;
2342
2343impl<L: Lang> Rule<L> for ArithReassoc {
2344 fn name(&self) -> &'static str {
2345 "arith_reassoc"
2346 }
2347 fn kinds(&self) -> &'static [NodeKind] {
2348 &[NodeKind::Binary]
2349 }
2350 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2351 let RewriteCtx { lang, walk: _ } = ctx;
2352 if lang.kind(id) != NodeKind::Binary {
2353 return None;
2354 }
2355 let op2 = lang.bin_op(id)?;
2356 if !matches!(op2, BinOp::Add | BinOp::Sub | BinOp::BitXor) {
2357 return None;
2358 }
2359 let och = lang.children(id).to_vec();
2360 let inner = och[0];
2361 if lang.kind(inner) != NodeKind::Binary {
2362 return None;
2363 }
2364 let op1 = lang.bin_op(inner)?;
2365 let ich = lang.children(inner).to_vec();
2366 let x = ich[0];
2367
2368 if op1 == BinOp::Add && op2 == BinOp::Add {
2374 if let (Some(LitRef::Str(k1)), Some(LitRef::Str(k2))) =
2375 (lang.literal(ich[1]), lang.literal(och[1]))
2376 {
2377 let mut chain: Vec<L::Id> = vec![id];
2379 let mut cur = id;
2380 loop {
2381 let cch = lang.children(cur).to_vec();
2382 let next = cch[0];
2383 let deeper = lang.kind(next) == NodeKind::Binary
2384 && lang.bin_op(next) == Some(BinOp::Add)
2385 && matches!(lang.literal(lang.children(next).to_vec()[1]),
2386 Some(LitRef::Str(_)));
2387 if deeper {
2388 chain.push(next);
2389 cur = next;
2390 } else {
2391 break;
2392 }
2393 }
2394 let innermost = *chain.last().unwrap();
2396 let ich2 = lang.children(innermost).to_vec();
2397 let base = ich2[0];
2398 let mut joined = String::new();
2399 for &n in chain.iter().rev() {
2400 if let Some(LitRef::Str(k)) =
2401 lang.literal(lang.children(n).to_vec()[1])
2402 {
2403 joined.push_str(k);
2404 }
2405 }
2406 let _ = (k1, k2); let lit = lang.build_str(&joined);
2408 let with = lang.build_bin(BinOp::Add, base, lit);
2409 return Some(Edit::Replace {
2410 target: id,
2411 with,
2412 });
2413 }
2414 }
2415
2416 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2420 let k2 = lang.literal(och[1]).and_then(|x| x.as_int())?;
2421 if !lang.is_exact_int(x) {
2422 return None;
2423 }
2424 let is_long = matches!(lang.literal(ich[1]), Some(LitRef::Long(_)))
2425 || matches!(lang.literal(och[1]), Some(LitRef::Long(_)));
2426 let with = match lang.reassoc_delta(op1, k1, op2, k2, is_long)? {
2427 ReassocOutcome::Neutral => x,
2428 ReassocOutcome::Lit(lit, op) => lang.build_bin(op, x, lit),
2429 };
2430 Some(Edit::Replace {
2431 target: id,
2432 with,
2433 })
2434 }
2435}
2436
2437pub struct IfToTernary;
2446
2447impl<L: Lang> Rule<L> for IfToTernary {
2448 fn name(&self) -> &'static str {
2449 "if_to_ternary"
2450 }
2451 fn kinds(&self) -> &'static [NodeKind] {
2452 &[NodeKind::If]
2453 }
2454 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2455 let RewriteCtx { lang, walk } = ctx;
2456 if lang.kind(id) != NodeKind::If {
2457 return None;
2458 }
2459 let ch = lang.children(id).to_vec();
2460 let c = ch[0];
2461
2462 let single_ret = |n: L::Id| -> Option<Option<L::Id>> {
2464 match lang.kind(n) {
2465 NodeKind::Return => Some(lang.children(n).first().copied()),
2466 NodeKind::Block if lang.children(n).len() == 1 => {
2467 let inner = lang.children(n)[0];
2468 if lang.kind(inner) == NodeKind::Return {
2469 Some(lang.children(inner).first().copied())
2470 } else {
2471 None
2472 }
2473 }
2474 _ => None,
2475 }
2476 };
2477
2478 if ch.len() == 3 {
2479 let a = single_ret(ch[1])?;
2481 let b = single_ret(ch[2])?;
2482 let with = match (a, b) {
2483 (Some(va), Some(vb)) => {
2484 let t = lang.build_ternary(c, va, vb);
2485 lang.build_return(Some(t))
2486 }
2487 (None, None) => lang.build_return(None),
2488 _ => return None,
2489 };
2490 return Some(Edit::Replace {
2491 target: id,
2492 with,
2493 });
2494 }
2495
2496 if ch.len() != 2 {
2498 return None;
2499 }
2500 let a = single_ret(ch[1])?;
2501 let parent = walk.parent(id)?;
2502 if lang.kind(parent) != NodeKind::Block {
2503 return None;
2504 }
2505 let idx = walk.index(id)?;
2506 let stmts = lang.children(parent).to_vec();
2507 if idx + 2 != stmts.len() {
2508 return None; }
2510 let tail = stmts[idx + 1];
2511 if lang.kind(tail) != NodeKind::Return {
2512 return None;
2513 }
2514 let b = lang.children(tail).first().copied();
2515 let with = match (a, b) {
2516 (Some(va), Some(vb)) => {
2517 let t = lang.build_ternary(c, va, vb);
2518 lang.build_return(Some(t))
2519 }
2520 (None, None) => lang.build_return(None),
2521 _ => return None,
2522 };
2523 Some(Edit::Splice {
2524 node: parent,
2525 index: idx,
2526 remove: 2,
2527 insert: vec![with],
2528 })
2529 }
2530}
2531
2532pub struct IfAssignTernary;
2539
2540impl<L: Lang> Rule<L> for IfAssignTernary {
2541 fn name(&self) -> &'static str {
2542 "if_assign_ternary"
2543 }
2544 fn kinds(&self) -> &'static [NodeKind] {
2545 &[NodeKind::If]
2546 }
2547 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2548 let RewriteCtx { lang, walk: _ } = ctx;
2549 if lang.kind(id) != NodeKind::If {
2550 return None;
2551 }
2552 let ch = lang.children(id).to_vec();
2553 if ch.len() != 3 {
2554 return None;
2555 }
2556 let c = ch[0];
2557 let single_assign = |n: L::Id| -> Option<(L::Id, L::Id)> {
2559 let unwrap = |i: L::Id| -> L::Id {
2560 if lang.kind(i) == NodeKind::ExprStmt {
2561 let ch = lang.children(i);
2562 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2563 return ch[0];
2564 }
2565 }
2566 i
2567 };
2568 let inner = match lang.kind(n) {
2569 NodeKind::Assign => n,
2570 NodeKind::ExprStmt => unwrap(n),
2571 NodeKind::Block if lang.children(n).len() == 1 => unwrap(lang.children(n)[0]),
2572 _ => return None,
2573 };
2574 if lang.kind(inner) != NodeKind::Assign {
2575 return None;
2576 }
2577 if lang.assign_op(inner).is_some() {
2578 return None;
2579 }
2580 let a = lang.children(inner).to_vec();
2581 if lang.kind(a[0]) != NodeKind::VarRef {
2582 return None;
2583 }
2584 Some((a[0], a[1]))
2585 };
2586 let (t1, va) = single_assign(ch[1])?;
2587 let (t2, vb) = single_assign(ch[2])?;
2588 if lang.var_name(t1) != lang.var_name(t2) || lang.var_name(t1).is_none() {
2589 return None;
2590 }
2591 let t = lang.build_ternary(c, va, vb);
2592 let with = lang.build_assign(t1, t);
2593 Some(Edit::Replace {
2594 target: id,
2595 with,
2596 })
2597 }
2598}
2599
2600
2601pub struct DeclAssignMerge;
2609
2610impl<L: Lang> Rule<L> for DeclAssignMerge {
2611 fn name(&self) -> &'static str {
2612 "decl_assign_merge"
2613 }
2614 fn kinds(&self) -> &'static [NodeKind] {
2615 &[NodeKind::VarDecl]
2616 }
2617 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2618 let RewriteCtx { lang, walk } = ctx;
2619 if lang.kind(id) != NodeKind::VarDecl || !lang.children(id).is_empty() {
2620 return None; }
2622 let name = lang.var_name(id)?.to_string();
2623 let parent = walk.parent(id)?;
2624 if lang.kind(parent) != NodeKind::Block {
2625 return None;
2626 }
2627 let idx = walk.index(id)?;
2628 let stmts = lang.children(parent).to_vec();
2629 let next = *stmts.get(idx + 1)?;
2630 let assign = match lang.kind(next) {
2632 NodeKind::Assign => next,
2633 NodeKind::ExprStmt => {
2634 let ch = lang.children(next);
2635 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2636 ch[0]
2637 } else {
2638 return None;
2639 }
2640 }
2641 _ => return None,
2642 };
2643 if lang.assign_op(assign).is_some() {
2644 return None;
2645 }
2646 let ach = lang.children(assign).to_vec();
2647 let (target, value) = (ach[0], ach[1]);
2648 if lang.kind(target) != NodeKind::VarRef || lang.var_name(target) != Some(name.as_str()) {
2649 return None;
2650 }
2651 if subtree_contains(&*lang, value, |n| {
2653 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2654 }) {
2655 return None;
2656 }
2657 Some(Edit::Multi(vec![
2658 Edit::Splice {
2659 node: id,
2660 index: 0,
2661 remove: 0,
2662 insert: vec![value],
2663 },
2664 Edit::Delete { node: next },
2665 ]))
2666 }
2667}
2668
2669pub struct AssignPropagation;
2678
2679impl<L: Lang> Rule<L> for AssignPropagation {
2680 fn name(&self) -> &'static str {
2681 "assign_propagation"
2682 }
2683 fn kinds(&self) -> &'static [NodeKind] {
2684 &[NodeKind::Assign, NodeKind::ExprStmt]
2685 }
2686 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2687 let RewriteCtx { lang, walk } = ctx;
2688 let (assign, stmt_node) = match lang.kind(id) {
2690 NodeKind::Assign => (id, id),
2691 NodeKind::ExprStmt => {
2692 let ch = lang.children(id);
2693 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2694 (ch[0], id)
2695 } else {
2696 return None;
2697 }
2698 }
2699 _ => return None,
2700 };
2701 if lang.assign_op(assign).is_some() {
2702 return None;
2703 }
2704 let ach = lang.children(assign).to_vec();
2705 let (target, value) = (ach[0], ach[1]);
2706 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
2707 return None;
2708 }
2709 if lang.kind(value) == NodeKind::ArrayLit {
2711 return None;
2712 }
2713 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
2715 return None;
2716 }
2717 let name = lang.var_name(target)?.to_string();
2718
2719 let parent = walk.parent(stmt_node)?;
2720 if lang.kind(parent) != NodeKind::Block {
2721 return None;
2722 }
2723 let idx = walk.index(stmt_node)?;
2724 let stmts = lang.children(parent).to_vec();
2725
2726 let anchored = stmts[..idx].iter().any(|&s| {
2728 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2729 });
2730 if !anchored {
2731 return None;
2732 }
2733
2734 if subtree_contains(&*lang, value, |n| {
2736 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2737 }) {
2738 return None;
2739 }
2740
2741 let name_key = lang.var_key(target)?;
2744 if count_name_refs(&*lang, &stmts[idx + 1..], name_key, &name) != 1 {
2745 return None;
2746 }
2747 let mut wa = Watch::<L>::new(name_key, &[]);
2748 for &s in &stmts[idx + 1..] {
2749 scan_region(&*lang, s, &mut wa);
2750 }
2751 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
2752 return None;
2753 }
2754 let use_id = wa.uses[0];
2755 if matches!(lang.literal(value), Some(LitRef::Null))
2762 && null_unsafe_position(&*lang, walk, use_id)
2763 {
2764 return None;
2765 }
2766 if lang.kind(value) == NodeKind::VarRef
2769 && typed_position_unsafe(&*lang, walk, use_id)
2770 {
2771 return None;
2772 }
2773 let mut watch_keys: Vec<L::NameKey> = Vec::new();
2775 collect_read_keys(&*lang, value, &mut watch_keys);
2776 watch_keys.push(name_key);
2777 let mut wb = Watch::<L>::new(name_key, &watch_keys);
2778 for &s in &stmts[idx + 1..] {
2779 let uses_before = wb.uses.len();
2780 scan_region(&*lang, s, &mut wb);
2781 if uses_before < wb.uses.len() {
2782 break; }
2784 }
2785 if wb.wrote(name_key) {
2787 return None;
2788 }
2789 let ve = lang.effect(value);
2790
2791 if ve <= Effect::MayRead {
2792 if let Some(target_key) = use_stmt_target_write_key(&*lang, &stmts[idx + 1..], use_id) {
2793 wb.clear_wrote(target_key);
2794 }
2795 if wb.any_write() {
2796 return None;
2797 }
2798 return Some(Edit::Multi(vec![
2799 Edit::Replace {
2800 target: use_id,
2801 with: value,
2802 },
2803 Edit::Delete { node: stmt_node },
2804 ]));
2805 }
2806
2807 if idx + 1 >= stmts.len() {
2809 return None;
2810 }
2811 let stmt = stmts[idx + 1];
2812 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
2813 return None;
2814 }
2815 match lang.kind(stmt) {
2816 NodeKind::Return
2817 | NodeKind::ExprStmt
2818 | NodeKind::Assign
2819 | NodeKind::Throw
2820 | NodeKind::VarDecl => {}
2821 _ => return None,
2822 }
2823 if !straight_path(&*lang, stmt, use_id) {
2824 return None;
2825 }
2826 if !prefix_effects_readable(&*lang, stmt, use_id) {
2827 return None;
2828 }
2829 Some(Edit::Multi(vec![
2830 Edit::Replace {
2831 target: use_id,
2832 with: value,
2833 },
2834 Edit::Delete { node: stmt_node },
2835 ]))
2836 }
2837}
2838
2839
2840fn use_stmt_target_write_key<L: Lang>(
2846 lang: &L,
2847 stmts_after: &[L::Id],
2848 use_id: L::Id,
2849) -> Option<L::NameKey> {
2850 for &s in stmts_after {
2851 if subtree_contains(lang, s, |n| n == use_id) {
2852 let assign = match lang.kind(s) {
2853 NodeKind::Assign => s,
2854 NodeKind::ExprStmt => {
2855 let ch = lang.children(s);
2856 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2857 ch[0]
2858 } else {
2859 return None;
2860 }
2861 }
2862 _ => return None,
2863 };
2864 if lang.assign_op(assign).is_some() {
2865 return None;
2866 }
2867 let ch = lang.children(assign);
2868 if ch.len() == 2 && ch[1] == use_id && lang.kind(ch[0]) == NodeKind::VarRef {
2869 return lang.var_key(ch[0]);
2870 }
2871 return None;
2872 }
2873 }
2874 None
2875}
2876
2877fn collect_read_keys<L: Lang>(lang: &L, id: L::Id, out: &mut Vec<L::NameKey>) {
2880 let mut stack = vec![id];
2881 while let Some(n) = stack.pop() {
2882 if lang.kind(n) == NodeKind::VarRef {
2883 if let Some(k) = lang.var_key(n) {
2884 if !out.contains(&k) {
2885 out.push(k);
2886 }
2887 }
2888 }
2889 for &c in lang.children(n) {
2890 stack.push(c);
2891 }
2892 }
2893}
2894
2895
2896pub struct TrailingContinue;
2903
2904impl<L: Lang> Rule<L> for TrailingContinue {
2905 fn name(&self) -> &'static str {
2906 "trailing_continue"
2907 }
2908 fn kinds(&self) -> &'static [NodeKind] {
2909 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
2910 }
2911 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2912 let RewriteCtx { lang, walk: _ } = ctx;
2913 let body = match lang.kind(id) {
2915 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
2916 *lang.children(id).last()?
2917 }
2918 _ => return None,
2919 };
2920 if lang.kind(body) != NodeKind::Block {
2921 return None;
2922 }
2923 let ch = lang.children(body).to_vec();
2924 let last = *ch.last()?;
2925 if lang.kind(last) != NodeKind::Continue || !lang.children(last).is_empty() {
2926 if lang.kind(last) != NodeKind::Continue {
2929 return None;
2930 }
2931 }
2932 Some(Edit::Delete { node: last })
2933 }
2934}
2935
2936pub struct MultiUseCopyPropagation;
2944
2945impl<L: Lang> Rule<L> for MultiUseCopyPropagation {
2946 fn name(&self) -> &'static str {
2947 "multi_use_copy"
2948 }
2949 fn kinds(&self) -> &'static [NodeKind] {
2950 &[NodeKind::Assign, NodeKind::ExprStmt]
2951 }
2952 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2953 let RewriteCtx { lang, walk } = ctx;
2954 let (assign, stmt_node) = match lang.kind(id) {
2956 NodeKind::Assign => (id, id),
2957 NodeKind::ExprStmt => {
2958 let ch = lang.children(id);
2959 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2960 (ch[0], id)
2961 } else {
2962 return None;
2963 }
2964 }
2965 _ => return None,
2966 };
2967 if lang.assign_op(assign).is_some() {
2968 return None;
2969 }
2970 let ach = lang.children(assign).to_vec();
2971 let (target, value) = (ach[0], ach[1]);
2972 if lang.kind(target) != NodeKind::VarRef || lang.kind(value) != NodeKind::VarRef {
2973 return None;
2974 }
2975 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
2977 return None;
2978 }
2979 if !lang.is_local_var(target) {
2980 return None;
2981 }
2982 let name = lang.var_name(target)?.to_string();
2983 let src = lang.var_name(value)?.to_string();
2984 if name == src {
2985 return None;
2986 }
2987
2988 let parent = walk.parent(stmt_node)?;
2989 if lang.kind(parent) != NodeKind::Block {
2990 return None;
2991 }
2992 let idx = walk.index(stmt_node)?;
2993 let stmts = lang.children(parent).to_vec();
2994
2995 let anchored = stmts[..idx].iter().any(|&s| {
2997 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2998 });
2999 if !anchored {
3000 return None;
3001 }
3002
3003 let name_key = lang.var_key(target)?;
3005 let src_key = lang.var_key(value)?;
3006 let mut w1 = Watch::<L>::new(name_key, &[name_key, src_key]);
3007 for &s in &stmts[idx + 1..] {
3008 scan_region(&*lang, s, &mut w1);
3009 }
3010 if w1.opaque || w1.shadowed || w1.uses.is_empty() {
3011 return None;
3012 }
3013 if w1.wrote(name_key) || w1.wrote(src_key) {
3014 return None;
3015 }
3016 let synth = count_name_refs(&*lang, &stmts[idx + 1..], name_key, &name);
3022 if synth != w1.uses.len() {
3023 return None;
3024 }
3025 if w1.uses
3034 .iter()
3035 .any(|&u| typed_position_unsafe(&*lang, walk, u))
3036 {
3037 return None;
3038 }
3039
3040 let mut edits: Vec<Edit<L>> = w1.uses
3041 .iter()
3042 .map(|&u| Edit::Replace {
3043 target: u,
3044 with: value,
3045 })
3046 .collect();
3047 edits.push(Edit::Delete { node: stmt_node });
3048 Some(Edit::Multi(edits))
3049 }
3050}
3051
3052
3053pub struct TrailingReturn;
3060
3061impl<L: Lang> Rule<L> for TrailingReturn {
3062 fn name(&self) -> &'static str {
3063 "trailing_return"
3064 }
3065 fn kinds(&self) -> &'static [NodeKind] {
3066 &[NodeKind::Return]
3067 }
3068 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3069 let root = ctx.root();
3070 let parent = ctx.parent(id)?;
3071 let lang = ctx.lang;
3072 if lang.kind(id) != NodeKind::Return || !lang.children(id).is_empty() {
3073 return None;
3074 }
3075 if parent != root || lang.kind(parent) != NodeKind::Block {
3076 return None;
3077 }
3078 if lang.children(parent).last() != Some(&id) {
3079 return None;
3080 }
3081 Some(Edit::Delete { node: id })
3082 }
3083}
3084
3085
3086pub struct TernaryBoolOp;
3093
3094impl<L: Lang> Rule<L> for TernaryBoolOp {
3095 fn name(&self) -> &'static str {
3096 "ternary_bool_op"
3097 }
3098 fn kinds(&self) -> &'static [NodeKind] {
3099 &[NodeKind::Ternary]
3100 }
3101 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3102 let RewriteCtx { lang, walk: _ } = ctx;
3103 if lang.kind(id) != NodeKind::Ternary {
3104 return None;
3105 }
3106 let ch = lang.children(id);
3107 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
3108 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
3109 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
3110 let with = if is_false(b) && lang.is_bool(a) {
3111 lang.build_bin(BinOp::And, c, a)
3112 } else if lb(b) && lang.is_bool(a) {
3113 return None;
3115 } else if lb(a) && lang.is_bool(b) {
3116 lang.build_bin(BinOp::Or, c, b)
3118 } else if is_false(a) && lang.is_bool(b) {
3119 return None;
3121 } else {
3122 return None;
3123 };
3124 Some(Edit::Replace {
3125 target: id,
3126 with,
3127 })
3128 }
3129}
3130
3131
3132pub struct StoreKill;
3141
3142pub fn scan_kill_event<L: Lang>(
3149 lang: &L,
3150 node: L::Id,
3151 name: &str,
3152 dominating: bool,
3153) -> StmtKill<L::Id> {
3154 match lang.kind(node) {
3155 NodeKind::VarRef => {
3156 if lang.var_name(node) == Some(name) {
3157 StmtKill::Read
3158 } else {
3159 StmtKill::None
3160 }
3161 }
3162 NodeKind::VarDecl => {
3163 if lang.var_name(node) == Some(name) {
3164 StmtKill::Shadow
3165 } else {
3166 for &c in lang.children(node) {
3168 let e = scan_kill_event(lang, c, name, dominating);
3169 if !matches!(e, StmtKill::None) {
3170 return e;
3171 }
3172 }
3173 StmtKill::None
3174 }
3175 }
3176 NodeKind::Assign => {
3177 let ch = lang.children(node);
3178 if let Some(&val) = ch.get(1) {
3180 let e = scan_kill_event(lang, val, name, dominating);
3181 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3182 return e;
3183 }
3184 }
3185 if lang.assign_op(node).is_none() {
3186 if let Some(&t) = ch.first() {
3187 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3188 if dominating {
3189 return StmtKill::DomWrite(node);
3190 }
3191 return StmtKill::None; }
3193 }
3194 }
3195 StmtKill::None
3196 }
3197 NodeKind::If | NodeKind::While | NodeKind::Ternary => {
3198 let ch = lang.children(node);
3201 if let Some(&c) = ch.first() {
3202 let e = scan_kill_event(lang, c, name, true);
3203 if !matches!(e, StmtKill::None) {
3204 return e;
3205 }
3206 }
3207 for &c in &ch[1..] {
3208 let e = scan_kill_event(lang, c, name, false);
3209 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3210 return e;
3211 }
3212 }
3213 StmtKill::None
3214 }
3215 NodeKind::Binary => {
3216 let Some(op) = lang.bin_op(node) else {
3217 return StmtKill::None;
3218 };
3219 let ch = lang.children(node);
3220 if op.is_short_circuit() {
3221 if let Some(&l) = ch.first() {
3223 let e = scan_kill_event(lang, l, name, true);
3224 if !matches!(e, StmtKill::None) {
3225 return e;
3226 }
3227 }
3228 if let Some(&r) = ch.get(1) {
3229 let e = scan_kill_event(lang, r, name, false);
3230 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3231 return e;
3232 }
3233 }
3234 StmtKill::None
3235 } else {
3236 for &c in ch {
3237 let e = scan_kill_event(lang, c, name, dominating);
3238 if !matches!(e, StmtKill::None) {
3239 return e;
3240 }
3241 }
3242 StmtKill::None
3243 }
3244 }
3245 NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
3246 let ch = lang.children(node);
3250 let dom_len = match lang.kind(node) {
3251 NodeKind::For => ch.len().saturating_sub(2),
3252 NodeKind::ForEach | NodeKind::Switch => 1,
3253 _ => 0,
3254 };
3255 for &c in &ch[..dom_len] {
3256 let e = scan_kill_event(lang, c, name, true);
3257 if !matches!(e, StmtKill::None) {
3258 return e;
3259 }
3260 }
3261 for &c in &ch[dom_len..] {
3262 let e = scan_kill_event(lang, c, name, false);
3263 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3264 return e;
3265 }
3266 }
3267 StmtKill::None
3268 }
3269 _ => {
3270 for &c in lang.children(node) {
3271 let e = scan_kill_event(lang, c, name, dominating);
3272 if !matches!(e, StmtKill::None) {
3273 return e;
3274 }
3275 }
3276 StmtKill::None
3277 }
3278 }
3279}
3280
3281pub fn collect_stmt_kill_events<L: Lang>(lang: &L, root: L::Id) -> Vec<(L::NameKey, StmtKill<L::Id>)> {
3286 use std::collections::BTreeMap;
3287 enum Act<Id> {
3288 Enter(Id, bool),
3289 AfterAssign(Id, bool),
3290 }
3291 let mut events: BTreeMap<L::NameKey, StmtKill<L::Id>> = BTreeMap::new();
3292 let mut stack = vec![Act::Enter(root, true)];
3293 while let Some(act) = stack.pop() {
3294 match act {
3295 Act::AfterAssign(id, dominating) => {
3296 if lang.assign_op(id).is_some() {
3297 continue;
3298 }
3299 let Some(&target) = lang.children(id).first() else {
3300 continue;
3301 };
3302 if lang.kind(target) != NodeKind::VarRef {
3303 continue;
3304 }
3305 let Some(key) = lang.var_key(target) else {
3306 continue;
3307 };
3308 if dominating {
3309 events.entry(key).or_insert(StmtKill::DomWrite(id));
3310 }
3311 }
3312 Act::Enter(node, dominating) => match lang.kind(node) {
3313 NodeKind::VarRef => {
3314 if let Some(key) = lang.var_key(node) {
3315 events.entry(key).or_insert(StmtKill::Read);
3316 }
3317 }
3318 NodeKind::VarDecl => {
3319 if let Some(key) = lang.var_key(node) {
3320 events.entry(key).or_insert(StmtKill::Shadow);
3321 }
3322 for &c in lang.children(node).iter().rev() {
3323 stack.push(Act::Enter(c, dominating));
3324 }
3325 }
3326 NodeKind::Assign => {
3327 let ch = lang.children(node);
3328 stack.push(Act::AfterAssign(node, dominating));
3329 if let Some(&val) = ch.get(1) {
3330 stack.push(Act::Enter(val, dominating));
3331 }
3332 }
3333 NodeKind::If | NodeKind::While | NodeKind::Ternary => {
3334 let ch = lang.children(node);
3335 for (i, &c) in ch.iter().enumerate().rev() {
3336 stack.push(Act::Enter(c, i == 0));
3337 }
3338 }
3339 NodeKind::Binary => {
3340 let ch = lang.children(node);
3341 let short = lang.bin_op(node).is_some_and(|op| op.is_short_circuit());
3342 for (i, &c) in ch.iter().enumerate().rev() {
3343 let child_dom = if short { i == 0 } else { dominating };
3344 stack.push(Act::Enter(c, child_dom));
3345 }
3346 }
3347 NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
3348 let ch = lang.children(node);
3349 let dom_len = match lang.kind(node) {
3350 NodeKind::For => ch.len().saturating_sub(2),
3351 NodeKind::ForEach | NodeKind::Switch => 1,
3352 _ => 0,
3353 };
3354 for (i, &c) in ch.iter().enumerate().rev() {
3355 stack.push(Act::Enter(c, i < dom_len));
3356 }
3357 }
3358 _ => {
3359 for &c in lang.children(node).iter().rev() {
3360 stack.push(Act::Enter(c, dominating));
3361 }
3362 }
3363 },
3364 }
3365 }
3366 events.into_iter().collect()
3367}
3368
3369impl<L: Lang> Rule<L> for StoreKill {
3370 fn name(&self) -> &'static str {
3371 "store_kill"
3372 }
3373 fn kinds(&self) -> &'static [NodeKind] {
3374 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
3375 }
3376 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3377 let RewriteCtx { lang, walk } = ctx;
3378 let (is_decl, stmt_node, first_value) = match lang.kind(id) {
3380 NodeKind::VarDecl => {
3381 if lang.children(id).is_empty() {
3382 return None;
3383 }
3384 (true, id, *lang.children(id).first()?)
3385 }
3386 NodeKind::Assign => {
3387 if lang.assign_op(id).is_some() {
3388 return None;
3389 }
3390 (false, id, *lang.children(id).get(1)?)
3391 }
3392 NodeKind::ExprStmt => {
3393 let ch = lang.children(id);
3394 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3395 let a = ch[0];
3396 if lang.assign_op(a).is_some() {
3397 return None;
3398 }
3399 (false, id, *lang.children(a).get(1)?)
3400 } else {
3401 return None;
3402 }
3403 }
3404 _ => return None,
3405 };
3406 let (name, name_key) = if is_decl {
3407 (lang.var_name(id)?.to_string(), lang.var_key(id)?)
3408 } else {
3409 let a = match lang.kind(id) {
3410 NodeKind::Assign => id,
3411 _ => lang.children(id)[0],
3412 };
3413 let t = *lang.children(a).first()?;
3414 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
3415 return None;
3416 }
3417 (lang.var_name(t)?.to_string(), lang.var_key(t)?)
3418 };
3419
3420 let parent = walk.parent(stmt_node)?;
3421 if lang.kind(parent) != NodeKind::Block {
3422 return None;
3423 }
3424 let idx = walk.index(stmt_node)?;
3425 let stmts = lang.children(parent).to_vec();
3426
3427 let mut killed_by: Option<(usize, L::Id)> = None;
3429 for (si, &s) in stmts.iter().enumerate().skip(idx + 1) {
3430 let event = lang
3431 .stmt_kill_event(s, name_key)
3432 .unwrap_or_else(|| scan_kill_event(&*lang, s, &name, true));
3433 match event {
3434 StmtKill::None => continue,
3435 StmtKill::Read | StmtKill::Shadow => return None,
3436 StmtKill::DomWrite(assign) => {
3437 killed_by = Some((si, assign));
3438 break;
3439 }
3440 }
3441 }
3442 let (si, kill_assign) = killed_by?;
3443 {
3451 let kill_container = match walk.parents.get(&kill_assign) {
3452 Some(&(p, _)) if lang.kind(p) == NodeKind::ExprStmt => p,
3453 _ => kill_assign,
3454 };
3455 if kill_container != stmts[si] {
3456 return None;
3457 }
3458 }
3459 for &s in &stmts[idx + 1..si] {
3465 if lang.effect(s) > Effect::MayRead {
3466 return None;
3467 }
3468 }
3469
3470 if is_decl {
3471 if lang.effect(first_value) > Effect::MayRead {
3474 return None;
3475 }
3476 let value = *lang.children(kill_assign).get(1)?;
3477 if is_literal(lang, value) {
3478 let assign_stmt = stmts[si];
3479 return Some(Edit::Multi(vec![
3480 Edit::Splice {
3481 node: id,
3482 index: 0,
3483 remove: 1,
3484 insert: vec![value],
3485 },
3486 Edit::Delete { node: assign_stmt },
3487 ]));
3488 }
3489 if lang.is_inferred_decl(id) {
3493 return None;
3494 }
3495 if lang.effect(first_value) > Effect::MayRead {
3496 return None;
3497 }
3498 return Some(Edit::Splice {
3499 node: id,
3500 index: 0,
3501 remove: 1,
3502 insert: Vec::new(),
3503 });
3504 }
3505 if lang.effect(first_value) > Effect::MayRead {
3507 return None;
3508 }
3509 Some(Edit::Delete { node: stmt_node })
3510 }
3511}
3512
3513fn is_literal<L: Lang>(lang: &L, n: L::Id) -> bool {
3515 matches!(lang.kind(n), NodeKind::Literal)
3516}
3517
3518pub fn default_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3524 vec![
3525 Box::new(DoubleNegFold),
3526 Box::new(BlockFlatten),
3527 Box::new(ParenRemoval),
3528 Box::new(ConstCondition),
3529 Box::new(BooleanReturn),
3530 Box::new(IfToTernary),
3531 Box::new(IfAssignTernary),
3532 Box::new(IfElseEmpty),
3533 Box::new(BoolCompare),
3534 Box::new(DoubleNot),
3535 Box::new(BoolNotFold),
3536 Box::new(BoolShortCircuit),
3537 Box::new(NotCompare),
3538 Box::new(TernaryFold),
3539 Box::new(TernaryBool),
3540 Box::new(TernaryBoolOp),
3541 Box::new(ConstFoldBin),
3542 Box::new(CmpConstFold),
3543 Box::new(BitIdentity),
3544 Box::new(ArithReassoc),
3545 Box::new(SelfAssign),
3546 Box::new(ArithIdentity),
3547 Box::new(ArithZero),
3548 Box::new(DeclAssignMerge),
3549 Box::new(LocalPropagation),
3550 Box::new(AssignPropagation),
3551 Box::new(MultiUseCopyPropagation),
3552 Box::new(TrailingReturn),
3553 Box::new(InverseAssignPair),
3554 Box::new(DeadDecl),
3555 Box::new(AssignBackFold),
3556 Box::new(DeadStore),
3557 Box::new(StoreKill),
3558 ]
3559}
3560
3561pub fn all_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3563 let mut rules = default_rules();
3564 rules.push(Box::new(UnreachableAfterTerminal));
3565 rules
3566}