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 if lang.assign_target_paren_required(inner) {
36 if let Some((p, i)) = walk.parents.get(&id) {
37 if lang.kind(*p) == NodeKind::Assign && *i == 0 {
38 return None;
39 }
40 }
41 }
42 if lang.grammar_paren_required(inner) {
47 return None;
48 }
49 Some(Edit::Replace {
50 target: id,
51 with: inner,
52 })
53 }
54}
55
56fn self_is_terminal<L: Lang>(lang: &L, n: L::Id) -> bool {
65 matches!(
66 lang.kind(n),
67 NodeKind::Return | NodeKind::Throw | NodeKind::Break | NodeKind::Continue | NodeKind::Yield
68 )
69}
70
71fn contains_yield<L: Lang>(lang: &L, n: L::Id) -> bool {
74 subtree_contains(lang, n, |x| lang.kind(x) == NodeKind::Yield)
75}
76
77pub struct ConstCondition;
78
79impl<L: Lang> Rule<L> for ConstCondition {
80 fn name(&self) -> &'static str {
81 "const_condition"
82 }
83 fn kinds(&self) -> &'static [NodeKind] {
84 &[NodeKind::If]
85 }
86 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
87 let RewriteCtx { lang, walk } = ctx;
88 if lang.kind(id) != NodeKind::If {
89 return None;
90 }
91 let ch = lang.children(id);
92 let cond = *ch.first()?;
93 match lang.literal(cond) {
94 Some(LitRef::Bool(true)) => {
95 let then = ch.get(1).copied()?;
101 if lang.contains_hoisted_decl(then) {
104 return None;
105 }
106 if let Some(&els) = ch.get(2) {
109 if contains_yield(&*lang, els) || lang.contains_hoisted_decl(els) {
110 return None;
111 }
112 }
113 let insert: Vec<L::Id> = if lang.kind(then) == NodeKind::Block {
114 lang.children(then).to_vec()
115 } else {
116 vec![then]
117 };
118 let terminates = insert.iter().any(|&s| self_is_terminal(lang, s));
119 if let (Some(parent), Some(index)) = (walk.parent(id), walk.index(id)) {
120 if lang.kind(parent) == NodeKind::Block {
121 let siblings = lang.children(parent).to_vec();
122 let remove = if terminates && index + 1 < siblings.len() {
123 siblings.len() - index
124 } else {
125 1
126 };
127 if remove > 1
130 && siblings[index + 1..]
131 .iter()
132 .any(|&s| contains_yield(&*lang, s))
133 {
134 return None;
135 }
136 return Some(Edit::Splice {
137 node: parent,
138 index,
139 remove,
140 insert,
141 });
142 }
143 if lang.kind(parent) == NodeKind::Case && terminates {
152 let siblings = lang.children(parent).to_vec();
153 if index + 1 < siblings.len() {
154 return None;
155 }
156 }
157 }
158 Some(Edit::Replace {
159 target: id,
160 with: then,
161 })
162 }
163 Some(LitRef::Bool(false)) => {
164 let then = *ch.get(1)?;
169 if contains_yield(&*lang, then) {
170 return None;
171 }
172 if let Some(&els) = ch.get(2) {
175 if lang.contains_hoisted_decl(els) {
176 return None;
177 }
178 }
179 let els = ch.get(2).copied();
180 if let (Some(parent), Some(index)) = (walk.parent(id), walk.index(id)) {
181 if lang.kind(parent) == NodeKind::Block {
182 let insert: Vec<L::Id> = match els {
183 Some(e) => {
184 if lang.kind(e) == NodeKind::Block {
185 lang.children(e).to_vec()
186 } else {
187 vec![e]
188 }
189 }
190 None => Vec::new(),
191 };
192 return Some(if insert.is_empty() {
193 Edit::Delete { node: id }
194 } else {
195 Edit::Splice {
196 node: parent,
197 index,
198 remove: 1,
199 insert,
200 }
201 });
202 }
203 }
204 match els {
205 Some(e) => Some(Edit::Replace {
206 target: id,
207 with: e,
208 }),
209 None => Some(Edit::Delete { node: id }),
210 }
211 }
212 _ => None,
213 }
214 }
215}
216
217static P_IF_RET_TF: Pat = Pat::Kind(
224 NodeKind::If,
225 &[Pat::Bind("cond"), Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)]), Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])],
226);
227static P_IF_RET_FT: Pat = Pat::Kind(
228 NodeKind::If,
229 &[Pat::Bind("cond"), Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)]), Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])],
230);
231static P_IF_BRET_TF: Pat = Pat::Kind(
233 NodeKind::If,
234 &[
235 Pat::Bind("cond"),
236 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])]),
237 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])]),
238 ],
239);
240static P_IF_BRET_FT: Pat = Pat::Kind(
241 NodeKind::If,
242 &[
243 Pat::Bind("cond"),
244 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])]),
245 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])]),
246 ],
247);
248
249pub struct BooleanReturn;
250
251impl<L: Lang> Rule<L> for BooleanReturn {
252 fn name(&self) -> &'static str {
253 "boolean_return"
254 }
255 fn kinds(&self) -> &'static [NodeKind] {
256 &[NodeKind::If]
257 }
258 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
259 let RewriteCtx { lang, walk: _ } = ctx;
260 if lang.kind(id) != NodeKind::If {
261 return None;
262 }
263 let negated = |cond: L::Id, lang: &mut L| {
265 let nc = lang.build_unary(UnOp::Not, cond);
266 lang.build_return(Some(nc))
267 };
268 for (pat, invert) in [
269 (&P_IF_RET_TF, false),
270 (&P_IF_BRET_TF, false),
271 (&P_IF_RET_FT, true),
272 (&P_IF_BRET_FT, true),
273 ] {
274 if let Some(b) = matches(&*lang, id, pat) {
275 let cond = lookup(&b, "cond")?;
276 if !lang.is_bool(cond) {
277 return None;
278 }
279 let ret = if invert {
280 negated(cond, lang)
281 } else {
282 lang.build_return(Some(cond))
283 };
284 return Some(Edit::Replace {
285 target: id,
286 with: ret,
287 });
288 }
289 }
290 None
291 }
292}
293
294fn lookup<Id: Copy>(binds: &[(&'static str, Id)], name: &str) -> Option<Id> {
295 binds.iter().find(|(n, _)| *n == name).map(|(_, id)| *id)
296}
297
298pub struct IfElseEmpty;
304
305impl<L: Lang> Rule<L> for IfElseEmpty {
306 fn name(&self) -> &'static str {
307 "if_else_empty"
308 }
309 fn kinds(&self) -> &'static [NodeKind] {
310 &[NodeKind::If]
311 }
312 fn structural(&self) -> bool {
313 true
317 }
318 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
319 let RewriteCtx { lang, walk } = ctx;
320 if lang.kind(id) != NodeKind::If {
321 return None;
322 }
323 let ch = lang.children(id);
324 let cond = *ch.first()?;
325 let is_empty_block = |n: L::Id| -> bool {
326 lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
327 };
328 match ch.len() {
329 3 => {
330 let (then, els) = (ch[1], ch[2]);
331 if is_empty_block(then) {
332 if is_empty_block(els) {
333 return Some(Edit::Splice {
335 node: id,
336 index: 0,
337 remove: 3,
338 insert: vec![cond, then],
339 });
340 }
341 let nc = lang.build_unary(UnOp::Not, cond);
343 return Some(Edit::Splice {
344 node: id,
345 index: 0,
346 remove: 3,
347 insert: vec![nc, els],
348 });
349 }
350 if is_empty_block(els) {
351 return Some(Edit::Splice {
352 node: id,
353 index: 0,
354 remove: 3,
355 insert: vec![cond, then],
356 });
357 }
358 None
359 }
360 2 => {
361 let then = ch[1];
362 let parent_is_block = walk
369 .parent(id)
370 .map(|p| lang.kind(p) == NodeKind::Block)
371 .unwrap_or(false);
372 if is_empty_block(then)
373 && lang.effect(cond) <= Effect::MayRead
374 && parent_is_block
375 {
376 return Some(Edit::Delete { node: id });
378 }
379 None
380 }
381 _ => None,
382 }
383 }
384}
385
386pub struct BoolCompare;
392
393impl<L: Lang> Rule<L> for BoolCompare {
394 fn name(&self) -> &'static str {
395 "bool_compare"
396 }
397 fn kinds(&self) -> &'static [NodeKind] {
398 &[NodeKind::Binary]
399 }
400 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
401 let RewriteCtx { lang, walk } = ctx;
402 if lang.kind(id) != NodeKind::Binary {
403 return None;
404 }
405 let op = lang.bin_op(id)?;
406 if !matches!(op, BinOp::Eq | BinOp::Ne) {
407 return None;
408 }
409 let ch = lang.children(id);
410 let (l, r) = (*ch.first()?, *ch.get(1)?);
411 let lit_bool = |n: L::Id| -> Option<bool> {
412 match lang.literal(n) {
413 Some(LitRef::Bool(b)) => Some(b),
414 _ => None,
415 }
416 };
417 let (x, lit) = match (lit_bool(l), lit_bool(r)) {
419 (None, Some(b)) => (l, b),
420 (Some(b), None) => (r, b),
421 _ => return None,
422 };
423 if !lang.is_bool(x) {
424 let in_cond = walk.parent(id).is_some_and(|p| {
429 matches!(lang.kind(p), NodeKind::If | NodeKind::While) && lang.children(p).first() == Some(&id)
430 });
431 if !in_cond {
432 return None;
433 }
434 }
435 let neg = match (op, lit) {
437 (BinOp::Eq, true) | (BinOp::Ne, false) => false,
438 (BinOp::Eq, false) | (BinOp::Ne, true) => true,
439 _ => return None,
440 };
441 let with = if neg {
442 lang.build_unary(UnOp::Not, x)
443 } else {
444 x
445 };
446 Some(Edit::Replace {
447 target: id,
448 with,
449 })
450 }
451}
452
453pub struct DoubleNot;
459
460impl<L: Lang> Rule<L> for DoubleNot {
461 fn name(&self) -> &'static str {
462 "double_not"
463 }
464 fn kinds(&self) -> &'static [NodeKind] {
465 &[NodeKind::Unary]
466 }
467 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
468 let RewriteCtx { lang, walk: _ } = ctx;
469 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
470 return None;
471 }
472 let inner = *lang.children(id).first()?;
473 if lang.kind(inner) != NodeKind::Unary || lang.un_op(inner) != Some(UnOp::Not) {
474 return None;
475 }
476 let x = *lang.children(inner).first()?;
477 if !lang.double_not_sound(x) {
481 return None;
482 }
483 Some(Edit::Replace {
484 target: id,
485 with: x,
486 })
487 }
488}
489
490pub struct BoolNotFold;
496
497impl<L: Lang> Rule<L> for BoolNotFold {
498 fn name(&self) -> &'static str {
499 "bool_not_fold"
500 }
501 fn kinds(&self) -> &'static [NodeKind] {
502 &[NodeKind::Unary]
503 }
504 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
505 let RewriteCtx { lang, walk: _ } = ctx;
506 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
507 return None;
508 }
509 let operand = *lang.children(id).first()?;
510 match lang.literal(operand) {
511 Some(LitRef::Bool(b)) => {
512 let folded = lang.build_bool(!b);
513 Some(Edit::Replace {
514 target: id,
515 with: folded,
516 })
517 }
518 _ => None,
519 }
520 }
521}
522
523pub struct SelfAssign;
528
529impl<L: Lang> Rule<L> for SelfAssign {
530 fn name(&self) -> &'static str {
531 "self_assign"
532 }
533 fn kinds(&self) -> &'static [NodeKind] {
534 &[NodeKind::Assign]
535 }
536 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
537 let RewriteCtx { lang, walk } = ctx;
538 if lang.kind(id) != NodeKind::Assign {
539 return None;
540 }
541 if lang.assign_op(id).is_some() {
542 return None; }
544 let ch = lang.children(id);
545 let (t, v) = (*ch.first()?, *ch.get(1)?);
546 if lang.kind(t) != NodeKind::VarRef || lang.kind(v) != NodeKind::VarRef {
547 return None;
548 }
549 if !lang.same_binding(t, v) || !lang.is_local_var(t) {
550 return None;
551 }
552 let (parent, _) = walk.parents.get(&id)?;
559 match lang.kind(*parent) {
560 NodeKind::ExprStmt => Some(Edit::Delete { node: *parent }),
561 NodeKind::Block => Some(Edit::Delete { node: id }),
562 _ => None,
563 }
564 }
565}
566
567pub struct DeadDecl;
581
582impl<L: Lang> Rule<L> for DeadDecl {
583 fn name(&self) -> &'static str {
584 "dead_decl"
585 }
586 fn kinds(&self) -> &'static [NodeKind] {
587 &[NodeKind::VarDecl]
588 }
589 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
590 let RewriteCtx { lang, walk } = ctx;
591 if lang.kind(id) != NodeKind::VarDecl {
592 return None;
593 }
594 if !lang.dead_decl_allowed(id) {
595 return None;
596 }
597 let ch = lang.children(id).to_vec();
598 if let Some(&v) = ch.first() {
601 if lang.effect(v) > Effect::MayRead {
602 return None;
603 }
604 if lang.value_may_throw_unresolved(v) {
605 return None;
606 }
607 let name_s = lang.var_name(id).unwrap_or("");
610 if subtree_contains(&*lang, v, |n| {
611 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name_s)
612 }) {
613 return None;
614 }
615 }
616 let parent = walk.parent(id)?;
617 if lang.kind(parent) != NodeKind::Block {
618 return None;
619 }
620 let idx = walk.index(id)?;
621 let stmts = lang.children(parent).to_vec();
622 let name_str = lang.var_name(id)?.to_string();
628 let name_key = lang.var_key(id)?;
629 for &s in &stmts[idx + 1..] {
630 if lang.stmt_has_opaque_region(s) {
632 return None;
633 }
634 match (lang.stmt_mentions(s, name_key), lang.stmt_opaque(s)) {
635 (Some(mentions), Some(opaque)) => {
636 if mentions || opaque {
639 return None;
640 }
641 }
642 _ => {
643 if subtree_contains(&*lang, s, |n| {
646 lang.kind(n) == NodeKind::VarRef
647 && lang.var_name(n) == Some(name_str.as_str())
648 }) {
649 return None;
650 }
651 let mut w = Watch::<L>::new(name_key, &[]);
652 scan_region(&*lang, s, &mut w);
653 if w.opaque || w.shadowed {
654 return None;
655 }
656 }
657 }
658 }
659 Some(Edit::Delete { node: id })
660 }
661}
662
663pub struct AssignBackFold;
678
679impl<L: Lang> Rule<L> for AssignBackFold {
680 fn name(&self) -> &'static str {
681 "assign_back_fold"
682 }
683 fn kinds(&self) -> &'static [NodeKind] {
684 &[NodeKind::VarDecl]
685 }
686 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
687 let RewriteCtx { lang, walk } = ctx;
688 if lang.kind(id) != NodeKind::VarDecl {
689 return None;
690 }
691 let ch = lang.children(id).to_vec();
692 let (&init, has_init) = (ch.first()?, !ch.is_empty());
693 if !has_init {
694 return None; }
696 let name_key = lang.var_key(id)?;
697 if !lang.is_wide_decl(id) {
701 return None;
702 }
703 let parent = walk.parent(id)?;
704 if lang.kind(parent) != NodeKind::Block {
705 return None;
706 }
707 let idx = walk.index(id)?;
708 let stmts = lang.children(parent).to_vec();
709
710 let name_str = lang.var_name(id)?.to_string();
715 for &s in &stmts[idx + 1..] {
716 let touched = match (lang.stmt_mentions(s, name_key), lang.stmt_opaque(s)) {
719 (Some(mentions), Some(opaque)) => mentions || opaque,
720 _ => {
721 let mut w = Watch::<L>::new(name_key, &[]);
722 scan_region(&*lang, s, &mut w);
723 let referenced = subtree_contains(&*lang, s, |n| {
724 lang.kind(n) == NodeKind::VarRef
725 && lang.var_name(n) == Some(name_str.as_str())
726 });
727 referenced || w.opaque || w.shadowed
728 }
729 };
730 if lang.effect(s) == Effect::Unknown {
734 return None;
735 }
736 if !touched {
737 continue; }
739 let assign = match lang.kind(s) {
741 NodeKind::Assign => s,
742 NodeKind::ExprStmt => {
743 let c = lang.children(s);
744 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
745 c[0]
746 } else {
747 return None;
748 }
749 }
750 _ => return None, };
752 let ach = lang.children(assign).to_vec();
753 if ach.len() != 2 {
754 return None;
755 }
756 let (target, rhs) = (ach[0], ach[1]);
757 if lang.kind(target) != NodeKind::VarRef || !lang.same_binding(id, target) {
758 return None;
759 }
760 let (op, k) = match lang.assign_op(assign) {
763 Some(op) => (op, lang.literal(rhs).and_then(|x| x.as_int())?),
764 None => {
765 if lang.kind(rhs) != NodeKind::Binary {
766 return None;
767 }
768 let op = lang.bin_op(rhs)?;
769 let rc = lang.children(rhs).to_vec();
770 if rc.len() != 2 || lang.kind(rc[0]) != NodeKind::VarRef {
771 return None;
772 }
773 if !lang.same_binding(id, rc[0]) {
775 return None;
776 }
777 (op, lang.literal(rc[1]).and_then(|x| x.as_int())?)
778 }
779 };
780 if op.is_short_circuit() || op.is_comparison() {
781 return None;
782 }
783 if matches!(op, BinOp::Div | BinOp::Rem) && k == 0 {
785 return None;
786 }
787 let lit = if lang.is_long_decl(id) {
792 lang.build_int(k, true)
793 } else {
794 lang.build_int(k, false)
795 };
796 let init = if lang.is_long_decl(id) {
797 match lang.literal(init) {
798 Some(crate::kind::LitRef::Int(v)) => lang.build_int(v, true),
799 _ => init,
800 }
801 } else {
802 init
803 };
804 let merged = lang.build_bin(op, init, lit);
805 return Some(Edit::Multi(vec![
807 Edit::Delete { node: s },
808 Edit::Splice { node: id, index: 0, remove: ch.len(), insert: vec![merged] },
809 ]));
810 }
811 None
812 }
813}
814
815pub struct ArithIdentity;
816
817impl<L: Lang> Rule<L> for ArithIdentity {
818 fn name(&self) -> &'static str {
819 "arith_identity"
820 }
821 fn kinds(&self) -> &'static [NodeKind] {
822 &[NodeKind::Binary]
823 }
824 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
825 let RewriteCtx { lang, walk: _ } = ctx;
826 if lang.kind(id) != NodeKind::Binary {
827 return None;
828 }
829 let op = lang.bin_op(id)?;
830 let ch = lang.children(id);
831 let (l, r) = (*ch.first()?, *ch.get(1)?);
832 let int_lit = |n: L::Id, v: i64| -> bool {
833 matches!(lang.literal(n), Some(l) if l.as_int() == Some(v))
834 };
835 let x = match op {
836 BinOp::Add if int_lit(r, 0) => l,
837 BinOp::Add if int_lit(l, 0) => r,
838 BinOp::Sub if int_lit(r, 0) => l,
839 BinOp::Mul if int_lit(r, 1) => l,
840 BinOp::Mul if int_lit(l, 1) => r,
841 BinOp::Div if int_lit(r, 1) => l,
842 _ => return None,
843 };
844 if !lang.is_exact_int(x) {
845 return None;
846 }
847 Some(Edit::Replace {
848 target: id,
849 with: x,
850 })
851 }
852}
853
854fn count_name_refs<L: Lang>(
875 lang: &L,
876 stmts: &[L::Id],
877 key: L::NameKey,
878 name: &str,
879) -> usize {
880 let mut n: usize = 0;
881 for &s in stmts {
882 match lang.stmt_var_ref_count(s, key) {
883 Some(c) => n += c as usize,
884 None => count_var_refs(lang, s, name, &mut n),
885 }
886 }
887 n
888}
889
890fn null_unsafe_position<L: Lang>(lang: &L, walk: &Walk<L>, use_id: L::Id) -> bool {
897 let mut cur = use_id;
898 while let Some(&(p, _)) = walk.parents.get(&cur) {
899 match lang.kind(p) {
900 NodeKind::Paren => cur = p,
901 NodeKind::Call
902 | NodeKind::New
903 | NodeKind::Member
904 | NodeKind::Index
905 | NodeKind::InstanceOf => return true,
906 _ => return false,
907 }
908 }
909 false
910}
911
912fn typed_position<L: Lang>(lang: &L, walk: &Walk<L>, use_id: L::Id) -> Option<L::Id> {
925 let mut cur = use_id;
926 while let Some(&(p, _)) = walk.parents.get(&cur) {
927 match lang.kind(p) {
928 NodeKind::Paren => cur = p,
929 NodeKind::Cast | NodeKind::InstanceOf => return Some(p),
930 _ => return None,
931 }
932 }
933 None
934}
935
936fn count_var_refs<L: Lang>(lang: &L, node: L::Id, name: &str, n: &mut usize) {
937 if lang.kind(node) == NodeKind::VarRef && lang.var_name(node) == Some(name) {
938 *n += 1;
939 }
940 for &c in lang.children(node) {
941 count_var_refs(lang, c, name, n);
942 }
943}
944
945pub struct LocalPropagation;
946
947impl<L: Lang> Rule<L> for LocalPropagation {
948 fn name(&self) -> &'static str {
949 "local_propagation"
950 }
951 fn kinds(&self) -> &'static [NodeKind] {
952 &[NodeKind::VarDecl]
953 }
954 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
955 let RewriteCtx { lang, walk } = ctx;
956 if lang.kind(id) != NodeKind::VarDecl {
957 return None;
958 }
959 let name = lang.var_name(id)?.to_string();
960 let ch = lang.children(id);
961 if ch.len() != 1 {
962 return None; }
964 let value = ch[0];
965 if lang.kind(value) == NodeKind::ArrayLit {
969 return None;
970 }
971 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
975 return None;
976 }
977 if subtree_contains(&*lang, value, |n| {
979 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
980 }) {
981 return None;
982 }
983 let parent = walk.parent(id)?;
984 if lang.kind(parent) != NodeKind::Block {
985 return None;
986 }
987 let index = walk.index(id)?;
988 let stmts: Vec<L::Id> = lang.children(parent).to_vec();
989 if index + 1 > stmts.len() {
990 return None;
991 }
992
993 let mut value = value;
999 if lang.is_char_decl(id) {
1000 match lang.literal(value) {
1001 Some(LitRef::Char(_)) => {}
1002 Some(LitRef::Int(v)) => {
1003 let c = u32::try_from(v)
1006 .ok()
1007 .filter(|&u| (0..=0xFFFF).contains(&u) && !(0xD800..=0xDFFF).contains(&u))
1008 .and_then(char::from_u32)?;
1009 value = lang.build_char(c);
1010 }
1011 _ => return None,
1012 }
1013 }
1014 if lang.is_long_decl(id) {
1019 if let Some(LitRef::Int(v)) = lang.literal(value) {
1020 value = lang.build_int(v, true);
1021 }
1022 }
1023 if !lang.is_wide_decl(id) && !lang.is_char_decl(id) {
1028 return None;
1029 }
1030
1031 if !lang.local_propagation_value_ok(id, value) {
1032 return None;
1033 }
1034 let name_str = lang.var_name(id)?.to_string();
1039 let name_key = lang.var_key(id)?;
1040 let mut captured = false;
1048 let mut syntactic_refs = 0usize;
1049 for &st in &stmts[index + 1..] {
1050 let (refs, cap) = match (
1051 lang.stmt_var_ref_count(st, name_key),
1052 lang.stmt_lambda_captures(st, name_key),
1053 ) {
1054 (Some(refs), Some(cap)) => (refs as usize, cap),
1055 _ => {
1056 let mut n = 0usize;
1057 count_var_refs(&*lang, st, &name_str, &mut n);
1058 let cap = subtree_contains(&*lang, st, |n| {
1059 if lang.is_function_like(n) {
1060 return subtree_contains(&*lang, n, |m| {
1061 lang.kind(m) == NodeKind::VarRef
1062 && lang.var_name(m) == Some(name_str.as_str())
1063 });
1064 }
1065 false
1066 });
1067 (n, cap)
1068 }
1069 };
1070 syntactic_refs += refs;
1071 captured |= cap;
1072 if captured || syntactic_refs > 1 {
1073 break;
1074 }
1075 }
1076 if captured || syntactic_refs != 1 {
1077 return None;
1078 }
1079 let mut wa = Watch::<L>::new(name_key, &[]);
1080 for &s in &stmts[index + 1..] {
1081 if lang.stmt_has_opaque_region(s) {
1083 return None;
1084 }
1085 scan_region(&*lang, s, &mut wa);
1086 }
1087 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
1088 return None;
1089 }
1090 let use_id = wa.uses[0];
1091
1092 if let Some(&(p, _)) = walk.parents.get(&use_id) {
1097 if lang.kind(p) == NodeKind::Assign && lang.assign_op(p).is_none() {
1098 let pch = lang.children(p);
1099 if pch.len() == 2
1100 && lang.kind(pch[0]) == NodeKind::VarRef
1101 && lang.same_binding(pch[0], use_id)
1102 && pch[1] == use_id
1103 {
1104 return None;
1105 }
1106 }
1107 }
1108 {
1113 let (up, head) = match walk.parents.get(&use_id) {
1114 Some(&(p, _)) => {
1115 (Some(p), lang.children(p).first() == Some(&use_id))
1116 }
1117 None => (None, false),
1118 };
1119 if lang.receiver_propagation_unsound(id, value, up, head) {
1120 return None;
1121 }
1122 }
1123 if matches!(
1130 lang.literal(value),
1131 Some(
1132 LitRef::Null
1133 | LitRef::Int(_)
1134 | LitRef::Long(_)
1135 | LitRef::Float(_)
1136 | LitRef::Double(_)
1137 | LitRef::Char(_)
1138 | LitRef::Bool(_)
1139 )
1140 ) {
1141 let mut cur = use_id;
1142 while let Some(&(p, _)) = walk.parents.get(&cur) {
1143 let at_head = lang.children(p).first() == Some(&cur);
1144 match lang.kind(p) {
1145 NodeKind::Member | NodeKind::Index if at_head => return None,
1147 NodeKind::Call if at_head => return None,
1149 NodeKind::InstanceOf if at_head => return None,
1153 NodeKind::ForEach => {
1155 let ch = lang.children(p);
1156 if ch.first() == Some(&cur) {
1157 return None;
1158 }
1159 }
1160 NodeKind::Synchronized => {
1162 let ch = lang.children(p);
1163 if ch.len() == 1 && ch[0] == cur {
1164 return None;
1165 }
1166 }
1167 _ => {}
1168 }
1169 cur = p;
1170 }
1171 }
1172 if matches!(lang.literal(value), Some(LitRef::Null))
1180 && null_unsafe_position(&*lang, walk, use_id)
1181 {
1182 return None;
1183 }
1184 if lang.kind(value) == NodeKind::VarRef
1188 && typed_position(&*lang, walk, use_id)
1189 .is_some_and(|t| !lang.var_cast_position_ok(value, t))
1190 {
1191 return None;
1192 }
1193 let mut watch_keys: Vec<L::NameKey> = Vec::new();
1195 collect_read_keys(&*lang, value, &mut watch_keys);
1196 watch_keys.push(name_key);
1197 let mut wb = Watch::<L>::new(name_key, &watch_keys);
1201 let mut use_stmt_idx = None;
1202 for (off, &s) in stmts[index + 1..].iter().enumerate() {
1203 let uses_before = wb.uses.len();
1204 scan_region(&*lang, s, &mut wb);
1205 if wb.uses.len() > uses_before {
1206 use_stmt_idx = Some(index + 1 + off);
1207 break; }
1209 }
1210 if subtree_contains(&*lang, value, |n| {
1217 if lang.kind(n) != NodeKind::VarRef || lang.is_local_var(n) {
1218 return false;
1219 }
1220 if !lang.var_name(n).is_some_and(|nm| lang.is_field_name(nm)) {
1223 return false;
1224 }
1225 let is_callee = match walk.parent(n) {
1226 Some(p) => {
1227 matches!(lang.kind(p), NodeKind::Call) && lang.children(p).first() == Some(&n)
1228 }
1229 None => false,
1230 };
1231 !is_callee
1232 }) {
1233 if let Some(ui) = use_stmt_idx {
1234 for &s in &stmts[index + 1..=ui] {
1235 if lang.effect(s) != Effect::Pure {
1236 return None;
1237 }
1238 }
1239 }
1240 }
1241 if let Some(ui) = use_stmt_idx {
1245 if count_name_refs(&*lang, &stmts[ui + 1..], name_key, &name_str) != 0 {
1247 return None;
1248 }
1249 let mut wc = Watch::<L>::new(name_key, &[name_key]);
1250 for &s in &stmts[ui + 1..] {
1251 scan_region(&*lang, s, &mut wc);
1252 if wc.opaque || !wc.uses.is_empty() || wc.wrote(name_key) || wc.shadowed {
1253 return None;
1254 }
1255 }
1256 }
1257 let ve = lang.effect(value);
1258
1259 if ve <= Effect::MayRead {
1260 if let Some(target_key) =
1263 use_stmt_target_write_key(&*lang, &stmts[index + 1..], use_id)
1264 {
1265 wb.clear_wrote(target_key);
1266 }
1267 if wb.opaque || wb.any_write() {
1268 return None;
1269 }
1270 return Some(Edit::Multi(vec![
1271 Edit::Replace {
1272 target: use_id,
1273 with: value,
1274 },
1275 Edit::Delete { node: id },
1276 ]));
1277 }
1278
1279 if index + 1 >= stmts.len() {
1281 return None;
1282 }
1283 let stmt = stmts[index + 1];
1284 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
1285 return None;
1286 }
1287 match lang.kind(stmt) {
1288 NodeKind::Return
1291 | NodeKind::ExprStmt
1292 | NodeKind::Assign
1293 | NodeKind::Throw
1294 | NodeKind::VarDecl => {}
1295 _ => return None,
1296 }
1297 if !straight_path(&*lang, stmt, use_id) {
1298 return None;
1299 }
1300 if !prefix_effects_readable(&*lang, stmt, use_id) {
1301 return None;
1302 }
1303 Some(Edit::Multi(vec![
1304 Edit::Replace {
1305 target: use_id,
1306 with: value,
1307 },
1308 Edit::Delete { node: id },
1309 ]))
1310 }
1311}
1312
1313pub(crate) struct Watch<L: Lang> {
1319 opaque: bool,
1321 name: L::NameKey,
1323 watch: Vec<L::NameKey>,
1325 writes_hit: Vec<bool>,
1327 shadowed: bool,
1328 uses: Vec<L::Id>,
1330}
1331
1332impl<L: Lang> Watch<L> {
1333 pub(crate) fn new(name: L::NameKey, watch: &[L::NameKey]) -> Self {
1334 Watch {
1335 opaque: false,
1336 name,
1337 watch: watch.to_vec(),
1338 writes_hit: vec![false; watch.len()],
1339 shadowed: false,
1340 uses: Vec::new(),
1341 }
1342 }
1343 pub(crate) fn wrote(&self, k: L::NameKey) -> bool {
1345 self.watch
1346 .iter()
1347 .zip(&self.writes_hit)
1348 .any(|(w, h)| *h && *w == k)
1349 }
1350 pub(crate) fn any_write(&self) -> bool {
1352 self.writes_hit.iter().any(|h| *h)
1353 }
1354 pub(crate) fn clear_wrote(&mut self, k: L::NameKey) {
1356 for (w, h) in self.watch.iter().zip(self.writes_hit.iter_mut()) {
1357 if *w == k {
1358 *h = false;
1359 }
1360 }
1361 }
1362}
1363
1364fn scan_region<L: Lang>(lang: &L, node: L::Id, w: &mut Watch<L>) {
1366 lang.debug_verify_events(node);
1367 if let Some(events) = lang.region_events(node) {
1368 let lo = events.partition_point(|e| e.key < w.name);
1370 let hi = events.partition_point(|e| e.key <= w.name);
1371 for e in &events[lo..hi] {
1372 match e.kind {
1373 crate::kind::EventKind::Use => w.uses.push(e.node),
1374 crate::kind::EventKind::Shadow => w.shadowed = true,
1375 crate::kind::EventKind::Write => {
1376 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1377 if *wn == w.name {
1378 *hit = true;
1379 }
1380 }
1381 }
1382 crate::kind::EventKind::Opaque => w.opaque = true,
1385 }
1386 }
1387 for (i, wk) in w.watch.iter().enumerate() {
1389 if *wk == w.name {
1390 continue; }
1392 let lo = events.partition_point(|e| e.key < *wk);
1393 let hi = events.partition_point(|e| e.key <= *wk);
1394 for e in &events[lo..hi] {
1395 if e.kind == crate::kind::EventKind::Write {
1396 w.writes_hit[i] = true;
1397 break; }
1399 }
1400 }
1401 return;
1402 }
1403 if lang.is_opaque(node) {
1405 w.opaque = true;
1406 return;
1407 }
1408 match lang.kind(node) {
1410 NodeKind::Assign => {
1411 let ch = lang.children(node);
1412 if let Some(&t) = ch.first() {
1413 if lang.kind(t) == NodeKind::VarRef {
1414 if let Some(k) = lang.var_key(t) {
1415 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1416 if *wn == k {
1417 *hit = true;
1418 }
1419 }
1420 }
1421 if lang.assign_op(node).is_some() {
1423 scan_region(lang, t, w);
1424 }
1425 } else {
1426 scan_region(lang, t, w);
1428 }
1429 }
1430 if let Some(&v) = ch.get(1) {
1431 scan_region(lang, v, w);
1432 }
1433 }
1434 NodeKind::Unary if lang.un_op(node).is_some_and(|o| o.is_incdec()) => {
1435 let ch = lang.children(node);
1436 if let Some(&t) = ch.first() {
1437 if lang.kind(t) == NodeKind::VarRef {
1438 if let Some(k) = lang.var_key(t) {
1439 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1440 if *wn == k {
1441 *hit = true;
1442 }
1443 }
1444 }
1445 scan_region(lang, t, w);
1447 } else {
1448 scan_region(lang, t, w);
1449 }
1450 }
1451 }
1452 NodeKind::VarDecl => {
1453 if let Some(k) = lang.var_key(node) {
1454 if k == w.name {
1455 w.shadowed = true;
1456 }
1457 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1458 if *wn == k {
1459 *hit = true;
1460 }
1461 }
1462 }
1463 for &c in lang.children(node) {
1464 scan_region(lang, c, w);
1465 }
1466 }
1467 NodeKind::ForEach | NodeKind::Catch => {
1470 if let Some(k) = lang.var_key(node) {
1471 if k == w.name {
1472 w.shadowed = true;
1473 }
1474 if lang.kind(node) == NodeKind::ForEach {
1475 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1476 if *wn == k {
1477 *hit = true;
1478 }
1479 }
1480 }
1481 }
1482 for &c in lang.children(node) {
1483 scan_region(lang, c, w);
1484 }
1485 }
1486 NodeKind::VarRef => {
1487 if lang.var_key(node) == Some(w.name) {
1488 w.uses.push(node);
1489 }
1490 }
1491 NodeKind::Raw => {
1492 w.opaque = true;
1494 }
1495 _ => {
1496 for &c in lang.children(node) {
1497 scan_region(lang, c, w);
1498 }
1499 }
1500 }
1501}
1502
1503fn straight_path<L: Lang>(lang: &L, root: L::Id, target: L::Id) -> bool {
1505 fn go<L: Lang>(lang: &L, node: L::Id, target: L::Id) -> bool {
1506 if node == target {
1507 return true;
1508 }
1509 match lang.kind(node) {
1510 NodeKind::Ternary | NodeKind::If => return false,
1511 NodeKind::Binary if lang.bin_op(node).is_some_and(|o| o.is_short_circuit()) => {
1512 return false
1513 }
1514 _ => {}
1515 }
1516 for &c in lang.children(node) {
1517 if subtree_contains(lang, c, |n| n == target) {
1518 return go(lang, c, target);
1519 }
1520 }
1521 false
1522 }
1523 go(lang, root, target)
1524}
1525
1526
1527fn in_tag_position<L: Lang>(lang: &L, walk: &crate::walk::Walk<L>, id: L::Id) -> bool {
1536 let Some((p, i)) = walk.parents.get(&id) else { return false };
1537 *i == 0
1538 && lang.kind(*p) == NodeKind::Custom(1) && lang.is_tagged_template(*p)
1540}
1541
1542pub struct BoolShortCircuit;
1543
1544impl<L: Lang> Rule<L> for BoolShortCircuit {
1545 fn name(&self) -> &'static str {
1546 "bool_short_circuit"
1547 }
1548 fn kinds(&self) -> &'static [NodeKind] {
1549 &[NodeKind::Binary]
1550 }
1551 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1552 let RewriteCtx { lang, walk } = ctx;
1553 if lang.kind(id) != NodeKind::Binary {
1554 return None;
1555 }
1556 let op = lang.bin_op(id)?;
1557 if !op.is_short_circuit() {
1558 return None;
1559 }
1560 if in_tag_position(&*lang, &walk, id) {
1564 return None;
1565 }
1566 if let Some((p, i)) = walk.parents.get(&id) {
1567 if *i == 0 && lang.kind(*p) == NodeKind::Unary && lang.un_op(*p) == Some(UnOp::Delete) {
1568 return None;
1569 }
1570 }
1571 let ch = lang.children(id);
1572 let (l, r) = (*ch.first()?, *ch.get(1)?);
1573 let is_lit = |n: L::Id, v: bool| matches!(lang.literal(n), Some(LitRef::Bool(b)) if b == v);
1574 let with = match (op, is_lit(l, true), is_lit(l, false)) {
1576 (BinOp::And, _, true) => l, (BinOp::Or, true, _) => l, (BinOp::And, true, _) => r, (BinOp::Or, _, true) => r, _ => {
1581 match (op, is_lit(r, true), is_lit(r, false)) {
1587 (BinOp::And, true, _) if lang.is_bool(l) => l,
1588 (BinOp::Or, _, true) if lang.is_bool(l) => l,
1589 (BinOp::And, _, true) | (BinOp::Or, true, _)
1594 if lang.is_bool(l) && lang.effect(l) <= Effect::MayRead => {
1595 r
1596 }
1597 _ => return None,
1598 }
1599 }
1600 };
1601 Some(Edit::Replace {
1602 target: id,
1603 with,
1604 })
1605 }
1606}
1607
1608pub struct NotCompare;
1614
1615impl<L: Lang> Rule<L> for NotCompare {
1616 fn name(&self) -> &'static str {
1617 "not_compare"
1618 }
1619 fn kinds(&self) -> &'static [NodeKind] {
1620 &[NodeKind::Unary]
1621 }
1622 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1623 let RewriteCtx { lang, walk: _ } = ctx;
1624 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
1625 return None;
1626 }
1627 let inner = *lang.children(id).first()?;
1628 if lang.kind(inner) != NodeKind::Binary {
1629 return None;
1630 }
1631 let op = lang.bin_op(inner)?;
1632 let neg = match op {
1633 BinOp::Eq => BinOp::Ne,
1634 BinOp::Ne => BinOp::Eq,
1635 BinOp::Lt => BinOp::Ge,
1636 BinOp::Le => BinOp::Gt,
1637 BinOp::Gt => BinOp::Le,
1638 BinOp::Ge => BinOp::Lt,
1639 _ => return None,
1640 };
1641 if op.is_short_circuit() {
1642 return None;
1643 }
1644 if matches!(op, BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) {
1646 let ch = lang.children(inner);
1647 if !lang.is_exact_int(*ch.first()?) || !lang.is_exact_int(*ch.get(1)?) {
1648 return None;
1649 }
1650 }
1651 let ch = lang.children(inner).to_vec();
1652 let rebuilt = lang.build_bin(neg, ch[0], ch[1]);
1653 Some(Edit::Replace {
1654 target: id,
1655 with: rebuilt,
1656 })
1657 }
1658}
1659
1660fn in_inferred_var_init<L: Lang>(lang: &L, walk: &Walk<L>, mut id: L::Id) -> bool {
1670 while let Some(&(p, _)) = walk.parents.get(&id) {
1671 match lang.kind(p) {
1672 NodeKind::Paren | NodeKind::Ternary => id = p,
1673 NodeKind::VarDecl => return lang.is_inferred_decl(p),
1674 _ => return false,
1675 }
1676 }
1677 false
1678}
1679
1680pub struct TernaryFold;
1681
1682impl<L: Lang> Rule<L> for TernaryFold {
1683 fn name(&self) -> &'static str {
1684 "ternary_fold"
1685 }
1686 fn kinds(&self) -> &'static [NodeKind] {
1687 &[NodeKind::Ternary]
1688 }
1689 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1690 let RewriteCtx { lang, walk } = ctx;
1691 if lang.kind(id) != NodeKind::Ternary {
1692 return None;
1693 }
1694 if in_tag_position(&*lang, &walk, id) {
1695 return None;
1696 }
1697 let ch = lang.children(id);
1698 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1699 match lang.literal(c) {
1700 Some(LitRef::Bool(true)) => {
1706 if !in_inferred_var_init(&*lang, walk, id) {
1707 return Some(Edit::Replace { target: id, with: a });
1708 }
1709 }
1710 Some(LitRef::Bool(false))
1711 if !in_inferred_var_init(&*lang, walk, id) => {
1712 return Some(Edit::Replace { target: id, with: b });
1713 }
1714 _ => {}
1715 }
1716 if crate::analysis::structurally_equal(&*lang, a, b) && lang.effect(c) <= Effect::MayRead {
1719 return Some(Edit::Replace { target: id, with: a });
1720 }
1721 None
1722 }
1723}
1724
1725pub struct TernaryBool;
1730
1731impl<L: Lang> Rule<L> for TernaryBool {
1732 fn name(&self) -> &'static str {
1733 "ternary_bool"
1734 }
1735 fn kinds(&self) -> &'static [NodeKind] {
1736 &[NodeKind::Ternary]
1737 }
1738 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1739 let RewriteCtx { lang, walk: _ } = ctx;
1740 if lang.kind(id) != NodeKind::Ternary {
1741 return None;
1742 }
1743 let ch = lang.children(id);
1744 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1745 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
1746 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
1747 if !lang.is_bool(c) {
1748 return None;
1749 }
1750 let with = if lb(a) && is_false(b) {
1751 c
1752 } else if is_false(a) && lb(b) {
1753 lang.build_unary(UnOp::Not, c)
1754 } else {
1755 return None;
1756 };
1757 Some(Edit::Replace {
1758 target: id,
1759 with,
1760 })
1761 }
1762}
1763
1764pub struct ConstFoldBin;
1771
1772impl<L: Lang> Rule<L> for ConstFoldBin {
1773 fn name(&self) -> &'static str {
1774 "const_fold_bin"
1775 }
1776 fn kinds(&self) -> &'static [NodeKind] {
1777 &[NodeKind::Binary]
1778 }
1779 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1780 let RewriteCtx { lang, walk: _ } = ctx;
1781 if lang.kind(id) != NodeKind::Binary {
1782 return None;
1783 }
1784 let op = lang.bin_op(id)?;
1785 if op.is_short_circuit() || op.is_comparison() {
1786 return None;
1787 }
1788 let ch = lang.children(id);
1789 let (l, r) = (*ch.first()?, *ch.get(1)?);
1790 lang.literal(l)?;
1792 lang.literal(r)?;
1793 let with = lang.fold_lit_bin(op, l, r)?;
1794 Some(Edit::Replace {
1795 target: id,
1796 with,
1797 })
1798 }
1799}
1800
1801pub struct ArithZero;
1810
1811impl<L: Lang> Rule<L> for ArithZero {
1812 fn name(&self) -> &'static str {
1813 "arith_zero"
1814 }
1815 fn kinds(&self) -> &'static [NodeKind] {
1816 &[NodeKind::Binary]
1817 }
1818 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1819 let RewriteCtx { lang, walk: _ } = ctx;
1820 if lang.kind(id) != NodeKind::Binary {
1821 return None;
1822 }
1823 let op = lang.bin_op(id)?;
1824 let ch = lang.children(id);
1825 let (l, r) = (*ch.first()?, *ch.get(1)?);
1826 let int_lit = |n: L::Id, v: i64| lang.literal(n).and_then(|x| x.as_int()) == Some(v);
1827
1828 if matches!(op, BinOp::Shl | BinOp::Shr | BinOp::UShr) && int_lit(r, 0) {
1832 if !lang.is_exact_int(l) {
1833 return None;
1834 }
1835 return Some(Edit::Replace {
1836 target: id,
1837 with: l,
1838 });
1839 }
1840 if op == BinOp::Mul {
1842 let (zero, other) = if int_lit(l, 0) {
1843 (l, r)
1844 } else if int_lit(r, 0) {
1845 (r, l)
1846 } else {
1847 return None;
1848 };
1849 if !lang.is_exact_int(other) || lang.effect(other) > Effect::MayRead {
1850 return None;
1851 }
1852 return Some(Edit::Replace {
1853 target: id,
1854 with: zero,
1855 });
1856 }
1857 if op == BinOp::Sub
1859 && lang.kind(l) == NodeKind::VarRef
1860 && lang.kind(r) == NodeKind::VarRef
1861 && lang.same_binding(l, r)
1862 && lang.is_local_var(l)
1863 && lang.is_exact_int(l)
1864 {
1865 let with = lang.build_int(0, false);
1866 return Some(Edit::Replace {
1867 target: id,
1868 with,
1869 });
1870 }
1871 None
1872 }
1873}
1874
1875pub struct InverseAssignPair;
1898
1899impl<L: Lang> Rule<L> for InverseAssignPair {
1900 fn name(&self) -> &'static str {
1901 "inverse_assign_pair"
1902 }
1903 fn kinds(&self) -> &'static [NodeKind] {
1904 &[NodeKind::Block]
1905 }
1906 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1907 let RewriteCtx { lang, .. } = ctx;
1908 let ch = lang.children(id).to_vec();
1909 if ch.len() < 2 {
1910 return None;
1911 }
1912 for w in 0..ch.len() - 1 {
1913 let (s1, s2) = (ch[w], ch[w + 1]);
1914 let Some(a1) = norm_assign(lang, s1) else { continue };
1916 let Some(a2) = norm_assign(lang, s2) else { continue };
1917 if a1.key != a2.key
1919 || !lang.same_binding(a1.target, a2.target)
1920 || !a1.local
1921 || !a2.local
1922 {
1923 continue;
1924 }
1925 if !a1.exact_int || !a2.exact_int {
1927 continue;
1928 }
1929 if a1.k != a2.k {
1931 continue;
1932 }
1933 let inverse = match (a1.op, a2.op) {
1934 (BinOp::Add, BinOp::Sub) | (BinOp::Sub, BinOp::Add) => true,
1935 (BinOp::BitXor, BinOp::BitXor) => true,
1936 _ => false,
1937 };
1938 if !inverse {
1939 continue;
1940 }
1941 return Some(Edit::Multi(vec![
1943 Edit::Delete { node: s2 },
1944 Edit::Delete { node: s1 },
1945 ]));
1946 }
1947 None
1948 }
1949}
1950
1951struct NormAssign<L: Lang> {
1955 key: L::NameKey,
1956 target: L::Id,
1957 op: BinOp,
1958 k: i64,
1959 local: bool,
1960 exact_int: bool,
1962}
1963
1964fn norm_assign<L: Lang>(lang: &L, stmt: L::Id) -> Option<NormAssign<L>> {
1965 let a = match lang.kind(stmt) {
1967 NodeKind::ExprStmt => {
1968 let c = lang.children(stmt);
1969 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
1970 c[0]
1971 } else {
1972 return None;
1973 }
1974 }
1975 NodeKind::Assign => stmt,
1976 _ => return None,
1977 };
1978 let ch = lang.children(a).to_vec();
1979 if ch.len() != 2 {
1980 return None;
1981 }
1982 let (target, rhs) = (ch[0], ch[1]);
1983 if lang.kind(target) != NodeKind::VarRef {
1984 return None;
1985 }
1986 let key = lang.var_key(target)?;
1987 let local = lang.is_local_var(target);
1988 let exact_int = lang.is_exact_int(target);
1989 match lang.assign_op(a) {
1993 Some(op) => {
1995 let k = lang.literal(rhs).and_then(|x| x.as_int())?;
1996 Some(NormAssign { key, target, op, k, local, exact_int })
1997 }
1998 None => {
2000 if lang.kind(rhs) != NodeKind::Binary {
2001 return None;
2002 }
2003 let op = lang.bin_op(rhs)?;
2004 let rc = lang.children(rhs).to_vec();
2005 if rc.len() != 2 {
2006 return None;
2007 }
2008 if lang.kind(rc[0]) != NodeKind::VarRef || !lang.same_binding(target, rc[0]) {
2010 return None;
2011 }
2012 let k = lang.literal(rc[1]).and_then(|x| x.as_int())?;
2013 Some(NormAssign { key, target, op, k, local, exact_int })
2014 }
2015 }
2016}
2017
2018pub struct DeadStore;
2019
2020impl<L: Lang> Rule<L> for DeadStore {
2021 fn name(&self) -> &'static str {
2022 "dead_store"
2023 }
2024 fn kinds(&self) -> &'static [NodeKind] {
2025 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
2026 }
2027 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2028 let RewriteCtx { lang, walk } = ctx;
2029 let (decl_form, first_assign) = match lang.kind(id) {
2031 NodeKind::VarDecl => {
2032 if lang.children(id).is_empty() {
2033 return None; }
2035 (true, None)
2036 }
2037 NodeKind::Assign => (false, Some(id)),
2038 NodeKind::ExprStmt => {
2039 let ch = lang.children(id);
2040 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2041 (false, Some(ch[0]))
2042 } else {
2043 return None;
2044 }
2045 }
2046 _ => return None,
2047 };
2048 if decl_form && !lang.dead_decl_allowed(id) {
2049 return None;
2052 }
2053 let first_value = if decl_form {
2054 *lang.children(id).first()?
2055 } else {
2056 let a = first_assign?;
2057 if lang.assign_op(a).is_some() {
2058 return None; }
2060 *lang.children(a).get(1)?
2061 };
2062 if lang.value_may_throw_unresolved(first_value) {
2063 return None;
2064 }
2065
2066 let parent = walk.parent(id)?;
2068 if lang.kind(parent) != NodeKind::Block {
2069 return None;
2070 }
2071 let idx = walk.index(id)?;
2072 let stmts = lang.children(parent).to_vec();
2073 let next_stmt = *stmts.get(idx + 1)?;
2074
2075 let second = match lang.kind(next_stmt) {
2078 NodeKind::Assign => Some(next_stmt),
2079 NodeKind::ExprStmt => {
2080 let ch = lang.children(next_stmt);
2081 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2082 Some(ch[0])
2083 } else {
2084 None
2085 }
2086 }
2087 _ => None,
2088 };
2089 let Some(second) = second else {
2090 let (name_node, target_local) = if decl_form {
2099 (id, true)
2100 } else {
2101 let t = *lang.children(first_assign?).first()?;
2102 if lang.kind(t) != NodeKind::VarRef {
2103 return None;
2104 }
2105 (t, lang.is_local_var(t))
2106 };
2107 if !target_local {
2109 return None;
2110 }
2111 let name = lang.var_name(name_node)?.to_string();
2112 let declared_in_this_block = if decl_form {
2113 true } else if parent == walk.root {
2115 true
2118 } else {
2119 stmts[..idx].iter().any(|&s| {
2122 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2123 })
2124 };
2125 if !declared_in_this_block {
2126 return None;
2127 }
2128 for &s in &stmts[..idx] {
2132 if lang.stmt_has_opaque_region(s) {
2133 return None;
2134 }
2135 }
2136 let name_key0 = lang.var_key(name_node)?;
2137 for &s in &stmts[idx + 1..] {
2143 if lang.stmt_has_opaque_region(s) {
2144 return None;
2145 }
2146 match (lang.stmt_mentions(s, name_key0), lang.stmt_opaque(s)) {
2147 (Some(mentions), Some(opaque)) => {
2148 if mentions || opaque {
2149 return None;
2150 }
2151 }
2152 _ => {
2153 let mut w0 = Watch::<L>::new(name_key0, &[name_key0]);
2154 scan_region(&*lang, s, &mut w0);
2155 if w0.opaque
2156 || !w0.uses.is_empty()
2157 || w0.wrote(name_key0)
2158 || w0.shadowed
2159 {
2160 return None;
2161 }
2162 if subtree_contains(&*lang, s, |n| {
2163 lang.kind(n) == NodeKind::VarRef
2164 && lang.var_name(n) == Some(name.as_str())
2165 }) {
2166 return None;
2167 }
2168 }
2169 }
2170 }
2171 if lang.effect(first_value) > Effect::MayRead {
2172 return None;
2173 }
2174 return Some(Edit::Delete { node: id });
2175 };
2176 if lang.assign_op(second).is_some() {
2177 return None;
2178 }
2179 let nch = lang.children(second);
2180 let (target, value) = (*nch.first()?, *nch.get(1)?);
2181 if lang.kind(target) != NodeKind::VarRef {
2182 return None;
2183 }
2184
2185 if decl_form {
2187 if !lang.same_binding(id, target) {
2188 return None;
2189 }
2190 } else {
2191 let a = first_assign?;
2192 let t = *lang.children(a).first()?;
2193 if lang.kind(t) != NodeKind::VarRef
2194 || !lang.same_binding(t, target)
2195 || !lang.is_local_var(t)
2196 {
2197 return None;
2198 }
2199 }
2200 if lang.stmt_has_opaque_region(value)
2204 || subtree_contains(&*lang, value, |n| {
2205 lang.kind(n) == NodeKind::VarRef && lang.same_binding(n, target)
2206 })
2207 {
2208 return None;
2209 }
2210 if lang.effect(first_value) > Effect::MayRead {
2212 return None;
2213 }
2214
2215 if decl_form {
2216 Some(Edit::Multi(vec![
2218 Edit::Replace {
2219 target: first_value,
2220 with: value,
2221 },
2222 Edit::Delete {
2223 node: next_stmt,
2224 },
2225 ]))
2226 } else {
2227 Some(Edit::Delete { node: id })
2229 }
2230 }
2231}
2232
2233pub struct BlockFlatten;
2242
2243impl<L: Lang> Rule<L> for BlockFlatten {
2244 fn name(&self) -> &'static str {
2245 "block_flatten"
2246 }
2247 fn kinds(&self) -> &'static [NodeKind] {
2248 &[NodeKind::Block]
2249 }
2250 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2251 let RewriteCtx { lang, walk } = ctx;
2252 let parent = walk.parent(id)?;
2253 if lang.kind(parent) != NodeKind::Block {
2254 return None;
2255 }
2256 let inner = lang.children(id).to_vec();
2258 let declares = |n: L::Id| -> bool {
2259 matches!(
2260 lang.kind(n),
2261 NodeKind::VarDecl | NodeKind::ForEach | NodeKind::Catch
2262 )
2263 };
2264 if inner.iter().any(|&s| declares(s)) {
2265 return None;
2266 }
2267 if lang.block_hoists_decl(id) {
2269 return None;
2270 }
2271 if lang.has_pattern_bindings(id) {
2277 return None;
2278 }
2279 let idx = walk.index(id)?;
2280 Some(Edit::Splice {
2281 node: parent,
2282 index: idx,
2283 remove: 1,
2284 insert: inner,
2285 })
2286 }
2287}
2288
2289pub struct DoubleNegFold;
2292
2293impl<L: Lang> Rule<L> for DoubleNegFold {
2294 fn name(&self) -> &'static str {
2295 "double_neg_fold"
2296 }
2297 fn kinds(&self) -> &'static [NodeKind] {
2298 &[NodeKind::Unary]
2299 }
2300 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2301 let RewriteCtx { lang, .. } = ctx;
2302 if lang.un_op(id) != Some(UnOp::Neg) {
2303 return None;
2304 }
2305 let inner = *lang.children(id).first()?;
2306 if lang.un_op(inner) == Some(UnOp::Neg) {
2307 let x = *lang.children(inner).first()?;
2308 if !lang.is_exact_int(x) {
2311 return None;
2312 }
2313 return Some(Edit::Replace {
2314 target: id,
2315 with: x,
2316 });
2317 }
2318 if let Some((v, wide)) = lang.literal(inner).and_then(|l| match l {
2321 LitRef::Int(v) => Some((v, false)),
2322 LitRef::Long(v) => Some((v, true)),
2323 _ => None,
2324 }) {
2325 if v < 0 {
2326 let n = lang.fold_lit_neg(v, wide)?;
2327 let with = lang.build_int(n, wide);
2328 return Some(Edit::Replace {
2329 target: id,
2330 with,
2331 });
2332 }
2333 }
2334 None
2335 }
2336}
2337
2338pub struct UnreachableAfterTerminal;
2339
2340impl<L: Lang> Rule<L> for UnreachableAfterTerminal {
2341 fn name(&self) -> &'static str {
2342 "unreachable_after_terminal"
2343 }
2344 fn kinds(&self) -> &'static [NodeKind] {
2345 &[NodeKind::Block]
2346 }
2347 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2348 let RewriteCtx { lang, walk: _ } = ctx;
2349 if lang.kind(id) != NodeKind::Block {
2350 return None;
2351 }
2352 let children = lang.children(id).to_vec();
2353 let terminal = |n: L::Id| -> bool {
2354 matches!(
2355 lang.kind(n),
2356 NodeKind::Return | NodeKind::Throw | NodeKind::Break | NodeKind::Continue
2357 )
2358 };
2359 let mut cut: Option<(usize, usize)> = None;
2360 for (i, &s) in children.iter().enumerate() {
2361 if terminal(s) && i + 1 < children.len() {
2362 cut = Some((i + 1, children.len() - i - 1));
2363 break;
2364 }
2365 }
2366 let (index, remove) = cut?;
2367 Some(Edit::Splice {
2368 node: id,
2369 index,
2370 remove,
2371 insert: Vec::new(),
2372 })
2373 }
2374}
2375
2376
2377pub struct CmpConstFold;
2384
2385impl<L: Lang> Rule<L> for CmpConstFold {
2386 fn name(&self) -> &'static str {
2387 "cmp_const_fold"
2388 }
2389 fn kinds(&self) -> &'static [NodeKind] {
2390 &[NodeKind::Binary]
2391 }
2392 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2393 let RewriteCtx { lang, walk: _ } = ctx;
2394 if lang.kind(id) != NodeKind::Binary {
2395 return None;
2396 }
2397 let op = lang.bin_op(id)?;
2398 if !op.is_comparison() {
2399 return None;
2400 }
2401 let ch = lang.children(id);
2402 let (l, r) = (*ch.first()?, *ch.get(1)?);
2403 lang.literal(l)?;
2406 lang.literal(r)?;
2407 let v = lang.fold_lit_cmp(op, l, r)?;
2408 let with = lang.build_bool(v);
2409 Some(Edit::Replace {
2410 target: id,
2411 with,
2412 })
2413 }
2414}
2415
2416pub struct BitIdentity;
2424
2425impl<L: Lang> Rule<L> for BitIdentity {
2426 fn name(&self) -> &'static str {
2427 "bit_identity"
2428 }
2429 fn kinds(&self) -> &'static [NodeKind] {
2430 &[NodeKind::Binary]
2431 }
2432 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2433 let RewriteCtx { lang, walk: _ } = ctx;
2434 if lang.kind(id) != NodeKind::Binary {
2435 return None;
2436 }
2437 let op = lang.bin_op(id)?;
2438 if !matches!(op, BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor) {
2439 return None;
2440 }
2441 let ch = lang.children(id);
2442 let (l, r) = (*ch.first()?, *ch.get(1)?);
2443 let int_lit = |n: L::Id| lang.literal(n).and_then(|x| x.as_int());
2444
2445 if op == BinOp::BitXor
2447 && lang.kind(l) == NodeKind::VarRef
2448 && lang.kind(r) == NodeKind::VarRef
2449 && lang.same_binding(l, r)
2450 && lang.is_local_var(l)
2451 && lang.is_exact_int(l)
2452 {
2453 let with = lang.build_int(0, false);
2454 return Some(Edit::Replace {
2455 target: id,
2456 with,
2457 });
2458 }
2459
2460 for (x, k) in [(l, r), (r, l)] {
2462 let Some(kv) = int_lit(k) else {
2463 continue;
2464 };
2465 if !lang.is_exact_int(x) {
2466 continue;
2467 }
2468 let identity = match (op, kv) {
2469 (BinOp::BitXor, 0) => Some(x), (BinOp::BitOr, 0) => Some(x), (BinOp::BitAnd, -1) => Some(x), (BinOp::BitAnd, 0) | (BinOp::BitOr, -1) => {
2473 if lang.effect(x) > Effect::MayRead {
2475 None
2476 } else {
2477 Some(k)
2478 }
2479 }
2480 _ => None,
2481 };
2482 if let Some(with) = identity {
2483 return Some(Edit::Replace {
2484 target: id,
2485 with,
2486 });
2487 }
2488 }
2489 None
2490 }
2491}
2492
2493pub struct ArithReassoc;
2499
2500impl<L: Lang> Rule<L> for ArithReassoc {
2501 fn name(&self) -> &'static str {
2502 "arith_reassoc"
2503 }
2504 fn kinds(&self) -> &'static [NodeKind] {
2505 &[NodeKind::Binary]
2506 }
2507 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2508 let RewriteCtx { lang, walk: _ } = ctx;
2509 if lang.kind(id) != NodeKind::Binary {
2510 return None;
2511 }
2512 let op2 = lang.bin_op(id)?;
2513 if !matches!(op2, BinOp::Add | BinOp::Sub | BinOp::BitXor) {
2514 return None;
2515 }
2516 let och = lang.children(id).to_vec();
2517 let inner = och[0];
2518 if lang.kind(inner) != NodeKind::Binary {
2519 return None;
2520 }
2521 let op1 = lang.bin_op(inner)?;
2522 let ich = lang.children(inner).to_vec();
2523 let x = ich[0];
2524
2525 if op1 == BinOp::Add && op2 == BinOp::Add {
2531 if let (Some(LitRef::Str(k1)), Some(LitRef::Str(k2))) =
2532 (lang.literal(ich[1]), lang.literal(och[1]))
2533 {
2534 let mut chain: Vec<L::Id> = vec![id];
2536 let mut cur = id;
2537 loop {
2538 let cch = lang.children(cur).to_vec();
2539 let next = cch[0];
2540 let deeper = lang.kind(next) == NodeKind::Binary
2541 && lang.bin_op(next) == Some(BinOp::Add)
2542 && matches!(lang.literal(lang.children(next).to_vec()[1]),
2543 Some(LitRef::Str(_)));
2544 if deeper {
2545 chain.push(next);
2546 cur = next;
2547 } else {
2548 break;
2549 }
2550 }
2551 let innermost = *chain.last().unwrap();
2553 let ich2 = lang.children(innermost).to_vec();
2554 let base = ich2[0];
2555 let mut joined = String::new();
2556 for &n in chain.iter().rev() {
2557 if let Some(LitRef::Str(k)) =
2558 lang.literal(lang.children(n).to_vec()[1])
2559 {
2560 joined.push_str(k);
2561 }
2562 }
2563 let _ = (k1, k2); let lit = lang.build_str(&joined);
2565 let with = lang.build_bin(BinOp::Add, base, lit);
2566 return Some(Edit::Replace {
2567 target: id,
2568 with,
2569 });
2570 }
2571 }
2572
2573 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2577 let k2 = lang.literal(och[1]).and_then(|x| x.as_int())?;
2578 if !lang.is_exact_int(x) {
2579 return None;
2580 }
2581 let is_long = matches!(lang.literal(ich[1]), Some(LitRef::Long(_)))
2582 || matches!(lang.literal(och[1]), Some(LitRef::Long(_)));
2583 let with = match lang.reassoc_delta(op1, k1, op2, k2, is_long)? {
2584 ReassocOutcome::Neutral => x,
2585 ReassocOutcome::Lit(lit, op) => lang.build_bin(op, x, lit),
2586 };
2587 Some(Edit::Replace {
2588 target: id,
2589 with,
2590 })
2591 }
2592}
2593
2594pub struct IfToTernary;
2603
2604impl<L: Lang> Rule<L> for IfToTernary {
2605 fn name(&self) -> &'static str {
2606 "if_to_ternary"
2607 }
2608 fn kinds(&self) -> &'static [NodeKind] {
2609 &[NodeKind::If]
2610 }
2611 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2612 let RewriteCtx { lang, walk } = ctx;
2613 if lang.kind(id) != NodeKind::If {
2614 return None;
2615 }
2616 let ch = lang.children(id).to_vec();
2617 let c = ch[0];
2618
2619 let single_ret = |n: L::Id| -> Option<Option<L::Id>> {
2621 match lang.kind(n) {
2622 NodeKind::Return => Some(lang.children(n).first().copied()),
2623 NodeKind::Block if lang.children(n).len() == 1 => {
2624 let inner = lang.children(n)[0];
2625 if lang.kind(inner) == NodeKind::Return {
2626 Some(lang.children(inner).first().copied())
2627 } else {
2628 None
2629 }
2630 }
2631 _ => None,
2632 }
2633 };
2634
2635 if ch.len() == 3 {
2636 let a = single_ret(ch[1])?;
2638 let b = single_ret(ch[2])?;
2639 let with = match (a, b) {
2640 (Some(va), Some(vb)) => {
2641 let t = lang.build_ternary(c, va, vb);
2642 lang.build_return(Some(t))
2643 }
2644 (None, None) => lang.build_return(None),
2645 _ => return None,
2646 };
2647 return Some(Edit::Replace {
2648 target: id,
2649 with,
2650 });
2651 }
2652
2653 if ch.len() != 2 {
2655 return None;
2656 }
2657 let a = single_ret(ch[1])?;
2658 let parent = walk.parent(id)?;
2659 if lang.kind(parent) != NodeKind::Block {
2660 return None;
2661 }
2662 let idx = walk.index(id)?;
2663 let stmts = lang.children(parent).to_vec();
2664 if idx + 2 != stmts.len() {
2665 return None; }
2667 let tail = stmts[idx + 1];
2668 if lang.kind(tail) != NodeKind::Return {
2669 return None;
2670 }
2671 let b = lang.children(tail).first().copied();
2672 let with = match (a, b) {
2673 (Some(va), Some(vb)) => {
2674 let t = lang.build_ternary(c, va, vb);
2675 lang.build_return(Some(t))
2676 }
2677 (None, None) => lang.build_return(None),
2678 _ => return None,
2679 };
2680 Some(Edit::Splice {
2681 node: parent,
2682 index: idx,
2683 remove: 2,
2684 insert: vec![with],
2685 })
2686 }
2687}
2688
2689pub struct IfAssignTernary;
2696
2697impl<L: Lang> Rule<L> for IfAssignTernary {
2698 fn name(&self) -> &'static str {
2699 "if_assign_ternary"
2700 }
2701 fn kinds(&self) -> &'static [NodeKind] {
2702 &[NodeKind::If]
2703 }
2704 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2705 let RewriteCtx { lang, walk: _ } = ctx;
2706 if lang.kind(id) != NodeKind::If {
2707 return None;
2708 }
2709 let ch = lang.children(id).to_vec();
2710 if ch.len() != 3 {
2711 return None;
2712 }
2713 let c = ch[0];
2714 let single_assign = |n: L::Id| -> Option<(L::Id, L::Id)> {
2716 let unwrap = |i: L::Id| -> L::Id {
2717 if lang.kind(i) == NodeKind::ExprStmt {
2718 let ch = lang.children(i);
2719 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2720 return ch[0];
2721 }
2722 }
2723 i
2724 };
2725 let inner = match lang.kind(n) {
2726 NodeKind::Assign => n,
2727 NodeKind::ExprStmt => unwrap(n),
2728 NodeKind::Block if lang.children(n).len() == 1 => unwrap(lang.children(n)[0]),
2729 _ => return None,
2730 };
2731 if lang.kind(inner) != NodeKind::Assign {
2732 return None;
2733 }
2734 if lang.assign_op(inner).is_some() {
2735 return None;
2736 }
2737 let a = lang.children(inner).to_vec();
2738 if lang.kind(a[0]) != NodeKind::VarRef {
2739 return None;
2740 }
2741 Some((a[0], a[1]))
2742 };
2743 let (t1, va) = single_assign(ch[1])?;
2744 let (t2, vb) = single_assign(ch[2])?;
2745 if !lang.same_binding(t1, t2) || lang.var_name(t1).is_none() {
2746 return None;
2747 }
2748 let t = lang.build_ternary(c, va, vb);
2749 let with = lang.build_assign(t1, t);
2750 Some(Edit::Replace {
2751 target: id,
2752 with,
2753 })
2754 }
2755}
2756
2757
2758pub struct DeclAssignMerge;
2766
2767impl<L: Lang> Rule<L> for DeclAssignMerge {
2768 fn name(&self) -> &'static str {
2769 "decl_assign_merge"
2770 }
2771 fn kinds(&self) -> &'static [NodeKind] {
2772 &[NodeKind::VarDecl]
2773 }
2774 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2775 let RewriteCtx { lang, walk } = ctx;
2776 if lang.kind(id) != NodeKind::VarDecl || !lang.children(id).is_empty() {
2777 return None; }
2779 let name = lang.var_name(id)?.to_string();
2780 let parent = walk.parent(id)?;
2781 if lang.kind(parent) != NodeKind::Block {
2782 return None;
2783 }
2784 let idx = walk.index(id)?;
2785 let stmts = lang.children(parent).to_vec();
2786 let next = *stmts.get(idx + 1)?;
2787 let assign = match lang.kind(next) {
2789 NodeKind::Assign => next,
2790 NodeKind::ExprStmt => {
2791 let ch = lang.children(next);
2792 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2793 ch[0]
2794 } else {
2795 return None;
2796 }
2797 }
2798 _ => return None,
2799 };
2800 if lang.assign_op(assign).is_some() {
2801 return None;
2802 }
2803 let ach = lang.children(assign).to_vec();
2804 let (target, value) = (ach[0], ach[1]);
2805 if lang.kind(target) != NodeKind::VarRef || lang.var_name(target) != Some(name.as_str()) {
2806 return None;
2807 }
2808 if subtree_contains(&*lang, value, |n| {
2810 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2811 }) {
2812 return None;
2813 }
2814 Some(Edit::Multi(vec![
2815 Edit::Splice {
2816 node: id,
2817 index: 0,
2818 remove: 0,
2819 insert: vec![value],
2820 },
2821 Edit::Delete { node: next },
2822 ]))
2823 }
2824}
2825
2826pub struct AssignPropagation;
2835
2836impl<L: Lang> Rule<L> for AssignPropagation {
2837 fn name(&self) -> &'static str {
2838 "assign_propagation"
2839 }
2840 fn kinds(&self) -> &'static [NodeKind] {
2841 &[NodeKind::Assign, NodeKind::ExprStmt]
2842 }
2843 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2844 let RewriteCtx { lang, walk } = ctx;
2845 let (assign, stmt_node) = match lang.kind(id) {
2847 NodeKind::Assign => (id, id),
2848 NodeKind::ExprStmt => {
2849 let ch = lang.children(id);
2850 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2851 (ch[0], id)
2852 } else {
2853 return None;
2854 }
2855 }
2856 _ => return None,
2857 };
2858 if lang.assign_op(assign).is_some() {
2859 return None;
2860 }
2861 let ach = lang.children(assign).to_vec();
2862 let (target, value) = (ach[0], ach[1]);
2863 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
2864 return None;
2865 }
2866 if lang.kind(value) == NodeKind::ArrayLit {
2868 return None;
2869 }
2870 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
2872 return None;
2873 }
2874 let name = lang.var_name(target)?.to_string();
2875
2876 let parent = walk.parent(stmt_node)?;
2877 if lang.kind(parent) != NodeKind::Block {
2878 return None;
2879 }
2880 let idx = walk.index(stmt_node)?;
2881 let stmts = lang.children(parent).to_vec();
2882
2883 let anchored = stmts[..idx].iter().any(|&s| {
2885 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2886 });
2887 if !anchored {
2888 return None;
2889 }
2890
2891 if subtree_contains(&*lang, value, |n| {
2893 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2894 }) {
2895 return None;
2896 }
2897
2898 let name_key = lang.var_key(target)?;
2901 if count_name_refs(&*lang, &stmts[idx + 1..], name_key, &name) != 1 {
2902 return None;
2903 }
2904 let mut wa = Watch::<L>::new(name_key, &[]);
2905 for &s in &stmts[idx + 1..] {
2906 if lang.stmt_has_opaque_region(s) {
2907 return None;
2908 }
2909 scan_region(&*lang, s, &mut wa);
2910 }
2911 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
2912 return None;
2913 }
2914 let use_id = wa.uses[0];
2915 if matches!(lang.literal(value), Some(LitRef::Null))
2922 && null_unsafe_position(&*lang, walk, use_id)
2923 {
2924 return None;
2925 }
2926 if lang.kind(value) == NodeKind::VarRef
2930 && typed_position(&*lang, walk, use_id)
2931 .is_some_and(|t| !lang.var_cast_position_ok(value, t))
2932 {
2933 return None;
2934 }
2935 let mut watch_keys: Vec<L::NameKey> = Vec::new();
2937 collect_read_keys(&*lang, value, &mut watch_keys);
2938 watch_keys.push(name_key);
2939 let mut wb = Watch::<L>::new(name_key, &watch_keys);
2940 for &s in &stmts[idx + 1..] {
2941 let uses_before = wb.uses.len();
2942 scan_region(&*lang, s, &mut wb);
2943 if uses_before < wb.uses.len() {
2944 break; }
2946 }
2947 if wb.wrote(name_key) {
2949 return None;
2950 }
2951 let ve = lang.effect(value);
2952
2953 if ve <= Effect::MayRead {
2954 if let Some(target_key) = use_stmt_target_write_key(&*lang, &stmts[idx + 1..], use_id) {
2955 wb.clear_wrote(target_key);
2956 }
2957 if wb.any_write() {
2958 return None;
2959 }
2960 return Some(Edit::Multi(vec![
2961 Edit::Replace {
2962 target: use_id,
2963 with: value,
2964 },
2965 Edit::Delete { node: stmt_node },
2966 ]));
2967 }
2968
2969 if idx + 1 >= stmts.len() {
2971 return None;
2972 }
2973 let stmt = stmts[idx + 1];
2974 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
2975 return None;
2976 }
2977 match lang.kind(stmt) {
2978 NodeKind::Return
2979 | NodeKind::ExprStmt
2980 | NodeKind::Assign
2981 | NodeKind::Throw
2982 | NodeKind::VarDecl => {}
2983 _ => return None,
2984 }
2985 if !straight_path(&*lang, stmt, use_id) {
2986 return None;
2987 }
2988 if !prefix_effects_readable(&*lang, stmt, use_id) {
2989 return None;
2990 }
2991 Some(Edit::Multi(vec![
2992 Edit::Replace {
2993 target: use_id,
2994 with: value,
2995 },
2996 Edit::Delete { node: stmt_node },
2997 ]))
2998 }
2999}
3000
3001
3002fn use_stmt_target_write_key<L: Lang>(
3008 lang: &L,
3009 stmts_after: &[L::Id],
3010 use_id: L::Id,
3011) -> Option<L::NameKey> {
3012 for &s in stmts_after {
3013 if subtree_contains(lang, s, |n| n == use_id) {
3014 let assign = match lang.kind(s) {
3015 NodeKind::Assign => s,
3016 NodeKind::ExprStmt => {
3017 let ch = lang.children(s);
3018 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3019 ch[0]
3020 } else {
3021 return None;
3022 }
3023 }
3024 _ => return None,
3025 };
3026 if lang.assign_op(assign).is_some() {
3027 return None;
3028 }
3029 let ch = lang.children(assign);
3030 if ch.len() == 2 && ch[1] == use_id && lang.kind(ch[0]) == NodeKind::VarRef {
3031 return lang.var_key(ch[0]);
3032 }
3033 return None;
3034 }
3035 }
3036 None
3037}
3038
3039fn collect_read_keys<L: Lang>(lang: &L, id: L::Id, out: &mut Vec<L::NameKey>) {
3042 let mut stack = vec![id];
3043 while let Some(n) = stack.pop() {
3044 if lang.kind(n) == NodeKind::VarRef {
3045 if let Some(k) = lang.var_key(n) {
3046 if !out.contains(&k) {
3047 out.push(k);
3048 }
3049 }
3050 }
3051 for &c in lang.children(n) {
3052 stack.push(c);
3053 }
3054 }
3055}
3056
3057
3058pub struct TrailingContinue;
3065
3066impl<L: Lang> Rule<L> for TrailingContinue {
3067 fn name(&self) -> &'static str {
3068 "trailing_continue"
3069 }
3070 fn kinds(&self) -> &'static [NodeKind] {
3071 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
3072 }
3073 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3074 let RewriteCtx { lang, walk: _ } = ctx;
3075 let body = match lang.kind(id) {
3077 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
3078 *lang.children(id).last()?
3079 }
3080 _ => return None,
3081 };
3082 if lang.kind(body) != NodeKind::Block {
3083 return None;
3084 }
3085 let ch = lang.children(body).to_vec();
3086 let last = *ch.last()?;
3087 if lang.kind(last) != NodeKind::Continue || !lang.children(last).is_empty() {
3088 if lang.kind(last) != NodeKind::Continue {
3091 return None;
3092 }
3093 }
3094 Some(Edit::Delete { node: last })
3095 }
3096}
3097
3098pub struct MultiUseCopyPropagation;
3106
3107impl<L: Lang> Rule<L> for MultiUseCopyPropagation {
3108 fn name(&self) -> &'static str {
3109 "multi_use_copy"
3110 }
3111 fn kinds(&self) -> &'static [NodeKind] {
3112 &[NodeKind::Assign, NodeKind::ExprStmt]
3113 }
3114 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3115 let RewriteCtx { lang, walk } = ctx;
3116 let (assign, stmt_node) = match lang.kind(id) {
3118 NodeKind::Assign => (id, id),
3119 NodeKind::ExprStmt => {
3120 let ch = lang.children(id);
3121 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3122 (ch[0], id)
3123 } else {
3124 return None;
3125 }
3126 }
3127 _ => return None,
3128 };
3129 if lang.assign_op(assign).is_some() {
3130 return None;
3131 }
3132 let ach = lang.children(assign).to_vec();
3133 let (target, value) = (ach[0], ach[1]);
3134 if lang.kind(target) != NodeKind::VarRef || lang.kind(value) != NodeKind::VarRef {
3135 return None;
3136 }
3137 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
3139 return None;
3140 }
3141 if !lang.is_local_var(target) {
3142 return None;
3143 }
3144 let name = lang.var_name(target)?.to_string();
3145 let src = lang.var_name(value)?.to_string();
3146 if name == src {
3147 return None;
3148 }
3149
3150 let parent = walk.parent(stmt_node)?;
3151 if lang.kind(parent) != NodeKind::Block {
3152 return None;
3153 }
3154 let idx = walk.index(stmt_node)?;
3155 let stmts = lang.children(parent).to_vec();
3156
3157 let anchored = stmts[..idx].iter().any(|&s| {
3159 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
3160 });
3161 if !anchored {
3162 return None;
3163 }
3164
3165 let name_key = lang.var_key(target)?;
3167 let src_key = lang.var_key(value)?;
3168 let mut w1 = Watch::<L>::new(name_key, &[name_key, src_key]);
3169 for &s in &stmts[idx + 1..] {
3170 scan_region(&*lang, s, &mut w1);
3171 }
3172 if w1.opaque || w1.shadowed || w1.uses.is_empty() {
3173 return None;
3174 }
3175 if w1.wrote(name_key) || w1.wrote(src_key) {
3176 return None;
3177 }
3178 let synth = count_name_refs(&*lang, &stmts[idx + 1..], name_key, &name);
3184 if synth != w1.uses.len() {
3185 return None;
3186 }
3187 if w1.uses.iter().any(|&u| {
3196 typed_position(&*lang, walk, u).is_some_and(|t| !lang.var_cast_position_ok(value, t))
3197 }) {
3198 return None;
3199 }
3200
3201 let mut edits: Vec<Edit<L>> = w1.uses
3202 .iter()
3203 .map(|&u| Edit::Replace {
3204 target: u,
3205 with: value,
3206 })
3207 .collect();
3208 edits.push(Edit::Delete { node: stmt_node });
3209 Some(Edit::Multi(edits))
3210 }
3211}
3212
3213
3214pub struct TrailingReturn;
3221
3222impl<L: Lang> Rule<L> for TrailingReturn {
3223 fn name(&self) -> &'static str {
3224 "trailing_return"
3225 }
3226 fn kinds(&self) -> &'static [NodeKind] {
3227 &[NodeKind::Return]
3228 }
3229 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3230 let root = ctx.root();
3231 let parent = ctx.parent(id)?;
3232 let lang = ctx.lang;
3233 if lang.kind(id) != NodeKind::Return || !lang.children(id).is_empty() {
3234 return None;
3235 }
3236 if parent != root || lang.kind(parent) != NodeKind::Block {
3237 return None;
3238 }
3239 if lang.children(parent).last() != Some(&id) {
3240 return None;
3241 }
3242 Some(Edit::Delete { node: id })
3243 }
3244}
3245
3246
3247pub struct TernaryBoolOp;
3254
3255impl<L: Lang> Rule<L> for TernaryBoolOp {
3256 fn name(&self) -> &'static str {
3257 "ternary_bool_op"
3258 }
3259 fn kinds(&self) -> &'static [NodeKind] {
3260 &[NodeKind::Ternary]
3261 }
3262 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3263 let RewriteCtx { lang, walk: _ } = ctx;
3264 if lang.kind(id) != NodeKind::Ternary {
3265 return None;
3266 }
3267 let ch = lang.children(id);
3268 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
3269 if !lang.is_bool(c) {
3272 return None;
3273 }
3274 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
3275 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
3276 let with = if is_false(b) && lang.is_bool(a) {
3277 lang.build_bin(BinOp::And, c, a)
3278 } else if lb(b) && lang.is_bool(a) {
3279 return None;
3281 } else if lb(a) && lang.is_bool(b) {
3282 lang.build_bin(BinOp::Or, c, b)
3284 } else if is_false(a) && lang.is_bool(b) {
3285 return None;
3287 } else {
3288 return None;
3289 };
3290 Some(Edit::Replace {
3291 target: id,
3292 with,
3293 })
3294 }
3295}
3296
3297
3298pub struct StoreKill;
3307
3308pub fn scan_kill_event<L: Lang>(
3315 lang: &L,
3316 node: L::Id,
3317 name: &str,
3318 dominating: bool,
3319) -> StmtKill<L::Id> {
3320 match lang.kind(node) {
3321 NodeKind::VarRef => {
3322 if lang.var_name(node) == Some(name) {
3323 StmtKill::Read
3324 } else {
3325 StmtKill::None
3326 }
3327 }
3328 NodeKind::VarDecl => {
3329 if lang.var_name(node) == Some(name) {
3330 StmtKill::Shadow
3331 } else {
3332 for &c in lang.children(node) {
3334 let e = scan_kill_event(lang, c, name, dominating);
3335 if !matches!(e, StmtKill::None) {
3336 return e;
3337 }
3338 }
3339 StmtKill::None
3340 }
3341 }
3342 NodeKind::Assign => {
3343 let ch = lang.children(node);
3344 if let Some(&val) = ch.get(1) {
3346 let e = scan_kill_event(lang, val, name, dominating);
3347 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3348 return e;
3349 }
3350 }
3351 let Some(&target) = ch.first() else {
3352 return StmtKill::None;
3353 };
3354 if lang.kind(target) == NodeKind::VarRef {
3355 if lang.assign_op(node).is_some() {
3356 if lang.var_name(target) == Some(name) {
3358 return StmtKill::Read;
3359 }
3360 } else if lang.var_name(target) == Some(name) {
3361 if dominating {
3362 return StmtKill::DomWrite(node);
3363 }
3364 return StmtKill::None; }
3366 } else {
3367 let e = scan_kill_event(lang, target, name, false);
3369 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3370 return e;
3371 }
3372 }
3373 StmtKill::None
3374 }
3375 NodeKind::If | NodeKind::While | NodeKind::Ternary => {
3376 let ch = lang.children(node);
3379 if let Some(&c) = ch.first() {
3380 let e = scan_kill_event(lang, c, name, true);
3381 if !matches!(e, StmtKill::None) {
3382 return e;
3383 }
3384 }
3385 for &c in &ch[1..] {
3386 let e = scan_kill_event(lang, c, name, false);
3387 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3388 return e;
3389 }
3390 }
3391 StmtKill::None
3392 }
3393 NodeKind::Binary => {
3394 let Some(op) = lang.bin_op(node) else {
3395 return StmtKill::None;
3396 };
3397 let ch = lang.children(node);
3398 if op.is_short_circuit() {
3399 if let Some(&l) = ch.first() {
3401 let e = scan_kill_event(lang, l, name, true);
3402 if !matches!(e, StmtKill::None) {
3403 return e;
3404 }
3405 }
3406 if let Some(&r) = ch.get(1) {
3407 let e = scan_kill_event(lang, r, name, false);
3408 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3409 return e;
3410 }
3411 }
3412 StmtKill::None
3413 } else {
3414 for &c in ch {
3415 let e = scan_kill_event(lang, c, name, dominating);
3416 if !matches!(e, StmtKill::None) {
3417 return e;
3418 }
3419 }
3420 StmtKill::None
3421 }
3422 }
3423 NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
3424 let ch = lang.children(node);
3428 let dom_len = match lang.kind(node) {
3429 NodeKind::For => ch.len().saturating_sub(2),
3430 NodeKind::ForEach | NodeKind::Switch => 1,
3431 _ => 0,
3432 };
3433 for &c in &ch[..dom_len] {
3434 let e = scan_kill_event(lang, c, name, true);
3435 if !matches!(e, StmtKill::None) {
3436 return e;
3437 }
3438 }
3439 for &c in &ch[dom_len..] {
3440 let e = scan_kill_event(lang, c, name, false);
3441 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3442 return e;
3443 }
3444 }
3445 StmtKill::None
3446 }
3447 _ => {
3448 for &c in lang.children(node) {
3449 let e = scan_kill_event(lang, c, name, dominating);
3450 if !matches!(e, StmtKill::None) {
3451 return e;
3452 }
3453 }
3454 StmtKill::None
3455 }
3456 }
3457}
3458
3459pub fn collect_stmt_kill_events<L: Lang>(lang: &L, root: L::Id) -> Vec<(L::NameKey, StmtKill<L::Id>)> {
3464 use std::collections::BTreeMap;
3465 enum Act<Id> {
3466 Enter(Id, bool),
3467 AfterAssign(Id, bool),
3468 }
3469 let mut events: BTreeMap<L::NameKey, StmtKill<L::Id>> = BTreeMap::new();
3470 let mut stack = vec![Act::Enter(root, true)];
3471 while let Some(act) = stack.pop() {
3472 match act {
3473 Act::AfterAssign(id, dominating) => {
3474 if lang.assign_op(id).is_some() {
3475 continue;
3476 }
3477 let Some(&target) = lang.children(id).first() else {
3478 continue;
3479 };
3480 if lang.kind(target) != NodeKind::VarRef {
3481 continue;
3482 }
3483 let Some(key) = lang.var_key(target) else {
3484 continue;
3485 };
3486 if dominating {
3487 events.entry(key).or_insert(StmtKill::DomWrite(id));
3488 }
3489 }
3490 Act::Enter(node, dominating) => match lang.kind(node) {
3491 NodeKind::VarRef => {
3492 if let Some(key) = lang.var_key(node) {
3493 events.entry(key).or_insert(StmtKill::Read);
3494 }
3495 }
3496 NodeKind::VarDecl => {
3497 if let Some(key) = lang.var_key(node) {
3498 events.entry(key).or_insert(StmtKill::Shadow);
3499 }
3500 for &c in lang.children(node).iter().rev() {
3501 stack.push(Act::Enter(c, dominating));
3502 }
3503 }
3504 NodeKind::Assign => {
3505 let ch = lang.children(node);
3506 stack.push(Act::AfterAssign(node, dominating));
3507 if let Some(&val) = ch.get(1) {
3508 stack.push(Act::Enter(val, dominating));
3509 }
3510 if let Some(&target) = ch.first() {
3513 if lang.kind(target) != NodeKind::VarRef
3514 || lang.assign_op(node).is_some()
3515 {
3516 stack.push(Act::Enter(target, false));
3517 }
3518 }
3519 }
3520 NodeKind::If | NodeKind::While | NodeKind::Ternary => {
3521 let ch = lang.children(node);
3522 for (i, &c) in ch.iter().enumerate().rev() {
3523 stack.push(Act::Enter(c, i == 0));
3524 }
3525 }
3526 NodeKind::Binary => {
3527 let ch = lang.children(node);
3528 let short = lang.bin_op(node).is_some_and(|op| op.is_short_circuit());
3529 for (i, &c) in ch.iter().enumerate().rev() {
3530 let child_dom = if short { i == 0 } else { dominating };
3531 stack.push(Act::Enter(c, child_dom));
3532 }
3533 }
3534 NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
3535 let ch = lang.children(node);
3536 let dom_len = match lang.kind(node) {
3537 NodeKind::For => ch.len().saturating_sub(2),
3538 NodeKind::ForEach | NodeKind::Switch => 1,
3539 _ => 0,
3540 };
3541 for (i, &c) in ch.iter().enumerate().rev() {
3542 stack.push(Act::Enter(c, i < dom_len));
3543 }
3544 }
3545 _ => {
3546 for &c in lang.children(node).iter().rev() {
3547 stack.push(Act::Enter(c, dominating));
3548 }
3549 }
3550 },
3551 }
3552 }
3553 events.into_iter().collect()
3554}
3555
3556impl<L: Lang> Rule<L> for StoreKill {
3557 fn name(&self) -> &'static str {
3558 "store_kill"
3559 }
3560 fn kinds(&self) -> &'static [NodeKind] {
3561 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
3562 }
3563 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3564 let RewriteCtx { lang, walk } = ctx;
3565 let (is_decl, stmt_node, first_value) = match lang.kind(id) {
3567 NodeKind::VarDecl => {
3568 if lang.children(id).is_empty() {
3569 return None;
3570 }
3571 (true, id, *lang.children(id).first()?)
3572 }
3573 NodeKind::Assign => {
3574 if lang.assign_op(id).is_some() {
3575 return None;
3576 }
3577 (false, id, *lang.children(id).get(1)?)
3578 }
3579 NodeKind::ExprStmt => {
3580 let ch = lang.children(id);
3581 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3582 let a = ch[0];
3583 if lang.assign_op(a).is_some() {
3584 return None;
3585 }
3586 (false, id, *lang.children(a).get(1)?)
3587 } else {
3588 return None;
3589 }
3590 }
3591 _ => return None,
3592 };
3593 let (name, name_key) = if is_decl {
3594 (lang.var_name(id)?.to_string(), lang.var_key(id)?)
3595 } else {
3596 let a = match lang.kind(id) {
3597 NodeKind::Assign => id,
3598 _ => lang.children(id)[0],
3599 };
3600 let t = *lang.children(a).first()?;
3601 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
3602 return None;
3603 }
3604 (lang.var_name(t)?.to_string(), lang.var_key(t)?)
3605 };
3606
3607 let parent = walk.parent(stmt_node)?;
3608 if lang.kind(parent) != NodeKind::Block {
3609 return None;
3610 }
3611 let idx = walk.index(stmt_node)?;
3612 let stmts = lang.children(parent).to_vec();
3613
3614 let mut killed_by: Option<(usize, L::Id)> = None;
3616 for (si, &s) in stmts.iter().enumerate().skip(idx + 1) {
3617 if lang.stmt_has_opaque_region(s) {
3621 return None;
3622 }
3623 let event = lang
3624 .stmt_kill_event(s, name_key)
3625 .unwrap_or_else(|| scan_kill_event(&*lang, s, &name, true));
3626 match event {
3627 StmtKill::None => continue,
3628 StmtKill::Read | StmtKill::Shadow => return None,
3629 StmtKill::DomWrite(assign) => {
3630 killed_by = Some((si, assign));
3631 break;
3632 }
3633 }
3634 }
3635 let (si, kill_assign) = killed_by?;
3636 {
3644 let kill_container = match walk.parents.get(&kill_assign) {
3645 Some(&(p, _)) if lang.kind(p) == NodeKind::ExprStmt => p,
3646 _ => kill_assign,
3647 };
3648 if kill_container != stmts[si] {
3649 return None;
3650 }
3651 }
3652 let mut has_try_ancestor = false;
3665 {
3666 let mut cur = stmt_node;
3667 while let Some(&(p, _)) = walk.parents.get(&cur) {
3668 if lang.kind(p) == NodeKind::Try {
3669 has_try_ancestor = true;
3670 break;
3671 }
3672 cur = p;
3673 }
3674 }
3675 if has_try_ancestor {
3676 for &s in &stmts[idx + 1..si] {
3677 if lang.effect(s) > Effect::MayRead {
3678 return None;
3679 }
3680 }
3681 }
3682
3683 if is_decl {
3684 if lang.effect(first_value) > Effect::MayRead {
3687 return None;
3688 }
3689 let value = *lang.children(kill_assign).get(1)?;
3690 if is_literal(lang, value) {
3691 let assign_stmt = stmts[si];
3692 return Some(Edit::Multi(vec![
3693 Edit::Splice {
3694 node: id,
3695 index: 0,
3696 remove: 1,
3697 insert: vec![value],
3698 },
3699 Edit::Delete { node: assign_stmt },
3700 ]));
3701 }
3702 if lang.is_inferred_decl(id) {
3706 return None;
3707 }
3708 if lang.effect(first_value) > Effect::MayRead {
3709 return None;
3710 }
3711 return Some(Edit::Splice {
3712 node: id,
3713 index: 0,
3714 remove: 1,
3715 insert: Vec::new(),
3716 });
3717 }
3718 if lang.effect(first_value) > Effect::MayRead {
3720 return None;
3721 }
3722 Some(Edit::Delete { node: stmt_node })
3723 }
3724}
3725
3726fn is_literal<L: Lang>(lang: &L, n: L::Id) -> bool {
3728 matches!(lang.kind(n), NodeKind::Literal)
3729}
3730
3731pub fn default_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3737 vec![
3738 Box::new(DoubleNegFold),
3739 Box::new(BlockFlatten),
3740 Box::new(ParenRemoval),
3741 Box::new(ConstCondition),
3742 Box::new(BooleanReturn),
3743 Box::new(IfToTernary),
3744 Box::new(IfAssignTernary),
3745 Box::new(IfElseEmpty),
3746 Box::new(BoolCompare),
3747 Box::new(DoubleNot),
3748 Box::new(BoolNotFold),
3749 Box::new(BoolShortCircuit),
3750 Box::new(NotCompare),
3751 Box::new(TernaryFold),
3752 Box::new(TernaryBool),
3753 Box::new(TernaryBoolOp),
3754 Box::new(ConstFoldBin),
3755 Box::new(CmpConstFold),
3756 Box::new(BitIdentity),
3757 Box::new(ArithReassoc),
3758 Box::new(SelfAssign),
3759 Box::new(ArithIdentity),
3760 Box::new(ArithZero),
3761 Box::new(DeclAssignMerge),
3762 Box::new(LocalPropagation),
3763 Box::new(AssignPropagation),
3764 Box::new(MultiUseCopyPropagation),
3765 Box::new(TrailingReturn),
3766 Box::new(InverseAssignPair),
3767 Box::new(DeadDecl),
3768 Box::new(AssignBackFold),
3769 Box::new(DeadStore),
3770 Box::new(StoreKill),
3771 ]
3772}
3773
3774pub fn all_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3776 let mut rules = default_rules();
3777 rules.push(Box::new(UnreachableAfterTerminal));
3778 rules
3779}