use std::collections::BTreeMap;
use super::super::common::{
AstBindingRef, AstBlock, AstCallKind, AstExpr, AstLValue, AstLocalBinding, AstNameRef, AstStmt,
AstTableField, AstTableKey,
};
#[derive(Clone, Copy)]
enum BindingUseScope {
CurrentFunctionOnly,
IncludingNestedFunctions,
}
#[derive(Debug, Default, Clone)]
pub(super) struct BindingUseIndex {
stmt_len: usize,
stmt_counts: Vec<BTreeMap<AstBindingRef, usize>>,
suffix_counts: BTreeMap<AstBindingRef, BindingUseSuffixCounts>,
}
#[derive(Debug, Clone)]
struct BindingUseSuffixCounts {
stmt_indices: Vec<usize>,
suffix_totals: Vec<usize>,
}
impl BindingUseIndex {
pub(super) fn for_stmts(stmts: &[AstStmt]) -> Self {
let mut stmt_counts = Vec::with_capacity(stmts.len());
let mut occurrences = BTreeMap::<AstBindingRef, Vec<(usize, usize)>>::new();
for (stmt_index, stmt) in stmts.iter().enumerate() {
let mut counts = BTreeMap::new();
collect_binding_uses_in_stmt_with_scope(
stmt,
BindingUseScope::CurrentFunctionOnly,
&mut counts,
);
for (&binding, &count) in &counts {
occurrences
.entry(binding)
.or_default()
.push((stmt_index, count));
}
stmt_counts.push(counts);
}
let suffix_counts = occurrences
.into_iter()
.map(|(binding, entries)| {
let mut stmt_indices = Vec::with_capacity(entries.len());
let mut suffix_totals = Vec::with_capacity(entries.len());
let mut running_total = 0usize;
for (stmt_index, count) in entries.iter().rev() {
running_total += *count;
stmt_indices.push(*stmt_index);
suffix_totals.push(running_total);
}
stmt_indices.reverse();
suffix_totals.reverse();
(
binding,
BindingUseSuffixCounts {
stmt_indices,
suffix_totals,
},
)
})
.collect();
Self {
stmt_len: stmts.len(),
stmt_counts,
suffix_counts,
}
}
pub(super) fn count_uses_in_suffix(&self, start: usize, binding: AstBindingRef) -> usize {
if start >= self.stmt_len {
return 0;
}
let Some(counts) = self.suffix_counts.get(&binding) else {
return 0;
};
let first_suffix_stmt = counts
.stmt_indices
.partition_point(|stmt_index| *stmt_index < start);
counts
.suffix_totals
.get(first_suffix_stmt)
.copied()
.unwrap_or(0)
}
pub(super) fn count_uses_in_range(
&self,
start: usize,
end: usize,
binding: AstBindingRef,
) -> usize {
if start >= end {
return 0;
}
self.count_uses_in_suffix(start, binding) - self.count_uses_in_suffix(end, binding)
}
pub(super) fn count_uses_in_stmt_index(
&self,
stmt_index: usize,
binding: AstBindingRef,
) -> usize {
self.stmt_counts
.get(stmt_index)
.and_then(|counts| counts.get(&binding))
.copied()
.unwrap_or(0)
}
}
pub(super) fn name_matches_binding(name: &AstNameRef, binding: AstBindingRef) -> bool {
match (binding, name) {
(AstBindingRef::Local(local), AstNameRef::Local(target)) => local == *target,
(AstBindingRef::Temp(temp), AstNameRef::Temp(target)) => temp == *target,
(AstBindingRef::SyntheticLocal(local), AstNameRef::SyntheticLocal(target)) => {
local == *target
}
_ => false,
}
}
pub(super) fn count_binding_uses_in_stmts(stmts: &[AstStmt], binding: AstBindingRef) -> usize {
count_binding_uses_in_stmts_with_scope(stmts, binding, BindingUseScope::CurrentFunctionOnly)
}
pub(super) fn count_binding_uses_in_stmts_deep(stmts: &[AstStmt], binding: AstBindingRef) -> usize {
count_binding_uses_in_stmts_with_scope(
stmts,
binding,
BindingUseScope::IncludingNestedFunctions,
)
}
pub(super) fn count_binding_uses_in_block_deep(block: &AstBlock, binding: AstBindingRef) -> usize {
count_binding_uses_in_block_with_scope(
block,
binding,
BindingUseScope::IncludingNestedFunctions,
)
}
pub(super) fn count_binding_mentions_in_block(block: &AstBlock, binding: AstBindingRef) -> usize {
block
.stmts
.iter()
.map(|stmt| count_binding_mentions_in_stmt(stmt, binding))
.sum()
}
pub(super) fn count_binding_uses_in_stmt(stmt: &AstStmt, binding: AstBindingRef) -> usize {
count_binding_uses_in_stmt_with_scope(stmt, binding, BindingUseScope::CurrentFunctionOnly)
}
fn count_binding_uses_in_stmts_with_scope(
stmts: &[AstStmt],
binding: AstBindingRef,
scope: BindingUseScope,
) -> usize {
stmts
.iter()
.map(|stmt| count_binding_uses_in_stmt_with_scope(stmt, binding, scope))
.sum()
}
fn count_binding_uses_in_block_with_scope(
block: &AstBlock,
binding: AstBindingRef,
scope: BindingUseScope,
) -> usize {
count_binding_uses_in_stmts_with_scope(&block.stmts, binding, scope)
}
fn count_binding_uses_in_stmt_with_scope(
stmt: &AstStmt,
binding: AstBindingRef,
scope: BindingUseScope,
) -> usize {
match stmt {
AstStmt::LocalDecl(local_decl) => local_decl
.values
.iter()
.map(|value| count_binding_uses_in_expr_with_scope(value, binding, scope))
.sum(),
AstStmt::GlobalDecl(global_decl) => global_decl
.values
.iter()
.map(|value| count_binding_uses_in_expr_with_scope(value, binding, scope))
.sum(),
AstStmt::Assign(assign) => {
assign
.targets
.iter()
.map(|target| count_binding_uses_in_lvalue_with_scope(target, binding, scope))
.sum::<usize>()
+ assign
.values
.iter()
.map(|value| count_binding_uses_in_expr_with_scope(value, binding, scope))
.sum::<usize>()
}
AstStmt::CallStmt(call_stmt) => {
count_binding_uses_in_call_with_scope(&call_stmt.call, binding, scope)
}
AstStmt::Return(ret) => ret
.values
.iter()
.map(|value| count_binding_uses_in_expr_with_scope(value, binding, scope))
.sum(),
AstStmt::If(if_stmt) => {
count_binding_uses_in_expr_with_scope(&if_stmt.cond, binding, scope)
+ count_binding_uses_in_block_with_scope(&if_stmt.then_block, binding, scope)
+ if_stmt
.else_block
.as_ref()
.map(|else_block| {
count_binding_uses_in_block_with_scope(else_block, binding, scope)
})
.unwrap_or(0)
}
AstStmt::While(while_stmt) => {
count_binding_uses_in_expr_with_scope(&while_stmt.cond, binding, scope)
+ count_binding_uses_in_block_with_scope(&while_stmt.body, binding, scope)
}
AstStmt::Repeat(repeat_stmt) => {
count_binding_uses_in_block_with_scope(&repeat_stmt.body, binding, scope)
+ count_binding_uses_in_expr_with_scope(&repeat_stmt.cond, binding, scope)
}
AstStmt::NumericFor(numeric_for) => {
count_binding_uses_in_expr_with_scope(&numeric_for.start, binding, scope)
+ count_binding_uses_in_expr_with_scope(&numeric_for.limit, binding, scope)
+ count_binding_uses_in_expr_with_scope(&numeric_for.step, binding, scope)
+ count_binding_uses_in_block_with_scope(&numeric_for.body, binding, scope)
}
AstStmt::GenericFor(generic_for) => {
generic_for
.iterator
.iter()
.map(|expr| count_binding_uses_in_expr_with_scope(expr, binding, scope))
.sum::<usize>()
+ count_binding_uses_in_block_with_scope(&generic_for.body, binding, scope)
}
AstStmt::DoBlock(block) => count_binding_uses_in_block_with_scope(block, binding, scope),
AstStmt::FunctionDecl(function_decl) => {
if matches!(scope, BindingUseScope::IncludingNestedFunctions) {
count_binding_uses_in_block_with_scope(&function_decl.func.body, binding, scope)
} else {
0
}
}
AstStmt::LocalFunctionDecl(function_decl) => {
if matches!(scope, BindingUseScope::IncludingNestedFunctions) {
count_binding_uses_in_block_with_scope(&function_decl.func.body, binding, scope)
} else {
0
}
}
AstStmt::Break | AstStmt::Continue | AstStmt::Goto(_) | AstStmt::Label(_) | AstStmt::Error(_) => 0,
}
}
fn collect_binding_uses_in_block_with_scope(
block: &AstBlock,
scope: BindingUseScope,
counts: &mut BTreeMap<AstBindingRef, usize>,
) {
for stmt in &block.stmts {
collect_binding_uses_in_stmt_with_scope(stmt, scope, counts);
}
}
fn collect_binding_uses_in_stmt_with_scope(
stmt: &AstStmt,
scope: BindingUseScope,
counts: &mut BTreeMap<AstBindingRef, usize>,
) {
match stmt {
AstStmt::LocalDecl(local_decl) => {
for value in &local_decl.values {
collect_binding_uses_in_expr_with_scope(value, scope, counts);
}
}
AstStmt::GlobalDecl(global_decl) => {
for value in &global_decl.values {
collect_binding_uses_in_expr_with_scope(value, scope, counts);
}
}
AstStmt::Assign(assign) => {
for target in &assign.targets {
collect_binding_uses_in_lvalue_with_scope(target, scope, counts);
}
for value in &assign.values {
collect_binding_uses_in_expr_with_scope(value, scope, counts);
}
}
AstStmt::CallStmt(call_stmt) => {
collect_binding_uses_in_call_with_scope(&call_stmt.call, scope, counts);
}
AstStmt::Return(ret) => {
for value in &ret.values {
collect_binding_uses_in_expr_with_scope(value, scope, counts);
}
}
AstStmt::If(if_stmt) => {
collect_binding_uses_in_expr_with_scope(&if_stmt.cond, scope, counts);
collect_binding_uses_in_block_with_scope(&if_stmt.then_block, scope, counts);
if let Some(else_block) = &if_stmt.else_block {
collect_binding_uses_in_block_with_scope(else_block, scope, counts);
}
}
AstStmt::While(while_stmt) => {
collect_binding_uses_in_expr_with_scope(&while_stmt.cond, scope, counts);
collect_binding_uses_in_block_with_scope(&while_stmt.body, scope, counts);
}
AstStmt::Repeat(repeat_stmt) => {
collect_binding_uses_in_block_with_scope(&repeat_stmt.body, scope, counts);
collect_binding_uses_in_expr_with_scope(&repeat_stmt.cond, scope, counts);
}
AstStmt::NumericFor(numeric_for) => {
collect_binding_uses_in_expr_with_scope(&numeric_for.start, scope, counts);
collect_binding_uses_in_expr_with_scope(&numeric_for.limit, scope, counts);
collect_binding_uses_in_expr_with_scope(&numeric_for.step, scope, counts);
collect_binding_uses_in_block_with_scope(&numeric_for.body, scope, counts);
}
AstStmt::GenericFor(generic_for) => {
for expr in &generic_for.iterator {
collect_binding_uses_in_expr_with_scope(expr, scope, counts);
}
collect_binding_uses_in_block_with_scope(&generic_for.body, scope, counts);
}
AstStmt::DoBlock(block) => collect_binding_uses_in_block_with_scope(block, scope, counts),
AstStmt::FunctionDecl(function_decl) => {
if matches!(scope, BindingUseScope::IncludingNestedFunctions) {
collect_binding_uses_in_block_with_scope(&function_decl.func.body, scope, counts);
}
}
AstStmt::LocalFunctionDecl(function_decl) => {
if matches!(scope, BindingUseScope::IncludingNestedFunctions) {
collect_binding_uses_in_block_with_scope(&function_decl.func.body, scope, counts);
}
}
AstStmt::Break | AstStmt::Continue | AstStmt::Goto(_) | AstStmt::Label(_) | AstStmt::Error(_) => {}
}
}
fn count_binding_mentions_in_stmt(stmt: &AstStmt, binding: AstBindingRef) -> usize {
match stmt {
AstStmt::LocalDecl(local_decl) => local_decl
.values
.iter()
.map(|value| count_binding_uses_in_expr(value, binding))
.sum(),
AstStmt::GlobalDecl(global_decl) => global_decl
.values
.iter()
.map(|value| count_binding_uses_in_expr(value, binding))
.sum(),
AstStmt::Assign(assign) => {
assign
.targets
.iter()
.map(|target| count_binding_mentions_in_lvalue(target, binding))
.sum::<usize>()
+ assign
.values
.iter()
.map(|value| count_binding_uses_in_expr(value, binding))
.sum::<usize>()
}
AstStmt::CallStmt(call_stmt) => count_binding_uses_in_call(&call_stmt.call, binding),
AstStmt::Return(ret) => ret
.values
.iter()
.map(|value| count_binding_uses_in_expr(value, binding))
.sum(),
AstStmt::If(if_stmt) => {
count_binding_uses_in_expr(&if_stmt.cond, binding)
+ count_binding_mentions_in_block(&if_stmt.then_block, binding)
+ if_stmt
.else_block
.as_ref()
.map(|else_block| count_binding_mentions_in_block(else_block, binding))
.unwrap_or(0)
}
AstStmt::While(while_stmt) => {
count_binding_uses_in_expr(&while_stmt.cond, binding)
+ count_binding_mentions_in_block(&while_stmt.body, binding)
}
AstStmt::Repeat(repeat_stmt) => {
count_binding_mentions_in_block(&repeat_stmt.body, binding)
+ count_binding_uses_in_expr(&repeat_stmt.cond, binding)
}
AstStmt::NumericFor(numeric_for) => {
count_binding_uses_in_expr(&numeric_for.start, binding)
+ count_binding_uses_in_expr(&numeric_for.limit, binding)
+ count_binding_uses_in_expr(&numeric_for.step, binding)
+ count_binding_mentions_in_block(&numeric_for.body, binding)
}
AstStmt::GenericFor(generic_for) => {
generic_for
.iterator
.iter()
.map(|expr| count_binding_uses_in_expr(expr, binding))
.sum::<usize>()
+ count_binding_mentions_in_block(&generic_for.body, binding)
}
AstStmt::DoBlock(block) => count_binding_mentions_in_block(block, binding),
AstStmt::FunctionDecl(_) | AstStmt::LocalFunctionDecl(_) => 0,
AstStmt::Break | AstStmt::Continue | AstStmt::Goto(_) | AstStmt::Label(_) | AstStmt::Error(_) => 0,
}
}
fn count_binding_uses_in_call(call: &AstCallKind, binding: AstBindingRef) -> usize {
count_binding_uses_in_call_with_scope(call, binding, BindingUseScope::CurrentFunctionOnly)
}
fn count_binding_uses_in_expr(expr: &AstExpr, binding: AstBindingRef) -> usize {
count_binding_uses_in_expr_with_scope(expr, binding, BindingUseScope::CurrentFunctionOnly)
}
fn count_binding_uses_in_call_with_scope(
call: &AstCallKind,
binding: AstBindingRef,
scope: BindingUseScope,
) -> usize {
match call {
AstCallKind::Call(call) => {
count_binding_uses_in_expr_with_scope(&call.callee, binding, scope)
+ call
.args
.iter()
.map(|arg| count_binding_uses_in_expr_with_scope(arg, binding, scope))
.sum::<usize>()
}
AstCallKind::MethodCall(call) => {
count_binding_uses_in_expr_with_scope(&call.receiver, binding, scope)
+ call
.args
.iter()
.map(|arg| count_binding_uses_in_expr_with_scope(arg, binding, scope))
.sum::<usize>()
}
}
}
fn collect_binding_uses_in_call_with_scope(
call: &AstCallKind,
scope: BindingUseScope,
counts: &mut BTreeMap<AstBindingRef, usize>,
) {
match call {
AstCallKind::Call(call) => {
collect_binding_uses_in_expr_with_scope(&call.callee, scope, counts);
for arg in &call.args {
collect_binding_uses_in_expr_with_scope(arg, scope, counts);
}
}
AstCallKind::MethodCall(call) => {
collect_binding_uses_in_expr_with_scope(&call.receiver, scope, counts);
for arg in &call.args {
collect_binding_uses_in_expr_with_scope(arg, scope, counts);
}
}
}
}
fn count_binding_uses_in_lvalue_with_scope(
target: &AstLValue,
binding: AstBindingRef,
scope: BindingUseScope,
) -> usize {
match target {
AstLValue::Name(_) => 0,
AstLValue::FieldAccess(access) => {
count_binding_uses_in_expr_with_scope(&access.base, binding, scope)
}
AstLValue::IndexAccess(access) => {
count_binding_uses_in_expr_with_scope(&access.base, binding, scope)
+ count_binding_uses_in_expr_with_scope(&access.index, binding, scope)
}
}
}
fn collect_binding_uses_in_lvalue_with_scope(
target: &AstLValue,
scope: BindingUseScope,
counts: &mut BTreeMap<AstBindingRef, usize>,
) {
match target {
AstLValue::Name(_) => {}
AstLValue::FieldAccess(access) => {
collect_binding_uses_in_expr_with_scope(&access.base, scope, counts);
}
AstLValue::IndexAccess(access) => {
collect_binding_uses_in_expr_with_scope(&access.base, scope, counts);
collect_binding_uses_in_expr_with_scope(&access.index, scope, counts);
}
}
}
fn count_binding_uses_in_expr_with_scope(
expr: &AstExpr,
binding: AstBindingRef,
scope: BindingUseScope,
) -> usize {
match expr {
AstExpr::Var(name) if name_matches_binding(name, binding) => 1,
AstExpr::FieldAccess(access) => {
count_binding_uses_in_expr_with_scope(&access.base, binding, scope)
}
AstExpr::IndexAccess(access) => {
count_binding_uses_in_expr_with_scope(&access.base, binding, scope)
+ count_binding_uses_in_expr_with_scope(&access.index, binding, scope)
}
AstExpr::Unary(unary) => count_binding_uses_in_expr_with_scope(&unary.expr, binding, scope),
AstExpr::Binary(binary) => {
count_binding_uses_in_expr_with_scope(&binary.lhs, binding, scope)
+ count_binding_uses_in_expr_with_scope(&binary.rhs, binding, scope)
}
AstExpr::LogicalAnd(logical) | AstExpr::LogicalOr(logical) => {
count_binding_uses_in_expr_with_scope(&logical.lhs, binding, scope)
+ count_binding_uses_in_expr_with_scope(&logical.rhs, binding, scope)
}
AstExpr::Call(call) => {
count_binding_uses_in_call_with_scope(&AstCallKind::Call(call.clone()), binding, scope)
}
AstExpr::MethodCall(call) => count_binding_uses_in_call_with_scope(
&AstCallKind::MethodCall(call.clone()),
binding,
scope,
),
AstExpr::SingleValue(expr) => count_binding_uses_in_expr_with_scope(expr, binding, scope),
AstExpr::TableConstructor(table) => table
.fields
.iter()
.map(|field| match field {
AstTableField::Array(value) => {
count_binding_uses_in_expr_with_scope(value, binding, scope)
}
AstTableField::Record(record) => {
let key_count = if let AstTableKey::Expr(key) = &record.key {
count_binding_uses_in_expr_with_scope(key, binding, scope)
} else {
0
};
key_count + count_binding_uses_in_expr_with_scope(&record.value, binding, scope)
}
})
.sum(),
AstExpr::FunctionExpr(function) => {
if matches!(scope, BindingUseScope::IncludingNestedFunctions) {
count_binding_uses_in_block_with_scope(&function.body, binding, scope)
} else {
0
}
}
AstExpr::Nil
| AstExpr::Boolean(_)
| AstExpr::Integer(_)
| AstExpr::Number(_)
| AstExpr::String(_)
| AstExpr::Int64(_)
| AstExpr::UInt64(_)
| AstExpr::Complex { .. }
| AstExpr::Var(_)
| AstExpr::VarArg | AstExpr::Error(_) => 0,
}
}
fn collect_binding_uses_in_expr_with_scope(
expr: &AstExpr,
scope: BindingUseScope,
counts: &mut BTreeMap<AstBindingRef, usize>,
) {
match expr {
AstExpr::Var(name) => {
if let Some(binding) = binding_from_name_ref(name) {
*counts.entry(binding).or_insert(0) += 1;
}
}
AstExpr::FieldAccess(access) => {
collect_binding_uses_in_expr_with_scope(&access.base, scope, counts);
}
AstExpr::IndexAccess(access) => {
collect_binding_uses_in_expr_with_scope(&access.base, scope, counts);
collect_binding_uses_in_expr_with_scope(&access.index, scope, counts);
}
AstExpr::Unary(unary) => {
collect_binding_uses_in_expr_with_scope(&unary.expr, scope, counts);
}
AstExpr::Binary(binary) => {
collect_binding_uses_in_expr_with_scope(&binary.lhs, scope, counts);
collect_binding_uses_in_expr_with_scope(&binary.rhs, scope, counts);
}
AstExpr::LogicalAnd(logical) | AstExpr::LogicalOr(logical) => {
collect_binding_uses_in_expr_with_scope(&logical.lhs, scope, counts);
collect_binding_uses_in_expr_with_scope(&logical.rhs, scope, counts);
}
AstExpr::Call(call) => {
collect_binding_uses_in_expr_with_scope(&call.callee, scope, counts);
for arg in &call.args {
collect_binding_uses_in_expr_with_scope(arg, scope, counts);
}
}
AstExpr::MethodCall(call) => {
collect_binding_uses_in_expr_with_scope(&call.receiver, scope, counts);
for arg in &call.args {
collect_binding_uses_in_expr_with_scope(arg, scope, counts);
}
}
AstExpr::SingleValue(expr) => {
collect_binding_uses_in_expr_with_scope(expr, scope, counts);
}
AstExpr::TableConstructor(table) => {
for field in &table.fields {
match field {
AstTableField::Array(value) => {
collect_binding_uses_in_expr_with_scope(value, scope, counts);
}
AstTableField::Record(record) => {
if let AstTableKey::Expr(key) = &record.key {
collect_binding_uses_in_expr_with_scope(key, scope, counts);
}
collect_binding_uses_in_expr_with_scope(&record.value, scope, counts);
}
}
}
}
AstExpr::FunctionExpr(function) => {
if matches!(scope, BindingUseScope::IncludingNestedFunctions) {
collect_binding_uses_in_block_with_scope(&function.body, scope, counts);
}
}
AstExpr::Nil
| AstExpr::Boolean(_)
| AstExpr::Integer(_)
| AstExpr::Number(_)
| AstExpr::String(_)
| AstExpr::Int64(_)
| AstExpr::UInt64(_)
| AstExpr::Complex { .. }
| AstExpr::VarArg | AstExpr::Error(_) => {}
}
}
pub(super) fn stmt_references_any_binding(stmt: &AstStmt, bindings: &[AstLocalBinding]) -> bool {
match stmt {
AstStmt::LocalDecl(local_decl) => {
local_decl
.bindings
.iter()
.any(|binding| bindings.iter().any(|pending| pending.id == binding.id))
|| local_decl
.values
.iter()
.any(|value| expr_references_any_binding(value, bindings))
}
AstStmt::GlobalDecl(global_decl) => global_decl
.values
.iter()
.any(|value| expr_references_any_binding(value, bindings)),
AstStmt::Assign(assign) => {
assign
.targets
.iter()
.any(|target| lvalue_references_any_binding(target, bindings))
|| assign
.values
.iter()
.any(|value| expr_references_any_binding(value, bindings))
}
AstStmt::CallStmt(call_stmt) => call_references_any_binding(&call_stmt.call, bindings),
AstStmt::Return(ret) => ret
.values
.iter()
.any(|value| expr_references_any_binding(value, bindings)),
AstStmt::If(if_stmt) => {
expr_references_any_binding(&if_stmt.cond, bindings)
|| block_references_any_binding(&if_stmt.then_block, bindings)
|| if_stmt
.else_block
.as_ref()
.is_some_and(|block| block_references_any_binding(block, bindings))
}
AstStmt::While(while_stmt) => {
expr_references_any_binding(&while_stmt.cond, bindings)
|| block_references_any_binding(&while_stmt.body, bindings)
}
AstStmt::Repeat(repeat_stmt) => {
block_references_any_binding(&repeat_stmt.body, bindings)
|| expr_references_any_binding(&repeat_stmt.cond, bindings)
}
AstStmt::NumericFor(numeric_for) => {
bindings
.iter()
.any(|binding| binding.id == numeric_for.binding)
|| expr_references_any_binding(&numeric_for.start, bindings)
|| expr_references_any_binding(&numeric_for.limit, bindings)
|| expr_references_any_binding(&numeric_for.step, bindings)
|| block_references_any_binding(&numeric_for.body, bindings)
}
AstStmt::GenericFor(generic_for) => {
generic_for
.bindings
.iter()
.any(|binding| bindings.iter().any(|pending| pending.id == *binding))
|| generic_for
.iterator
.iter()
.any(|expr| expr_references_any_binding(expr, bindings))
|| block_references_any_binding(&generic_for.body, bindings)
}
AstStmt::DoBlock(block) => block_references_any_binding(block, bindings),
AstStmt::FunctionDecl(function_decl) => {
function_name_references_any_binding(&function_decl.target, bindings)
}
AstStmt::LocalFunctionDecl(function_decl) => bindings
.iter()
.any(|binding| binding.id == function_decl.name),
AstStmt::Break | AstStmt::Continue | AstStmt::Goto(_) | AstStmt::Label(_) | AstStmt::Error(_) => false,
}
}
pub(super) fn block_references_any_binding(block: &AstBlock, bindings: &[AstLocalBinding]) -> bool {
block
.stmts
.iter()
.any(|stmt| stmt_references_any_binding(stmt, bindings))
}
pub(super) fn expr_references_any_binding(expr: &AstExpr, bindings: &[AstLocalBinding]) -> bool {
match expr {
AstExpr::Var(name) => name_ref_matches_any_binding(name, bindings),
AstExpr::FieldAccess(access) => expr_references_any_binding(&access.base, bindings),
AstExpr::IndexAccess(access) => {
expr_references_any_binding(&access.base, bindings)
|| expr_references_any_binding(&access.index, bindings)
}
AstExpr::Unary(unary) => expr_references_any_binding(&unary.expr, bindings),
AstExpr::Binary(binary) => {
expr_references_any_binding(&binary.lhs, bindings)
|| expr_references_any_binding(&binary.rhs, bindings)
}
AstExpr::LogicalAnd(logical) | AstExpr::LogicalOr(logical) => {
expr_references_any_binding(&logical.lhs, bindings)
|| expr_references_any_binding(&logical.rhs, bindings)
}
AstExpr::Call(call) => {
expr_references_any_binding(&call.callee, bindings)
|| call
.args
.iter()
.any(|arg| expr_references_any_binding(arg, bindings))
}
AstExpr::MethodCall(call) => {
expr_references_any_binding(&call.receiver, bindings)
|| call
.args
.iter()
.any(|arg| expr_references_any_binding(arg, bindings))
}
AstExpr::SingleValue(expr) => expr_references_any_binding(expr, bindings),
AstExpr::TableConstructor(table) => table.fields.iter().any(|field| match field {
AstTableField::Array(value) => expr_references_any_binding(value, bindings),
AstTableField::Record(record) => {
let key_references_binding = match &record.key {
AstTableKey::Name(_) => false,
AstTableKey::Expr(expr) => expr_references_any_binding(expr, bindings),
};
key_references_binding || expr_references_any_binding(&record.value, bindings)
}
}),
AstExpr::FunctionExpr(_) => false,
AstExpr::Nil
| AstExpr::Boolean(_)
| AstExpr::Integer(_)
| AstExpr::Number(_)
| AstExpr::String(_)
| AstExpr::Int64(_)
| AstExpr::UInt64(_)
| AstExpr::Complex { .. }
| AstExpr::VarArg | AstExpr::Error(_) => false,
}
}
fn count_binding_mentions_in_lvalue(target: &AstLValue, binding: AstBindingRef) -> usize {
match target {
AstLValue::Name(name) if name_ref_matches_binding(name, binding) => 1,
AstLValue::Name(_) => 0,
AstLValue::FieldAccess(access) => count_binding_uses_in_expr(&access.base, binding),
AstLValue::IndexAccess(access) => {
count_binding_uses_in_expr(&access.base, binding)
+ count_binding_uses_in_expr(&access.index, binding)
}
}
}
fn call_references_any_binding(call: &AstCallKind, bindings: &[AstLocalBinding]) -> bool {
match call {
AstCallKind::Call(call) => {
expr_references_any_binding(&call.callee, bindings)
|| call
.args
.iter()
.any(|arg| expr_references_any_binding(arg, bindings))
}
AstCallKind::MethodCall(call) => {
expr_references_any_binding(&call.receiver, bindings)
|| call
.args
.iter()
.any(|arg| expr_references_any_binding(arg, bindings))
}
}
}
fn function_name_references_any_binding(
target: &super::super::common::AstFunctionName,
bindings: &[AstLocalBinding],
) -> bool {
let path = match target {
super::super::common::AstFunctionName::Plain(path) => path,
super::super::common::AstFunctionName::Method(path, _) => path,
};
name_ref_matches_any_binding(&path.root, bindings)
}
fn lvalue_references_any_binding(target: &AstLValue, bindings: &[AstLocalBinding]) -> bool {
match target {
AstLValue::Name(name) => name_ref_matches_any_binding(name, bindings),
AstLValue::FieldAccess(access) => expr_references_any_binding(&access.base, bindings),
AstLValue::IndexAccess(access) => {
expr_references_any_binding(&access.base, bindings)
|| expr_references_any_binding(&access.index, bindings)
}
}
}
fn name_ref_matches_any_binding(name: &AstNameRef, bindings: &[AstLocalBinding]) -> bool {
bindings
.iter()
.any(|binding| name_ref_matches_binding(name, binding.id))
}
fn name_ref_matches_binding(name: &AstNameRef, binding: AstBindingRef) -> bool {
name_matches_binding(name, binding)
}
fn binding_from_name_ref(name: &AstNameRef) -> Option<AstBindingRef> {
match name {
AstNameRef::Local(local) => Some(AstBindingRef::Local(*local)),
AstNameRef::Temp(temp) => Some(AstBindingRef::Temp(*temp)),
AstNameRef::SyntheticLocal(local) => Some(AstBindingRef::SyntheticLocal(*local)),
AstNameRef::Param(_) | AstNameRef::Upvalue(_) | AstNameRef::Global(_) => None,
}
}
#[cfg(test)]
mod tests;