use super::visit::HirVisitor;
use crate::hir::common::{
HirAssign, HirBlock, HirDecisionExpr, HirDecisionNode, HirDecisionNodeRef, HirDecisionTarget,
HirExpr, HirIf, HirLValue, HirLocalDecl, HirStmt, LocalId,
};
pub(super) fn fold_branch_value_locals_in_block(stmts: &mut Vec<HirStmt>) -> bool {
let mut changed = false;
let mut index = 1;
while index < stmts.len() {
let Some((binding, value)) =
collapsible_branch_value_local(&stmts[index - 1], &stmts[index])
else {
index += 1;
continue;
};
stmts[index - 1] = HirStmt::LocalDecl(Box::new(HirLocalDecl {
bindings: vec![binding],
values: vec![value],
}));
stmts.remove(index);
changed = true;
}
changed
}
fn collapsible_branch_value_local(
local_decl_stmt: &HirStmt,
if_stmt: &HirStmt,
) -> Option<(LocalId, HirExpr)> {
let binding = empty_single_local_decl_binding(local_decl_stmt)?;
let HirStmt::If(if_stmt) = if_stmt else {
return None;
};
let value = branch_value_expr(binding, if_stmt)?;
Some((binding, value))
}
fn branch_value_expr(binding: LocalId, if_stmt: &HirIf) -> Option<HirExpr> {
let truthy = try_collapse_block_to_value(&if_stmt.then_block, binding)?;
let else_block = if_stmt.else_block.as_ref()?;
let falsy = try_collapse_block_to_value(else_block, binding)?;
if expr_mentions_local(&if_stmt.cond, binding)
|| expr_mentions_local(&truthy, binding)
|| expr_mentions_local(&falsy, binding)
{
return None;
}
finalize_branch_value(&if_stmt.cond, truthy, falsy)
}
fn finalize_branch_value(cond: &HirExpr, truthy: HirExpr, falsy: HirExpr) -> Option<HirExpr> {
let decision = HirDecisionExpr {
entry: HirDecisionNodeRef(0),
nodes: vec![HirDecisionNode {
id: HirDecisionNodeRef(0),
test: cond.clone(),
truthy: HirDecisionTarget::Expr(truthy),
falsy: HirDecisionTarget::Expr(falsy),
}],
};
let value = crate::hir::decision::finalize_value_decision_expr(decision);
(!matches!(value, HirExpr::Decision(_))).then_some(value)
}
fn try_collapse_block_to_value(block: &HirBlock, binding: LocalId) -> Option<HirExpr> {
match block.stmts.as_slice() {
[HirStmt::Assign(assign)] => single_assign_value(assign, binding).cloned(),
[HirStmt::If(if_stmt)] => branch_value_expr(binding, if_stmt),
[HirStmt::LocalDecl(decl), HirStmt::If(if_stmt)] => {
collapse_temp_guard_pattern(decl, if_stmt, binding)
}
_ => None,
}
}
fn single_assign_value(assign: &HirAssign, binding: LocalId) -> Option<&HirExpr> {
let [target] = assign.targets.as_slice() else {
return None;
};
let [value] = assign.values.as_slice() else {
return None;
};
matches_local_lvalue(target, binding).then_some(value)
}
fn collapse_temp_guard_pattern(
decl: &HirLocalDecl,
if_stmt: &HirIf,
binding: LocalId,
) -> Option<HirExpr> {
let [lx] = decl.bindings.as_slice() else {
return None;
};
let [lx_value] = decl.values.as_slice() else {
return None;
};
let lx = *lx;
let HirExpr::LocalRef(cond_local) = &if_stmt.cond else {
return None;
};
if *cond_local != lx {
return None;
}
let [HirStmt::Assign(then_assign)] = if_stmt.then_block.stmts.as_slice() else {
return None;
};
let then_value = single_assign_value(then_assign, binding)?;
let HirExpr::LocalRef(then_local) = then_value else {
return None;
};
if *then_local != lx {
return None;
}
let else_block = if_stmt.else_block.as_ref()?;
if expr_mentions_local(lx_value, lx)
|| expr_mentions_local(lx_value, binding)
|| block_mentions_local(else_block, lx)
{
return None;
}
let rest_value = try_collapse_block_to_value(else_block, binding)?;
if expr_mentions_local(&rest_value, binding) || expr_mentions_local(&rest_value, lx) {
return None;
}
finalize_branch_value(lx_value, lx_value.clone(), rest_value)
}
pub(super) fn empty_single_local_decl_binding(stmt: &HirStmt) -> Option<LocalId> {
let HirStmt::LocalDecl(local_decl) = stmt else {
return None;
};
let [binding] = local_decl.bindings.as_slice() else {
return None;
};
local_decl.values.is_empty().then_some(*binding)
}
pub(super) fn matches_local_lvalue(target: &HirLValue, binding: LocalId) -> bool {
matches!(target, HirLValue::Local(local) if *local == binding)
}
fn expr_mentions_local(expr: &HirExpr, binding: LocalId) -> bool {
let mut visitor = LocalMentionVisitor {
binding,
mentioned: false,
};
super::visit::visit_expr(expr, &mut visitor);
visitor.mentioned
}
fn block_mentions_local(block: &HirBlock, binding: LocalId) -> bool {
let mut visitor = LocalMentionVisitor {
binding,
mentioned: false,
};
super::visit::visit_block(block, &mut visitor);
visitor.mentioned
}
struct LocalMentionVisitor {
binding: LocalId,
mentioned: bool,
}
impl HirVisitor for LocalMentionVisitor {
fn visit_expr(&mut self, expr: &HirExpr) {
self.mentioned |= matches!(expr, HirExpr::LocalRef(local) if *local == self.binding);
}
}