use crate::analysis::{prefix_effects_readable, subtree_contains};
use crate::effect::Effect;
use crate::kind::{BinOp, LitRef, NodeKind, UnOp};
use crate::pattern::{matches, Pat};
use crate::rule::{Edit, RewriteCtx, Rule};
use crate::lang::{Lang, ReassocOutcome};
use crate::walk::Walk;
pub struct ParenRemoval;
impl<L: Lang> Rule<L> for ParenRemoval {
fn name(&self) -> &'static str {
"paren_removal"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Paren]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Paren {
return None;
}
let inner = *lang.children(id).first()?;
Some(Edit::Replace {
target: id,
with: inner,
})
}
}
fn self_is_terminal<L: Lang>(lang: &L, n: L::Id) -> bool {
matches!(
lang.kind(n),
NodeKind::Return | NodeKind::Throw | NodeKind::Break | NodeKind::Continue | NodeKind::Yield
)
}
fn contains_yield<L: Lang>(lang: &L, n: L::Id) -> bool {
subtree_contains(lang, n, |x| lang.kind(x) == NodeKind::Yield)
}
pub struct ConstCondition;
impl<L: Lang> Rule<L> for ConstCondition {
fn name(&self) -> &'static str {
"const_condition"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::If]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
if lang.kind(id) != NodeKind::If {
return None;
}
let ch = lang.children(id);
let cond = *ch.first()?;
match lang.literal(cond) {
Some(LitRef::Bool(true)) => {
let then = ch.get(1).copied()?;
if let Some(&els) = ch.get(2) {
if contains_yield(&*lang, els) {
return None;
}
}
let insert: Vec<L::Id> = if lang.kind(then) == NodeKind::Block {
lang.children(then).to_vec()
} else {
vec![then]
};
let terminates = insert.iter().any(|&s| self_is_terminal(lang, s));
if let (Some(parent), Some(index)) = (walk.parent(id), walk.index(id)) {
if lang.kind(parent) == NodeKind::Block {
let siblings = lang.children(parent).to_vec();
let remove = if terminates && index + 1 < siblings.len() {
siblings.len() - index
} else {
1
};
if remove > 1
&& siblings[index + 1..]
.iter()
.any(|&s| contains_yield(&*lang, s))
{
return None;
}
return Some(Edit::Splice {
node: parent,
index,
remove,
insert,
});
}
if lang.kind(parent) == NodeKind::Case && terminates {
let siblings = lang.children(parent).to_vec();
if index + 1 < siblings.len() {
return None;
}
}
}
Some(Edit::Replace {
target: id,
with: then,
})
}
Some(LitRef::Bool(false)) => {
let then = *ch.get(1)?;
if contains_yield(&*lang, then) {
return None;
}
let els = ch.get(2).copied();
if let (Some(parent), Some(index)) = (walk.parent(id), walk.index(id)) {
if lang.kind(parent) == NodeKind::Block {
let insert: Vec<L::Id> = match els {
Some(e) => {
if lang.kind(e) == NodeKind::Block {
lang.children(e).to_vec()
} else {
vec![e]
}
}
None => Vec::new(),
};
return Some(if insert.is_empty() {
Edit::Delete { node: id }
} else {
Edit::Splice {
node: parent,
index,
remove: 1,
insert,
}
});
}
}
match els {
Some(e) => Some(Edit::Replace {
target: id,
with: e,
}),
None => Some(Edit::Delete { node: id }),
}
}
_ => None,
}
}
}
static P_IF_RET_TF: Pat = Pat::Kind(
NodeKind::If,
&[Pat::Bind("cond"), Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)]), Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])],
);
static P_IF_RET_FT: Pat = Pat::Kind(
NodeKind::If,
&[Pat::Bind("cond"), Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)]), Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])],
);
static P_IF_BRET_TF: Pat = Pat::Kind(
NodeKind::If,
&[
Pat::Bind("cond"),
Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])]),
Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])]),
],
);
static P_IF_BRET_FT: Pat = Pat::Kind(
NodeKind::If,
&[
Pat::Bind("cond"),
Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(false)])]),
Pat::Kind(NodeKind::Block, &[Pat::Kind(NodeKind::Return, &[Pat::LitBool(true)])]),
],
);
pub struct BooleanReturn;
impl<L: Lang> Rule<L> for BooleanReturn {
fn name(&self) -> &'static str {
"boolean_return"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::If]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::If {
return None;
}
let negated = |cond: L::Id, lang: &mut L| {
let nc = lang.build_unary(UnOp::Not, cond);
lang.build_return(Some(nc))
};
for (pat, invert) in [
(&P_IF_RET_TF, false),
(&P_IF_BRET_TF, false),
(&P_IF_RET_FT, true),
(&P_IF_BRET_FT, true),
] {
if let Some(b) = matches(&*lang, id, pat) {
let cond = lookup(&b, "cond")?;
if !lang.is_bool(cond) {
return None;
}
let ret = if invert {
negated(cond, lang)
} else {
lang.build_return(Some(cond))
};
return Some(Edit::Replace {
target: id,
with: ret,
});
}
}
None
}
}
fn lookup<Id: Copy>(binds: &[(&'static str, Id)], name: &str) -> Option<Id> {
binds.iter().find(|(n, _)| *n == name).map(|(_, id)| *id)
}
pub struct IfElseEmpty;
impl<L: Lang> Rule<L> for IfElseEmpty {
fn name(&self) -> &'static str {
"if_else_empty"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::If]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
if lang.kind(id) != NodeKind::If {
return None;
}
let ch = lang.children(id);
let cond = *ch.first()?;
let is_empty_block = |n: L::Id| -> bool {
lang.kind(n) == NodeKind::Block && lang.children(n).is_empty()
};
match ch.len() {
3 => {
let (then, els) = (ch[1], ch[2]);
if is_empty_block(then) {
if is_empty_block(els) {
return Some(Edit::Splice {
node: id,
index: 0,
remove: 3,
insert: vec![cond, then],
});
}
let nc = lang.build_unary(UnOp::Not, cond);
return Some(Edit::Splice {
node: id,
index: 0,
remove: 3,
insert: vec![nc, els],
});
}
if is_empty_block(els) {
return Some(Edit::Splice {
node: id,
index: 0,
remove: 3,
insert: vec![cond, then],
});
}
None
}
2 => {
let then = ch[1];
let parent_is_block = walk
.parent(id)
.map(|p| lang.kind(p) == NodeKind::Block)
.unwrap_or(false);
if is_empty_block(then)
&& lang.effect(cond) <= Effect::MayRead
&& parent_is_block
{
return Some(Edit::Delete { node: id });
}
None
}
_ => None,
}
}
}
pub struct BoolCompare;
impl<L: Lang> Rule<L> for BoolCompare {
fn name(&self) -> &'static str {
"bool_compare"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Binary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
if lang.kind(id) != NodeKind::Binary {
return None;
}
let op = lang.bin_op(id)?;
if !matches!(op, BinOp::Eq | BinOp::Ne) {
return None;
}
let ch = lang.children(id);
let (l, r) = (*ch.first()?, *ch.get(1)?);
let lit_bool = |n: L::Id| -> Option<bool> {
match lang.literal(n) {
Some(LitRef::Bool(b)) => Some(b),
_ => None,
}
};
let (x, lit) = match (lit_bool(l), lit_bool(r)) {
(None, Some(b)) => (l, b),
(Some(b), None) => (r, b),
_ => return None,
};
if !lang.is_bool(x) {
let in_cond = walk.parent(id).is_some_and(|p| {
matches!(lang.kind(p), NodeKind::If | NodeKind::While) && lang.children(p).first() == Some(&id)
});
if !in_cond {
return None;
}
}
let neg = match (op, lit) {
(BinOp::Eq, true) | (BinOp::Ne, false) => false,
(BinOp::Eq, false) | (BinOp::Ne, true) => true,
_ => return None,
};
let with = if neg {
lang.build_unary(UnOp::Not, x)
} else {
x
};
Some(Edit::Replace {
target: id,
with,
})
}
}
pub struct DoubleNot;
impl<L: Lang> Rule<L> for DoubleNot {
fn name(&self) -> &'static str {
"double_not"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Unary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
return None;
}
let inner = *lang.children(id).first()?;
if lang.kind(inner) != NodeKind::Unary || lang.un_op(inner) != Some(UnOp::Not) {
return None;
}
let x = *lang.children(inner).first()?;
Some(Edit::Replace {
target: id,
with: x,
})
}
}
pub struct BoolNotFold;
impl<L: Lang> Rule<L> for BoolNotFold {
fn name(&self) -> &'static str {
"bool_not_fold"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Unary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
return None;
}
let operand = *lang.children(id).first()?;
match lang.literal(operand) {
Some(LitRef::Bool(b)) => {
let folded = lang.build_bool(!b);
Some(Edit::Replace {
target: id,
with: folded,
})
}
_ => None,
}
}
}
pub struct SelfAssign;
impl<L: Lang> Rule<L> for SelfAssign {
fn name(&self) -> &'static str {
"self_assign"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Assign]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Assign {
return None;
}
if lang.assign_op(id).is_some() {
return None; }
let ch = lang.children(id);
let (t, v) = (*ch.first()?, *ch.get(1)?);
if lang.kind(t) != NodeKind::VarRef || lang.kind(v) != NodeKind::VarRef {
return None;
}
if lang.var_name(t) != lang.var_name(v) || !lang.is_local_var(t) {
return None;
}
Some(Edit::Delete { node: id })
}
}
pub struct DeadDecl;
impl<L: Lang> Rule<L> for DeadDecl {
fn name(&self) -> &'static str {
"dead_decl"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::VarDecl]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
if lang.kind(id) != NodeKind::VarDecl {
return None;
}
let ch = lang.children(id).to_vec();
if let Some(&v) = ch.first() {
if lang.effect(v) > Effect::MayRead {
return None;
}
}
let parent = walk.parent(id)?;
if lang.kind(parent) != NodeKind::Block {
return None;
}
let idx = walk.index(id)?;
let stmts = lang.children(parent).to_vec();
let name_str = lang.var_name(id)?.to_string();
let referenced = stmts[idx + 1..].iter().any(|&s| {
subtree_contains(&*lang, s, |n| {
lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name_str.as_str())
})
});
if referenced {
return None;
}
let name_key = lang.var_key(id)?;
let mut w = Watch::<L>::new(name_key, &[]);
for &s in &stmts[idx + 1..] {
scan_region(&*lang, s, &mut w);
}
if w.opaque || w.shadowed {
return None;
}
Some(Edit::Delete { node: id })
}
}
pub struct AssignBackFold;
impl<L: Lang> Rule<L> for AssignBackFold {
fn name(&self) -> &'static str {
"assign_back_fold"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::VarDecl]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
if lang.kind(id) != NodeKind::VarDecl {
return None;
}
let ch = lang.children(id).to_vec();
let (&init, has_init) = (ch.first()?, !ch.is_empty());
if !has_init {
return None; }
let name_key = lang.var_key(id)?;
if !lang.is_wide_decl(id) {
return None;
}
let parent = walk.parent(id)?;
if lang.kind(parent) != NodeKind::Block {
return None;
}
let idx = walk.index(id)?;
let stmts = lang.children(parent).to_vec();
let name_str = lang.var_name(id)?.to_string();
for &s in &stmts[idx + 1..] {
let mut w = Watch::<L>::new(name_key, &[]);
scan_region(&*lang, s, &mut w);
let referenced = subtree_contains(&*lang, s, |n| {
lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name_str.as_str())
});
let touched = referenced || w.opaque || w.shadowed;
if !touched {
continue; }
let assign = match lang.kind(s) {
NodeKind::Assign => s,
NodeKind::ExprStmt => {
let c = lang.children(s);
if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
c[0]
} else {
return None;
}
}
_ => return None, };
let ach = lang.children(assign).to_vec();
if ach.len() != 2 {
return None;
}
let (target, rhs) = (ach[0], ach[1]);
if lang.kind(target) != NodeKind::VarRef || lang.var_key(target) != Some(name_key) {
return None;
}
let (op, k) = match lang.assign_op(assign) {
Some(op) => (op, lang.literal(rhs).and_then(|x| x.as_int())?),
None => {
if lang.kind(rhs) != NodeKind::Binary {
return None;
}
let op = lang.bin_op(rhs)?;
let rc = lang.children(rhs).to_vec();
if rc.len() != 2 || lang.kind(rc[0]) != NodeKind::VarRef {
return None;
}
if lang.var_key(rc[0]) != Some(name_key) {
return None;
}
(op, lang.literal(rc[1]).and_then(|x| x.as_int())?)
}
};
if op.is_short_circuit() || op.is_comparison() {
return None;
}
if matches!(op, BinOp::Div | BinOp::Rem) && k == 0 {
return None;
}
let lit = if lang.is_long_decl(id) {
lang.build_int(k, true)
} else {
lang.build_int(k, false)
};
let init = if lang.is_long_decl(id) {
match lang.literal(init) {
Some(crate::kind::LitRef::Int(v)) => lang.build_int(v, true),
_ => init,
}
} else {
init
};
let merged = lang.build_bin(op, init, lit);
return Some(Edit::Multi(vec![
Edit::Delete { node: s },
Edit::Splice { node: id, index: 0, remove: ch.len(), insert: vec![merged] },
]));
}
None
}
}
pub struct ArithIdentity;
impl<L: Lang> Rule<L> for ArithIdentity {
fn name(&self) -> &'static str {
"arith_identity"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Binary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Binary {
return None;
}
let op = lang.bin_op(id)?;
let ch = lang.children(id);
let (l, r) = (*ch.first()?, *ch.get(1)?);
let int_lit = |n: L::Id, v: i64| -> bool {
matches!(lang.literal(n), Some(l) if l.as_int() == Some(v))
};
let x = match op {
BinOp::Add if int_lit(r, 0) => l,
BinOp::Add if int_lit(l, 0) => r,
BinOp::Sub if int_lit(r, 0) => l,
BinOp::Mul if int_lit(r, 1) => l,
BinOp::Mul if int_lit(l, 1) => r,
BinOp::Div if int_lit(r, 1) => l,
_ => return None,
};
if !lang.is_exact_int(x) {
return None;
}
Some(Edit::Replace {
target: id,
with: x,
})
}
}
fn count_name_refs<L: Lang>(lang: &L, stmts: &[L::Id], name: &str) -> usize {
let mut n: usize = 0;
for &s in stmts {
count_var_refs(lang, s, name, &mut n);
}
n
}
fn count_var_refs<L: Lang>(lang: &L, node: L::Id, name: &str, n: &mut usize) {
if lang.kind(node) == NodeKind::VarRef && lang.var_name(node) == Some(name) {
*n += 1;
}
for &c in lang.children(node) {
count_var_refs(lang, c, name, n);
}
}
pub struct LocalPropagation;
impl<L: Lang> Rule<L> for LocalPropagation {
fn name(&self) -> &'static str {
"local_propagation"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::VarDecl]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
if lang.kind(id) != NodeKind::VarDecl {
return None;
}
let name = lang.var_name(id)?.to_string();
let ch = lang.children(id);
if ch.len() != 1 {
return None; }
let value = ch[0];
if lang.kind(value) == NodeKind::ArrayLit {
return None;
}
if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
return None;
}
if subtree_contains(&*lang, value, |n| {
lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
}) {
return None;
}
let parent = walk.parent(id)?;
if lang.kind(parent) != NodeKind::Block {
return None;
}
let index = walk.index(id)?;
let stmts: Vec<L::Id> = lang.children(parent).to_vec();
if index + 1 > stmts.len() {
return None;
}
let mut value = value;
if lang.is_char_decl(id) {
match lang.literal(value) {
Some(LitRef::Char(_)) => {}
Some(LitRef::Int(v)) => {
let c = u32::try_from(v)
.ok()
.filter(|&u| (0..=0xFFFF).contains(&u) && !(0xD800..=0xDFFF).contains(&u))
.and_then(char::from_u32)?;
value = lang.build_char(c);
}
_ => return None,
}
}
if lang.is_long_decl(id) {
if let Some(LitRef::Int(v)) = lang.literal(value) {
value = lang.build_int(v, true);
}
}
if !lang.is_wide_decl(id) && !lang.is_char_decl(id) {
return None;
}
let name_str = lang.var_name(id)?.to_string();
{
let mut captured = false;
for &st in &stmts[index + 1..] {
if subtree_contains(&*lang, st, |n| {
if lang.kind(n) == NodeKind::Lambda {
return subtree_contains(&*lang, n, |m| {
lang.kind(m) == NodeKind::VarRef
&& lang.var_name(m) == Some(name_str.as_str())
});
}
false
}) {
captured = true;
break;
}
}
if captured {
return None;
}
}
let syntactic_refs = count_name_refs(&*lang, &stmts[index + 1..], &name_str);
if syntactic_refs != 1 {
return None;
}
let name_key = lang.var_key(id)?;
let mut wa = Watch::<L>::new(name_key, &[]);
for &s in &stmts[index + 1..] {
scan_region(&*lang, s, &mut wa);
}
if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
return None;
}
let use_id = wa.uses[0];
if let Some(&(p, _)) = walk.parents.get(&use_id) {
if lang.kind(p) == NodeKind::Assign && lang.assign_op(p).is_none() {
let pch = lang.children(p);
if pch.len() == 2
&& lang.kind(pch[0]) == NodeKind::VarRef
&& lang.var_key(pch[0]) == Some(name_key)
&& pch[1] == use_id
{
return None;
}
}
}
{
let (up, head) = match walk.parents.get(&use_id) {
Some(&(p, _)) => {
(Some(p), lang.children(p).first() == Some(&use_id))
}
None => (None, false),
};
if lang.receiver_propagation_unsound(id, value, up, head) {
return None;
}
}
if matches!(
lang.literal(value),
Some(
LitRef::Null
| LitRef::Int(_)
| LitRef::Long(_)
| LitRef::Float(_)
| LitRef::Double(_)
| LitRef::Char(_)
| LitRef::Bool(_)
)
) {
let mut cur = use_id;
while let Some(&(p, _)) = walk.parents.get(&cur) {
let at_head = lang.children(p).first() == Some(&cur);
match lang.kind(p) {
NodeKind::Member | NodeKind::Index if at_head => return None,
NodeKind::Call if at_head => return None,
NodeKind::InstanceOf if at_head => return None,
NodeKind::ForEach => {
let ch = lang.children(p);
if ch.first() == Some(&cur) {
return None;
}
}
NodeKind::Synchronized => {
let ch = lang.children(p);
if ch.len() == 1 && ch[0] == cur {
return None;
}
}
_ => {}
}
cur = p;
}
}
let mut watch_keys: Vec<L::NameKey> = Vec::new();
collect_read_keys(&*lang, value, &mut watch_keys);
watch_keys.push(name_key);
let mut wb = Watch::<L>::new(name_key, &watch_keys);
let mut use_stmt_idx = None;
for (off, &s) in stmts[index + 1..].iter().enumerate() {
let uses_before = wb.uses.len();
scan_region(&*lang, s, &mut wb);
if wb.uses.len() > uses_before {
use_stmt_idx = Some(index + 1 + off);
break; }
}
if subtree_contains(&*lang, value, |n| {
if lang.kind(n) != NodeKind::VarRef || lang.is_local_var(n) {
return false;
}
if !lang.var_name(n).is_some_and(|nm| lang.is_field_name(nm)) {
return false;
}
let is_callee = match walk.parent(n) {
Some(p) => {
matches!(lang.kind(p), NodeKind::Call) && lang.children(p).first() == Some(&n)
}
None => false,
};
!is_callee
}) {
if let Some(ui) = use_stmt_idx {
for &s in &stmts[index + 1..=ui] {
if lang.effect(s) != Effect::Pure {
return None;
}
}
}
}
if let Some(ui) = use_stmt_idx {
if count_name_refs(&*lang, &stmts[ui + 1..], &name_str) != 0 {
return None;
}
let mut wc = Watch::<L>::new(name_key, &[name_key]);
for &s in &stmts[ui + 1..] {
scan_region(&*lang, s, &mut wc);
if wc.opaque || !wc.uses.is_empty() || wc.wrote(name_key) || wc.shadowed {
return None;
}
}
}
let ve = lang.effect(value);
if ve <= Effect::MayRead {
if let Some(target_key) =
use_stmt_target_write_key(&*lang, &stmts[index + 1..], use_id)
{
wb.clear_wrote(target_key);
}
if wb.opaque || wb.any_write() {
return None;
}
return Some(Edit::Multi(vec![
Edit::Replace {
target: use_id,
with: value,
},
Edit::Delete { node: id },
]));
}
if index + 1 >= stmts.len() {
return None;
}
let stmt = stmts[index + 1];
if !subtree_contains(&*lang, stmt, |n| n == use_id) {
return None;
}
match lang.kind(stmt) {
NodeKind::Return
| NodeKind::ExprStmt
| NodeKind::Assign
| NodeKind::Throw
| NodeKind::VarDecl => {}
_ => return None,
}
if !straight_path(&*lang, stmt, use_id) {
return None;
}
if !prefix_effects_readable(&*lang, stmt, use_id) {
return None;
}
Some(Edit::Multi(vec![
Edit::Replace {
target: use_id,
with: value,
},
Edit::Delete { node: id },
]))
}
}
pub(crate) struct Watch<L: Lang> {
opaque: bool,
name: L::NameKey,
watch: Vec<L::NameKey>,
writes_hit: Vec<bool>,
shadowed: bool,
uses: Vec<L::Id>,
}
impl<L: Lang> Watch<L> {
pub(crate) fn new(name: L::NameKey, watch: &[L::NameKey]) -> Self {
Watch {
opaque: false,
name,
watch: watch.to_vec(),
writes_hit: vec![false; watch.len()],
shadowed: false,
uses: Vec::new(),
}
}
pub(crate) fn wrote(&self, k: L::NameKey) -> bool {
self.watch
.iter()
.zip(&self.writes_hit)
.any(|(w, h)| *h && *w == k)
}
pub(crate) fn any_write(&self) -> bool {
self.writes_hit.iter().any(|h| *h)
}
pub(crate) fn clear_wrote(&mut self, k: L::NameKey) {
for (w, h) in self.watch.iter().zip(self.writes_hit.iter_mut()) {
if *w == k {
*h = false;
}
}
}
}
fn scan_region<L: Lang>(lang: &L, node: L::Id, w: &mut Watch<L>) {
lang.debug_verify_events(node);
if let Some(events) = lang.region_events(node) {
let lo = events.partition_point(|e| e.key < w.name);
let hi = events.partition_point(|e| e.key <= w.name);
for e in &events[lo..hi] {
match e.kind {
crate::kind::EventKind::Use => w.uses.push(e.node),
crate::kind::EventKind::Shadow => w.shadowed = true,
crate::kind::EventKind::Write => {
for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
if *wn == w.name {
*hit = true;
}
}
}
crate::kind::EventKind::Opaque => w.opaque = true,
}
}
for (i, wk) in w.watch.iter().enumerate() {
if *wk == w.name {
continue; }
let lo = events.partition_point(|e| e.key < *wk);
let hi = events.partition_point(|e| e.key <= *wk);
for e in &events[lo..hi] {
if e.kind == crate::kind::EventKind::Write {
w.writes_hit[i] = true;
break; }
}
}
return;
}
if lang.is_opaque(node) {
w.opaque = true;
return;
}
match lang.kind(node) {
NodeKind::Assign => {
let ch = lang.children(node);
if let Some(&t) = ch.first() {
if lang.kind(t) == NodeKind::VarRef {
if let Some(k) = lang.var_key(t) {
for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
if *wn == k {
*hit = true;
}
}
}
}
}
if let Some(&v) = ch.get(1) {
scan_region(lang, v, w);
}
}
NodeKind::Unary if lang.un_op(node).is_some_and(|o| o.is_incdec()) => {
let ch = lang.children(node);
if let Some(&t) = ch.first() {
if lang.kind(t) == NodeKind::VarRef {
if let Some(k) = lang.var_key(t) {
for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
if *wn == k {
*hit = true;
}
}
}
}
}
}
NodeKind::VarDecl => {
if let Some(k) = lang.var_key(node) {
if k == w.name {
w.shadowed = true;
}
for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
if *wn == k {
*hit = true;
}
}
}
for &c in lang.children(node) {
scan_region(lang, c, w);
}
}
NodeKind::ForEach | NodeKind::Catch => {
if let Some(k) = lang.var_key(node) {
if k == w.name {
w.shadowed = true;
}
if lang.kind(node) == NodeKind::ForEach {
for (wn, hit) in w.watch.iter().zip(w.writes_hit.iter_mut()) {
if *wn == k {
*hit = true;
}
}
}
}
for &c in lang.children(node) {
scan_region(lang, c, w);
}
}
NodeKind::VarRef => {
if lang.var_key(node) == Some(w.name) {
w.uses.push(node);
}
}
NodeKind::Raw => {
w.opaque = true;
}
_ => {
for &c in lang.children(node) {
scan_region(lang, c, w);
}
}
}
}
fn straight_path<L: Lang>(lang: &L, root: L::Id, target: L::Id) -> bool {
fn go<L: Lang>(lang: &L, node: L::Id, target: L::Id) -> bool {
if node == target {
return true;
}
match lang.kind(node) {
NodeKind::Ternary | NodeKind::If => return false,
NodeKind::Binary if lang.bin_op(node).is_some_and(|o| o.is_short_circuit()) => {
return false
}
_ => {}
}
for &c in lang.children(node) {
if subtree_contains(lang, c, |n| n == target) {
return go(lang, c, target);
}
}
false
}
go(lang, root, target)
}
pub struct BoolShortCircuit;
impl<L: Lang> Rule<L> for BoolShortCircuit {
fn name(&self) -> &'static str {
"bool_short_circuit"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Binary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Binary {
return None;
}
let op = lang.bin_op(id)?;
if !op.is_short_circuit() {
return None;
}
let ch = lang.children(id);
let (l, r) = (*ch.first()?, *ch.get(1)?);
let is_lit = |n: L::Id, v: bool| matches!(lang.literal(n), Some(LitRef::Bool(b)) if b == v);
let with = match (op, is_lit(l, true), is_lit(l, false)) {
(BinOp::And, _, true) => l, (BinOp::Or, true, _) => l, (BinOp::And, true, _) => r, (BinOp::Or, _, true) => r, _ => {
match (op, is_lit(r, true), is_lit(r, false)) {
(BinOp::And, true, _) => l,
(BinOp::Or, _, true) => l,
(BinOp::And, _, true) | (BinOp::Or, true, _)
if lang.effect(l) <= Effect::MayRead => {
r
}
_ => return None,
}
}
};
Some(Edit::Replace {
target: id,
with,
})
}
}
pub struct NotCompare;
impl<L: Lang> Rule<L> for NotCompare {
fn name(&self) -> &'static str {
"not_compare"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Unary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Unary || lang.un_op(id) != Some(UnOp::Not) {
return None;
}
let inner = *lang.children(id).first()?;
if lang.kind(inner) != NodeKind::Binary {
return None;
}
let op = lang.bin_op(inner)?;
let neg = match op {
BinOp::Eq => BinOp::Ne,
BinOp::Ne => BinOp::Eq,
BinOp::Lt => BinOp::Ge,
BinOp::Le => BinOp::Gt,
BinOp::Gt => BinOp::Le,
BinOp::Ge => BinOp::Lt,
_ => return None,
};
if op.is_short_circuit() {
return None;
}
if matches!(op, BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge) {
let ch = lang.children(inner);
if !lang.is_exact_int(*ch.first()?) || !lang.is_exact_int(*ch.get(1)?) {
return None;
}
}
let ch = lang.children(inner).to_vec();
let rebuilt = lang.build_bin(neg, ch[0], ch[1]);
Some(Edit::Replace {
target: id,
with: rebuilt,
})
}
}
fn in_inferred_var_init<L: Lang>(lang: &L, walk: &Walk<L>, mut id: L::Id) -> bool {
while let Some(&(p, _)) = walk.parents.get(&id) {
match lang.kind(p) {
NodeKind::Paren | NodeKind::Ternary => id = p,
NodeKind::VarDecl => return lang.is_inferred_decl(p),
_ => return false,
}
}
false
}
pub struct TernaryFold;
impl<L: Lang> Rule<L> for TernaryFold {
fn name(&self) -> &'static str {
"ternary_fold"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Ternary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
if lang.kind(id) != NodeKind::Ternary {
return None;
}
let ch = lang.children(id);
let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
match lang.literal(c) {
Some(LitRef::Bool(true)) => {
if !in_inferred_var_init(&*lang, walk, id) {
return Some(Edit::Replace { target: id, with: a });
}
}
Some(LitRef::Bool(false))
if !in_inferred_var_init(&*lang, walk, id) => {
return Some(Edit::Replace { target: id, with: b });
}
_ => {}
}
if crate::analysis::structurally_equal(&*lang, a, b) && lang.effect(c) <= Effect::MayRead {
return Some(Edit::Replace { target: id, with: a });
}
None
}
}
pub struct TernaryBool;
impl<L: Lang> Rule<L> for TernaryBool {
fn name(&self) -> &'static str {
"ternary_bool"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Ternary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Ternary {
return None;
}
let ch = lang.children(id);
let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
if !lang.is_bool(c) {
return None;
}
let with = if lb(a) && is_false(b) {
c
} else if is_false(a) && lb(b) {
lang.build_unary(UnOp::Not, c)
} else {
return None;
};
Some(Edit::Replace {
target: id,
with,
})
}
}
pub struct ConstFoldBin;
impl<L: Lang> Rule<L> for ConstFoldBin {
fn name(&self) -> &'static str {
"const_fold_bin"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Binary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Binary {
return None;
}
let op = lang.bin_op(id)?;
if op.is_short_circuit() || op.is_comparison() {
return None;
}
let ch = lang.children(id);
let (l, r) = (*ch.first()?, *ch.get(1)?);
lang.literal(l)?;
lang.literal(r)?;
let with = lang.fold_lit_bin(op, l, r)?;
Some(Edit::Replace {
target: id,
with,
})
}
}
pub struct ArithZero;
impl<L: Lang> Rule<L> for ArithZero {
fn name(&self) -> &'static str {
"arith_zero"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Binary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Binary {
return None;
}
let op = lang.bin_op(id)?;
let ch = lang.children(id);
let (l, r) = (*ch.first()?, *ch.get(1)?);
let int_lit = |n: L::Id, v: i64| lang.literal(n).and_then(|x| x.as_int()) == Some(v);
if matches!(op, BinOp::Shl | BinOp::Shr | BinOp::UShr) && int_lit(r, 0) {
return Some(Edit::Replace {
target: id,
with: l,
});
}
if op == BinOp::Mul {
let (zero, other) = if int_lit(l, 0) {
(l, r)
} else if int_lit(r, 0) {
(r, l)
} else {
return None;
};
if !lang.is_exact_int(other) || lang.effect(other) > Effect::MayRead {
return None;
}
return Some(Edit::Replace {
target: id,
with: zero,
});
}
if op == BinOp::Sub
&& lang.kind(l) == NodeKind::VarRef
&& lang.kind(r) == NodeKind::VarRef
&& lang.var_name(l) == lang.var_name(r)
&& lang.is_local_var(l)
&& lang.is_exact_int(l)
{
let with = lang.build_int(0, false);
return Some(Edit::Replace {
target: id,
with,
});
}
None
}
}
pub struct InverseAssignPair;
impl<L: Lang> Rule<L> for InverseAssignPair {
fn name(&self) -> &'static str {
"inverse_assign_pair"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Block]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, .. } = ctx;
let ch = lang.children(id).to_vec();
if ch.len() < 2 {
return None;
}
for w in 0..ch.len() - 1 {
let (s1, s2) = (ch[w], ch[w + 1]);
let Some(a1) = norm_assign(lang, s1) else { continue };
let Some(a2) = norm_assign(lang, s2) else { continue };
if a1.key != a2.key || !a1.local || !a2.local {
continue;
}
if !a1.exact_int || !a2.exact_int {
continue;
}
if a1.k != a2.k {
continue;
}
let inverse = match (a1.op, a2.op) {
(BinOp::Add, BinOp::Sub) | (BinOp::Sub, BinOp::Add) => true,
(BinOp::BitXor, BinOp::BitXor) => true,
_ => false,
};
if !inverse {
continue;
}
return Some(Edit::Multi(vec![
Edit::Delete { node: s2 },
Edit::Delete { node: s1 },
]));
}
None
}
}
struct NormAssign<K> {
key: K,
op: BinOp,
k: i64,
local: bool,
exact_int: bool,
}
fn norm_assign<L: Lang>(lang: &L, stmt: L::Id) -> Option<NormAssign<L::NameKey>> {
let a = match lang.kind(stmt) {
NodeKind::ExprStmt => {
let c = lang.children(stmt);
if c.len() == 1 && lang.kind(c[0]) == NodeKind::Assign {
c[0]
} else {
return None;
}
}
NodeKind::Assign => stmt,
_ => return None,
};
let ch = lang.children(a).to_vec();
if ch.len() != 2 {
return None;
}
let (target, rhs) = (ch[0], ch[1]);
if lang.kind(target) != NodeKind::VarRef {
return None;
}
let key = lang.var_key(target)?;
let local = lang.is_local_var(target);
let exact_int = lang.is_exact_int(target);
match lang.assign_op(a) {
Some(op) => {
let k = lang.literal(rhs).and_then(|x| x.as_int())?;
Some(NormAssign { key, op, k, local, exact_int })
}
None => {
if lang.kind(rhs) != NodeKind::Binary {
return None;
}
let op = lang.bin_op(rhs)?;
let rc = lang.children(rhs).to_vec();
if rc.len() != 2 {
return None;
}
if lang.kind(rc[0]) != NodeKind::VarRef || lang.var_key(rc[0]) != Some(key) {
return None;
}
let k = lang.literal(rc[1]).and_then(|x| x.as_int())?;
Some(NormAssign { key, op, k, local, exact_int })
}
}
}
pub struct DeadStore;
impl<L: Lang> Rule<L> for DeadStore {
fn name(&self) -> &'static str {
"dead_store"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
let (decl_form, first_assign) = match lang.kind(id) {
NodeKind::VarDecl => {
if lang.children(id).is_empty() {
return None; }
(true, None)
}
NodeKind::Assign => (false, Some(id)),
NodeKind::ExprStmt => {
let ch = lang.children(id);
if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
(false, Some(ch[0]))
} else {
return None;
}
}
_ => return None,
};
let first_value = if decl_form {
*lang.children(id).first()?
} else {
let a = first_assign?;
if lang.assign_op(a).is_some() {
return None; }
*lang.children(a).get(1)?
};
let parent = walk.parent(id)?;
if lang.kind(parent) != NodeKind::Block {
return None;
}
let idx = walk.index(id)?;
let stmts = lang.children(parent).to_vec();
let next_stmt = *stmts.get(idx + 1)?;
let second = match lang.kind(next_stmt) {
NodeKind::Assign => Some(next_stmt),
NodeKind::ExprStmt => {
let ch = lang.children(next_stmt);
if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
Some(ch[0])
} else {
None
}
}
_ => None,
};
let Some(second) = second else {
let (name_node, target_local) = if decl_form {
(id, true)
} else {
let t = *lang.children(first_assign?).first()?;
if lang.kind(t) != NodeKind::VarRef {
return None;
}
(t, lang.is_local_var(t))
};
if !target_local {
return None;
}
let name = lang.var_name(name_node)?.to_string();
let declared_in_this_block = if decl_form {
true } else if parent == walk.root {
true
} else {
stmts[..idx].iter().any(|&s| {
lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
})
};
if !declared_in_this_block {
return None;
}
let name_key0 = lang.var_key(name_node)?;
let mut w0 = Watch::<L>::new(name_key0, &[name_key0]);
for &s in &stmts[idx + 1..] {
scan_region(&*lang, s, &mut w0);
}
if w0.opaque || !w0.uses.is_empty() || w0.wrote(name_key0) || w0.shadowed {
return None;
}
for &s in &stmts[idx + 1..] {
if subtree_contains(&*lang, s, |n| {
lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
}) {
return None;
}
}
if lang.effect(first_value) > Effect::MayRead {
return None;
}
return Some(Edit::Delete { node: id });
};
if lang.assign_op(second).is_some() {
return None;
}
let nch = lang.children(second);
let (target, value) = (*nch.first()?, *nch.get(1)?);
if lang.kind(target) != NodeKind::VarRef {
return None;
}
let name = if decl_form {
if lang.var_name(id) != lang.var_name(target) {
return None;
}
lang.var_name(id).map(|s| s.to_string())
} else {
let a = first_assign?;
let t = *lang.children(a).first()?;
if lang.kind(t) != NodeKind::VarRef {
return None;
}
if lang.var_name(t) != lang.var_name(target) || !lang.is_local_var(t) {
return None;
}
lang.var_name(t).map(|s| s.to_string())
}?;
if subtree_contains(&*lang, value, |n| {
lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
}) {
return None;
}
if lang.effect(first_value) > Effect::MayRead {
return None;
}
if decl_form {
Some(Edit::Multi(vec![
Edit::Replace {
target: first_value,
with: value,
},
Edit::Delete {
node: next_stmt,
},
]))
} else {
Some(Edit::Delete { node: id })
}
}
}
pub struct BlockFlatten;
impl<L: Lang> Rule<L> for BlockFlatten {
fn name(&self) -> &'static str {
"block_flatten"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Block]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
let parent = walk.parent(id)?;
if lang.kind(parent) != NodeKind::Block {
return None;
}
let inner = lang.children(id).to_vec();
let declares = |n: L::Id| -> bool {
matches!(
lang.kind(n),
NodeKind::VarDecl | NodeKind::ForEach | NodeKind::Catch
)
};
if inner.iter().any(|&s| declares(s)) {
return None;
}
if lang.has_pattern_bindings(id) {
return None;
}
let idx = walk.index(id)?;
Some(Edit::Splice {
node: parent,
index: idx,
remove: 1,
insert: inner,
})
}
}
pub struct DoubleNegFold;
impl<L: Lang> Rule<L> for DoubleNegFold {
fn name(&self) -> &'static str {
"double_neg_fold"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Unary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, .. } = ctx;
if lang.un_op(id) != Some(UnOp::Neg) {
return None;
}
let inner = *lang.children(id).first()?;
if lang.un_op(inner) == Some(UnOp::Neg) {
let x = *lang.children(inner).first()?;
return Some(Edit::Replace {
target: id,
with: x,
});
}
if let Some((v, wide)) = lang.literal(inner).and_then(|l| match l {
LitRef::Int(v) => Some((v, false)),
LitRef::Long(v) => Some((v, true)),
_ => None,
}) {
if v < 0 {
let n = lang.fold_lit_neg(v, wide)?;
let with = lang.build_int(n, wide);
return Some(Edit::Replace {
target: id,
with,
});
}
}
None
}
}
pub struct UnreachableAfterTerminal;
impl<L: Lang> Rule<L> for UnreachableAfterTerminal {
fn name(&self) -> &'static str {
"unreachable_after_terminal"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Block]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Block {
return None;
}
let children = lang.children(id).to_vec();
let terminal = |n: L::Id| -> bool {
matches!(
lang.kind(n),
NodeKind::Return | NodeKind::Throw | NodeKind::Break | NodeKind::Continue
)
};
let mut cut: Option<(usize, usize)> = None;
for (i, &s) in children.iter().enumerate() {
if terminal(s) && i + 1 < children.len() {
cut = Some((i + 1, children.len() - i - 1));
break;
}
}
let (index, remove) = cut?;
Some(Edit::Splice {
node: id,
index,
remove,
insert: Vec::new(),
})
}
}
pub struct CmpConstFold;
impl<L: Lang> Rule<L> for CmpConstFold {
fn name(&self) -> &'static str {
"cmp_const_fold"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Binary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Binary {
return None;
}
let op = lang.bin_op(id)?;
if !op.is_comparison() {
return None;
}
let ch = lang.children(id);
let (l, r) = (*ch.first()?, *ch.get(1)?);
lang.literal(l)?;
lang.literal(r)?;
let v = lang.fold_lit_cmp(op, l, r)?;
let with = lang.build_bool(v);
Some(Edit::Replace {
target: id,
with,
})
}
}
pub struct BitIdentity;
impl<L: Lang> Rule<L> for BitIdentity {
fn name(&self) -> &'static str {
"bit_identity"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Binary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Binary {
return None;
}
let op = lang.bin_op(id)?;
if !matches!(op, BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor) {
return None;
}
let ch = lang.children(id);
let (l, r) = (*ch.first()?, *ch.get(1)?);
let int_lit = |n: L::Id| lang.literal(n).and_then(|x| x.as_int());
if op == BinOp::BitXor
&& lang.kind(l) == NodeKind::VarRef
&& lang.kind(r) == NodeKind::VarRef
&& lang.var_name(l) == lang.var_name(r)
&& lang.is_local_var(l)
&& lang.is_exact_int(l)
{
let with = lang.build_int(0, false);
return Some(Edit::Replace {
target: id,
with,
});
}
for (x, k) in [(l, r), (r, l)] {
let Some(kv) = int_lit(k) else {
continue;
};
if !lang.is_exact_int(x) {
continue;
}
let identity = match (op, kv) {
(BinOp::BitXor, 0) => Some(x), (BinOp::BitOr, 0) => Some(x), (BinOp::BitAnd, -1) => Some(x), (BinOp::BitAnd, 0) | (BinOp::BitOr, -1) => {
if lang.effect(x) > Effect::MayRead {
None
} else {
Some(k)
}
}
_ => None,
};
if let Some(with) = identity {
return Some(Edit::Replace {
target: id,
with,
});
}
}
None
}
}
pub struct ArithReassoc;
impl<L: Lang> Rule<L> for ArithReassoc {
fn name(&self) -> &'static str {
"arith_reassoc"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Binary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Binary {
return None;
}
let op2 = lang.bin_op(id)?;
if !matches!(op2, BinOp::Add | BinOp::Sub | BinOp::BitXor) {
return None;
}
let och = lang.children(id).to_vec();
let inner = och[0];
if lang.kind(inner) != NodeKind::Binary {
return None;
}
let op1 = lang.bin_op(inner)?;
let ich = lang.children(inner).to_vec();
let x = ich[0];
if op1 == BinOp::Add && op2 == BinOp::Add {
if let (Some(LitRef::Str(k1)), Some(LitRef::Str(k2))) =
(lang.literal(ich[1]), lang.literal(och[1]))
{
let mut chain: Vec<L::Id> = vec![id];
let mut cur = id;
loop {
let cch = lang.children(cur).to_vec();
let next = cch[0];
let deeper = lang.kind(next) == NodeKind::Binary
&& lang.bin_op(next) == Some(BinOp::Add)
&& matches!(lang.literal(lang.children(next).to_vec()[1]),
Some(LitRef::Str(_)));
if deeper {
chain.push(next);
cur = next;
} else {
break;
}
}
let innermost = *chain.last().unwrap();
let ich2 = lang.children(innermost).to_vec();
let base = ich2[0];
let mut joined = String::new();
for &n in chain.iter().rev() {
if let Some(LitRef::Str(k)) =
lang.literal(lang.children(n).to_vec()[1])
{
joined.push_str(k);
}
}
let _ = (k1, k2); let lit = lang.build_str(&joined);
let with = lang.build_bin(BinOp::Add, base, lit);
return Some(Edit::Replace {
target: id,
with,
});
}
}
let k1 = lang.literal(ich[1]).and_then(|x| x.as_int())?;
let k2 = lang.literal(och[1]).and_then(|x| x.as_int())?;
if !lang.is_exact_int(x) {
return None;
}
let is_long = matches!(lang.literal(ich[1]), Some(LitRef::Long(_)))
|| matches!(lang.literal(och[1]), Some(LitRef::Long(_)));
let with = match lang.reassoc_delta(op1, k1, op2, k2, is_long)? {
ReassocOutcome::Neutral => x,
ReassocOutcome::Lit(lit, op) => lang.build_bin(op, x, lit),
};
Some(Edit::Replace {
target: id,
with,
})
}
}
pub struct IfToTernary;
impl<L: Lang> Rule<L> for IfToTernary {
fn name(&self) -> &'static str {
"if_to_ternary"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::If]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
if lang.kind(id) != NodeKind::If {
return None;
}
let ch = lang.children(id).to_vec();
let c = ch[0];
let single_ret = |n: L::Id| -> Option<Option<L::Id>> {
match lang.kind(n) {
NodeKind::Return => Some(lang.children(n).first().copied()),
NodeKind::Block if lang.children(n).len() == 1 => {
let inner = lang.children(n)[0];
if lang.kind(inner) == NodeKind::Return {
Some(lang.children(inner).first().copied())
} else {
None
}
}
_ => None,
}
};
if ch.len() == 3 {
let a = single_ret(ch[1])?;
let b = single_ret(ch[2])?;
let with = match (a, b) {
(Some(va), Some(vb)) => {
let t = lang.build_ternary(c, va, vb);
lang.build_return(Some(t))
}
(None, None) => lang.build_return(None),
_ => return None,
};
return Some(Edit::Replace {
target: id,
with,
});
}
if ch.len() != 2 {
return None;
}
let a = single_ret(ch[1])?;
let parent = walk.parent(id)?;
if lang.kind(parent) != NodeKind::Block {
return None;
}
let idx = walk.index(id)?;
let stmts = lang.children(parent).to_vec();
if idx + 2 != stmts.len() {
return None; }
let tail = stmts[idx + 1];
if lang.kind(tail) != NodeKind::Return {
return None;
}
let b = lang.children(tail).first().copied();
let with = match (a, b) {
(Some(va), Some(vb)) => {
let t = lang.build_ternary(c, va, vb);
lang.build_return(Some(t))
}
(None, None) => lang.build_return(None),
_ => return None,
};
Some(Edit::Splice {
node: parent,
index: idx,
remove: 2,
insert: vec![with],
})
}
}
pub struct IfAssignTernary;
impl<L: Lang> Rule<L> for IfAssignTernary {
fn name(&self) -> &'static str {
"if_assign_ternary"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::If]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::If {
return None;
}
let ch = lang.children(id).to_vec();
if ch.len() != 3 {
return None;
}
let c = ch[0];
let single_assign = |n: L::Id| -> Option<(L::Id, L::Id)> {
let unwrap = |i: L::Id| -> L::Id {
if lang.kind(i) == NodeKind::ExprStmt {
let ch = lang.children(i);
if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
return ch[0];
}
}
i
};
let inner = match lang.kind(n) {
NodeKind::Assign => n,
NodeKind::ExprStmt => unwrap(n),
NodeKind::Block if lang.children(n).len() == 1 => unwrap(lang.children(n)[0]),
_ => return None,
};
if lang.kind(inner) != NodeKind::Assign {
return None;
}
if lang.assign_op(inner).is_some() {
return None;
}
let a = lang.children(inner).to_vec();
if lang.kind(a[0]) != NodeKind::VarRef {
return None;
}
Some((a[0], a[1]))
};
let (t1, va) = single_assign(ch[1])?;
let (t2, vb) = single_assign(ch[2])?;
if lang.var_name(t1) != lang.var_name(t2) || lang.var_name(t1).is_none() {
return None;
}
let t = lang.build_ternary(c, va, vb);
let with = lang.build_assign(t1, t);
Some(Edit::Replace {
target: id,
with,
})
}
}
pub struct DeclAssignMerge;
impl<L: Lang> Rule<L> for DeclAssignMerge {
fn name(&self) -> &'static str {
"decl_assign_merge"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::VarDecl]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
if lang.kind(id) != NodeKind::VarDecl || !lang.children(id).is_empty() {
return None; }
let name = lang.var_name(id)?.to_string();
let parent = walk.parent(id)?;
if lang.kind(parent) != NodeKind::Block {
return None;
}
let idx = walk.index(id)?;
let stmts = lang.children(parent).to_vec();
let next = *stmts.get(idx + 1)?;
let assign = match lang.kind(next) {
NodeKind::Assign => next,
NodeKind::ExprStmt => {
let ch = lang.children(next);
if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
ch[0]
} else {
return None;
}
}
_ => return None,
};
if lang.assign_op(assign).is_some() {
return None;
}
let ach = lang.children(assign).to_vec();
let (target, value) = (ach[0], ach[1]);
if lang.kind(target) != NodeKind::VarRef || lang.var_name(target) != Some(name.as_str()) {
return None;
}
if subtree_contains(&*lang, value, |n| {
lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
}) {
return None;
}
Some(Edit::Multi(vec![
Edit::Splice {
node: id,
index: 0,
remove: 0,
insert: vec![value],
},
Edit::Delete { node: next },
]))
}
}
pub struct AssignPropagation;
impl<L: Lang> Rule<L> for AssignPropagation {
fn name(&self) -> &'static str {
"assign_propagation"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Assign, NodeKind::ExprStmt]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
let (assign, stmt_node) = match lang.kind(id) {
NodeKind::Assign => (id, id),
NodeKind::ExprStmt => {
let ch = lang.children(id);
if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
(ch[0], id)
} else {
return None;
}
}
_ => return None,
};
if lang.assign_op(assign).is_some() {
return None;
}
let ach = lang.children(assign).to_vec();
let (target, value) = (ach[0], ach[1]);
if lang.kind(target) != NodeKind::VarRef || !lang.is_local_var(target) {
return None;
}
if lang.kind(value) == NodeKind::ArrayLit {
return None;
}
if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
return None;
}
let name = lang.var_name(target)?.to_string();
let parent = walk.parent(stmt_node)?;
if lang.kind(parent) != NodeKind::Block {
return None;
}
let idx = walk.index(stmt_node)?;
let stmts = lang.children(parent).to_vec();
let anchored = stmts[..idx].iter().any(|&s| {
lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
});
if !anchored {
return None;
}
if subtree_contains(&*lang, value, |n| {
lang.kind(n) == NodeKind::VarRef && lang.var_name(n) == Some(name.as_str())
}) {
return None;
}
let name_key = lang.var_key(target)?;
if count_name_refs(&*lang, &stmts[idx + 1..], &name) != 1 {
return None;
}
let mut wa = Watch::<L>::new(name_key, &[]);
for &s in &stmts[idx + 1..] {
scan_region(&*lang, s, &mut wa);
}
if wa.opaque || wa.shadowed || wa.uses.len() != 1 {
return None;
}
let use_id = wa.uses[0];
if matches!(lang.literal(value), Some(LitRef::Null)) {
if let Some(&(p, _)) = walk.parents.get(&use_id) {
let at_head = lang.children(p).first() == Some(&use_id);
if at_head && matches!(lang.kind(p), NodeKind::Member | NodeKind::Call) {
return None;
}
}
}
let mut watch_keys: Vec<L::NameKey> = Vec::new();
collect_read_keys(&*lang, value, &mut watch_keys);
watch_keys.push(name_key);
let mut wb = Watch::<L>::new(name_key, &watch_keys);
for &s in &stmts[idx + 1..] {
let uses_before = wb.uses.len();
scan_region(&*lang, s, &mut wb);
if uses_before < wb.uses.len() {
break; }
}
if wb.wrote(name_key) {
return None;
}
let ve = lang.effect(value);
if ve <= Effect::MayRead {
if let Some(target_key) = use_stmt_target_write_key(&*lang, &stmts[idx + 1..], use_id) {
wb.clear_wrote(target_key);
}
if wb.any_write() {
return None;
}
return Some(Edit::Multi(vec![
Edit::Replace {
target: use_id,
with: value,
},
Edit::Delete { node: stmt_node },
]));
}
if idx + 1 >= stmts.len() {
return None;
}
let stmt = stmts[idx + 1];
if !subtree_contains(&*lang, stmt, |n| n == use_id) {
return None;
}
match lang.kind(stmt) {
NodeKind::Return
| NodeKind::ExprStmt
| NodeKind::Assign
| NodeKind::Throw
| NodeKind::VarDecl => {}
_ => return None,
}
if !straight_path(&*lang, stmt, use_id) {
return None;
}
if !prefix_effects_readable(&*lang, stmt, use_id) {
return None;
}
Some(Edit::Multi(vec![
Edit::Replace {
target: use_id,
with: value,
},
Edit::Delete { node: stmt_node },
]))
}
}
fn use_stmt_target_write_key<L: Lang>(
lang: &L,
stmts_after: &[L::Id],
use_id: L::Id,
) -> Option<L::NameKey> {
for &s in stmts_after {
if subtree_contains(lang, s, |n| n == use_id) {
let assign = match lang.kind(s) {
NodeKind::Assign => s,
NodeKind::ExprStmt => {
let ch = lang.children(s);
if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
ch[0]
} else {
return None;
}
}
_ => return None,
};
if lang.assign_op(assign).is_some() {
return None;
}
let ch = lang.children(assign);
if ch.len() == 2 && ch[1] == use_id && lang.kind(ch[0]) == NodeKind::VarRef {
return lang.var_key(ch[0]);
}
return None;
}
}
None
}
fn collect_read_keys<L: Lang>(lang: &L, id: L::Id, out: &mut Vec<L::NameKey>) {
let mut stack = vec![id];
while let Some(n) = stack.pop() {
if lang.kind(n) == NodeKind::VarRef {
if let Some(k) = lang.var_key(n) {
if !out.contains(&k) {
out.push(k);
}
}
}
for &c in lang.children(n) {
stack.push(c);
}
}
}
pub struct TrailingContinue;
impl<L: Lang> Rule<L> for TrailingContinue {
fn name(&self) -> &'static str {
"trailing_continue"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::While, NodeKind::For, NodeKind::DoWhile, NodeKind::ForEach]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
let body = match lang.kind(id) {
NodeKind::While | NodeKind::For | NodeKind::DoWhile | NodeKind::ForEach => {
*lang.children(id).last()?
}
_ => return None,
};
if lang.kind(body) != NodeKind::Block {
return None;
}
let ch = lang.children(body).to_vec();
let last = *ch.last()?;
if lang.kind(last) != NodeKind::Continue || !lang.children(last).is_empty() {
if lang.kind(last) != NodeKind::Continue {
return None;
}
}
Some(Edit::Delete { node: last })
}
}
pub struct MultiUseCopyPropagation;
impl<L: Lang> Rule<L> for MultiUseCopyPropagation {
fn name(&self) -> &'static str {
"multi_use_copy"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Assign, NodeKind::ExprStmt]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
let (assign, stmt_node) = match lang.kind(id) {
NodeKind::Assign => (id, id),
NodeKind::ExprStmt => {
let ch = lang.children(id);
if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
(ch[0], id)
} else {
return None;
}
}
_ => return None,
};
if lang.assign_op(assign).is_some() {
return None;
}
let ach = lang.children(assign).to_vec();
let (target, value) = (ach[0], ach[1]);
if lang.kind(target) != NodeKind::VarRef || lang.kind(value) != NodeKind::VarRef {
return None;
}
if matches!(lang.kind(value), NodeKind::Lambda | NodeKind::MethodRef) {
return None;
}
if !lang.is_local_var(target) {
return None;
}
let name = lang.var_name(target)?.to_string();
let src = lang.var_name(value)?.to_string();
if name == src {
return None;
}
let parent = walk.parent(stmt_node)?;
if lang.kind(parent) != NodeKind::Block {
return None;
}
let idx = walk.index(stmt_node)?;
let stmts = lang.children(parent).to_vec();
let anchored = stmts[..idx].iter().any(|&s| {
lang.kind(s) == NodeKind::VarDecl && lang.var_name(s) == Some(name.as_str())
});
if !anchored {
return None;
}
let name_key = lang.var_key(target)?;
let src_key = lang.var_key(value)?;
let mut w1 = Watch::<L>::new(name_key, &[name_key, src_key]);
for &s in &stmts[idx + 1..] {
scan_region(&*lang, s, &mut w1);
}
if w1.opaque || w1.shadowed || w1.uses.is_empty() {
return None;
}
if w1.wrote(name_key) || w1.wrote(src_key) {
return None;
}
let synth = count_name_refs(&*lang, &stmts[idx + 1..], &name);
if synth != w1.uses.len() {
return None;
}
let mut edits: Vec<Edit<L>> = w1.uses
.iter()
.map(|&u| Edit::Replace {
target: u,
with: value,
})
.collect();
edits.push(Edit::Delete { node: stmt_node });
Some(Edit::Multi(edits))
}
}
pub struct TrailingReturn;
impl<L: Lang> Rule<L> for TrailingReturn {
fn name(&self) -> &'static str {
"trailing_return"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Return]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let root = ctx.root();
let parent = ctx.parent(id)?;
let lang = ctx.lang;
if lang.kind(id) != NodeKind::Return || !lang.children(id).is_empty() {
return None;
}
if parent != root || lang.kind(parent) != NodeKind::Block {
return None;
}
if lang.children(parent).last() != Some(&id) {
return None;
}
Some(Edit::Delete { node: id })
}
}
pub struct TernaryBoolOp;
impl<L: Lang> Rule<L> for TernaryBoolOp {
fn name(&self) -> &'static str {
"ternary_bool_op"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::Ternary]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk: _ } = ctx;
if lang.kind(id) != NodeKind::Ternary {
return None;
}
let ch = lang.children(id);
let (c, a, b) = (*ch.first()?, *ch.get(1)?, *ch.get(2)?);
let lb = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if v);
let is_false = |n: L::Id| matches!(lang.literal(n), Some(LitRef::Bool(v)) if !v);
let with = if is_false(b) && lang.is_bool(a) {
lang.build_bin(BinOp::And, c, a)
} else if lb(b) && lang.is_bool(a) {
return None;
} else if lb(a) && lang.is_bool(b) {
lang.build_bin(BinOp::Or, c, b)
} else if is_false(a) && lang.is_bool(b) {
return None;
} else {
return None;
};
Some(Edit::Replace {
target: id,
with,
})
}
}
pub struct StoreKill;
enum KillEvent<L: Lang> {
Read,
DomWrite(L::Id),
Shadow,
}
fn scan_kill_event<L: Lang>(
lang: &L,
node: L::Id,
name: &str,
dominating: bool,
) -> Option<KillEvent<L>> {
match lang.kind(node) {
NodeKind::VarRef => {
if lang.var_name(node) == Some(name) {
Some(KillEvent::Read)
} else {
None
}
}
NodeKind::VarDecl => {
if lang.var_name(node) == Some(name) {
Some(KillEvent::Shadow)
} else {
for &c in lang.children(node) {
if let Some(e) = scan_kill_event(lang, c, name, dominating) {
return Some(e);
}
}
None
}
}
NodeKind::Assign => {
let ch = lang.children(node);
if let Some(&val) = ch.get(1) {
if let Some(e) = scan_kill_event(lang, val, name, dominating) {
if matches!(e, KillEvent::Read | KillEvent::Shadow) {
return Some(e);
}
}
}
if lang.assign_op(node).is_none() {
if let Some(&t) = ch.first() {
if lang.kind(t) == NodeKind::VarRef && lang.var_name(t) == Some(name) {
if dominating {
return Some(KillEvent::DomWrite(node));
}
return None; }
}
}
None
}
NodeKind::If | NodeKind::While | NodeKind::Ternary => {
let ch = lang.children(node);
if let Some(&c) = ch.first() {
if let Some(e) = scan_kill_event(lang, c, name, true) {
return Some(e);
}
}
for &c in &ch[1..] {
if let Some(e) = scan_kill_event(lang, c, name, false) {
if matches!(e, KillEvent::Read | KillEvent::Shadow) {
return Some(e);
}
}
}
None
}
NodeKind::Binary => {
let op = lang.bin_op(node)?;
let ch = lang.children(node);
if op.is_short_circuit() {
if let Some(&l) = ch.first() {
if let Some(e) = scan_kill_event(lang, l, name, true) {
return Some(e);
}
}
if let Some(&r) = ch.get(1) {
if let Some(e) = scan_kill_event(lang, r, name, false) {
if matches!(e, KillEvent::Read | KillEvent::Shadow) {
return Some(e);
}
}
}
None
} else {
for &c in ch {
if let Some(e) = scan_kill_event(lang, c, name, dominating) {
return Some(e);
}
}
None
}
}
NodeKind::For | NodeKind::ForEach | NodeKind::Switch | NodeKind::Try => {
let ch = lang.children(node);
let dom_len = match lang.kind(node) {
NodeKind::For => ch.len().saturating_sub(2),
NodeKind::ForEach | NodeKind::Switch => 1,
_ => 0,
};
for &c in &ch[..dom_len] {
if let Some(e) = scan_kill_event(lang, c, name, true) {
return Some(e);
}
}
for &c in &ch[dom_len..] {
if let Some(e) = scan_kill_event(lang, c, name, false) {
if matches!(e, KillEvent::Read | KillEvent::Shadow) {
return Some(e);
}
}
}
None
}
_ => {
for &c in lang.children(node) {
if let Some(e) = scan_kill_event(lang, c, name, dominating) {
return Some(e);
}
}
None
}
}
}
impl<L: Lang> Rule<L> for StoreKill {
fn name(&self) -> &'static str {
"store_kill"
}
fn kinds(&self) -> &'static [NodeKind] {
&[NodeKind::VarDecl, NodeKind::Assign, NodeKind::ExprStmt]
}
fn check(&self, ctx: RewriteCtx<'_, L>, id: L::Id) -> Option<Edit<L>> {
let RewriteCtx { lang, walk } = ctx;
let (is_decl, stmt_node, first_value) = match lang.kind(id) {
NodeKind::VarDecl => {
if lang.children(id).is_empty() {
return None;
}
(true, id, *lang.children(id).first()?)
}
NodeKind::Assign => {
if lang.assign_op(id).is_some() {
return None;
}
(false, id, *lang.children(id).get(1)?)
}
NodeKind::ExprStmt => {
let ch = lang.children(id);
if ch.len() == 1 && lang.kind(ch[0]) == NodeKind::Assign {
let a = ch[0];
if lang.assign_op(a).is_some() {
return None;
}
(false, id, *lang.children(a).get(1)?)
} else {
return None;
}
}
_ => return None,
};
let name = if is_decl {
lang.var_name(id)?.to_string()
} else {
let a = match lang.kind(id) {
NodeKind::Assign => id,
_ => lang.children(id)[0],
};
let t = *lang.children(a).first()?;
if lang.kind(t) != NodeKind::VarRef || !lang.is_local_var(t) {
return None;
}
lang.var_name(t)?.to_string()
};
let parent = walk.parent(stmt_node)?;
if lang.kind(parent) != NodeKind::Block {
return None;
}
let idx = walk.index(stmt_node)?;
let stmts = lang.children(parent).to_vec();
let mut killed_by: Option<(usize, L::Id)> = None;
for (si, &s) in stmts.iter().enumerate().skip(idx + 1) {
match scan_kill_event(&*lang, s, &name, true) {
Some(KillEvent::Read) | Some(KillEvent::Shadow) => return None,
Some(KillEvent::DomWrite(assign)) => {
killed_by = Some((si, assign));
break;
}
None => continue,
}
}
let (si, kill_assign) = killed_by?;
{
let kill_container = match walk.parents.get(&kill_assign) {
Some(&(p, _)) if lang.kind(p) == NodeKind::ExprStmt => p,
_ => kill_assign,
};
if kill_container != stmts[si] {
return None;
}
}
for &s in &stmts[idx + 1..si] {
if lang.effect(s) > Effect::MayRead {
return None;
}
}
if is_decl {
if lang.effect(first_value) > Effect::MayRead {
return None;
}
let value = *lang.children(kill_assign).get(1)?;
if is_literal(lang, value) {
let assign_stmt = stmts[si];
return Some(Edit::Multi(vec![
Edit::Splice {
node: id,
index: 0,
remove: 1,
insert: vec![value],
},
Edit::Delete { node: assign_stmt },
]));
}
if lang.is_inferred_decl(id) {
return None;
}
if lang.effect(first_value) > Effect::MayRead {
return None;
}
return Some(Edit::Splice {
node: id,
index: 0,
remove: 1,
insert: Vec::new(),
});
}
if lang.effect(first_value) > Effect::MayRead {
return None;
}
Some(Edit::Delete { node: stmt_node })
}
}
fn is_literal<L: Lang>(lang: &L, n: L::Id) -> bool {
matches!(lang.kind(n), NodeKind::Literal)
}
pub fn default_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
vec![
Box::new(DoubleNegFold),
Box::new(BlockFlatten),
Box::new(ParenRemoval),
Box::new(ConstCondition),
Box::new(BooleanReturn),
Box::new(IfToTernary),
Box::new(IfAssignTernary),
Box::new(IfElseEmpty),
Box::new(BoolCompare),
Box::new(DoubleNot),
Box::new(BoolNotFold),
Box::new(BoolShortCircuit),
Box::new(NotCompare),
Box::new(TernaryFold),
Box::new(TernaryBool),
Box::new(TernaryBoolOp),
Box::new(ConstFoldBin),
Box::new(CmpConstFold),
Box::new(BitIdentity),
Box::new(ArithReassoc),
Box::new(SelfAssign),
Box::new(ArithIdentity),
Box::new(ArithZero),
Box::new(DeclAssignMerge),
Box::new(LocalPropagation),
Box::new(AssignPropagation),
Box::new(MultiUseCopyPropagation),
Box::new(TrailingReturn),
Box::new(InverseAssignPair),
Box::new(DeadDecl),
Box::new(AssignBackFold),
Box::new(DeadStore),
Box::new(StoreKill),
]
}
pub fn all_rules<L: Lang>() -> Vec<Box<dyn Rule<L>>> {
let mut rules = default_rules();
rules.push(Box::new(UnreachableAfterTerminal));
rules
}