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 }
1422 }
1423 if let Some(&v) = ch.get(1) {
1424 scan_region(lang, v, w);
1425 }
1426 }
1427 NodeKind::Unary if lang.un_op(node).is_some_and(|o| o.is_incdec()) => {
1428 let ch = lang.children(node);
1429 if let Some(&t) = ch.first() {
1430 if lang.kind(t) == NodeKind::VarRef {
1431 if let Some(k) = lang.var_key(t) {
1432 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1433 if *wn == k {
1434 *hit = true;
1435 }
1436 }
1437 }
1438 }
1439 }
1440 }
1441 NodeKind::VarDecl => {
1442 if let Some(k) = lang.var_key(node) {
1443 if k == w.name {
1444 w.shadowed = true;
1445 }
1446 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1447 if *wn == k {
1448 *hit = true;
1449 }
1450 }
1451 }
1452 for &c in lang.children(node) {
1453 scan_region(lang, c, w);
1454 }
1455 }
1456 NodeKind::ForEach | NodeKind::Catch => {
1459 if let Some(k) = lang.var_key(node) {
1460 if k == w.name {
1461 w.shadowed = true;
1462 }
1463 if lang.kind(node) == NodeKind::ForEach {
1464 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1465 if *wn == k {
1466 *hit = true;
1467 }
1468 }
1469 }
1470 }
1471 for &c in lang.children(node) {
1472 scan_region(lang, c, w);
1473 }
1474 }
1475 NodeKind::VarRef => {
1476 if lang.var_key(node) == Some(w.name) {
1477 w.uses.push(node);
1478 }
1479 }
1480 NodeKind::Raw => {
1481 w.opaque = true;
1483 }
1484 _ => {
1485 for &c in lang.children(node) {
1486 scan_region(lang, c, w);
1487 }
1488 }
1489 }
1490}
1491
1492fn straight_path<L: Lang>(lang: &L, root: L::Id, target: L::Id) -> bool {
1494 fn go<L: Lang>(lang: &L, node: L::Id, target: L::Id) -> bool {
1495 if node == target {
1496 return true;
1497 }
1498 match lang.kind(node) {
1499 NodeKind::Ternary | NodeKind::If => return false,
1500 NodeKind::Binary if lang.bin_op(node).is_some_and(|o| o.is_short_circuit()) => {
1501 return false
1502 }
1503 _ => {}
1504 }
1505 for &c in lang.children(node) {
1506 if subtree_contains(lang, c, |n| n == target) {
1507 return go(lang, c, target);
1508 }
1509 }
1510 false
1511 }
1512 go(lang, root, target)
1513}
1514
1515
1516fn in_tag_position<L: Lang>(lang: &L, walk: &crate::walk::Walk<L>, id: L::Id) -> bool {
1525 let Some((p, i)) = walk.parents.get(&id) else { return false };
1526 *i == 0
1527 && lang.kind(*p) == NodeKind::Custom(1) && lang.is_tagged_template(*p)
1529}
1530
1531pub struct BoolShortCircuit;
1532
1533impl<L: Lang> Rule<L> for BoolShortCircuit {
1534 fn name(&self) -> &'static str {
1535 "bool_short_circuit"
1536 }
1537 fn kinds(&self) -> &'static [NodeKind] {
1538 &[NodeKind::Binary]
1539 }
1540 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1541 let RewriteCtx { lang, walk } = ctx;
1542 if lang.kind(id) != NodeKind::Binary {
1543 return None;
1544 }
1545 let op = lang.bin_op(id)?;
1546 if !op.is_short_circuit() {
1547 return None;
1548 }
1549 if in_tag_position(&*lang, &walk, id) {
1553 return None;
1554 }
1555 if let Some((p, i)) = walk.parents.get(&id) {
1556 if *i == 0 && lang.kind(*p) == NodeKind::Unary && lang.un_op(*p) == Some(UnOp::Delete) {
1557 return None;
1558 }
1559 }
1560 let ch = lang.children(id);
1561 let (l, r) = (*ch.first()?, *ch.get(1)?);
1562 let is_lit = |n: L::Id, v: bool| matches!(lang.literal(n), Some(LitRef::Bool(b)) if b == v);
1563 let with = match (op, is_lit(l, true), is_lit(l, false)) {
1565 (BinOp::And, _, true) => l, (BinOp::Or, true, _) => l, (BinOp::And, true, _) => r, (BinOp::Or, _, true) => r, _ => {
1570 match (op, is_lit(r, true), is_lit(r, false)) {
1576 (BinOp::And, true, _) if lang.is_bool(l) => l,
1577 (BinOp::Or, _, true) if lang.is_bool(l) => l,
1578 (BinOp::And, _, true) | (BinOp::Or, true, _)
1583 if lang.is_bool(l) && lang.effect(l) <= Effect::MayRead => {
1584 r
1585 }
1586 _ => return None,
1587 }
1588 }
1589 };
1590 Some(Edit::Replace {
1591 target: id,
1592 with,
1593 })
1594 }
1595}
1596
1597pub struct NotCompare;
1603
1604impl<L: Lang> Rule<L> for NotCompare {
1605 fn name(&self) -> &'static str {
1606 "not_compare"
1607 }
1608 fn kinds(&self) -> &'static [NodeKind] {
1609 &[NodeKind::Unary]
1610 }
1611 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1612 let RewriteCtx { lang, walk: _ } = ctx;
1613 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
1614 return None;
1615 }
1616 let inner = *lang.children(id).first()?;
1617 if lang.kind(inner) != NodeKind::Binary {
1618 return None;
1619 }
1620 let op = lang.bin_op(inner)?;
1621 let neg = match op {
1622 BinOp::Eq => BinOp::Ne,
1623 BinOp::Ne => BinOp::Eq,
1624 BinOp::Lt => BinOp::Ge,
1625 BinOp::Le => BinOp::Gt,
1626 BinOp::Gt => BinOp::Le,
1627 BinOp::Ge => BinOp::Lt,
1628 _ => return None,
1629 };
1630 if op.is_short_circuit() {
1631 return None;
1632 }
1633 if matches!(op, BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) {
1635 let ch = lang.children(inner);
1636 if !lang.is_exact_int(*ch.first()?) || !lang.is_exact_int(*ch.get(1)?) {
1637 return None;
1638 }
1639 }
1640 let ch = lang.children(inner).to_vec();
1641 let rebuilt = lang.build_bin(neg, ch[0], ch[1]);
1642 Some(Edit::Replace {
1643 target: id,
1644 with: rebuilt,
1645 })
1646 }
1647}
1648
1649fn in_inferred_var_init<L: Lang>(lang: &L, walk: &Walk<L>, mut id: L::Id) -> bool {
1659 while let Some(&(p, _)) = walk.parents.get(&id) {
1660 match lang.kind(p) {
1661 NodeKind::Paren | NodeKind::Ternary => id = p,
1662 NodeKind::VarDecl => return lang.is_inferred_decl(p),
1663 _ => return false,
1664 }
1665 }
1666 false
1667}
1668
1669pub struct TernaryFold;
1670
1671impl<L: Lang> Rule<L> for TernaryFold {
1672 fn name(&self) -> &'static str {
1673 "ternary_fold"
1674 }
1675 fn kinds(&self) -> &'static [NodeKind] {
1676 &[NodeKind::Ternary]
1677 }
1678 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1679 let RewriteCtx { lang, walk } = ctx;
1680 if lang.kind(id) != NodeKind::Ternary {
1681 return None;
1682 }
1683 if in_tag_position(&*lang, &walk, id) {
1684 return None;
1685 }
1686 let ch = lang.children(id);
1687 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1688 match lang.literal(c) {
1689 Some(LitRef::Bool(true)) => {
1695 if !in_inferred_var_init(&*lang, walk, id) {
1696 return Some(Edit::Replace { target: id, with: a });
1697 }
1698 }
1699 Some(LitRef::Bool(false))
1700 if !in_inferred_var_init(&*lang, walk, id) => {
1701 return Some(Edit::Replace { target: id, with: b });
1702 }
1703 _ => {}
1704 }
1705 if crate::analysis::structurally_equal(&*lang, a, b) && lang.effect(c) <= Effect::MayRead {
1708 return Some(Edit::Replace { target: id, with: a });
1709 }
1710 None
1711 }
1712}
1713
1714pub struct TernaryBool;
1719
1720impl<L: Lang> Rule<L> for TernaryBool {
1721 fn name(&self) -> &'static str {
1722 "ternary_bool"
1723 }
1724 fn kinds(&self) -> &'static [NodeKind] {
1725 &[NodeKind::Ternary]
1726 }
1727 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1728 let RewriteCtx { lang, walk: _ } = ctx;
1729 if lang.kind(id) != NodeKind::Ternary {
1730 return None;
1731 }
1732 let ch = lang.children(id);
1733 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1734 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
1735 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
1736 if !lang.is_bool(c) {
1737 return None;
1738 }
1739 let with = if lb(a) && is_false(b) {
1740 c
1741 } else if is_false(a) && lb(b) {
1742 lang.build_unary(UnOp::Not, c)
1743 } else {
1744 return None;
1745 };
1746 Some(Edit::Replace {
1747 target: id,
1748 with,
1749 })
1750 }
1751}
1752
1753pub struct ConstFoldBin;
1760
1761impl<L: Lang> Rule<L> for ConstFoldBin {
1762 fn name(&self) -> &'static str {
1763 "const_fold_bin"
1764 }
1765 fn kinds(&self) -> &'static [NodeKind] {
1766 &[NodeKind::Binary]
1767 }
1768 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1769 let RewriteCtx { lang, walk: _ } = ctx;
1770 if lang.kind(id) != NodeKind::Binary {
1771 return None;
1772 }
1773 let op = lang.bin_op(id)?;
1774 if op.is_short_circuit() || op.is_comparison() {
1775 return None;
1776 }
1777 let ch = lang.children(id);
1778 let (l, r) = (*ch.first()?, *ch.get(1)?);
1779 lang.literal(l)?;
1781 lang.literal(r)?;
1782 let with = lang.fold_lit_bin(op, l, r)?;
1783 Some(Edit::Replace {
1784 target: id,
1785 with,
1786 })
1787 }
1788}
1789
1790pub struct ArithZero;
1799
1800impl<L: Lang> Rule<L> for ArithZero {
1801 fn name(&self) -> &'static str {
1802 "arith_zero"
1803 }
1804 fn kinds(&self) -> &'static [NodeKind] {
1805 &[NodeKind::Binary]
1806 }
1807 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1808 let RewriteCtx { lang, walk: _ } = ctx;
1809 if lang.kind(id) != NodeKind::Binary {
1810 return None;
1811 }
1812 let op = lang.bin_op(id)?;
1813 let ch = lang.children(id);
1814 let (l, r) = (*ch.first()?, *ch.get(1)?);
1815 let int_lit = |n: L::Id, v: i64| lang.literal(n).and_then(|x| x.as_int()) == Some(v);
1816
1817 if matches!(op, BinOp::Shl | BinOp::Shr | BinOp::UShr) && int_lit(r, 0) {
1821 if !lang.is_exact_int(l) {
1822 return None;
1823 }
1824 return Some(Edit::Replace {
1825 target: id,
1826 with: l,
1827 });
1828 }
1829 if op == BinOp::Mul {
1831 let (zero, other) = if int_lit(l, 0) {
1832 (l, r)
1833 } else if int_lit(r, 0) {
1834 (r, l)
1835 } else {
1836 return None;
1837 };
1838 if !lang.is_exact_int(other) || lang.effect(other) > Effect::MayRead {
1839 return None;
1840 }
1841 return Some(Edit::Replace {
1842 target: id,
1843 with: zero,
1844 });
1845 }
1846 if op == BinOp::Sub
1848 && lang.kind(l) == NodeKind::VarRef
1849 && lang.kind(r) == NodeKind::VarRef
1850 && lang.same_binding(l, r)
1851 && lang.is_local_var(l)
1852 && lang.is_exact_int(l)
1853 {
1854 let with = lang.build_int(0, false);
1855 return Some(Edit::Replace {
1856 target: id,
1857 with,
1858 });
1859 }
1860 None
1861 }
1862}
1863
1864pub struct InverseAssignPair;
1887
1888impl<L: Lang> Rule<L> for InverseAssignPair {
1889 fn name(&self) -> &'static str {
1890 "inverse_assign_pair"
1891 }
1892 fn kinds(&self) -> &'static [NodeKind] {
1893 &[NodeKind::Block]
1894 }
1895 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1896 let RewriteCtx { lang, .. } = ctx;
1897 let ch = lang.children(id).to_vec();
1898 if ch.len() < 2 {
1899 return None;
1900 }
1901 for w in 0..ch.len() - 1 {
1902 let (s1, s2) = (ch[w], ch[w + 1]);
1903 let Some(a1) = norm_assign(lang, s1) else { continue };
1905 let Some(a2) = norm_assign(lang, s2) else { continue };
1906 if a1.key != a2.key
1908 || !lang.same_binding(a1.target, a2.target)
1909 || !a1.local
1910 || !a2.local
1911 {
1912 continue;
1913 }
1914 if !a1.exact_int || !a2.exact_int {
1916 continue;
1917 }
1918 if a1.k != a2.k {
1920 continue;
1921 }
1922 let inverse = match (a1.op, a2.op) {
1923 (BinOp::Add, BinOp::Sub) | (BinOp::Sub, BinOp::Add) => true,
1924 (BinOp::BitXor, BinOp::BitXor) => true,
1925 _ => false,
1926 };
1927 if !inverse {
1928 continue;
1929 }
1930 return Some(Edit::Multi(vec![
1932 Edit::Delete { node: s2 },
1933 Edit::Delete { node: s1 },
1934 ]));
1935 }
1936 None
1937 }
1938}
1939
1940struct NormAssign<L: Lang> {
1944 key: L::NameKey,
1945 target: L::Id,
1946 op: BinOp,
1947 k: i64,
1948 local: bool,
1949 exact_int: bool,
1951}
1952
1953fn norm_assign<L: Lang>(lang: &L, stmt: L::Id) -> Option<NormAssign<L>> {
1954 let a = match lang.kind(stmt) {
1956 NodeKind::ExprStmt => {
1957 let c = lang.children(stmt);
1958 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
1959 c[0]
1960 } else {
1961 return None;
1962 }
1963 }
1964 NodeKind::Assign => stmt,
1965 _ => return None,
1966 };
1967 let ch = lang.children(a).to_vec();
1968 if ch.len() != 2 {
1969 return None;
1970 }
1971 let (target, rhs) = (ch[0], ch[1]);
1972 if lang.kind(target) != NodeKind::VarRef {
1973 return None;
1974 }
1975 let key = lang.var_key(target)?;
1976 let local = lang.is_local_var(target);
1977 let exact_int = lang.is_exact_int(target);
1978 match lang.assign_op(a) {
1982 Some(op) => {
1984 let k = lang.literal(rhs).and_then(|x| x.as_int())?;
1985 Some(NormAssign { key, target, op, k, local, exact_int })
1986 }
1987 None => {
1989 if lang.kind(rhs) != NodeKind::Binary {
1990 return None;
1991 }
1992 let op = lang.bin_op(rhs)?;
1993 let rc = lang.children(rhs).to_vec();
1994 if rc.len() != 2 {
1995 return None;
1996 }
1997 if lang.kind(rc[0]) != NodeKind::VarRef || !lang.same_binding(target, rc[0]) {
1999 return None;
2000 }
2001 let k = lang.literal(rc[1]).and_then(|x| x.as_int())?;
2002 Some(NormAssign { key, target, op, k, local, exact_int })
2003 }
2004 }
2005}
2006
2007pub struct DeadStore;
2008
2009impl<L: Lang> Rule<L> for DeadStore {
2010 fn name(&self) -> &'static str {
2011 "dead_store"
2012 }
2013 fn kinds(&self) -> &'static [NodeKind] {
2014 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
2015 }
2016 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2017 let RewriteCtx { lang, walk } = ctx;
2018 let (decl_form, first_assign) = match lang.kind(id) {
2020 NodeKind::VarDecl => {
2021 if lang.children(id).is_empty() {
2022 return None; }
2024 (true, None)
2025 }
2026 NodeKind::Assign => (false, Some(id)),
2027 NodeKind::ExprStmt => {
2028 let ch = lang.children(id);
2029 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2030 (false, Some(ch[0]))
2031 } else {
2032 return None;
2033 }
2034 }
2035 _ => return None,
2036 };
2037 if decl_form && !lang.dead_decl_allowed(id) {
2038 return None;
2041 }
2042 let first_value = if decl_form {
2043 *lang.children(id).first()?
2044 } else {
2045 let a = first_assign?;
2046 if lang.assign_op(a).is_some() {
2047 return None; }
2049 *lang.children(a).get(1)?
2050 };
2051 if lang.value_may_throw_unresolved(first_value) {
2052 return None;
2053 }
2054
2055 let parent = walk.parent(id)?;
2057 if lang.kind(parent) != NodeKind::Block {
2058 return None;
2059 }
2060 let idx = walk.index(id)?;
2061 let stmts = lang.children(parent).to_vec();
2062 let next_stmt = *stmts.get(idx + 1)?;
2063
2064 let second = match lang.kind(next_stmt) {
2067 NodeKind::Assign => Some(next_stmt),
2068 NodeKind::ExprStmt => {
2069 let ch = lang.children(next_stmt);
2070 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2071 Some(ch[0])
2072 } else {
2073 None
2074 }
2075 }
2076 _ => None,
2077 };
2078 let Some(second) = second else {
2079 let (name_node, target_local) = if decl_form {
2088 (id, true)
2089 } else {
2090 let t = *lang.children(first_assign?).first()?;
2091 if lang.kind(t) != NodeKind::VarRef {
2092 return None;
2093 }
2094 (t, lang.is_local_var(t))
2095 };
2096 if !target_local {
2098 return None;
2099 }
2100 let name = lang.var_name(name_node)?.to_string();
2101 let declared_in_this_block = if decl_form {
2102 true } else if parent == walk.root {
2104 true
2107 } else {
2108 stmts[..idx].iter().any(|&s| {
2111 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2112 })
2113 };
2114 if !declared_in_this_block {
2115 return None;
2116 }
2117 for &s in &stmts[..idx] {
2121 if lang.stmt_has_opaque_region(s) {
2122 return None;
2123 }
2124 }
2125 let name_key0 = lang.var_key(name_node)?;
2126 for &s in &stmts[idx + 1..] {
2132 if lang.stmt_has_opaque_region(s) {
2133 return None;
2134 }
2135 match (lang.stmt_mentions(s, name_key0), lang.stmt_opaque(s)) {
2136 (Some(mentions), Some(opaque)) => {
2137 if mentions || opaque {
2138 return None;
2139 }
2140 }
2141 _ => {
2142 let mut w0 = Watch::<L>::new(name_key0, &[name_key0]);
2143 scan_region(&*lang, s, &mut w0);
2144 if w0.opaque
2145 || !w0.uses.is_empty()
2146 || w0.wrote(name_key0)
2147 || w0.shadowed
2148 {
2149 return None;
2150 }
2151 if subtree_contains(&*lang, s, |n| {
2152 lang.kind(n) == NodeKind::VarRef
2153 && lang.var_name(n) == Some(name.as_str())
2154 }) {
2155 return None;
2156 }
2157 }
2158 }
2159 }
2160 if lang.effect(first_value) > Effect::MayRead {
2161 return None;
2162 }
2163 return Some(Edit::Delete { node: id });
2164 };
2165 if lang.assign_op(second).is_some() {
2166 return None;
2167 }
2168 let nch = lang.children(second);
2169 let (target, value) = (*nch.first()?, *nch.get(1)?);
2170 if lang.kind(target) != NodeKind::VarRef {
2171 return None;
2172 }
2173
2174 if decl_form {
2176 if !lang.same_binding(id, target) {
2177 return None;
2178 }
2179 } else {
2180 let a = first_assign?;
2181 let t = *lang.children(a).first()?;
2182 if lang.kind(t) != NodeKind::VarRef
2183 || !lang.same_binding(t, target)
2184 || !lang.is_local_var(t)
2185 {
2186 return None;
2187 }
2188 }
2189 if subtree_contains(&*lang, value, |n| {
2191 lang.kind(n) == NodeKind::VarRef && lang.same_binding(n, target)
2192 }) {
2193 return None;
2194 }
2195 if lang.effect(first_value) > Effect::MayRead {
2197 return None;
2198 }
2199
2200 if decl_form {
2201 Some(Edit::Multi(vec![
2203 Edit::Replace {
2204 target: first_value,
2205 with: value,
2206 },
2207 Edit::Delete {
2208 node: next_stmt,
2209 },
2210 ]))
2211 } else {
2212 Some(Edit::Delete { node: id })
2214 }
2215 }
2216}
2217
2218pub struct BlockFlatten;
2227
2228impl<L: Lang> Rule<L> for BlockFlatten {
2229 fn name(&self) -> &'static str {
2230 "block_flatten"
2231 }
2232 fn kinds(&self) -> &'static [NodeKind] {
2233 &[NodeKind::Block]
2234 }
2235 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2236 let RewriteCtx { lang, walk } = ctx;
2237 let parent = walk.parent(id)?;
2238 if lang.kind(parent) != NodeKind::Block {
2239 return None;
2240 }
2241 let inner = lang.children(id).to_vec();
2243 let declares = |n: L::Id| -> bool {
2244 matches!(
2245 lang.kind(n),
2246 NodeKind::VarDecl | NodeKind::ForEach | NodeKind::Catch
2247 )
2248 };
2249 if inner.iter().any(|&s| declares(s)) {
2250 return None;
2251 }
2252 if lang.block_hoists_decl(id) {
2254 return None;
2255 }
2256 if lang.has_pattern_bindings(id) {
2262 return None;
2263 }
2264 let idx = walk.index(id)?;
2265 Some(Edit::Splice {
2266 node: parent,
2267 index: idx,
2268 remove: 1,
2269 insert: inner,
2270 })
2271 }
2272}
2273
2274pub struct DoubleNegFold;
2277
2278impl<L: Lang> Rule<L> for DoubleNegFold {
2279 fn name(&self) -> &'static str {
2280 "double_neg_fold"
2281 }
2282 fn kinds(&self) -> &'static [NodeKind] {
2283 &[NodeKind::Unary]
2284 }
2285 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2286 let RewriteCtx { lang, .. } = ctx;
2287 if lang.un_op(id) != Some(UnOp::Neg) {
2288 return None;
2289 }
2290 let inner = *lang.children(id).first()?;
2291 if lang.un_op(inner) == Some(UnOp::Neg) {
2292 let x = *lang.children(inner).first()?;
2293 if !lang.is_exact_int(x) {
2296 return None;
2297 }
2298 return Some(Edit::Replace {
2299 target: id,
2300 with: x,
2301 });
2302 }
2303 if let Some((v, wide)) = lang.literal(inner).and_then(|l| match l {
2306 LitRef::Int(v) => Some((v, false)),
2307 LitRef::Long(v) => Some((v, true)),
2308 _ => None,
2309 }) {
2310 if v < 0 {
2311 let n = lang.fold_lit_neg(v, wide)?;
2312 let with = lang.build_int(n, wide);
2313 return Some(Edit::Replace {
2314 target: id,
2315 with,
2316 });
2317 }
2318 }
2319 None
2320 }
2321}
2322
2323pub struct UnreachableAfterTerminal;
2324
2325impl<L: Lang> Rule<L> for UnreachableAfterTerminal {
2326 fn name(&self) -> &'static str {
2327 "unreachable_after_terminal"
2328 }
2329 fn kinds(&self) -> &'static [NodeKind] {
2330 &[NodeKind::Block]
2331 }
2332 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2333 let RewriteCtx { lang, walk: _ } = ctx;
2334 if lang.kind(id) != NodeKind::Block {
2335 return None;
2336 }
2337 let children = lang.children(id).to_vec();
2338 let terminal = |n: L::Id| -> bool {
2339 matches!(
2340 lang.kind(n),
2341 NodeKind::Return | NodeKind::Throw | NodeKind::Break | NodeKind::Continue
2342 )
2343 };
2344 let mut cut: Option<(usize, usize)> = None;
2345 for (i, &s) in children.iter().enumerate() {
2346 if terminal(s) && i + 1 < children.len() {
2347 cut = Some((i + 1, children.len() - i - 1));
2348 break;
2349 }
2350 }
2351 let (index, remove) = cut?;
2352 Some(Edit::Splice {
2353 node: id,
2354 index,
2355 remove,
2356 insert: Vec::new(),
2357 })
2358 }
2359}
2360
2361
2362pub struct CmpConstFold;
2369
2370impl<L: Lang> Rule<L> for CmpConstFold {
2371 fn name(&self) -> &'static str {
2372 "cmp_const_fold"
2373 }
2374 fn kinds(&self) -> &'static [NodeKind] {
2375 &[NodeKind::Binary]
2376 }
2377 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2378 let RewriteCtx { lang, walk: _ } = ctx;
2379 if lang.kind(id) != NodeKind::Binary {
2380 return None;
2381 }
2382 let op = lang.bin_op(id)?;
2383 if !op.is_comparison() {
2384 return None;
2385 }
2386 let ch = lang.children(id);
2387 let (l, r) = (*ch.first()?, *ch.get(1)?);
2388 lang.literal(l)?;
2391 lang.literal(r)?;
2392 let v = lang.fold_lit_cmp(op, l, r)?;
2393 let with = lang.build_bool(v);
2394 Some(Edit::Replace {
2395 target: id,
2396 with,
2397 })
2398 }
2399}
2400
2401pub struct BitIdentity;
2409
2410impl<L: Lang> Rule<L> for BitIdentity {
2411 fn name(&self) -> &'static str {
2412 "bit_identity"
2413 }
2414 fn kinds(&self) -> &'static [NodeKind] {
2415 &[NodeKind::Binary]
2416 }
2417 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2418 let RewriteCtx { lang, walk: _ } = ctx;
2419 if lang.kind(id) != NodeKind::Binary {
2420 return None;
2421 }
2422 let op = lang.bin_op(id)?;
2423 if !matches!(op, BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor) {
2424 return None;
2425 }
2426 let ch = lang.children(id);
2427 let (l, r) = (*ch.first()?, *ch.get(1)?);
2428 let int_lit = |n: L::Id| lang.literal(n).and_then(|x| x.as_int());
2429
2430 if op == BinOp::BitXor
2432 && lang.kind(l) == NodeKind::VarRef
2433 && lang.kind(r) == NodeKind::VarRef
2434 && lang.same_binding(l, r)
2435 && lang.is_local_var(l)
2436 && lang.is_exact_int(l)
2437 {
2438 let with = lang.build_int(0, false);
2439 return Some(Edit::Replace {
2440 target: id,
2441 with,
2442 });
2443 }
2444
2445 for (x, k) in [(l, r), (r, l)] {
2447 let Some(kv) = int_lit(k) else {
2448 continue;
2449 };
2450 if !lang.is_exact_int(x) {
2451 continue;
2452 }
2453 let identity = match (op, kv) {
2454 (BinOp::BitXor, 0) => Some(x), (BinOp::BitOr, 0) => Some(x), (BinOp::BitAnd, -1) => Some(x), (BinOp::BitAnd, 0) | (BinOp::BitOr, -1) => {
2458 if lang.effect(x) > Effect::MayRead {
2460 None
2461 } else {
2462 Some(k)
2463 }
2464 }
2465 _ => None,
2466 };
2467 if let Some(with) = identity {
2468 return Some(Edit::Replace {
2469 target: id,
2470 with,
2471 });
2472 }
2473 }
2474 None
2475 }
2476}
2477
2478pub struct ArithReassoc;
2484
2485impl<L: Lang> Rule<L> for ArithReassoc {
2486 fn name(&self) -> &'static str {
2487 "arith_reassoc"
2488 }
2489 fn kinds(&self) -> &'static [NodeKind] {
2490 &[NodeKind::Binary]
2491 }
2492 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2493 let RewriteCtx { lang, walk: _ } = ctx;
2494 if lang.kind(id) != NodeKind::Binary {
2495 return None;
2496 }
2497 let op2 = lang.bin_op(id)?;
2498 if !matches!(op2, BinOp::Add | BinOp::Sub | BinOp::BitXor) {
2499 return None;
2500 }
2501 let och = lang.children(id).to_vec();
2502 let inner = och[0];
2503 if lang.kind(inner) != NodeKind::Binary {
2504 return None;
2505 }
2506 let op1 = lang.bin_op(inner)?;
2507 let ich = lang.children(inner).to_vec();
2508 let x = ich[0];
2509
2510 if op1 == BinOp::Add && op2 == BinOp::Add {
2516 if let (Some(LitRef::Str(k1)), Some(LitRef::Str(k2))) =
2517 (lang.literal(ich[1]), lang.literal(och[1]))
2518 {
2519 let mut chain: Vec<L::Id> = vec![id];
2521 let mut cur = id;
2522 loop {
2523 let cch = lang.children(cur).to_vec();
2524 let next = cch[0];
2525 let deeper = lang.kind(next) == NodeKind::Binary
2526 && lang.bin_op(next) == Some(BinOp::Add)
2527 && matches!(lang.literal(lang.children(next).to_vec()[1]),
2528 Some(LitRef::Str(_)));
2529 if deeper {
2530 chain.push(next);
2531 cur = next;
2532 } else {
2533 break;
2534 }
2535 }
2536 let innermost = *chain.last().unwrap();
2538 let ich2 = lang.children(innermost).to_vec();
2539 let base = ich2[0];
2540 let mut joined = String::new();
2541 for &n in chain.iter().rev() {
2542 if let Some(LitRef::Str(k)) =
2543 lang.literal(lang.children(n).to_vec()[1])
2544 {
2545 joined.push_str(k);
2546 }
2547 }
2548 let _ = (k1, k2); let lit = lang.build_str(&joined);
2550 let with = lang.build_bin(BinOp::Add, base, lit);
2551 return Some(Edit::Replace {
2552 target: id,
2553 with,
2554 });
2555 }
2556 }
2557
2558 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2562 let k2 = lang.literal(och[1]).and_then(|x| x.as_int())?;
2563 if !lang.is_exact_int(x) {
2564 return None;
2565 }
2566 let is_long = matches!(lang.literal(ich[1]), Some(LitRef::Long(_)))
2567 || matches!(lang.literal(och[1]), Some(LitRef::Long(_)));
2568 let with = match lang.reassoc_delta(op1, k1, op2, k2, is_long)? {
2569 ReassocOutcome::Neutral => x,
2570 ReassocOutcome::Lit(lit, op) => lang.build_bin(op, x, lit),
2571 };
2572 Some(Edit::Replace {
2573 target: id,
2574 with,
2575 })
2576 }
2577}
2578
2579pub struct IfToTernary;
2588
2589impl<L: Lang> Rule<L> for IfToTernary {
2590 fn name(&self) -> &'static str {
2591 "if_to_ternary"
2592 }
2593 fn kinds(&self) -> &'static [NodeKind] {
2594 &[NodeKind::If]
2595 }
2596 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2597 let RewriteCtx { lang, walk } = ctx;
2598 if lang.kind(id) != NodeKind::If {
2599 return None;
2600 }
2601 let ch = lang.children(id).to_vec();
2602 let c = ch[0];
2603
2604 let single_ret = |n: L::Id| -> Option<Option<L::Id>> {
2606 match lang.kind(n) {
2607 NodeKind::Return => Some(lang.children(n).first().copied()),
2608 NodeKind::Block if lang.children(n).len() == 1 => {
2609 let inner = lang.children(n)[0];
2610 if lang.kind(inner) == NodeKind::Return {
2611 Some(lang.children(inner).first().copied())
2612 } else {
2613 None
2614 }
2615 }
2616 _ => None,
2617 }
2618 };
2619
2620 if ch.len() == 3 {
2621 let a = single_ret(ch[1])?;
2623 let b = single_ret(ch[2])?;
2624 let with = match (a, b) {
2625 (Some(va), Some(vb)) => {
2626 let t = lang.build_ternary(c, va, vb);
2627 lang.build_return(Some(t))
2628 }
2629 (None, None) => lang.build_return(None),
2630 _ => return None,
2631 };
2632 return Some(Edit::Replace {
2633 target: id,
2634 with,
2635 });
2636 }
2637
2638 if ch.len() != 2 {
2640 return None;
2641 }
2642 let a = single_ret(ch[1])?;
2643 let parent = walk.parent(id)?;
2644 if lang.kind(parent) != NodeKind::Block {
2645 return None;
2646 }
2647 let idx = walk.index(id)?;
2648 let stmts = lang.children(parent).to_vec();
2649 if idx + 2 != stmts.len() {
2650 return None; }
2652 let tail = stmts[idx + 1];
2653 if lang.kind(tail) != NodeKind::Return {
2654 return None;
2655 }
2656 let b = lang.children(tail).first().copied();
2657 let with = match (a, b) {
2658 (Some(va), Some(vb)) => {
2659 let t = lang.build_ternary(c, va, vb);
2660 lang.build_return(Some(t))
2661 }
2662 (None, None) => lang.build_return(None),
2663 _ => return None,
2664 };
2665 Some(Edit::Splice {
2666 node: parent,
2667 index: idx,
2668 remove: 2,
2669 insert: vec![with],
2670 })
2671 }
2672}
2673
2674pub struct IfAssignTernary;
2681
2682impl<L: Lang> Rule<L> for IfAssignTernary {
2683 fn name(&self) -> &'static str {
2684 "if_assign_ternary"
2685 }
2686 fn kinds(&self) -> &'static [NodeKind] {
2687 &[NodeKind::If]
2688 }
2689 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2690 let RewriteCtx { lang, walk: _ } = ctx;
2691 if lang.kind(id) != NodeKind::If {
2692 return None;
2693 }
2694 let ch = lang.children(id).to_vec();
2695 if ch.len() != 3 {
2696 return None;
2697 }
2698 let c = ch[0];
2699 let single_assign = |n: L::Id| -> Option<(L::Id, L::Id)> {
2701 let unwrap = |i: L::Id| -> L::Id {
2702 if lang.kind(i) == NodeKind::ExprStmt {
2703 let ch = lang.children(i);
2704 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2705 return ch[0];
2706 }
2707 }
2708 i
2709 };
2710 let inner = match lang.kind(n) {
2711 NodeKind::Assign => n,
2712 NodeKind::ExprStmt => unwrap(n),
2713 NodeKind::Block if lang.children(n).len() == 1 => unwrap(lang.children(n)[0]),
2714 _ => return None,
2715 };
2716 if lang.kind(inner) != NodeKind::Assign {
2717 return None;
2718 }
2719 if lang.assign_op(inner).is_some() {
2720 return None;
2721 }
2722 let a = lang.children(inner).to_vec();
2723 if lang.kind(a[0]) != NodeKind::VarRef {
2724 return None;
2725 }
2726 Some((a[0], a[1]))
2727 };
2728 let (t1, va) = single_assign(ch[1])?;
2729 let (t2, vb) = single_assign(ch[2])?;
2730 if !lang.same_binding(t1, t2) || lang.var_name(t1).is_none() {
2731 return None;
2732 }
2733 let t = lang.build_ternary(c, va, vb);
2734 let with = lang.build_assign(t1, t);
2735 Some(Edit::Replace {
2736 target: id,
2737 with,
2738 })
2739 }
2740}
2741
2742
2743pub struct DeclAssignMerge;
2751
2752impl<L: Lang> Rule<L> for DeclAssignMerge {
2753 fn name(&self) -> &'static str {
2754 "decl_assign_merge"
2755 }
2756 fn kinds(&self) -> &'static [NodeKind] {
2757 &[NodeKind::VarDecl]
2758 }
2759 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2760 let RewriteCtx { lang, walk } = ctx;
2761 if lang.kind(id) != NodeKind::VarDecl || !lang.children(id).is_empty() {
2762 return None; }
2764 let name = lang.var_name(id)?.to_string();
2765 let parent = walk.parent(id)?;
2766 if lang.kind(parent) != NodeKind::Block {
2767 return None;
2768 }
2769 let idx = walk.index(id)?;
2770 let stmts = lang.children(parent).to_vec();
2771 let next = *stmts.get(idx + 1)?;
2772 let assign = match lang.kind(next) {
2774 NodeKind::Assign => next,
2775 NodeKind::ExprStmt => {
2776 let ch = lang.children(next);
2777 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2778 ch[0]
2779 } else {
2780 return None;
2781 }
2782 }
2783 _ => return None,
2784 };
2785 if lang.assign_op(assign).is_some() {
2786 return None;
2787 }
2788 let ach = lang.children(assign).to_vec();
2789 let (target, value) = (ach[0], ach[1]);
2790 if lang.kind(target) != NodeKind::VarRef || lang.var_name(target) != Some(name.as_str()) {
2791 return None;
2792 }
2793 if subtree_contains(&*lang, value, |n| {
2795 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2796 }) {
2797 return None;
2798 }
2799 Some(Edit::Multi(vec![
2800 Edit::Splice {
2801 node: id,
2802 index: 0,
2803 remove: 0,
2804 insert: vec![value],
2805 },
2806 Edit::Delete { node: next },
2807 ]))
2808 }
2809}
2810
2811pub struct AssignPropagation;
2820
2821impl<L: Lang> Rule<L> for AssignPropagation {
2822 fn name(&self) -> &'static str {
2823 "assign_propagation"
2824 }
2825 fn kinds(&self) -> &'static [NodeKind] {
2826 &[NodeKind::Assign, NodeKind::ExprStmt]
2827 }
2828 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2829 let RewriteCtx { lang, walk } = ctx;
2830 let (assign, stmt_node) = match lang.kind(id) {
2832 NodeKind::Assign => (id, id),
2833 NodeKind::ExprStmt => {
2834 let ch = lang.children(id);
2835 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2836 (ch[0], id)
2837 } else {
2838 return None;
2839 }
2840 }
2841 _ => return None,
2842 };
2843 if lang.assign_op(assign).is_some() {
2844 return None;
2845 }
2846 let ach = lang.children(assign).to_vec();
2847 let (target, value) = (ach[0], ach[1]);
2848 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
2849 return None;
2850 }
2851 if lang.kind(value) == NodeKind::ArrayLit {
2853 return None;
2854 }
2855 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
2857 return None;
2858 }
2859 let name = lang.var_name(target)?.to_string();
2860
2861 let parent = walk.parent(stmt_node)?;
2862 if lang.kind(parent) != NodeKind::Block {
2863 return None;
2864 }
2865 let idx = walk.index(stmt_node)?;
2866 let stmts = lang.children(parent).to_vec();
2867
2868 let anchored = stmts[..idx].iter().any(|&s| {
2870 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2871 });
2872 if !anchored {
2873 return None;
2874 }
2875
2876 if subtree_contains(&*lang, value, |n| {
2878 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2879 }) {
2880 return None;
2881 }
2882
2883 let name_key = lang.var_key(target)?;
2886 if count_name_refs(&*lang, &stmts[idx + 1..], name_key, &name) != 1 {
2887 return None;
2888 }
2889 let mut wa = Watch::<L>::new(name_key, &[]);
2890 for &s in &stmts[idx + 1..] {
2891 if lang.stmt_has_opaque_region(s) {
2892 return None;
2893 }
2894 scan_region(&*lang, s, &mut wa);
2895 }
2896 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
2897 return None;
2898 }
2899 let use_id = wa.uses[0];
2900 if matches!(lang.literal(value), Some(LitRef::Null))
2907 && null_unsafe_position(&*lang, walk, use_id)
2908 {
2909 return None;
2910 }
2911 if lang.kind(value) == NodeKind::VarRef
2915 && typed_position(&*lang, walk, use_id)
2916 .is_some_and(|t| !lang.var_cast_position_ok(value, t))
2917 {
2918 return None;
2919 }
2920 let mut watch_keys: Vec<L::NameKey> = Vec::new();
2922 collect_read_keys(&*lang, value, &mut watch_keys);
2923 watch_keys.push(name_key);
2924 let mut wb = Watch::<L>::new(name_key, &watch_keys);
2925 for &s in &stmts[idx + 1..] {
2926 let uses_before = wb.uses.len();
2927 scan_region(&*lang, s, &mut wb);
2928 if uses_before < wb.uses.len() {
2929 break; }
2931 }
2932 if wb.wrote(name_key) {
2934 return None;
2935 }
2936 let ve = lang.effect(value);
2937
2938 if ve <= Effect::MayRead {
2939 if let Some(target_key) = use_stmt_target_write_key(&*lang, &stmts[idx + 1..], use_id) {
2940 wb.clear_wrote(target_key);
2941 }
2942 if wb.any_write() {
2943 return None;
2944 }
2945 return Some(Edit::Multi(vec![
2946 Edit::Replace {
2947 target: use_id,
2948 with: value,
2949 },
2950 Edit::Delete { node: stmt_node },
2951 ]));
2952 }
2953
2954 if idx + 1 >= stmts.len() {
2956 return None;
2957 }
2958 let stmt = stmts[idx + 1];
2959 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
2960 return None;
2961 }
2962 match lang.kind(stmt) {
2963 NodeKind::Return
2964 | NodeKind::ExprStmt
2965 | NodeKind::Assign
2966 | NodeKind::Throw
2967 | NodeKind::VarDecl => {}
2968 _ => return None,
2969 }
2970 if !straight_path(&*lang, stmt, use_id) {
2971 return None;
2972 }
2973 if !prefix_effects_readable(&*lang, stmt, use_id) {
2974 return None;
2975 }
2976 Some(Edit::Multi(vec![
2977 Edit::Replace {
2978 target: use_id,
2979 with: value,
2980 },
2981 Edit::Delete { node: stmt_node },
2982 ]))
2983 }
2984}
2985
2986
2987fn use_stmt_target_write_key<L: Lang>(
2993 lang: &L,
2994 stmts_after: &[L::Id],
2995 use_id: L::Id,
2996) -> Option<L::NameKey> {
2997 for &s in stmts_after {
2998 if subtree_contains(lang, s, |n| n == use_id) {
2999 let assign = match lang.kind(s) {
3000 NodeKind::Assign => s,
3001 NodeKind::ExprStmt => {
3002 let ch = lang.children(s);
3003 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3004 ch[0]
3005 } else {
3006 return None;
3007 }
3008 }
3009 _ => return None,
3010 };
3011 if lang.assign_op(assign).is_some() {
3012 return None;
3013 }
3014 let ch = lang.children(assign);
3015 if ch.len() == 2 && ch[1] == use_id && lang.kind(ch[0]) == NodeKind::VarRef {
3016 return lang.var_key(ch[0]);
3017 }
3018 return None;
3019 }
3020 }
3021 None
3022}
3023
3024fn collect_read_keys<L: Lang>(lang: &L, id: L::Id, out: &mut Vec<L::NameKey>) {
3027 let mut stack = vec![id];
3028 while let Some(n) = stack.pop() {
3029 if lang.kind(n) == NodeKind::VarRef {
3030 if let Some(k) = lang.var_key(n) {
3031 if !out.contains(&k) {
3032 out.push(k);
3033 }
3034 }
3035 }
3036 for &c in lang.children(n) {
3037 stack.push(c);
3038 }
3039 }
3040}
3041
3042
3043pub struct TrailingContinue;
3050
3051impl<L: Lang> Rule<L> for TrailingContinue {
3052 fn name(&self) -> &'static str {
3053 "trailing_continue"
3054 }
3055 fn kinds(&self) -> &'static [NodeKind] {
3056 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
3057 }
3058 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3059 let RewriteCtx { lang, walk: _ } = ctx;
3060 let body = match lang.kind(id) {
3062 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
3063 *lang.children(id).last()?
3064 }
3065 _ => return None,
3066 };
3067 if lang.kind(body) != NodeKind::Block {
3068 return None;
3069 }
3070 let ch = lang.children(body).to_vec();
3071 let last = *ch.last()?;
3072 if lang.kind(last) != NodeKind::Continue || !lang.children(last).is_empty() {
3073 if lang.kind(last) != NodeKind::Continue {
3076 return None;
3077 }
3078 }
3079 Some(Edit::Delete { node: last })
3080 }
3081}
3082
3083pub struct MultiUseCopyPropagation;
3091
3092impl<L: Lang> Rule<L> for MultiUseCopyPropagation {
3093 fn name(&self) -> &'static str {
3094 "multi_use_copy"
3095 }
3096 fn kinds(&self) -> &'static [NodeKind] {
3097 &[NodeKind::Assign, NodeKind::ExprStmt]
3098 }
3099 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3100 let RewriteCtx { lang, walk } = ctx;
3101 let (assign, stmt_node) = match lang.kind(id) {
3103 NodeKind::Assign => (id, id),
3104 NodeKind::ExprStmt => {
3105 let ch = lang.children(id);
3106 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3107 (ch[0], id)
3108 } else {
3109 return None;
3110 }
3111 }
3112 _ => return None,
3113 };
3114 if lang.assign_op(assign).is_some() {
3115 return None;
3116 }
3117 let ach = lang.children(assign).to_vec();
3118 let (target, value) = (ach[0], ach[1]);
3119 if lang.kind(target) != NodeKind::VarRef || lang.kind(value) != NodeKind::VarRef {
3120 return None;
3121 }
3122 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
3124 return None;
3125 }
3126 if !lang.is_local_var(target) {
3127 return None;
3128 }
3129 let name = lang.var_name(target)?.to_string();
3130 let src = lang.var_name(value)?.to_string();
3131 if name == src {
3132 return None;
3133 }
3134
3135 let parent = walk.parent(stmt_node)?;
3136 if lang.kind(parent) != NodeKind::Block {
3137 return None;
3138 }
3139 let idx = walk.index(stmt_node)?;
3140 let stmts = lang.children(parent).to_vec();
3141
3142 let anchored = stmts[..idx].iter().any(|&s| {
3144 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
3145 });
3146 if !anchored {
3147 return None;
3148 }
3149
3150 let name_key = lang.var_key(target)?;
3152 let src_key = lang.var_key(value)?;
3153 let mut w1 = Watch::<L>::new(name_key, &[name_key, src_key]);
3154 for &s in &stmts[idx + 1..] {
3155 scan_region(&*lang, s, &mut w1);
3156 }
3157 if w1.opaque || w1.shadowed || w1.uses.is_empty() {
3158 return None;
3159 }
3160 if w1.wrote(name_key) || w1.wrote(src_key) {
3161 return None;
3162 }
3163 let synth = count_name_refs(&*lang, &stmts[idx + 1..], name_key, &name);
3169 if synth != w1.uses.len() {
3170 return None;
3171 }
3172 if w1.uses.iter().any(|&u| {
3181 typed_position(&*lang, walk, u).is_some_and(|t| !lang.var_cast_position_ok(value, t))
3182 }) {
3183 return None;
3184 }
3185
3186 let mut edits: Vec<Edit<L>> = w1.uses
3187 .iter()
3188 .map(|&u| Edit::Replace {
3189 target: u,
3190 with: value,
3191 })
3192 .collect();
3193 edits.push(Edit::Delete { node: stmt_node });
3194 Some(Edit::Multi(edits))
3195 }
3196}
3197
3198
3199pub struct TrailingReturn;
3206
3207impl<L: Lang> Rule<L> for TrailingReturn {
3208 fn name(&self) -> &'static str {
3209 "trailing_return"
3210 }
3211 fn kinds(&self) -> &'static [NodeKind] {
3212 &[NodeKind::Return]
3213 }
3214 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3215 let root = ctx.root();
3216 let parent = ctx.parent(id)?;
3217 let lang = ctx.lang;
3218 if lang.kind(id) != NodeKind::Return || !lang.children(id).is_empty() {
3219 return None;
3220 }
3221 if parent != root || lang.kind(parent) != NodeKind::Block {
3222 return None;
3223 }
3224 if lang.children(parent).last() != Some(&id) {
3225 return None;
3226 }
3227 Some(Edit::Delete { node: id })
3228 }
3229}
3230
3231
3232pub struct TernaryBoolOp;
3239
3240impl<L: Lang> Rule<L> for TernaryBoolOp {
3241 fn name(&self) -> &'static str {
3242 "ternary_bool_op"
3243 }
3244 fn kinds(&self) -> &'static [NodeKind] {
3245 &[NodeKind::Ternary]
3246 }
3247 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3248 let RewriteCtx { lang, walk: _ } = ctx;
3249 if lang.kind(id) != NodeKind::Ternary {
3250 return None;
3251 }
3252 let ch = lang.children(id);
3253 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
3254 if !lang.is_bool(c) {
3257 return None;
3258 }
3259 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
3260 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
3261 let with = if is_false(b) && lang.is_bool(a) {
3262 lang.build_bin(BinOp::And, c, a)
3263 } else if lb(b) && lang.is_bool(a) {
3264 return None;
3266 } else if lb(a) && lang.is_bool(b) {
3267 lang.build_bin(BinOp::Or, c, b)
3269 } else if is_false(a) && lang.is_bool(b) {
3270 return None;
3272 } else {
3273 return None;
3274 };
3275 Some(Edit::Replace {
3276 target: id,
3277 with,
3278 })
3279 }
3280}
3281
3282
3283pub struct StoreKill;
3292
3293pub fn scan_kill_event<L: Lang>(
3300 lang: &L,
3301 node: L::Id,
3302 name: &str,
3303 dominating: bool,
3304) -> StmtKill<L::Id> {
3305 match lang.kind(node) {
3306 NodeKind::VarRef => {
3307 if lang.var_name(node) == Some(name) {
3308 StmtKill::Read
3309 } else {
3310 StmtKill::None
3311 }
3312 }
3313 NodeKind::VarDecl => {
3314 if lang.var_name(node) == Some(name) {
3315 StmtKill::Shadow
3316 } else {
3317 for &c in lang.children(node) {
3319 let e = scan_kill_event(lang, c, name, dominating);
3320 if !matches!(e, StmtKill::None) {
3321 return e;
3322 }
3323 }
3324 StmtKill::None
3325 }
3326 }
3327 NodeKind::Assign => {
3328 let ch = lang.children(node);
3329 if let Some(&val) = ch.get(1) {
3331 let e = scan_kill_event(lang, val, name, dominating);
3332 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3333 return e;
3334 }
3335 }
3336 if lang.assign_op(node).is_none() {
3337 if let Some(&t) = ch.first() {
3338 if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
3339 if dominating {
3340 return StmtKill::DomWrite(node);
3341 }
3342 return StmtKill::None; }
3344 }
3345 }
3346 StmtKill::None
3347 }
3348 NodeKind::If | NodeKind::While | NodeKind::Ternary => {
3349 let ch = lang.children(node);
3352 if let Some(&c) = ch.first() {
3353 let e = scan_kill_event(lang, c, name, true);
3354 if !matches!(e, StmtKill::None) {
3355 return e;
3356 }
3357 }
3358 for &c in &ch[1..] {
3359 let e = scan_kill_event(lang, c, name, false);
3360 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3361 return e;
3362 }
3363 }
3364 StmtKill::None
3365 }
3366 NodeKind::Binary => {
3367 let Some(op) = lang.bin_op(node) else {
3368 return StmtKill::None;
3369 };
3370 let ch = lang.children(node);
3371 if op.is_short_circuit() {
3372 if let Some(&l) = ch.first() {
3374 let e = scan_kill_event(lang, l, name, true);
3375 if !matches!(e, StmtKill::None) {
3376 return e;
3377 }
3378 }
3379 if let Some(&r) = ch.get(1) {
3380 let e = scan_kill_event(lang, r, name, false);
3381 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3382 return e;
3383 }
3384 }
3385 StmtKill::None
3386 } else {
3387 for &c in ch {
3388 let e = scan_kill_event(lang, c, name, dominating);
3389 if !matches!(e, StmtKill::None) {
3390 return e;
3391 }
3392 }
3393 StmtKill::None
3394 }
3395 }
3396 NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
3397 let ch = lang.children(node);
3401 let dom_len = match lang.kind(node) {
3402 NodeKind::For => ch.len().saturating_sub(2),
3403 NodeKind::ForEach | NodeKind::Switch => 1,
3404 _ => 0,
3405 };
3406 for &c in &ch[..dom_len] {
3407 let e = scan_kill_event(lang, c, name, true);
3408 if !matches!(e, StmtKill::None) {
3409 return e;
3410 }
3411 }
3412 for &c in &ch[dom_len..] {
3413 let e = scan_kill_event(lang, c, name, false);
3414 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3415 return e;
3416 }
3417 }
3418 StmtKill::None
3419 }
3420 _ => {
3421 for &c in lang.children(node) {
3422 let e = scan_kill_event(lang, c, name, dominating);
3423 if !matches!(e, StmtKill::None) {
3424 return e;
3425 }
3426 }
3427 StmtKill::None
3428 }
3429 }
3430}
3431
3432pub fn collect_stmt_kill_events<L: Lang>(lang: &L, root: L::Id) -> Vec<(L::NameKey, StmtKill<L::Id>)> {
3437 use std::collections::BTreeMap;
3438 enum Act<Id> {
3439 Enter(Id, bool),
3440 AfterAssign(Id, bool),
3441 }
3442 let mut events: BTreeMap<L::NameKey, StmtKill<L::Id>> = BTreeMap::new();
3443 let mut stack = vec![Act::Enter(root, true)];
3444 while let Some(act) = stack.pop() {
3445 match act {
3446 Act::AfterAssign(id, dominating) => {
3447 if lang.assign_op(id).is_some() {
3448 continue;
3449 }
3450 let Some(&target) = lang.children(id).first() else {
3451 continue;
3452 };
3453 if lang.kind(target) != NodeKind::VarRef {
3454 continue;
3455 }
3456 let Some(key) = lang.var_key(target) else {
3457 continue;
3458 };
3459 if dominating {
3460 events.entry(key).or_insert(StmtKill::DomWrite(id));
3461 }
3462 }
3463 Act::Enter(node, dominating) => match lang.kind(node) {
3464 NodeKind::VarRef => {
3465 if let Some(key) = lang.var_key(node) {
3466 events.entry(key).or_insert(StmtKill::Read);
3467 }
3468 }
3469 NodeKind::VarDecl => {
3470 if let Some(key) = lang.var_key(node) {
3471 events.entry(key).or_insert(StmtKill::Shadow);
3472 }
3473 for &c in lang.children(node).iter().rev() {
3474 stack.push(Act::Enter(c, dominating));
3475 }
3476 }
3477 NodeKind::Assign => {
3478 let ch = lang.children(node);
3479 stack.push(Act::AfterAssign(node, dominating));
3480 if let Some(&val) = ch.get(1) {
3481 stack.push(Act::Enter(val, dominating));
3482 }
3483 }
3484 NodeKind::If | NodeKind::While | NodeKind::Ternary => {
3485 let ch = lang.children(node);
3486 for (i, &c) in ch.iter().enumerate().rev() {
3487 stack.push(Act::Enter(c, i == 0));
3488 }
3489 }
3490 NodeKind::Binary => {
3491 let ch = lang.children(node);
3492 let short = lang.bin_op(node).is_some_and(|op| op.is_short_circuit());
3493 for (i, &c) in ch.iter().enumerate().rev() {
3494 let child_dom = if short { i == 0 } else { dominating };
3495 stack.push(Act::Enter(c, child_dom));
3496 }
3497 }
3498 NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
3499 let ch = lang.children(node);
3500 let dom_len = match lang.kind(node) {
3501 NodeKind::For => ch.len().saturating_sub(2),
3502 NodeKind::ForEach | NodeKind::Switch => 1,
3503 _ => 0,
3504 };
3505 for (i, &c) in ch.iter().enumerate().rev() {
3506 stack.push(Act::Enter(c, i < dom_len));
3507 }
3508 }
3509 _ => {
3510 for &c in lang.children(node).iter().rev() {
3511 stack.push(Act::Enter(c, dominating));
3512 }
3513 }
3514 },
3515 }
3516 }
3517 events.into_iter().collect()
3518}
3519
3520impl<L: Lang> Rule<L> for StoreKill {
3521 fn name(&self) -> &'static str {
3522 "store_kill"
3523 }
3524 fn kinds(&self) -> &'static [NodeKind] {
3525 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
3526 }
3527 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3528 let RewriteCtx { lang, walk } = ctx;
3529 let (is_decl, stmt_node, first_value) = match lang.kind(id) {
3531 NodeKind::VarDecl => {
3532 if lang.children(id).is_empty() {
3533 return None;
3534 }
3535 (true, id, *lang.children(id).first()?)
3536 }
3537 NodeKind::Assign => {
3538 if lang.assign_op(id).is_some() {
3539 return None;
3540 }
3541 (false, id, *lang.children(id).get(1)?)
3542 }
3543 NodeKind::ExprStmt => {
3544 let ch = lang.children(id);
3545 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3546 let a = ch[0];
3547 if lang.assign_op(a).is_some() {
3548 return None;
3549 }
3550 (false, id, *lang.children(a).get(1)?)
3551 } else {
3552 return None;
3553 }
3554 }
3555 _ => return None,
3556 };
3557 let (name, name_key) = if is_decl {
3558 (lang.var_name(id)?.to_string(), lang.var_key(id)?)
3559 } else {
3560 let a = match lang.kind(id) {
3561 NodeKind::Assign => id,
3562 _ => lang.children(id)[0],
3563 };
3564 let t = *lang.children(a).first()?;
3565 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
3566 return None;
3567 }
3568 (lang.var_name(t)?.to_string(), lang.var_key(t)?)
3569 };
3570
3571 let parent = walk.parent(stmt_node)?;
3572 if lang.kind(parent) != NodeKind::Block {
3573 return None;
3574 }
3575 let idx = walk.index(stmt_node)?;
3576 let stmts = lang.children(parent).to_vec();
3577
3578 let mut killed_by: Option<(usize, L::Id)> = None;
3580 for (si, &s) in stmts.iter().enumerate().skip(idx + 1) {
3581 if lang.stmt_has_opaque_region(s) {
3585 return None;
3586 }
3587 let event = lang
3588 .stmt_kill_event(s, name_key)
3589 .unwrap_or_else(|| scan_kill_event(&*lang, s, &name, true));
3590 match event {
3591 StmtKill::None => continue,
3592 StmtKill::Read | StmtKill::Shadow => return None,
3593 StmtKill::DomWrite(assign) => {
3594 killed_by = Some((si, assign));
3595 break;
3596 }
3597 }
3598 }
3599 let (si, kill_assign) = killed_by?;
3600 {
3608 let kill_container = match walk.parents.get(&kill_assign) {
3609 Some(&(p, _)) if lang.kind(p) == NodeKind::ExprStmt => p,
3610 _ => kill_assign,
3611 };
3612 if kill_container != stmts[si] {
3613 return None;
3614 }
3615 }
3616 let mut has_try_ancestor = false;
3629 {
3630 let mut cur = stmt_node;
3631 while let Some(&(p, _)) = walk.parents.get(&cur) {
3632 if lang.kind(p) == NodeKind::Try {
3633 has_try_ancestor = true;
3634 break;
3635 }
3636 cur = p;
3637 }
3638 }
3639 if has_try_ancestor {
3640 for &s in &stmts[idx + 1..si] {
3641 if lang.effect(s) > Effect::MayRead {
3642 return None;
3643 }
3644 }
3645 }
3646
3647 if is_decl {
3648 if lang.effect(first_value) > Effect::MayRead {
3651 return None;
3652 }
3653 let value = *lang.children(kill_assign).get(1)?;
3654 if is_literal(lang, value) {
3655 let assign_stmt = stmts[si];
3656 return Some(Edit::Multi(vec![
3657 Edit::Splice {
3658 node: id,
3659 index: 0,
3660 remove: 1,
3661 insert: vec![value],
3662 },
3663 Edit::Delete { node: assign_stmt },
3664 ]));
3665 }
3666 if lang.is_inferred_decl(id) {
3670 return None;
3671 }
3672 if lang.effect(first_value) > Effect::MayRead {
3673 return None;
3674 }
3675 return Some(Edit::Splice {
3676 node: id,
3677 index: 0,
3678 remove: 1,
3679 insert: Vec::new(),
3680 });
3681 }
3682 if lang.effect(first_value) > Effect::MayRead {
3684 return None;
3685 }
3686 Some(Edit::Delete { node: stmt_node })
3687 }
3688}
3689
3690fn is_literal<L: Lang>(lang: &L, n: L::Id) -> bool {
3692 matches!(lang.kind(n), NodeKind::Literal)
3693}
3694
3695pub fn default_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3701 vec![
3702 Box::new(DoubleNegFold),
3703 Box::new(BlockFlatten),
3704 Box::new(ParenRemoval),
3705 Box::new(ConstCondition),
3706 Box::new(BooleanReturn),
3707 Box::new(IfToTernary),
3708 Box::new(IfAssignTernary),
3709 Box::new(IfElseEmpty),
3710 Box::new(BoolCompare),
3711 Box::new(DoubleNot),
3712 Box::new(BoolNotFold),
3713 Box::new(BoolShortCircuit),
3714 Box::new(NotCompare),
3715 Box::new(TernaryFold),
3716 Box::new(TernaryBool),
3717 Box::new(TernaryBoolOp),
3718 Box::new(ConstFoldBin),
3719 Box::new(CmpConstFold),
3720 Box::new(BitIdentity),
3721 Box::new(ArithReassoc),
3722 Box::new(SelfAssign),
3723 Box::new(ArithIdentity),
3724 Box::new(ArithZero),
3725 Box::new(DeclAssignMerge),
3726 Box::new(LocalPropagation),
3727 Box::new(AssignPropagation),
3728 Box::new(MultiUseCopyPropagation),
3729 Box::new(TrailingReturn),
3730 Box::new(InverseAssignPair),
3731 Box::new(DeadDecl),
3732 Box::new(AssignBackFold),
3733 Box::new(DeadStore),
3734 Box::new(StoreKill),
3735 ]
3736}
3737
3738pub fn all_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3740 let mut rules = default_rules();
3741 rules.push(Box::new(UnreachableAfterTerminal));
3742 rules
3743}