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 terminates {
125 let mut p = walk.parent(id);
126 let mut i = walk.index(id);
127 if let (Some(pp), Some(_)) = (p, i) {
128 if lang.kind(pp) == NodeKind::Block {
129 p = walk.parent(pp);
130 i = walk.index(pp);
131 }
132 }
133 while let (Some(pp), Some(ii)) = (p, i) {
134 if ii + 1 < lang.children(pp).len() {
135 return None;
136 }
137 if lang.kind(pp) != NodeKind::Block {
138 break;
139 }
140 p = walk.parent(pp);
141 i = walk.index(pp);
142 }
143 }
144 if let (Some(parent), Some(index)) = (walk.parent(id), walk.index(id)) {
145 if lang.kind(parent) == NodeKind::Block {
146 let siblings = lang.children(parent).to_vec();
147 let remove = if terminates && index + 1 < siblings.len() {
148 siblings.len() - index
149 } else {
150 1
151 };
152 if remove > 1
155 && siblings[index + 1..]
156 .iter()
157 .any(|&s| contains_yield(&*lang, s))
158 {
159 return None;
160 }
161 return Some(Edit::Splice {
162 node: parent,
163 index,
164 remove,
165 insert,
166 });
167 }
168 if lang.kind(parent) == NodeKind::Case && terminates {
177 let siblings = lang.children(parent).to_vec();
178 if index + 1 < siblings.len() {
179 return None;
180 }
181 }
182 }
183 Some(Edit::Replace {
184 target: id,
185 with: then,
186 })
187 }
188 Some(LitRef::Bool(false)) => {
189 let then = *ch.get(1)?;
194 if contains_yield(&*lang, then) {
195 return None;
196 }
197 if let Some(&els) = ch.get(2) {
200 if lang.contains_hoisted_decl(els) {
201 return None;
202 }
203 }
204 let els = ch.get(2).copied();
205 if let (Some(parent), Some(index)) = (walk.parent(id), walk.index(id)) {
206 if lang.kind(parent) == NodeKind::Block {
207 let insert: Vec<L::Id> = match els {
208 Some(e) => {
209 if lang.kind(e) == NodeKind::Block {
210 lang.children(e).to_vec()
211 } else {
212 vec![e]
213 }
214 }
215 None => Vec::new(),
216 };
217 return Some(if insert.is_empty() {
218 Edit::Delete { node: id }
219 } else {
220 Edit::Splice {
221 node: parent,
222 index,
223 remove: 1,
224 insert,
225 }
226 });
227 }
228 }
229 match els {
230 Some(e) => Some(Edit::Replace {
231 target: id,
232 with: e,
233 }),
234 None => {
235 if walk.parent(id).is_some() {
240 Some(Edit::Replace {
241 target: id,
242 with: lang.build_block(Vec::new()),
243 })
244 } else {
245 Some(Edit::Delete { node: id })
246 }
247 }
248 }
249 }
250 _ => None,
251 }
252 }
253}
254
255static P_IF_RET_TF: Pat = Pat::Kind(
262 NodeKind::If,
263 &[Pat::Bind("cond"), Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)]), Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])],
264);
265static P_IF_RET_FT: Pat = Pat::Kind(
266 NodeKind::If,
267 &[Pat::Bind("cond"), Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)]), Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])],
268);
269static P_IF_BRET_TF: Pat = Pat::Kind(
271 NodeKind::If,
272 &[
273 Pat::Bind("cond"),
274 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])]),
275 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])]),
276 ],
277);
278static P_IF_BRET_FT: Pat = Pat::Kind(
279 NodeKind::If,
280 &[
281 Pat::Bind("cond"),
282 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])]),
283 Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])]),
284 ],
285);
286
287pub struct BooleanReturn;
288
289impl<L: Lang> Rule<L> for BooleanReturn {
290 fn name(&self) -> &'static str {
291 "boolean_return"
292 }
293 fn kinds(&self) -> &'static [NodeKind] {
294 &[NodeKind::If]
295 }
296 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
297 let RewriteCtx { lang, walk: _ } = ctx;
298 if lang.kind(id) != NodeKind::If {
299 return None;
300 }
301 let negated = |cond: L::Id, lang: &mut L| {
303 let nc = lang.build_unary(UnOp::Not, cond);
304 lang.build_return(Some(nc))
305 };
306 for (pat, invert) in [
307 (&P_IF_RET_TF, false),
308 (&P_IF_BRET_TF, false),
309 (&P_IF_RET_FT, true),
310 (&P_IF_BRET_FT, true),
311 ] {
312 if let Some(b) = matches(&*lang, id, pat) {
313 let cond = lookup(&b, "cond")?;
314 if !lang.is_bool(cond) {
315 return None;
316 }
317 let ret = if invert {
318 negated(cond, lang)
319 } else {
320 lang.build_return(Some(cond))
321 };
322 return Some(Edit::Replace {
323 target: id,
324 with: ret,
325 });
326 }
327 }
328 None
329 }
330}
331
332fn lookup<Id: Copy>(binds: &[(&'static str, Id)], name: &str) -> Option<Id> {
333 binds.iter().find(|(n, _)| *n == name).map(|(_, id)| *id)
334}
335
336pub struct IfElseEmpty;
342
343impl<L: Lang> Rule<L> for IfElseEmpty {
344 fn name(&self) -> &'static str {
345 "if_else_empty"
346 }
347 fn kinds(&self) -> &'static [NodeKind] {
348 &[NodeKind::If]
349 }
350 fn structural(&self) -> bool {
351 true
355 }
356 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
357 let RewriteCtx { lang, walk } = ctx;
358 if lang.kind(id) != NodeKind::If {
359 return None;
360 }
361 let ch = lang.children(id);
362 let cond = *ch.first()?;
363 let is_empty_block = |n: L::Id| -> bool {
364 lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
365 };
366 match ch.len() {
367 3 => {
368 let (then, els) = (ch[1], ch[2]);
369 if is_empty_block(then) {
370 if is_empty_block(els) {
371 return Some(Edit::Splice {
373 node: id,
374 index: 0,
375 remove: 3,
376 insert: vec![cond, then],
377 });
378 }
379 let nc = lang.build_unary(UnOp::Not, cond);
381 return Some(Edit::Splice {
382 node: id,
383 index: 0,
384 remove: 3,
385 insert: vec![nc, els],
386 });
387 }
388 if is_empty_block(els) {
389 return Some(Edit::Splice {
390 node: id,
391 index: 0,
392 remove: 3,
393 insert: vec![cond, then],
394 });
395 }
396 None
397 }
398 2 => {
399 let then = ch[1];
400 let parent_is_block = walk
407 .parent(id)
408 .map(|p| lang.kind(p) == NodeKind::Block)
409 .unwrap_or(false);
410 if is_empty_block(then)
411 && lang.effect(cond) <= Effect::MayRead
412 && parent_is_block
413 {
414 return Some(Edit::Delete { node: id });
416 }
417 None
418 }
419 _ => None,
420 }
421 }
422}
423
424pub struct BoolCompare;
430
431impl<L: Lang> Rule<L> for BoolCompare {
432 fn name(&self) -> &'static str {
433 "bool_compare"
434 }
435 fn kinds(&self) -> &'static [NodeKind] {
436 &[NodeKind::Binary]
437 }
438 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
439 let RewriteCtx { lang, walk } = ctx;
440 if lang.kind(id) != NodeKind::Binary {
441 return None;
442 }
443 let op = lang.bin_op(id)?;
444 if !matches!(op, BinOp::Eq | BinOp::Ne) {
445 return None;
446 }
447 let ch = lang.children(id);
448 let (l, r) = (*ch.first()?, *ch.get(1)?);
449 let lit_bool = |n: L::Id| -> Option<bool> {
450 match lang.literal(n) {
451 Some(LitRef::Bool(b)) => Some(b),
452 _ => None,
453 }
454 };
455 let (x, lit) = match (lit_bool(l), lit_bool(r)) {
457 (None, Some(b)) => (l, b),
458 (Some(b), None) => (r, b),
459 _ => return None,
460 };
461 if !lang.is_bool(x) {
462 let in_cond = walk.parent(id).is_some_and(|p| {
467 matches!(lang.kind(p), NodeKind::If | NodeKind::While) && lang.children(p).first() == Some(&id)
468 });
469 if !in_cond {
470 return None;
471 }
472 }
473 let neg = match (op, lit) {
475 (BinOp::Eq, true) | (BinOp::Ne, false) => false,
476 (BinOp::Eq, false) | (BinOp::Ne, true) => true,
477 _ => return None,
478 };
479 let with = if neg {
480 lang.build_unary(UnOp::Not, x)
481 } else {
482 x
483 };
484 Some(Edit::Replace {
485 target: id,
486 with,
487 })
488 }
489}
490
491pub struct DoubleNot;
497
498impl<L: Lang> Rule<L> for DoubleNot {
499 fn name(&self) -> &'static str {
500 "double_not"
501 }
502 fn kinds(&self) -> &'static [NodeKind] {
503 &[NodeKind::Unary]
504 }
505 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
506 let RewriteCtx { lang, walk: _ } = ctx;
507 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
508 return None;
509 }
510 let inner = *lang.children(id).first()?;
511 if lang.kind(inner) != NodeKind::Unary || lang.un_op(inner) != Some(UnOp::Not) {
512 return None;
513 }
514 let x = *lang.children(inner).first()?;
515 if !lang.double_not_sound(x) {
519 return None;
520 }
521 Some(Edit::Replace {
522 target: id,
523 with: x,
524 })
525 }
526}
527
528pub struct BoolNotFold;
534
535impl<L: Lang> Rule<L> for BoolNotFold {
536 fn name(&self) -> &'static str {
537 "bool_not_fold"
538 }
539 fn kinds(&self) -> &'static [NodeKind] {
540 &[NodeKind::Unary]
541 }
542 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
543 let RewriteCtx { lang, walk: _ } = ctx;
544 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
545 return None;
546 }
547 let operand = *lang.children(id).first()?;
548 match lang.literal(operand) {
549 Some(LitRef::Bool(b)) => {
550 let folded = lang.build_bool(!b);
551 Some(Edit::Replace {
552 target: id,
553 with: folded,
554 })
555 }
556 _ => None,
557 }
558 }
559}
560
561pub struct SelfAssign;
566
567impl<L: Lang> Rule<L> for SelfAssign {
568 fn name(&self) -> &'static str {
569 "self_assign"
570 }
571 fn kinds(&self) -> &'static [NodeKind] {
572 &[NodeKind::Assign]
573 }
574 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
575 let RewriteCtx { lang, walk } = ctx;
576 if lang.kind(id) != NodeKind::Assign {
577 return None;
578 }
579 if lang.assign_op(id).is_some() {
580 return None; }
582 let ch = lang.children(id);
583 let (t, v) = (*ch.first()?, *ch.get(1)?);
584 if lang.kind(t) != NodeKind::VarRef || lang.kind(v) != NodeKind::VarRef {
585 return None;
586 }
587 if !lang.same_binding(t, v) || !lang.is_local_var(t) {
588 return None;
589 }
590 let (parent, _) = walk.parents.get(&id)?;
597 match lang.kind(*parent) {
598 NodeKind::ExprStmt => {
599 let (gp, _) = walk.parents.get(parent)?;
605 match lang.kind(*gp) {
606 NodeKind::Block | NodeKind::Case => Some(Edit::Delete { node: *parent }),
607 _ => None,
608 }
609 }
610 NodeKind::Block => Some(Edit::Delete { node: id }),
611 _ => None,
612 }
613 }
614}
615
616pub struct DeadDecl;
630
631impl<L: Lang> Rule<L> for DeadDecl {
632 fn name(&self) -> &'static str {
633 "dead_decl"
634 }
635 fn kinds(&self) -> &'static [NodeKind] {
636 &[NodeKind::VarDecl]
637 }
638 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
639 let RewriteCtx { lang, walk } = ctx;
640 if lang.kind(id) != NodeKind::VarDecl {
641 return None;
642 }
643 if !lang.dead_decl_allowed(id) {
644 return None;
645 }
646 let ch = lang.children(id).to_vec();
647 if let Some(&v) = ch.first() {
650 if lang.effect(v) > Effect::MayRead {
651 return None;
652 }
653 if lang.value_may_throw_unresolved(v) {
654 return None;
655 }
656 let name_s = lang.var_name(id).unwrap_or("");
659 if subtree_contains(&*lang, v, |n| {
660 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name_s)
661 }) {
662 return None;
663 }
664 }
665 let parent = walk.parent(id)?;
666 if lang.kind(parent) != NodeKind::Block {
667 return None;
668 }
669 let idx = walk.index(id)?;
670 let stmts = lang.children(parent).to_vec();
671 let name_str = lang.var_name(id)?.to_string();
677 let name_key = lang.var_key(id)?;
678 for &s in &stmts[idx + 1..] {
679 if lang.stmt_has_opaque_region(s) {
681 return None;
682 }
683 match (lang.stmt_mentions(s, name_key), lang.stmt_opaque(s)) {
684 (Some(mentions), Some(opaque)) => {
685 if mentions || opaque {
688 return None;
689 }
690 }
691 _ => {
692 if subtree_contains(&*lang, s, |n| {
695 lang.kind(n) == NodeKind::VarRef
696 && lang.var_name(n) == Some(name_str.as_str())
697 }) {
698 return None;
699 }
700 let mut w = Watch::<L>::new(name_key, &[]);
701 scan_region(&*lang, s, &mut w);
702 if w.opaque || w.shadowed {
703 return None;
704 }
705 }
706 }
707 }
708 Some(Edit::Delete { node: id })
709 }
710}
711
712pub struct AssignBackFold;
727
728impl<L: Lang> Rule<L> for AssignBackFold {
729 fn name(&self) -> &'static str {
730 "assign_back_fold"
731 }
732 fn kinds(&self) -> &'static [NodeKind] {
733 &[NodeKind::VarDecl]
734 }
735 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
736 let RewriteCtx { lang, walk } = ctx;
737 if lang.kind(id) != NodeKind::VarDecl {
738 return None;
739 }
740 let ch = lang.children(id).to_vec();
741 let (&init, has_init) = (ch.first()?, !ch.is_empty());
742 if !has_init {
743 return None; }
745 let name_key = lang.var_key(id)?;
746 if !lang.is_wide_decl(id) {
750 return None;
751 }
752 let parent = walk.parent(id)?;
753 if lang.kind(parent) != NodeKind::Block {
754 return None;
755 }
756 let idx = walk.index(id)?;
757 let stmts = lang.children(parent).to_vec();
758
759 let name_str = lang.var_name(id)?.to_string();
764 for &s in &stmts[idx + 1..] {
765 let touched = match (lang.stmt_mentions(s, name_key), lang.stmt_opaque(s)) {
768 (Some(mentions), Some(opaque)) => mentions || opaque,
769 _ => {
770 let mut w = Watch::<L>::new(name_key, &[]);
771 scan_region(&*lang, s, &mut w);
772 let referenced = subtree_contains(&*lang, s, |n| {
773 lang.kind(n) == NodeKind::VarRef
774 && lang.var_name(n) == Some(name_str.as_str())
775 });
776 referenced || w.opaque || w.shadowed
777 }
778 };
779 if lang.effect(s) == Effect::Unknown {
783 return None;
784 }
785 if !touched {
786 continue; }
788 let assign = match lang.kind(s) {
790 NodeKind::Assign => s,
791 NodeKind::ExprStmt => {
792 let c = lang.children(s);
793 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
794 c[0]
795 } else {
796 return None;
797 }
798 }
799 _ => return None, };
801 let ach = lang.children(assign).to_vec();
802 if ach.len() != 2 {
803 return None;
804 }
805 let (target, rhs) = (ach[0], ach[1]);
806 if lang.kind(target) != NodeKind::VarRef || !lang.same_binding(id, target) {
807 return None;
808 }
809 let (op, k) = match lang.assign_op(assign) {
812 Some(op) => (op, lang.literal(rhs).and_then(|x| x.as_int())?),
813 None => {
814 if lang.kind(rhs) != NodeKind::Binary {
815 return None;
816 }
817 let op = lang.bin_op(rhs)?;
818 let rc = lang.children(rhs).to_vec();
819 if rc.len() != 2 || lang.kind(rc[0]) != NodeKind::VarRef {
820 return None;
821 }
822 if !lang.same_binding(id, rc[0]) {
824 return None;
825 }
826 (op, lang.literal(rc[1]).and_then(|x| x.as_int())?)
827 }
828 };
829 if op.is_short_circuit() || op.is_comparison() {
830 return None;
831 }
832 if matches!(op, BinOp::Div | BinOp::Rem) && k == 0 {
834 return None;
835 }
836 let lit = if lang.is_long_decl(id) {
841 lang.build_int(k, true)
842 } else {
843 lang.build_int(k, false)
844 };
845 let init = if lang.is_long_decl(id) {
846 match lang.literal(init) {
847 Some(crate::kind::LitRef::Int(v)) => lang.build_int(v, true),
848 _ => init,
849 }
850 } else {
851 init
852 };
853 let merged = lang.build_bin(op, init, lit);
854 return Some(Edit::Multi(vec![
856 Edit::Delete { node: s },
857 Edit::Splice { node: id, index: 0, remove: ch.len(), insert: vec![merged] },
858 ]));
859 }
860 None
861 }
862}
863
864pub struct ArithIdentity;
865
866impl<L: Lang> Rule<L> for ArithIdentity {
867 fn name(&self) -> &'static str {
868 "arith_identity"
869 }
870 fn kinds(&self) -> &'static [NodeKind] {
871 &[NodeKind::Binary]
872 }
873 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
874 let RewriteCtx { lang, walk: _ } = ctx;
875 if lang.kind(id) != NodeKind::Binary {
876 return None;
877 }
878 let op = lang.bin_op(id)?;
879 let ch = lang.children(id);
880 let (l, r) = (*ch.first()?, *ch.get(1)?);
881 let int_lit = |n: L::Id, v: i64| -> bool {
882 matches!(lang.literal(n), Some(l) if l.as_int() == Some(v))
883 };
884 let x = match op {
885 BinOp::Add if int_lit(r, 0) => l,
886 BinOp::Add if int_lit(l, 0) => r,
887 BinOp::Sub if int_lit(r, 0) => l,
888 BinOp::Mul if int_lit(r, 1) => l,
889 BinOp::Mul if int_lit(l, 1) => r,
890 BinOp::Div if int_lit(r, 1) => l,
891 _ => return None,
892 };
893 if !lang.is_exact_int(x) {
894 return None;
895 }
896 Some(Edit::Replace {
897 target: id,
898 with: x,
899 })
900 }
901}
902
903fn count_name_refs<L: Lang>(
924 lang: &L,
925 stmts: &[L::Id],
926 key: L::NameKey,
927 name: &str,
928) -> usize {
929 let mut n: usize = 0;
930 for &s in stmts {
931 match lang.stmt_var_ref_count(s, key) {
932 Some(c) => n += c as usize,
933 None => count_var_refs(lang, s, name, &mut n),
934 }
935 }
936 n
937}
938
939fn null_unsafe_position<L: Lang>(lang: &L, walk: &Walk<L>, use_id: L::Id) -> bool {
946 let mut cur = use_id;
947 while let Some(&(p, _)) = walk.parents.get(&cur) {
948 match lang.kind(p) {
949 NodeKind::Paren => cur = p,
950 NodeKind::Call
951 | NodeKind::New
952 | NodeKind::Member
953 | NodeKind::Index
954 | NodeKind::InstanceOf => return true,
955 _ => return false,
956 }
957 }
958 false
959}
960
961fn typed_position<L: Lang>(lang: &L, walk: &Walk<L>, use_id: L::Id) -> Option<L::Id> {
974 let mut cur = use_id;
975 while let Some(&(p, _)) = walk.parents.get(&cur) {
976 match lang.kind(p) {
977 NodeKind::Paren => cur = p,
978 NodeKind::Cast | NodeKind::InstanceOf => return Some(p),
979 _ => return None,
980 }
981 }
982 None
983}
984
985fn count_var_refs<L: Lang>(lang: &L, node: L::Id, name: &str, n: &mut usize) {
986 if lang.kind(node) == NodeKind::VarRef && lang.var_name(node) == Some(name) {
987 *n += 1;
988 }
989 for &c in lang.children(node) {
990 count_var_refs(lang, c, name, n);
991 }
992}
993
994pub struct LocalPropagation;
995
996impl<L: Lang> Rule<L> for LocalPropagation {
997 fn name(&self) -> &'static str {
998 "local_propagation"
999 }
1000 fn kinds(&self) -> &'static [NodeKind] {
1001 &[NodeKind::VarDecl]
1002 }
1003 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1004 let RewriteCtx { lang, walk } = ctx;
1005 if lang.kind(id) != NodeKind::VarDecl {
1006 return None;
1007 }
1008 let name = lang.var_name(id)?.to_string();
1009 let ch = lang.children(id);
1010 if ch.len() != 1 {
1011 return None; }
1013 let value = ch[0];
1014 if lang.kind(value) == NodeKind::ArrayLit {
1018 return None;
1019 }
1020 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
1024 return None;
1025 }
1026 if subtree_contains(&*lang, value, |n| {
1028 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
1029 }) {
1030 return None;
1031 }
1032 let parent = walk.parent(id)?;
1033 if lang.kind(parent) != NodeKind::Block {
1034 return None;
1035 }
1036 let index = walk.index(id)?;
1037 let stmts: Vec<L::Id> = lang.children(parent).to_vec();
1038 if index + 1 > stmts.len() {
1039 return None;
1040 }
1041
1042 let mut value = value;
1048 if lang.is_char_decl(id) {
1049 match lang.literal(value) {
1050 Some(LitRef::Char(_)) => {}
1051 Some(LitRef::Int(v)) => {
1052 let c = u32::try_from(v)
1055 .ok()
1056 .filter(|&u| (0..=0xFFFF).contains(&u) && !(0xD800..=0xDFFF).contains(&u))
1057 .and_then(char::from_u32)?;
1058 value = lang.build_char(c);
1059 }
1060 _ => return None,
1061 }
1062 }
1063 if lang.is_long_decl(id) {
1068 if let Some(LitRef::Int(v)) = lang.literal(value) {
1069 value = lang.build_int(v, true);
1070 }
1071 }
1072 if !lang.is_wide_decl(id) && !lang.is_char_decl(id) {
1077 return None;
1078 }
1079
1080 if !lang.local_propagation_value_ok(id, value) {
1081 return None;
1082 }
1083 let name_str = lang.var_name(id)?.to_string();
1088 let name_key = lang.var_key(id)?;
1089 let mut captured = false;
1097 let mut syntactic_refs = 0usize;
1098 for &st in &stmts[index + 1..] {
1099 let (refs, cap) = match (
1100 lang.stmt_var_ref_count(st, name_key),
1101 lang.stmt_lambda_captures(st, name_key),
1102 ) {
1103 (Some(refs), Some(cap)) => (refs as usize, cap),
1104 _ => {
1105 let mut n = 0usize;
1106 count_var_refs(&*lang, st, &name_str, &mut n);
1107 let cap = subtree_contains(&*lang, st, |n| {
1108 if lang.is_function_like(n) {
1109 return subtree_contains(&*lang, n, |m| {
1110 lang.kind(m) == NodeKind::VarRef
1111 && lang.var_name(m) == Some(name_str.as_str())
1112 });
1113 }
1114 false
1115 });
1116 (n, cap)
1117 }
1118 };
1119 syntactic_refs += refs;
1120 captured |= cap;
1121 if captured || syntactic_refs > 1 {
1122 break;
1123 }
1124 }
1125 if captured || syntactic_refs != 1 {
1126 return None;
1127 }
1128 let mut wa = Watch::<L>::new(name_key, &[]);
1129 for &s in &stmts[index + 1..] {
1130 if lang.stmt_has_opaque_region(s) {
1132 return None;
1133 }
1134 scan_region(&*lang, s, &mut wa);
1135 }
1136 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
1137 return None;
1138 }
1139 let use_id = wa.uses[0];
1140
1141 {
1147 let mut cur = use_id;
1148 loop {
1149 let Some(&(p, _)) = walk.parents.get(&cur) else {
1150 break;
1151 };
1152 match lang.kind(p) {
1153 NodeKind::Try => return None,
1154 NodeKind::Block
1156 | NodeKind::Case
1157 | NodeKind::Lambda
1158 | NodeKind::Catch => break,
1159 _ => cur = p,
1160 }
1161 }
1162 }
1163 if lang.kind(value) == NodeKind::Ternary && lang.ternary_arg_unsound(id, value) {
1168 let mut cur = use_id;
1169 while let Some(&(p, _)) = walk.parents.get(&cur) {
1170 match lang.kind(p) {
1171 NodeKind::Paren => cur = p,
1172 NodeKind::Call | NodeKind::New => return None,
1173 _ => break,
1174 }
1175 }
1176 }
1177
1178 if let Some(&(p, _)) = walk.parents.get(&use_id) {
1183 if lang.kind(p) == NodeKind::Assign && lang.assign_op(p).is_none() {
1184 let pch = lang.children(p);
1185 if pch.len() == 2
1186 && lang.kind(pch[0]) == NodeKind::VarRef
1187 && lang.same_binding(pch[0], use_id)
1188 && pch[1] == use_id
1189 {
1190 return None;
1191 }
1192 }
1193 }
1194 {
1199 let (up, head) = match walk.parents.get(&use_id) {
1200 Some(&(p, _)) => {
1201 (Some(p), lang.children(p).first() == Some(&use_id))
1202 }
1203 None => (None, false),
1204 };
1205 if lang.receiver_propagation_unsound(id, value, up, head) {
1206 return None;
1207 }
1208 }
1209 if matches!(
1216 lang.literal(value),
1217 Some(
1218 LitRef::Null
1219 | LitRef::Int(_)
1220 | LitRef::Long(_)
1221 | LitRef::Float(_)
1222 | LitRef::Double(_)
1223 | LitRef::Char(_)
1224 | LitRef::Bool(_)
1225 )
1226 ) {
1227 let mut cur = use_id;
1228 while let Some(&(p, _)) = walk.parents.get(&cur) {
1229 let at_head = lang.children(p).first() == Some(&cur);
1230 match lang.kind(p) {
1231 NodeKind::Member | NodeKind::Index if at_head => return None,
1233 NodeKind::Call if at_head => return None,
1235 NodeKind::InstanceOf if at_head => return None,
1239 NodeKind::ForEach => {
1241 let ch = lang.children(p);
1242 if ch.first() == Some(&cur) {
1243 return None;
1244 }
1245 }
1246 NodeKind::Synchronized => {
1248 if at_head {
1249 return None;
1250 }
1251 }
1252 _ => {}
1253 }
1254 cur = p;
1255 }
1256 }
1257 if matches!(lang.literal(value), Some(LitRef::Null))
1265 && null_unsafe_position(&*lang, walk, use_id)
1266 {
1267 return None;
1268 }
1269 if lang.kind(value) == NodeKind::VarRef {
1274 let mut cur = use_id;
1275 let mut in_target = false;
1276 while let Some(&(p, idx)) = walk.parents.get(&cur) {
1277 if lang.kind(p) == NodeKind::Assign && idx == 0 {
1278 in_target = true;
1279 break;
1280 }
1281 cur = p;
1282 }
1283 if in_target {
1284 return None;
1285 }
1286 }
1287 if let Some(t) = typed_position(&*lang, walk, use_id) {
1291 if lang.kind(value) == NodeKind::VarRef {
1292 if !lang.var_cast_position_ok(value, t) {
1293 return None;
1294 }
1295 } else if lang.propagation_cast_unsound(id, value) {
1296 return None;
1299 }
1300 }
1301 let mut watch_keys: Vec<L::NameKey> = Vec::new();
1303 collect_read_keys(&*lang, value, &mut watch_keys);
1304 watch_keys.push(name_key);
1305 let mut wb = Watch::<L>::new(name_key, &watch_keys);
1309 let mut use_stmt_idx = None;
1310 for (off, &s) in stmts[index + 1..].iter().enumerate() {
1311 let uses_before = wb.uses.len();
1312 scan_region(&*lang, s, &mut wb);
1313 if wb.uses.len() > uses_before {
1314 use_stmt_idx = Some(index + 1 + off);
1315 break; }
1317 }
1318 if subtree_contains(&*lang, value, |n| {
1325 if lang.kind(n) != NodeKind::VarRef || lang.is_local_var(n) {
1326 return false;
1327 }
1328 if !lang.var_name(n).is_some_and(|nm| lang.is_field_name(nm)) {
1331 return false;
1332 }
1333 let is_callee = match walk.parent(n) {
1334 Some(p) => {
1335 matches!(lang.kind(p), NodeKind::Call) && lang.children(p).first() == Some(&n)
1336 }
1337 None => false,
1338 };
1339 !is_callee
1340 }) {
1341 if let Some(ui) = use_stmt_idx {
1342 for &s in &stmts[index + 1..=ui] {
1343 if lang.effect(s) != Effect::Pure {
1344 return None;
1345 }
1346 }
1347 }
1348 }
1349 if let Some(ui) = use_stmt_idx {
1353 if count_name_refs(&*lang, &stmts[ui + 1..], name_key, &name_str) != 0 {
1355 return None;
1356 }
1357 let mut wc = Watch::<L>::new(name_key, &[name_key]);
1358 for &s in &stmts[ui + 1..] {
1359 scan_region(&*lang, s, &mut wc);
1360 if wc.opaque || !wc.uses.is_empty() || wc.wrote(name_key) || wc.shadowed {
1361 return None;
1362 }
1363 }
1364 }
1365 let ve = lang.effect(value);
1366
1367 if ve <= Effect::MayRead {
1368 if let Some(target_key) =
1371 use_stmt_target_write_key(&*lang, &stmts[index + 1..], use_id)
1372 {
1373 wb.clear_wrote(target_key);
1374 }
1375 if wb.opaque || wb.any_write() {
1376 return None;
1377 }
1378 return Some(Edit::Multi(vec![
1379 Edit::Replace {
1380 target: use_id,
1381 with: value,
1382 },
1383 Edit::Delete { node: id },
1384 ]));
1385 }
1386
1387 if index + 1 >= stmts.len() {
1389 return None;
1390 }
1391 let stmt = stmts[index + 1];
1392 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
1393 return None;
1394 }
1395 match lang.kind(stmt) {
1396 NodeKind::Return
1399 | NodeKind::ExprStmt
1400 | NodeKind::Assign
1401 | NodeKind::Throw
1402 | NodeKind::VarDecl => {}
1403 _ => return None,
1404 }
1405 if !straight_path(&*lang, stmt, use_id) {
1406 return None;
1407 }
1408 if !prefix_effects_readable(&*lang, stmt, use_id) {
1409 return None;
1410 }
1411 Some(Edit::Multi(vec![
1412 Edit::Replace {
1413 target: use_id,
1414 with: value,
1415 },
1416 Edit::Delete { node: id },
1417 ]))
1418 }
1419}
1420
1421pub(crate) struct Watch<L: Lang> {
1427 opaque: bool,
1429 name: L::NameKey,
1431 watch: Vec<L::NameKey>,
1433 writes_hit: Vec<bool>,
1435 shadowed: bool,
1436 uses: Vec<L::Id>,
1438}
1439
1440impl<L: Lang> Watch<L> {
1441 pub(crate) fn new(name: L::NameKey, watch: &[L::NameKey]) -> Self {
1442 Watch {
1443 opaque: false,
1444 name,
1445 watch: watch.to_vec(),
1446 writes_hit: vec![false; watch.len()],
1447 shadowed: false,
1448 uses: Vec::new(),
1449 }
1450 }
1451 pub(crate) fn wrote(&self, k: L::NameKey) -> bool {
1453 self.watch
1454 .iter()
1455 .zip(&self.writes_hit)
1456 .any(|(w, h)| *h && *w == k)
1457 }
1458 pub(crate) fn any_write(&self) -> bool {
1460 self.writes_hit.iter().any(|h| *h)
1461 }
1462 pub(crate) fn clear_wrote(&mut self, k: L::NameKey) {
1464 for (w, h) in self.watch.iter().zip(self.writes_hit.iter_mut()) {
1465 if *w == k {
1466 *h = false;
1467 }
1468 }
1469 }
1470}
1471
1472fn scan_region<L: Lang>(lang: &L, node: L::Id, w: &mut Watch<L>) {
1474 lang.debug_verify_events(node);
1475 if let Some(events) = lang.region_events(node) {
1476 let lo = events.partition_point(|e| e.key < w.name);
1478 let hi = events.partition_point(|e| e.key <= w.name);
1479 for e in &events[lo..hi] {
1480 match e.kind {
1481 crate::kind::EventKind::Use => w.uses.push(e.node),
1482 crate::kind::EventKind::Shadow => w.shadowed = true,
1483 crate::kind::EventKind::Write => {
1484 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1485 if *wn == w.name {
1486 *hit = true;
1487 }
1488 }
1489 }
1490 crate::kind::EventKind::Opaque => w.opaque = true,
1493 }
1494 }
1495 for (i, wk) in w.watch.iter().enumerate() {
1497 if *wk == w.name {
1498 continue; }
1500 let lo = events.partition_point(|e| e.key < *wk);
1501 let hi = events.partition_point(|e| e.key <= *wk);
1502 for e in &events[lo..hi] {
1503 if e.kind == crate::kind::EventKind::Write {
1504 w.writes_hit[i] = true;
1505 break; }
1507 }
1508 }
1509 return;
1510 }
1511 if lang.is_opaque(node) {
1513 w.opaque = true;
1514 return;
1515 }
1516 match lang.kind(node) {
1518 NodeKind::Assign => {
1519 let ch = lang.children(node);
1520 if let Some(&t) = ch.first() {
1521 if lang.kind(t) == NodeKind::VarRef {
1522 if let Some(k) = lang.var_key(t) {
1523 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1524 if *wn == k {
1525 *hit = true;
1526 }
1527 }
1528 }
1529 if lang.assign_op(node).is_some() {
1531 scan_region(lang, t, w);
1532 }
1533 } else {
1534 scan_region(lang, t, w);
1536 }
1537 }
1538 if let Some(&v) = ch.get(1) {
1539 scan_region(lang, v, w);
1540 }
1541 }
1542 NodeKind::Unary if lang.un_op(node).is_some_and(|o| o.is_incdec()) => {
1543 let ch = lang.children(node);
1544 if let Some(&t) = ch.first() {
1545 if lang.kind(t) == NodeKind::VarRef {
1546 if let Some(k) = lang.var_key(t) {
1547 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1548 if *wn == k {
1549 *hit = true;
1550 }
1551 }
1552 }
1553 scan_region(lang, t, w);
1555 } else {
1556 scan_region(lang, t, w);
1557 }
1558 }
1559 }
1560 NodeKind::VarDecl => {
1561 if let Some(k) = lang.var_key(node) {
1562 if k == w.name {
1563 w.shadowed = true;
1564 }
1565 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1566 if *wn == k {
1567 *hit = true;
1568 }
1569 }
1570 }
1571 for &c in lang.children(node) {
1572 scan_region(lang, c, w);
1573 }
1574 }
1575 NodeKind::ForEach | NodeKind::Catch => {
1578 if let Some(k) = lang.var_key(node) {
1579 if k == w.name {
1580 w.shadowed = true;
1581 }
1582 if lang.kind(node) == NodeKind::ForEach {
1583 for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
1584 if *wn == k {
1585 *hit = true;
1586 }
1587 }
1588 }
1589 }
1590 for &c in lang.children(node) {
1591 scan_region(lang, c, w);
1592 }
1593 }
1594 NodeKind::VarRef => {
1595 if lang.var_key(node) == Some(w.name) {
1596 w.uses.push(node);
1597 }
1598 }
1599 NodeKind::Raw => {
1600 w.opaque = true;
1602 }
1603 _ => {
1604 for &c in lang.children(node) {
1605 scan_region(lang, c, w);
1606 }
1607 }
1608 }
1609}
1610
1611fn straight_path<L: Lang>(lang: &L, root: L::Id, target: L::Id) -> bool {
1613 fn go<L: Lang>(lang: &L, node: L::Id, target: L::Id) -> bool {
1614 if node == target {
1615 return true;
1616 }
1617 match lang.kind(node) {
1618 NodeKind::Ternary | NodeKind::If => return false,
1619 NodeKind::Binary if lang.bin_op(node).is_some_and(|o| o.is_short_circuit()) => {
1620 return false
1621 }
1622 _ => {}
1623 }
1624 for &c in lang.children(node) {
1625 if subtree_contains(lang, c, |n| n == target) {
1626 return go(lang, c, target);
1627 }
1628 }
1629 false
1630 }
1631 go(lang, root, target)
1632}
1633
1634
1635fn in_tag_position<L: Lang>(lang: &L, walk: &crate::walk::Walk<L>, id: L::Id) -> bool {
1644 let Some((p, i)) = walk.parents.get(&id) else { return false };
1645 *i == 0
1646 && lang.kind(*p) == NodeKind::Custom(1) && lang.is_tagged_template(*p)
1648}
1649
1650pub struct BoolShortCircuit;
1651
1652impl<L: Lang> Rule<L> for BoolShortCircuit {
1653 fn name(&self) -> &'static str {
1654 "bool_short_circuit"
1655 }
1656 fn kinds(&self) -> &'static [NodeKind] {
1657 &[NodeKind::Binary]
1658 }
1659 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1660 let RewriteCtx { lang, walk } = ctx;
1661 if lang.kind(id) != NodeKind::Binary {
1662 return None;
1663 }
1664 let op = lang.bin_op(id)?;
1665 if !op.is_short_circuit() {
1666 return None;
1667 }
1668 if in_tag_position(&*lang, &walk, id) {
1672 return None;
1673 }
1674 if let Some((p, i)) = walk.parents.get(&id) {
1675 if *i == 0 && lang.kind(*p) == NodeKind::Unary && lang.un_op(*p) == Some(UnOp::Delete) {
1676 return None;
1677 }
1678 }
1679 let ch = lang.children(id);
1680 let (l, r) = (*ch.first()?, *ch.get(1)?);
1681 let is_lit = |n: L::Id, v: bool| matches!(lang.literal(n), Some(LitRef::Bool(b)) if b == v);
1682 let with = match (op, is_lit(l, true), is_lit(l, false)) {
1684 (BinOp::And, _, true) => l, (BinOp::Or, true, _) => l, (BinOp::And, true, _) => r, (BinOp::Or, _, true) => r, _ => {
1689 match (op, is_lit(r, true), is_lit(r, false)) {
1695 (BinOp::And, true, _) if lang.is_bool(l) => l,
1696 (BinOp::Or, _, true) if lang.is_bool(l) => l,
1697 (BinOp::And, _, true) | (BinOp::Or, true, _)
1702 if lang.is_bool(l) && lang.effect(l) <= Effect::MayRead => {
1703 r
1704 }
1705 _ => return None,
1706 }
1707 }
1708 };
1709 Some(Edit::Replace {
1710 target: id,
1711 with,
1712 })
1713 }
1714}
1715
1716pub struct NotCompare;
1722
1723impl<L: Lang> Rule<L> for NotCompare {
1724 fn name(&self) -> &'static str {
1725 "not_compare"
1726 }
1727 fn kinds(&self) -> &'static [NodeKind] {
1728 &[NodeKind::Unary]
1729 }
1730 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1731 let RewriteCtx { lang, walk: _ } = ctx;
1732 if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
1733 return None;
1734 }
1735 let inner = *lang.children(id).first()?;
1736 if lang.kind(inner) != NodeKind::Binary {
1737 return None;
1738 }
1739 let op = lang.bin_op(inner)?;
1740 let neg = match op {
1741 BinOp::Eq => BinOp::Ne,
1742 BinOp::Ne => BinOp::Eq,
1743 BinOp::Lt => BinOp::Ge,
1744 BinOp::Le => BinOp::Gt,
1745 BinOp::Gt => BinOp::Le,
1746 BinOp::Ge => BinOp::Lt,
1747 _ => return None,
1748 };
1749 if op.is_short_circuit() {
1750 return None;
1751 }
1752 if matches!(op, BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) {
1754 let ch = lang.children(inner);
1755 if !lang.is_exact_int(*ch.first()?) || !lang.is_exact_int(*ch.get(1)?) {
1756 return None;
1757 }
1758 }
1759 let ch = lang.children(inner).to_vec();
1760 let rebuilt = lang.build_bin(neg, ch[0], ch[1]);
1761 Some(Edit::Replace {
1762 target: id,
1763 with: rebuilt,
1764 })
1765 }
1766}
1767
1768fn in_inferred_var_init<L: Lang>(lang: &L, walk: &Walk<L>, mut id: L::Id) -> bool {
1778 while let Some(&(p, _)) = walk.parents.get(&id) {
1779 match lang.kind(p) {
1780 NodeKind::Paren | NodeKind::Ternary => id = p,
1781 NodeKind::VarDecl => return lang.is_inferred_decl(p),
1782 _ => return false,
1783 }
1784 }
1785 false
1786}
1787
1788pub struct TernaryFold;
1789
1790impl<L: Lang> Rule<L> for TernaryFold {
1791 fn name(&self) -> &'static str {
1792 "ternary_fold"
1793 }
1794 fn kinds(&self) -> &'static [NodeKind] {
1795 &[NodeKind::Ternary]
1796 }
1797 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1798 let RewriteCtx { lang, walk } = ctx;
1799 if lang.kind(id) != NodeKind::Ternary {
1800 return None;
1801 }
1802 if in_tag_position(&*lang, &walk, id) {
1803 return None;
1804 }
1805 let ch = lang.children(id);
1806 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1807 match lang.literal(c) {
1808 Some(LitRef::Bool(true)) => {
1814 if !in_inferred_var_init(&*lang, walk, id) {
1815 return Some(Edit::Replace { target: id, with: a });
1816 }
1817 }
1818 Some(LitRef::Bool(false))
1819 if !in_inferred_var_init(&*lang, walk, id) => {
1820 return Some(Edit::Replace { target: id, with: b });
1821 }
1822 _ => {}
1823 }
1824 if crate::analysis::structurally_equal(&*lang, a, b) && lang.effect(c) <= Effect::MayRead {
1827 return Some(Edit::Replace { target: id, with: a });
1828 }
1829 None
1830 }
1831}
1832
1833pub struct TernaryBool;
1838
1839impl<L: Lang> Rule<L> for TernaryBool {
1840 fn name(&self) -> &'static str {
1841 "ternary_bool"
1842 }
1843 fn kinds(&self) -> &'static [NodeKind] {
1844 &[NodeKind::Ternary]
1845 }
1846 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1847 let RewriteCtx { lang, walk: _ } = ctx;
1848 if lang.kind(id) != NodeKind::Ternary {
1849 return None;
1850 }
1851 let ch = lang.children(id);
1852 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
1853 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
1854 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
1855 if !lang.is_bool(c) {
1856 return None;
1857 }
1858 let with = if lb(a) && is_false(b) {
1859 c
1860 } else if is_false(a) && lb(b) {
1861 lang.build_unary(UnOp::Not, c)
1862 } else {
1863 return None;
1864 };
1865 Some(Edit::Replace {
1866 target: id,
1867 with,
1868 })
1869 }
1870}
1871
1872pub struct ConstFoldBin;
1879
1880impl<L: Lang> Rule<L> for ConstFoldBin {
1881 fn name(&self) -> &'static str {
1882 "const_fold_bin"
1883 }
1884 fn kinds(&self) -> &'static [NodeKind] {
1885 &[NodeKind::Binary]
1886 }
1887 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1888 let RewriteCtx { lang, walk: _ } = ctx;
1889 if lang.kind(id) != NodeKind::Binary {
1890 return None;
1891 }
1892 let op = lang.bin_op(id)?;
1893 if op.is_short_circuit() || op.is_comparison() {
1894 return None;
1895 }
1896 let ch = lang.children(id);
1897 let (l, r) = (*ch.first()?, *ch.get(1)?);
1898 lang.literal(l)?;
1900 lang.literal(r)?;
1901 let with = lang.fold_lit_bin(op, l, r)?;
1902 Some(Edit::Replace {
1903 target: id,
1904 with,
1905 })
1906 }
1907}
1908
1909pub struct ArithZero;
1918
1919impl<L: Lang> Rule<L> for ArithZero {
1920 fn name(&self) -> &'static str {
1921 "arith_zero"
1922 }
1923 fn kinds(&self) -> &'static [NodeKind] {
1924 &[NodeKind::Binary]
1925 }
1926 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
1927 let RewriteCtx { lang, walk: _ } = ctx;
1928 if lang.kind(id) != NodeKind::Binary {
1929 return None;
1930 }
1931 let op = lang.bin_op(id)?;
1932 let ch = lang.children(id);
1933 let (l, r) = (*ch.first()?, *ch.get(1)?);
1934 let int_lit = |n: L::Id, v: i64| lang.literal(n).and_then(|x| x.as_int()) == Some(v);
1935
1936 if matches!(op, BinOp::Shl | BinOp::Shr | BinOp::UShr) && int_lit(r, 0) {
1940 if !lang.is_exact_int(l) {
1941 return None;
1942 }
1943 return Some(Edit::Replace {
1944 target: id,
1945 with: l,
1946 });
1947 }
1948 if op == BinOp::Mul {
1950 let (zero, other) = if int_lit(l, 0) {
1951 (l, r)
1952 } else if int_lit(r, 0) {
1953 (r, l)
1954 } else {
1955 return None;
1956 };
1957 if !lang.is_exact_int(other) || lang.effect(other) > Effect::MayRead {
1958 return None;
1959 }
1960 return Some(Edit::Replace {
1961 target: id,
1962 with: zero,
1963 });
1964 }
1965 if op == BinOp::Sub
1967 && lang.kind(l) == NodeKind::VarRef
1968 && lang.kind(r) == NodeKind::VarRef
1969 && lang.same_binding(l, r)
1970 && lang.is_local_var(l)
1971 && lang.is_exact_int(l)
1972 {
1973 let with = lang.build_int(0, false);
1974 return Some(Edit::Replace {
1975 target: id,
1976 with,
1977 });
1978 }
1979 None
1980 }
1981}
1982
1983pub struct InverseAssignPair;
2006
2007impl<L: Lang> Rule<L> for InverseAssignPair {
2008 fn name(&self) -> &'static str {
2009 "inverse_assign_pair"
2010 }
2011 fn kinds(&self) -> &'static [NodeKind] {
2012 &[NodeKind::Block]
2013 }
2014 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2015 let RewriteCtx { lang, .. } = ctx;
2016 let ch = lang.children(id).to_vec();
2017 if ch.len() < 2 {
2018 return None;
2019 }
2020 for w in 0..ch.len() - 1 {
2021 let (s1, s2) = (ch[w], ch[w + 1]);
2022 let Some(a1) = norm_assign(lang, s1) else { continue };
2024 let Some(a2) = norm_assign(lang, s2) else { continue };
2025 if a1.key != a2.key
2027 || !lang.same_binding(a1.target, a2.target)
2028 || !a1.local
2029 || !a2.local
2030 {
2031 continue;
2032 }
2033 if !a1.exact_int || !a2.exact_int {
2035 continue;
2036 }
2037 if a1.k != a2.k {
2039 continue;
2040 }
2041 let inverse = match (a1.op, a2.op) {
2042 (BinOp::Add, BinOp::Sub) | (BinOp::Sub, BinOp::Add) => true,
2043 (BinOp::BitXor, BinOp::BitXor) => true,
2044 _ => false,
2045 };
2046 if !inverse {
2047 continue;
2048 }
2049 return Some(Edit::Multi(vec![
2051 Edit::Delete { node: s2 },
2052 Edit::Delete { node: s1 },
2053 ]));
2054 }
2055 None
2056 }
2057}
2058
2059struct NormAssign<L: Lang> {
2063 key: L::NameKey,
2064 target: L::Id,
2065 op: BinOp,
2066 k: i64,
2067 local: bool,
2068 exact_int: bool,
2070}
2071
2072fn norm_assign<L: Lang>(lang: &L, stmt: L::Id) -> Option<NormAssign<L>> {
2073 let a = match lang.kind(stmt) {
2075 NodeKind::ExprStmt => {
2076 let c = lang.children(stmt);
2077 if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
2078 c[0]
2079 } else {
2080 return None;
2081 }
2082 }
2083 NodeKind::Assign => stmt,
2084 _ => return None,
2085 };
2086 let ch = lang.children(a).to_vec();
2087 if ch.len() != 2 {
2088 return None;
2089 }
2090 let (target, rhs) = (ch[0], ch[1]);
2091 if lang.kind(target) != NodeKind::VarRef {
2092 return None;
2093 }
2094 let key = lang.var_key(target)?;
2095 let local = lang.is_local_var(target);
2096 let exact_int = lang.is_exact_int(target);
2097 match lang.assign_op(a) {
2101 Some(op) => {
2103 let k = lang.literal(rhs).and_then(|x| x.as_int())?;
2104 Some(NormAssign { key, target, op, k, local, exact_int })
2105 }
2106 None => {
2108 if lang.kind(rhs) != NodeKind::Binary {
2109 return None;
2110 }
2111 let op = lang.bin_op(rhs)?;
2112 let rc = lang.children(rhs).to_vec();
2113 if rc.len() != 2 {
2114 return None;
2115 }
2116 if lang.kind(rc[0]) != NodeKind::VarRef || !lang.same_binding(target, rc[0]) {
2118 return None;
2119 }
2120 let k = lang.literal(rc[1]).and_then(|x| x.as_int())?;
2121 Some(NormAssign { key, target, op, k, local, exact_int })
2122 }
2123 }
2124}
2125
2126pub struct DeadStore;
2127
2128impl<L: Lang> Rule<L> for DeadStore {
2129 fn name(&self) -> &'static str {
2130 "dead_store"
2131 }
2132 fn kinds(&self) -> &'static [NodeKind] {
2133 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
2134 }
2135 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2136 let RewriteCtx { lang, walk } = ctx;
2137 let (decl_form, first_assign) = match lang.kind(id) {
2139 NodeKind::VarDecl => {
2140 if lang.children(id).is_empty() {
2141 return None; }
2143 (true, None)
2144 }
2145 NodeKind::Assign => (false, Some(id)),
2146 NodeKind::ExprStmt => {
2147 let ch = lang.children(id);
2148 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2149 (false, Some(ch[0]))
2150 } else {
2151 return None;
2152 }
2153 }
2154 _ => return None,
2155 };
2156 if decl_form && !lang.dead_decl_allowed(id) {
2157 return None;
2160 }
2161 let first_value = if decl_form {
2162 *lang.children(id).first()?
2163 } else {
2164 let a = first_assign?;
2165 if lang.assign_op(a).is_some() {
2166 return None; }
2168 *lang.children(a).get(1)?
2169 };
2170 if lang.value_may_throw_unresolved(first_value) {
2171 return None;
2172 }
2173
2174 let parent = walk.parent(id)?;
2176 if lang.kind(parent) != NodeKind::Block {
2177 return None;
2178 }
2179 let idx = walk.index(id)?;
2180 let stmts = lang.children(parent).to_vec();
2181 let next_stmt = *stmts.get(idx + 1)?;
2182
2183 let second = match lang.kind(next_stmt) {
2186 NodeKind::Assign => Some(next_stmt),
2187 NodeKind::ExprStmt => {
2188 let ch = lang.children(next_stmt);
2189 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2190 Some(ch[0])
2191 } else {
2192 None
2193 }
2194 }
2195 _ => None,
2196 };
2197 let Some(second) = second else {
2198 let (name_node, target_local) = if decl_form {
2207 (id, true)
2208 } else {
2209 let t = *lang.children(first_assign?).first()?;
2210 if lang.kind(t) != NodeKind::VarRef {
2211 return None;
2212 }
2213 (t, lang.is_local_var(t))
2214 };
2215 if !target_local {
2217 return None;
2218 }
2219 let name = lang.var_name(name_node)?.to_string();
2220 let declared_in_this_block = if decl_form {
2221 true } else if parent == walk.root {
2223 true
2226 } else {
2227 stmts[..idx].iter().any(|&s| {
2230 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
2231 })
2232 };
2233 if !declared_in_this_block {
2234 return None;
2235 }
2236 for &s in &stmts[..idx] {
2240 if lang.stmt_has_opaque_region(s) {
2241 return None;
2242 }
2243 }
2244 let name_key0 = lang.var_key(name_node)?;
2245 for &s in &stmts[idx + 1..] {
2251 if lang.stmt_has_opaque_region(s) {
2252 return None;
2253 }
2254 match (lang.stmt_mentions(s, name_key0), lang.stmt_opaque(s)) {
2255 (Some(mentions), Some(opaque)) => {
2256 if mentions || opaque {
2257 return None;
2258 }
2259 }
2260 _ => {
2261 let mut w0 = Watch::<L>::new(name_key0, &[name_key0]);
2262 scan_region(&*lang, s, &mut w0);
2263 if w0.opaque
2264 || !w0.uses.is_empty()
2265 || w0.wrote(name_key0)
2266 || w0.shadowed
2267 {
2268 return None;
2269 }
2270 if subtree_contains(&*lang, s, |n| {
2271 lang.kind(n) == NodeKind::VarRef
2272 && lang.var_name(n) == Some(name.as_str())
2273 }) {
2274 return None;
2275 }
2276 }
2277 }
2278 }
2279 if lang.effect(first_value) > Effect::MayRead {
2280 return None;
2281 }
2282 return Some(Edit::Delete { node: id });
2283 };
2284 if lang.assign_op(second).is_some() {
2285 return None;
2286 }
2287 let nch = lang.children(second);
2288 let (target, value) = (*nch.first()?, *nch.get(1)?);
2289 if lang.kind(target) != NodeKind::VarRef {
2290 return None;
2291 }
2292
2293 if decl_form {
2295 if !lang.same_binding(id, target) {
2296 return None;
2297 }
2298 } else {
2299 let a = first_assign?;
2300 let t = *lang.children(a).first()?;
2301 if lang.kind(t) != NodeKind::VarRef
2302 || !lang.same_binding(t, target)
2303 || !lang.is_local_var(t)
2304 {
2305 return None;
2306 }
2307 }
2308 if lang.stmt_has_opaque_region(value)
2312 || subtree_contains(&*lang, value, |n| {
2313 lang.kind(n) == NodeKind::VarRef && lang.same_binding(n, target)
2314 })
2315 {
2316 return None;
2317 }
2318 if lang.effect(first_value) > Effect::MayRead {
2320 return None;
2321 }
2322
2323 if decl_form {
2324 Some(Edit::Multi(vec![
2326 Edit::Replace {
2327 target: first_value,
2328 with: value,
2329 },
2330 Edit::Delete {
2331 node: next_stmt,
2332 },
2333 ]))
2334 } else {
2335 Some(Edit::Delete { node: id })
2337 }
2338 }
2339}
2340
2341pub struct BlockFlatten;
2350
2351impl<L: Lang> Rule<L> for BlockFlatten {
2352 fn name(&self) -> &'static str {
2353 "block_flatten"
2354 }
2355 fn kinds(&self) -> &'static [NodeKind] {
2356 &[NodeKind::Block]
2357 }
2358 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2359 let RewriteCtx { lang, walk } = ctx;
2360 let parent = walk.parent(id)?;
2361 if lang.kind(parent) != NodeKind::Block {
2362 return None;
2363 }
2364 let inner = lang.children(id).to_vec();
2366 let declares = |n: L::Id| -> bool {
2367 matches!(
2368 lang.kind(n),
2369 NodeKind::VarDecl | NodeKind::ForEach | NodeKind::Catch
2370 )
2371 };
2372 if inner.iter().any(|&s| declares(s)) {
2373 return None;
2374 }
2375 if lang.block_hoists_decl(id) {
2377 return None;
2378 }
2379 if lang.has_pattern_bindings(id) {
2385 return None;
2386 }
2387 let idx = walk.index(id)?;
2388 Some(Edit::Splice {
2389 node: parent,
2390 index: idx,
2391 remove: 1,
2392 insert: inner,
2393 })
2394 }
2395}
2396
2397pub struct DoubleNegFold;
2400
2401impl<L: Lang> Rule<L> for DoubleNegFold {
2402 fn name(&self) -> &'static str {
2403 "double_neg_fold"
2404 }
2405 fn kinds(&self) -> &'static [NodeKind] {
2406 &[NodeKind::Unary]
2407 }
2408 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2409 let RewriteCtx { lang, .. } = ctx;
2410 if lang.un_op(id) != Some(UnOp::Neg) {
2411 return None;
2412 }
2413 let inner = *lang.children(id).first()?;
2414 if lang.un_op(inner) == Some(UnOp::Neg) {
2415 let x = *lang.children(inner).first()?;
2416 if !lang.is_exact_int(x) {
2419 return None;
2420 }
2421 return Some(Edit::Replace {
2422 target: id,
2423 with: x,
2424 });
2425 }
2426 if let Some((v, wide)) = lang.literal(inner).and_then(|l| match l {
2429 LitRef::Int(v) => Some((v, false)),
2430 LitRef::Long(v) => Some((v, true)),
2431 _ => None,
2432 }) {
2433 if v < 0 {
2434 let n = lang.fold_lit_neg(v, wide)?;
2435 let with = lang.build_int(n, wide);
2436 return Some(Edit::Replace {
2437 target: id,
2438 with,
2439 });
2440 }
2441 }
2442 None
2443 }
2444}
2445
2446pub struct UnreachableAfterTerminal;
2447
2448impl<L: Lang> Rule<L> for UnreachableAfterTerminal {
2449 fn name(&self) -> &'static str {
2450 "unreachable_after_terminal"
2451 }
2452 fn kinds(&self) -> &'static [NodeKind] {
2453 &[NodeKind::Block]
2454 }
2455 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2456 let RewriteCtx { lang, walk: _ } = ctx;
2457 if lang.kind(id) != NodeKind::Block {
2458 return None;
2459 }
2460 let children = lang.children(id).to_vec();
2461 let terminal = |n: L::Id| -> bool {
2462 matches!(
2463 lang.kind(n),
2464 NodeKind::Return | NodeKind::Throw | NodeKind::Break | NodeKind::Continue
2465 )
2466 };
2467 let mut cut: Option<(usize, usize)> = None;
2468 for (i, &s) in children.iter().enumerate() {
2469 if terminal(s) && i + 1 < children.len() {
2470 cut = Some((i + 1, children.len() - i - 1));
2471 break;
2472 }
2473 }
2474 let (index, remove) = cut?;
2475 Some(Edit::Splice {
2476 node: id,
2477 index,
2478 remove,
2479 insert: Vec::new(),
2480 })
2481 }
2482}
2483
2484
2485pub struct CmpConstFold;
2492
2493impl<L: Lang> Rule<L> for CmpConstFold {
2494 fn name(&self) -> &'static str {
2495 "cmp_const_fold"
2496 }
2497 fn kinds(&self) -> &'static [NodeKind] {
2498 &[NodeKind::Binary]
2499 }
2500 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2501 let RewriteCtx { lang, walk: _ } = ctx;
2502 if lang.kind(id) != NodeKind::Binary {
2503 return None;
2504 }
2505 let op = lang.bin_op(id)?;
2506 if !op.is_comparison() {
2507 return None;
2508 }
2509 let ch = lang.children(id);
2510 let (l, r) = (*ch.first()?, *ch.get(1)?);
2511 lang.literal(l)?;
2514 lang.literal(r)?;
2515 let v = lang.fold_lit_cmp(op, l, r)?;
2516 let with = lang.build_bool(v);
2517 Some(Edit::Replace {
2518 target: id,
2519 with,
2520 })
2521 }
2522}
2523
2524pub struct BitIdentity;
2532
2533impl<L: Lang> Rule<L> for BitIdentity {
2534 fn name(&self) -> &'static str {
2535 "bit_identity"
2536 }
2537 fn kinds(&self) -> &'static [NodeKind] {
2538 &[NodeKind::Binary]
2539 }
2540 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2541 let RewriteCtx { lang, walk: _ } = ctx;
2542 if lang.kind(id) != NodeKind::Binary {
2543 return None;
2544 }
2545 let op = lang.bin_op(id)?;
2546 if !matches!(op, BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor) {
2547 return None;
2548 }
2549 let ch = lang.children(id);
2550 let (l, r) = (*ch.first()?, *ch.get(1)?);
2551 let int_lit = |n: L::Id| lang.literal(n).and_then(|x| x.as_int());
2552
2553 if op == BinOp::BitXor
2555 && lang.kind(l) == NodeKind::VarRef
2556 && lang.kind(r) == NodeKind::VarRef
2557 && lang.same_binding(l, r)
2558 && lang.is_local_var(l)
2559 && lang.is_exact_int(l)
2560 {
2561 let with = lang.build_int(0, false);
2562 return Some(Edit::Replace {
2563 target: id,
2564 with,
2565 });
2566 }
2567
2568 for (x, k) in [(l, r), (r, l)] {
2570 let Some(kv) = int_lit(k) else {
2571 continue;
2572 };
2573 if !lang.is_exact_int(x) {
2574 continue;
2575 }
2576 let identity = match (op, kv) {
2577 (BinOp::BitXor, 0) => Some(x), (BinOp::BitOr, 0) => Some(x), (BinOp::BitAnd, -1) => Some(x), (BinOp::BitAnd, 0) | (BinOp::BitOr, -1) => {
2581 if lang.effect(x) > Effect::MayRead {
2583 None
2584 } else {
2585 Some(k)
2586 }
2587 }
2588 _ => None,
2589 };
2590 if let Some(with) = identity {
2591 return Some(Edit::Replace {
2592 target: id,
2593 with,
2594 });
2595 }
2596 }
2597 None
2598 }
2599}
2600
2601pub struct ArithReassoc;
2607
2608impl<L: Lang> Rule<L> for ArithReassoc {
2609 fn name(&self) -> &'static str {
2610 "arith_reassoc"
2611 }
2612 fn kinds(&self) -> &'static [NodeKind] {
2613 &[NodeKind::Binary]
2614 }
2615 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2616 let RewriteCtx { lang, walk: _ } = ctx;
2617 if lang.kind(id) != NodeKind::Binary {
2618 return None;
2619 }
2620 let op2 = lang.bin_op(id)?;
2621 if !matches!(op2, BinOp::Add | BinOp::Sub | BinOp::BitXor) {
2622 return None;
2623 }
2624 let och = lang.children(id).to_vec();
2625 let inner = och[0];
2626 if lang.kind(inner) != NodeKind::Binary {
2627 return None;
2628 }
2629 let op1 = lang.bin_op(inner)?;
2630 let ich = lang.children(inner).to_vec();
2631 let x = ich[0];
2632
2633 if op1 == BinOp::Add && op2 == BinOp::Add {
2639 if let (Some(LitRef::Str(k1)), Some(LitRef::Str(k2))) =
2640 (lang.literal(ich[1]), lang.literal(och[1]))
2641 {
2642 let mut chain: Vec<L::Id> = vec![id];
2644 let mut cur = id;
2645 loop {
2646 let cch = lang.children(cur).to_vec();
2647 let next = cch[0];
2648 let deeper = lang.kind(next) == NodeKind::Binary
2649 && lang.bin_op(next) == Some(BinOp::Add)
2650 && matches!(lang.literal(lang.children(next).to_vec()[1]),
2651 Some(LitRef::Str(_)));
2652 if deeper {
2653 chain.push(next);
2654 cur = next;
2655 } else {
2656 break;
2657 }
2658 }
2659 let innermost = *chain.last().unwrap();
2661 let ich2 = lang.children(innermost).to_vec();
2662 let base = ich2[0];
2663 let mut joined = String::new();
2664 for &n in chain.iter().rev() {
2665 if let Some(LitRef::Str(k)) =
2666 lang.literal(lang.children(n).to_vec()[1])
2667 {
2668 joined.push_str(k);
2669 }
2670 }
2671 let _ = (k1, k2); let lit = lang.build_str(&joined);
2673 let with = lang.build_bin(BinOp::Add, base, lit);
2674 return Some(Edit::Replace {
2675 target: id,
2676 with,
2677 });
2678 }
2679 }
2680
2681 let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
2685 let k2 = lang.literal(och[1]).and_then(|x| x.as_int())?;
2686 if !lang.is_exact_int(x) {
2687 return None;
2688 }
2689 let is_long = matches!(lang.literal(ich[1]), Some(LitRef::Long(_)))
2690 || matches!(lang.literal(och[1]), Some(LitRef::Long(_)));
2691 let with = match lang.reassoc_delta(op1, k1, op2, k2, is_long)? {
2692 ReassocOutcome::Neutral => x,
2693 ReassocOutcome::Lit(lit, op) => lang.build_bin(op, x, lit),
2694 };
2695 Some(Edit::Replace {
2696 target: id,
2697 with,
2698 })
2699 }
2700}
2701
2702pub struct IfToTernary;
2711
2712impl<L: Lang> Rule<L> for IfToTernary {
2713 fn name(&self) -> &'static str {
2714 "if_to_ternary"
2715 }
2716 fn kinds(&self) -> &'static [NodeKind] {
2717 &[NodeKind::If]
2718 }
2719 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2720 let RewriteCtx { lang, walk } = ctx;
2721 if lang.kind(id) != NodeKind::If {
2722 return None;
2723 }
2724 let ch = lang.children(id).to_vec();
2725 let c = ch[0];
2726
2727 let single_ret = |n: L::Id| -> Option<Option<L::Id>> {
2729 match lang.kind(n) {
2730 NodeKind::Return => Some(lang.children(n).first().copied()),
2731 NodeKind::Block if lang.children(n).len() == 1 => {
2732 let inner = lang.children(n)[0];
2733 if lang.kind(inner) == NodeKind::Return {
2734 Some(lang.children(inner).first().copied())
2735 } else {
2736 None
2737 }
2738 }
2739 _ => None,
2740 }
2741 };
2742
2743 if ch.len() == 3 {
2744 let a = single_ret(ch[1])?;
2746 let b = single_ret(ch[2])?;
2747 let with = match (a, b) {
2748 (Some(va), Some(vb)) => {
2749 if !lang.ternary_merge_ok(None, &[va, vb]) {
2750 return None;
2751 }
2752 let t = lang.build_ternary(c, va, vb);
2753 lang.build_return(Some(t))
2754 }
2755 (None, None) => lang.build_return(None),
2756 _ => return None,
2757 };
2758 return Some(Edit::Replace {
2759 target: id,
2760 with,
2761 });
2762 }
2763
2764 if ch.len() != 2 {
2766 return None;
2767 }
2768 let a = single_ret(ch[1])?;
2769 let parent = walk.parent(id)?;
2770 if lang.kind(parent) != NodeKind::Block {
2771 return None;
2772 }
2773 let idx = walk.index(id)?;
2774 let stmts = lang.children(parent).to_vec();
2775 if idx + 2 != stmts.len() {
2776 return None; }
2778 let tail = stmts[idx + 1];
2779 if lang.kind(tail) != NodeKind::Return {
2780 return None;
2781 }
2782 let b = lang.children(tail).first().copied();
2783 let with = match (a, b) {
2784 (Some(va), Some(vb)) => {
2785 if !lang.ternary_merge_ok(None, &[va, vb]) {
2786 return None;
2787 }
2788 let t = lang.build_ternary(c, va, vb);
2789 lang.build_return(Some(t))
2790 }
2791 (None, None) => lang.build_return(None),
2792 _ => return None,
2793 };
2794 Some(Edit::Splice {
2795 node: parent,
2796 index: idx,
2797 remove: 2,
2798 insert: vec![with],
2799 })
2800 }
2801}
2802
2803pub struct IfAssignTernary;
2810
2811impl<L: Lang> Rule<L> for IfAssignTernary {
2812 fn name(&self) -> &'static str {
2813 "if_assign_ternary"
2814 }
2815 fn kinds(&self) -> &'static [NodeKind] {
2816 &[NodeKind::If]
2817 }
2818 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2819 let RewriteCtx { lang, walk: _ } = ctx;
2820 if lang.kind(id) != NodeKind::If {
2821 return None;
2822 }
2823 let ch = lang.children(id).to_vec();
2824 if ch.len() != 3 {
2825 return None;
2826 }
2827 let c = ch[0];
2828 let single_assign = |n: L::Id| -> Option<(L::Id, L::Id)> {
2830 let unwrap = |i: L::Id| -> L::Id {
2831 if lang.kind(i) == NodeKind::ExprStmt {
2832 let ch = lang.children(i);
2833 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2834 return ch[0];
2835 }
2836 }
2837 i
2838 };
2839 let inner = match lang.kind(n) {
2840 NodeKind::Assign => n,
2841 NodeKind::ExprStmt => unwrap(n),
2842 NodeKind::Block if lang.children(n).len() == 1 => unwrap(lang.children(n)[0]),
2843 _ => return None,
2844 };
2845 if lang.kind(inner) != NodeKind::Assign {
2846 return None;
2847 }
2848 if lang.assign_op(inner).is_some() {
2849 return None;
2850 }
2851 let a = lang.children(inner).to_vec();
2852 if lang.kind(a[0]) != NodeKind::VarRef {
2853 return None;
2854 }
2855 Some((a[0], a[1]))
2856 };
2857 let (t1, va) = single_assign(ch[1])?;
2858 let (t2, vb) = single_assign(ch[2])?;
2859 if !lang.same_binding(t1, t2) || lang.var_name(t1).is_none() {
2860 return None;
2861 }
2862 if !lang.ternary_merge_ok(Some(t1), &[va, vb]) {
2863 return None;
2864 }
2865 let t = lang.build_ternary(c, va, vb);
2866 let with = lang.build_assign(t1, t);
2867 Some(Edit::Replace {
2868 target: id,
2869 with,
2870 })
2871 }
2872}
2873
2874
2875pub struct DeclAssignMerge;
2883
2884impl<L: Lang> Rule<L> for DeclAssignMerge {
2885 fn name(&self) -> &'static str {
2886 "decl_assign_merge"
2887 }
2888 fn kinds(&self) -> &'static [NodeKind] {
2889 &[NodeKind::VarDecl]
2890 }
2891 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2892 let RewriteCtx { lang, walk } = ctx;
2893 if lang.kind(id) != NodeKind::VarDecl || !lang.children(id).is_empty() {
2894 return None; }
2896 let name = lang.var_name(id)?.to_string();
2897 let parent = walk.parent(id)?;
2898 if lang.kind(parent) != NodeKind::Block {
2899 return None;
2900 }
2901 let idx = walk.index(id)?;
2902 let stmts = lang.children(parent).to_vec();
2903 let next = *stmts.get(idx + 1)?;
2904 let assign = match lang.kind(next) {
2906 NodeKind::Assign => next,
2907 NodeKind::ExprStmt => {
2908 let ch = lang.children(next);
2909 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2910 ch[0]
2911 } else {
2912 return None;
2913 }
2914 }
2915 _ => return None,
2916 };
2917 if lang.assign_op(assign).is_some() {
2918 return None;
2919 }
2920 let ach = lang.children(assign).to_vec();
2921 let (target, value) = (ach[0], ach[1]);
2922 if lang.kind(target) != NodeKind::VarRef || lang.var_name(target) != Some(name.as_str()) {
2923 return None;
2924 }
2925 if subtree_contains(&*lang, value, |n| {
2927 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
2928 }) {
2929 return None;
2930 }
2931 Some(Edit::Multi(vec![
2932 Edit::Splice {
2933 node: id,
2934 index: 0,
2935 remove: 0,
2936 insert: vec![value],
2937 },
2938 Edit::Delete { node: next },
2939 ]))
2940 }
2941}
2942
2943pub struct AssignPropagation;
2952
2953impl<L: Lang> Rule<L> for AssignPropagation {
2954 fn name(&self) -> &'static str {
2955 "assign_propagation"
2956 }
2957 fn kinds(&self) -> &'static [NodeKind] {
2958 &[NodeKind::Assign, NodeKind::ExprStmt]
2959 }
2960 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
2961 let RewriteCtx { lang, walk } = ctx;
2962 let (assign, stmt_node) = match lang.kind(id) {
2964 NodeKind::Assign => (id, id),
2965 NodeKind::ExprStmt => {
2966 let ch = lang.children(id);
2967 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
2968 (ch[0], id)
2969 } else {
2970 return None;
2971 }
2972 }
2973 _ => return None,
2974 };
2975 if lang.assign_op(assign).is_some() {
2976 return None;
2977 }
2978 let ach = lang.children(assign).to_vec();
2979 let (target, value) = (ach[0], ach[1]);
2980 if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
2981 return None;
2982 }
2983 if lang.kind(value) == NodeKind::ArrayLit {
2985 return None;
2986 }
2987 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
2989 return None;
2990 }
2991 let name = lang.var_name(target)?.to_string();
2992
2993 let parent = walk.parent(stmt_node)?;
2994 if lang.kind(parent) != NodeKind::Block {
2995 return None;
2996 }
2997 let idx = walk.index(stmt_node)?;
2998 let stmts = lang.children(parent).to_vec();
2999
3000 let anchored = stmts[..idx].iter().any(|&s| {
3002 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
3003 });
3004 if !anchored {
3005 return None;
3006 }
3007
3008 if subtree_contains(&*lang, value, |n| {
3010 lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
3011 }) {
3012 return None;
3013 }
3014
3015 let name_key = lang.var_key(target)?;
3018 if count_name_refs(&*lang, &stmts[idx + 1..], name_key, &name) != 1 {
3019 return None;
3020 }
3021 let mut wa = Watch::<L>::new(name_key, &[]);
3022 for &s in &stmts[idx + 1..] {
3023 if lang.stmt_has_opaque_region(s) {
3024 return None;
3025 }
3026 scan_region(&*lang, s, &mut wa);
3027 }
3028 if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
3029 return None;
3030 }
3031 let use_id = wa.uses[0];
3032 if matches!(lang.literal(value), Some(LitRef::Null))
3039 && null_unsafe_position(&*lang, walk, use_id)
3040 {
3041 return None;
3042 }
3043 if lang.kind(value) == NodeKind::VarRef
3047 && typed_position(&*lang, walk, use_id)
3048 .is_some_and(|t| !lang.var_cast_position_ok(value, t))
3049 {
3050 return None;
3051 }
3052 let mut watch_keys: Vec<L::NameKey> = Vec::new();
3054 collect_read_keys(&*lang, value, &mut watch_keys);
3055 watch_keys.push(name_key);
3056 let mut wb = Watch::<L>::new(name_key, &watch_keys);
3057 for &s in &stmts[idx + 1..] {
3058 let uses_before = wb.uses.len();
3059 scan_region(&*lang, s, &mut wb);
3060 if uses_before < wb.uses.len() {
3061 break; }
3063 }
3064 if wb.wrote(name_key) {
3066 return None;
3067 }
3068 let ve = lang.effect(value);
3069
3070 if ve <= Effect::MayRead {
3071 if let Some(target_key) = use_stmt_target_write_key(&*lang, &stmts[idx + 1..], use_id) {
3072 wb.clear_wrote(target_key);
3073 }
3074 if wb.any_write() {
3075 return None;
3076 }
3077 return Some(Edit::Multi(vec![
3078 Edit::Replace {
3079 target: use_id,
3080 with: value,
3081 },
3082 Edit::Delete { node: stmt_node },
3083 ]));
3084 }
3085
3086 if idx + 1 >= stmts.len() {
3088 return None;
3089 }
3090 let stmt = stmts[idx + 1];
3091 if !subtree_contains(&*lang, stmt, |n| n == use_id) {
3092 return None;
3093 }
3094 match lang.kind(stmt) {
3095 NodeKind::Return
3096 | NodeKind::ExprStmt
3097 | NodeKind::Assign
3098 | NodeKind::Throw
3099 | NodeKind::VarDecl => {}
3100 _ => return None,
3101 }
3102 if !straight_path(&*lang, stmt, use_id) {
3103 return None;
3104 }
3105 if !prefix_effects_readable(&*lang, stmt, use_id) {
3106 return None;
3107 }
3108 Some(Edit::Multi(vec![
3109 Edit::Replace {
3110 target: use_id,
3111 with: value,
3112 },
3113 Edit::Delete { node: stmt_node },
3114 ]))
3115 }
3116}
3117
3118
3119fn use_stmt_target_write_key<L: Lang>(
3125 lang: &L,
3126 stmts_after: &[L::Id],
3127 use_id: L::Id,
3128) -> Option<L::NameKey> {
3129 for &s in stmts_after {
3130 if subtree_contains(lang, s, |n| n == use_id) {
3131 let assign = match lang.kind(s) {
3132 NodeKind::Assign => s,
3133 NodeKind::ExprStmt => {
3134 let ch = lang.children(s);
3135 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3136 ch[0]
3137 } else {
3138 return None;
3139 }
3140 }
3141 _ => return None,
3142 };
3143 if lang.assign_op(assign).is_some() {
3144 return None;
3145 }
3146 let ch = lang.children(assign);
3147 if ch.len() == 2 && ch[1] == use_id && lang.kind(ch[0]) == NodeKind::VarRef {
3148 return lang.var_key(ch[0]);
3149 }
3150 return None;
3151 }
3152 }
3153 None
3154}
3155
3156fn collect_read_keys<L: Lang>(lang: &L, id: L::Id, out: &mut Vec<L::NameKey>) {
3159 let mut stack = vec![id];
3160 while let Some(n) = stack.pop() {
3161 if lang.kind(n) == NodeKind::VarRef {
3162 if let Some(k) = lang.var_key(n) {
3163 if !out.contains(&k) {
3164 out.push(k);
3165 }
3166 }
3167 }
3168 for &c in lang.children(n) {
3169 stack.push(c);
3170 }
3171 }
3172}
3173
3174
3175pub struct TrailingContinue;
3182
3183impl<L: Lang> Rule<L> for TrailingContinue {
3184 fn name(&self) -> &'static str {
3185 "trailing_continue"
3186 }
3187 fn kinds(&self) -> &'static [NodeKind] {
3188 &[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
3189 }
3190 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3191 let RewriteCtx { lang, walk: _ } = ctx;
3192 let body = match lang.kind(id) {
3194 NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
3195 *lang.children(id).last()?
3196 }
3197 _ => return None,
3198 };
3199 if lang.kind(body) != NodeKind::Block {
3200 return None;
3201 }
3202 let ch = lang.children(body).to_vec();
3203 let last = *ch.last()?;
3204 if lang.kind(last) != NodeKind::Continue || !lang.children(last).is_empty() {
3205 if lang.kind(last) != NodeKind::Continue {
3208 return None;
3209 }
3210 }
3211 Some(Edit::Delete { node: last })
3212 }
3213}
3214
3215pub struct MultiUseCopyPropagation;
3223
3224impl<L: Lang> Rule<L> for MultiUseCopyPropagation {
3225 fn name(&self) -> &'static str {
3226 "multi_use_copy"
3227 }
3228 fn kinds(&self) -> &'static [NodeKind] {
3229 &[NodeKind::Assign, NodeKind::ExprStmt]
3230 }
3231 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3232 let RewriteCtx { lang, walk } = ctx;
3233 let (assign, stmt_node) = match lang.kind(id) {
3235 NodeKind::Assign => (id, id),
3236 NodeKind::ExprStmt => {
3237 let ch = lang.children(id);
3238 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3239 (ch[0], id)
3240 } else {
3241 return None;
3242 }
3243 }
3244 _ => return None,
3245 };
3246 if lang.assign_op(assign).is_some() {
3247 return None;
3248 }
3249 let ach = lang.children(assign).to_vec();
3250 let (target, value) = (ach[0], ach[1]);
3251 if lang.kind(target) != NodeKind::VarRef || lang.kind(value) != NodeKind::VarRef {
3252 return None;
3253 }
3254 if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
3256 return None;
3257 }
3258 if !lang.is_local_var(target) {
3259 return None;
3260 }
3261 let name = lang.var_name(target)?.to_string();
3262 let src = lang.var_name(value)?.to_string();
3263 if name == src {
3264 return None;
3265 }
3266
3267 let parent = walk.parent(stmt_node)?;
3268 if lang.kind(parent) != NodeKind::Block {
3269 return None;
3270 }
3271 let idx = walk.index(stmt_node)?;
3272 let stmts = lang.children(parent).to_vec();
3273
3274 let anchored = stmts[..idx].iter().any(|&s| {
3276 lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
3277 });
3278 if !anchored {
3279 return None;
3280 }
3281
3282 let name_key = lang.var_key(target)?;
3284 let src_key = lang.var_key(value)?;
3285 let mut w1 = Watch::<L>::new(name_key, &[name_key, src_key]);
3286 for &s in &stmts[idx + 1..] {
3287 scan_region(&*lang, s, &mut w1);
3288 }
3289 if w1.opaque || w1.shadowed || w1.uses.is_empty() {
3290 return None;
3291 }
3292 if w1.wrote(name_key) || w1.wrote(src_key) {
3293 return None;
3294 }
3295 let synth = count_name_refs(&*lang, &stmts[idx + 1..], name_key, &name);
3301 if synth != w1.uses.len() {
3302 return None;
3303 }
3304 if w1.uses.iter().any(|&u| {
3313 typed_position(&*lang, walk, u).is_some_and(|t| !lang.var_cast_position_ok(value, t))
3314 }) {
3315 return None;
3316 }
3317
3318 let mut edits: Vec<Edit<L>> = w1.uses
3319 .iter()
3320 .map(|&u| Edit::Replace {
3321 target: u,
3322 with: value,
3323 })
3324 .collect();
3325 edits.push(Edit::Delete { node: stmt_node });
3326 Some(Edit::Multi(edits))
3327 }
3328}
3329
3330
3331pub struct TrailingReturn;
3338
3339impl<L: Lang> Rule<L> for TrailingReturn {
3340 fn name(&self) -> &'static str {
3341 "trailing_return"
3342 }
3343 fn kinds(&self) -> &'static [NodeKind] {
3344 &[NodeKind::Return]
3345 }
3346 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3347 let root = ctx.root();
3348 let parent = ctx.parent(id)?;
3349 let lang = ctx.lang;
3350 if lang.kind(id) != NodeKind::Return || !lang.children(id).is_empty() {
3351 return None;
3352 }
3353 if parent != root || lang.kind(parent) != NodeKind::Block {
3354 return None;
3355 }
3356 if lang.children(parent).last() != Some(&id) {
3357 return None;
3358 }
3359 Some(Edit::Delete { node: id })
3360 }
3361}
3362
3363
3364pub struct TernaryBoolOp;
3371
3372impl<L: Lang> Rule<L> for TernaryBoolOp {
3373 fn name(&self) -> &'static str {
3374 "ternary_bool_op"
3375 }
3376 fn kinds(&self) -> &'static [NodeKind] {
3377 &[NodeKind::Ternary]
3378 }
3379 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3380 let RewriteCtx { lang, walk: _ } = ctx;
3381 if lang.kind(id) != NodeKind::Ternary {
3382 return None;
3383 }
3384 let ch = lang.children(id);
3385 let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
3386 if !lang.is_bool(c) {
3389 return None;
3390 }
3391 let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
3392 let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
3393 let with = if is_false(b) && lang.is_bool(a) {
3394 lang.build_bin(BinOp::And, c, a)
3395 } else if lb(b) && lang.is_bool(a) {
3396 return None;
3398 } else if lb(a) && lang.is_bool(b) {
3399 lang.build_bin(BinOp::Or, c, b)
3401 } else if is_false(a) && lang.is_bool(b) {
3402 return None;
3404 } else {
3405 return None;
3406 };
3407 Some(Edit::Replace {
3408 target: id,
3409 with,
3410 })
3411 }
3412}
3413
3414
3415pub struct StoreKill;
3424
3425pub fn scan_kill_event<L: Lang>(
3432 lang: &L,
3433 node: L::Id,
3434 name: &str,
3435 dominating: bool,
3436) -> StmtKill<L::Id> {
3437 match lang.kind(node) {
3438 NodeKind::VarRef => {
3439 if lang.var_name(node) == Some(name) {
3440 StmtKill::Read
3441 } else {
3442 StmtKill::None
3443 }
3444 }
3445 NodeKind::VarDecl => {
3446 if lang.var_name(node) == Some(name) {
3447 StmtKill::Shadow
3448 } else {
3449 for &c in lang.children(node) {
3451 let e = scan_kill_event(lang, c, name, dominating);
3452 if !matches!(e, StmtKill::None) {
3453 return e;
3454 }
3455 }
3456 StmtKill::None
3457 }
3458 }
3459 NodeKind::Assign => {
3460 let ch = lang.children(node);
3461 if let Some(&val) = ch.get(1) {
3463 let e = scan_kill_event(lang, val, name, dominating);
3464 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3465 return e;
3466 }
3467 }
3468 let Some(&target) = ch.first() else {
3469 return StmtKill::None;
3470 };
3471 if lang.kind(target) == NodeKind::VarRef {
3472 if lang.assign_op(node).is_some() {
3473 if lang.var_name(target) == Some(name) {
3475 return StmtKill::Read;
3476 }
3477 } else if lang.var_name(target) == Some(name) {
3478 if dominating {
3479 return StmtKill::DomWrite(node);
3480 }
3481 return StmtKill::None; }
3483 } else {
3484 let e = scan_kill_event(lang, target, name, false);
3486 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3487 return e;
3488 }
3489 }
3490 StmtKill::None
3491 }
3492 NodeKind::If | NodeKind::While | NodeKind::Ternary => {
3493 let ch = lang.children(node);
3496 if let Some(&c) = ch.first() {
3497 let e = scan_kill_event(lang, c, name, true);
3498 if !matches!(e, StmtKill::None) {
3499 return e;
3500 }
3501 }
3502 for &c in &ch[1..] {
3503 let e = scan_kill_event(lang, c, name, false);
3504 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3505 return e;
3506 }
3507 }
3508 StmtKill::None
3509 }
3510 NodeKind::Binary => {
3511 let Some(op) = lang.bin_op(node) else {
3512 return StmtKill::None;
3513 };
3514 let ch = lang.children(node);
3515 if op.is_short_circuit() {
3516 if let Some(&l) = ch.first() {
3518 let e = scan_kill_event(lang, l, name, true);
3519 if !matches!(e, StmtKill::None) {
3520 return e;
3521 }
3522 }
3523 if let Some(&r) = ch.get(1) {
3524 let e = scan_kill_event(lang, r, name, false);
3525 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3526 return e;
3527 }
3528 }
3529 StmtKill::None
3530 } else {
3531 for &c in ch {
3532 let e = scan_kill_event(lang, c, name, dominating);
3533 if !matches!(e, StmtKill::None) {
3534 return e;
3535 }
3536 }
3537 StmtKill::None
3538 }
3539 }
3540 NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
3541 let ch = lang.children(node);
3545 let dom_len = match lang.kind(node) {
3546 NodeKind::For => ch.len().saturating_sub(2),
3547 NodeKind::ForEach | NodeKind::Switch => 1,
3548 _ => 0,
3549 };
3550 for &c in &ch[..dom_len] {
3551 let e = scan_kill_event(lang, c, name, true);
3552 if !matches!(e, StmtKill::None) {
3553 return e;
3554 }
3555 }
3556 for &c in &ch[dom_len..] {
3557 let e = scan_kill_event(lang, c, name, false);
3558 if matches!(e, StmtKill::Read | StmtKill::Shadow) {
3559 return e;
3560 }
3561 }
3562 StmtKill::None
3563 }
3564 _ => {
3565 for &c in lang.children(node) {
3566 let e = scan_kill_event(lang, c, name, dominating);
3567 if !matches!(e, StmtKill::None) {
3568 return e;
3569 }
3570 }
3571 StmtKill::None
3572 }
3573 }
3574}
3575
3576pub fn collect_stmt_kill_events<L: Lang>(lang: &L, root: L::Id) -> Vec<(L::NameKey, StmtKill<L::Id>)> {
3581 use std::collections::BTreeMap;
3582 enum Act<Id> {
3583 Enter(Id, bool),
3584 AfterAssign(Id, bool),
3585 }
3586 let mut events: BTreeMap<L::NameKey, StmtKill<L::Id>> = BTreeMap::new();
3587 let mut stack = vec![Act::Enter(root, true)];
3588 while let Some(act) = stack.pop() {
3589 match act {
3590 Act::AfterAssign(id, dominating) => {
3591 if lang.assign_op(id).is_some() {
3592 continue;
3593 }
3594 let Some(&target) = lang.children(id).first() else {
3595 continue;
3596 };
3597 if lang.kind(target) != NodeKind::VarRef {
3598 continue;
3599 }
3600 let Some(key) = lang.var_key(target) else {
3601 continue;
3602 };
3603 if dominating {
3604 events.entry(key).or_insert(StmtKill::DomWrite(id));
3605 }
3606 }
3607 Act::Enter(node, dominating) => match lang.kind(node) {
3608 NodeKind::VarRef => {
3609 if let Some(key) = lang.var_key(node) {
3610 events.entry(key).or_insert(StmtKill::Read);
3611 }
3612 }
3613 NodeKind::VarDecl => {
3614 if let Some(key) = lang.var_key(node) {
3615 events.entry(key).or_insert(StmtKill::Shadow);
3616 }
3617 for &c in lang.children(node).iter().rev() {
3618 stack.push(Act::Enter(c, dominating));
3619 }
3620 }
3621 NodeKind::Assign => {
3622 let ch = lang.children(node);
3623 stack.push(Act::AfterAssign(node, dominating));
3624 if let Some(&val) = ch.get(1) {
3625 stack.push(Act::Enter(val, dominating));
3626 }
3627 if let Some(&target) = ch.first() {
3630 if lang.kind(target) != NodeKind::VarRef
3631 || lang.assign_op(node).is_some()
3632 {
3633 stack.push(Act::Enter(target, false));
3634 }
3635 }
3636 }
3637 NodeKind::If | NodeKind::While | NodeKind::Ternary => {
3638 let ch = lang.children(node);
3639 for (i, &c) in ch.iter().enumerate().rev() {
3640 stack.push(Act::Enter(c, i == 0));
3641 }
3642 }
3643 NodeKind::Binary => {
3644 let ch = lang.children(node);
3645 let short = lang.bin_op(node).is_some_and(|op| op.is_short_circuit());
3646 for (i, &c) in ch.iter().enumerate().rev() {
3647 let child_dom = if short { i == 0 } else { dominating };
3648 stack.push(Act::Enter(c, child_dom));
3649 }
3650 }
3651 NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
3652 let ch = lang.children(node);
3653 let dom_len = match lang.kind(node) {
3654 NodeKind::For => ch.len().saturating_sub(2),
3655 NodeKind::ForEach | NodeKind::Switch => 1,
3656 _ => 0,
3657 };
3658 for (i, &c) in ch.iter().enumerate().rev() {
3659 stack.push(Act::Enter(c, i < dom_len));
3660 }
3661 }
3662 _ => {
3663 for &c in lang.children(node).iter().rev() {
3664 stack.push(Act::Enter(c, dominating));
3665 }
3666 }
3667 },
3668 }
3669 }
3670 events.into_iter().collect()
3671}
3672
3673impl<L: Lang> Rule<L> for StoreKill {
3674 fn name(&self) -> &'static str {
3675 "store_kill"
3676 }
3677 fn kinds(&self) -> &'static [NodeKind] {
3678 &[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
3679 }
3680 fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
3681 let RewriteCtx { lang, walk } = ctx;
3682 let (is_decl, stmt_node, first_value) = match lang.kind(id) {
3684 NodeKind::VarDecl => {
3685 if lang.children(id).is_empty() {
3686 return None;
3687 }
3688 (true, id, *lang.children(id).first()?)
3689 }
3690 NodeKind::Assign => {
3691 if lang.assign_op(id).is_some() {
3692 return None;
3693 }
3694 (false, id, *lang.children(id).get(1)?)
3695 }
3696 NodeKind::ExprStmt => {
3697 let ch = lang.children(id);
3698 if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
3699 let a = ch[0];
3700 if lang.assign_op(a).is_some() {
3701 return None;
3702 }
3703 (false, id, *lang.children(a).get(1)?)
3704 } else {
3705 return None;
3706 }
3707 }
3708 _ => return None,
3709 };
3710 let (name, name_key) = if is_decl {
3711 (lang.var_name(id)?.to_string(), lang.var_key(id)?)
3712 } else {
3713 let a = match lang.kind(id) {
3714 NodeKind::Assign => id,
3715 _ => lang.children(id)[0],
3716 };
3717 let t = *lang.children(a).first()?;
3718 if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
3719 return None;
3720 }
3721 (lang.var_name(t)?.to_string(), lang.var_key(t)?)
3722 };
3723
3724 let parent = walk.parent(stmt_node)?;
3725 if lang.kind(parent) != NodeKind::Block {
3726 return None;
3727 }
3728 let idx = walk.index(stmt_node)?;
3729 let stmts = lang.children(parent).to_vec();
3730
3731 let mut killed_by: Option<(usize, L::Id)> = None;
3733 for (si, &s) in stmts.iter().enumerate().skip(idx + 1) {
3734 if lang.stmt_has_opaque_region(s) {
3738 return None;
3739 }
3740 let event = lang
3741 .stmt_kill_event(s, name_key)
3742 .unwrap_or_else(|| scan_kill_event(&*lang, s, &name, true));
3743 match event {
3744 StmtKill::None => continue,
3745 StmtKill::Read | StmtKill::Shadow => return None,
3746 StmtKill::DomWrite(assign) => {
3747 killed_by = Some((si, assign));
3748 break;
3749 }
3750 }
3751 }
3752 let (si, kill_assign) = killed_by?;
3753 {
3761 let kill_container = match walk.parents.get(&kill_assign) {
3762 Some(&(p, _)) if lang.kind(p) == NodeKind::ExprStmt => p,
3763 _ => kill_assign,
3764 };
3765 if kill_container != stmts[si] {
3766 return None;
3767 }
3768 }
3769 let mut has_try_ancestor = false;
3782 {
3783 let mut cur = stmt_node;
3784 while let Some(&(p, _)) = walk.parents.get(&cur) {
3785 if lang.kind(p) == NodeKind::Try {
3786 has_try_ancestor = true;
3787 break;
3788 }
3789 cur = p;
3790 }
3791 }
3792 if has_try_ancestor {
3793 for &s in &stmts[idx + 1..si] {
3794 if lang.effect(s) > Effect::MayRead {
3795 return None;
3796 }
3797 }
3798 }
3799
3800 if is_decl {
3801 if lang.effect(first_value) > Effect::MayRead {
3804 return None;
3805 }
3806 let value = *lang.children(kill_assign).get(1)?;
3807 if is_literal(lang, value) {
3808 let assign_stmt = stmts[si];
3809 return Some(Edit::Multi(vec![
3810 Edit::Splice {
3811 node: id,
3812 index: 0,
3813 remove: 1,
3814 insert: vec![value],
3815 },
3816 Edit::Delete { node: assign_stmt },
3817 ]));
3818 }
3819 if lang.is_inferred_decl(id) {
3823 return None;
3824 }
3825 if lang.effect(first_value) > Effect::MayRead {
3826 return None;
3827 }
3828 return Some(Edit::Splice {
3829 node: id,
3830 index: 0,
3831 remove: 1,
3832 insert: Vec::new(),
3833 });
3834 }
3835 if lang.effect(first_value) > Effect::MayRead {
3837 return None;
3838 }
3839 Some(Edit::Delete { node: stmt_node })
3840 }
3841}
3842
3843fn is_literal<L: Lang>(lang: &L, n: L::Id) -> bool {
3845 matches!(lang.kind(n), NodeKind::Literal)
3846}
3847
3848pub fn default_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3854 vec![
3855 Box::new(DoubleNegFold),
3856 Box::new(BlockFlatten),
3857 Box::new(ParenRemoval),
3858 Box::new(ConstCondition),
3859 Box::new(BooleanReturn),
3860 Box::new(IfToTernary),
3861 Box::new(IfAssignTernary),
3862 Box::new(IfElseEmpty),
3863 Box::new(BoolCompare),
3864 Box::new(DoubleNot),
3865 Box::new(BoolNotFold),
3866 Box::new(BoolShortCircuit),
3867 Box::new(NotCompare),
3868 Box::new(TernaryFold),
3869 Box::new(TernaryBool),
3870 Box::new(TernaryBoolOp),
3871 Box::new(ConstFoldBin),
3872 Box::new(CmpConstFold),
3873 Box::new(BitIdentity),
3874 Box::new(ArithReassoc),
3875 Box::new(SelfAssign),
3876 Box::new(ArithIdentity),
3877 Box::new(ArithZero),
3878 Box::new(DeclAssignMerge),
3879 Box::new(LocalPropagation),
3880 Box::new(AssignPropagation),
3881 Box::new(MultiUseCopyPropagation),
3882 Box::new(TrailingReturn),
3883 Box::new(InverseAssignPair),
3884 Box::new(DeadDecl),
3885 Box::new(AssignBackFold),
3886 Box::new(DeadStore),
3887 Box::new(StoreKill),
3888 ]
3889}
3890
3891pub fn all_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
3893 let mut rules = default_rules();
3894 rules.push(Box::new(UnreachableAfterTerminal));
3895 rules
3896}