///|
priv struct InternalCalls {
program : Program
functions : Array[Int32]
direct_uses : Array[Int32]
argument_observers : @hashset.HashSet[Int32]
trailing_undefined_calls : Array[Int32]
is_module : Bool
rename_top_level : Bool
strict_top_level : Bool
}
///|
/// A private binding is callable only when its declaration and direct calls
/// account for every reference. Writes, exports and escaping function values
/// therefore exclude it without a separate assignment analysis.
fn InternalCalls::new(
program : Program,
rename_top_level : Bool,
) -> InternalCalls {
let is_module = program.modules.length() > 0
{
program,
functions: Array::make(program.symbols.length(), -1),
direct_uses: Array::make(program.symbols.length(), 0),
argument_observers: @hashset.HashSet([]),
trailing_undefined_calls: [],
is_module,
rename_top_level,
strict_top_level: is_module || program.has_strict_directive(program.root),
}
}
///|
fn InternalCalls::collect_node(
self : InternalCalls,
id : Int32,
node : Node,
) -> Unit {
let program = self.program
match node.kind {
FunctionDeclaration => {
let declaration = program.functions[node.a]
guard declaration.name >= 0 else { return }
let symbol = program.references[declaration.name].symbol
let scope = program.symbols[symbol].scope
// Block functions have additional Annex B binding behavior. Script
// globals are private only under the existing top-level-mangle contract.
guard program.scopes[scope].kind == SCOPE_FUNCTION ||
(scope == 0 && (self.is_module || self.rename_top_level)) else {
return
}
self.register_function(symbol, node.a)
}
VariableDeclaration if node.a == DECLARATION_CONST =>
for index in 0..= 0 else {
continue
}
let value = program.nodes[program.unwrapped_expression(declaration.b)]
guard value.kind is (FunctionExpression | ArrowExpression) else {
continue
}
// A named expression has another binding for its own function object.
// Checking only the outer const would miss innerName.arguments reads.
guard program.functions[value.a].name < 0 else { continue }
self.register_function(program.references[pattern.a].symbol, value.a)
}
Identifier if program.references[node.a].name == NAME_ARGUMENTS =>
self.observe_arguments(program.references[node.a].scope)
Call => self.collect_call(id, node)
_ => ()
}
}
///|
fn InternalCalls::finish(self : InternalCalls) -> Unit {
let program = self.program
for statement in program.modules {
guard statement is ExportDefault(body) else { continue }
let declaration = program.nodes[body]
guard declaration.kind is FunctionDeclaration else { continue }
let name = program.functions[declaration.a].name
guard name >= 0 else { continue }
self.functions[program.references[name].symbol] = -1
}
for id in self.trailing_undefined_calls {
self.trim_call(id)
}
}
///|
fn InternalCalls::register_function(
self : InternalCalls,
symbol : Int32,
function_id : Int32,
) -> Unit {
let program = self.program
let function = program.functions[function_id]
// A sloppy callee can expose its argument count through an external
// function's .caller.arguments, without an arguments reference in this file.
guard self.strict_top_level ||
(function.flags & FUNCTION_ARROW) != 0 ||
program.has_strict_directive(function.body) else {
return
}
let binding = program.symbols[symbol]
guard (binding.flags & SYMBOL_PRESERVED) == 0 &&
!self.program.preserved_names.contains(binding.name) else {
return
}
self.functions[symbol] = function_id
}
///|
fn Program::has_strict_directive(self : Program, id : Int32) -> Bool {
let body = self.nodes[id]
guard body.kind is Block else { return false }
for index in 0.. Unit {
let program = self.program
let callee = program.nodes[program.unwrapped_expression(call.a)]
guard callee.kind is Identifier else { return }
let symbol = program.references[callee.a].symbol
guard symbol >= 0 else { return }
self.direct_uses[symbol] += 1
guard call.c > 0 &&
program.is_undefined_value(program.edges[call.b + call.c - 1]) else {
return
}
self.trailing_undefined_calls.push(id)
}
///|
/// Ordinary functions own arguments; nested arrows inherit that same object.
fn InternalCalls::observe_arguments(
self : InternalCalls,
scope : Int32,
) -> Unit {
for current = scope {
guard current >= 0 else { break }
let environment = self.program.scopes[current]
if environment.kind == SCOPE_PARAMETERS {
self.argument_observers.add(current)
break
}
continue environment.parent
}
}
///|
fn InternalCalls::ignores_argument_count(
self : InternalCalls,
function_id : Int32,
) -> Bool {
let program = self.program
let function = program.functions[function_id]
let is_arrow = (function.flags & FUNCTION_ARROW) != 0
// MoonBit uses simple parameters. Keep destructuring/rest outside this rule;
// simple defaults still receive undefined when their argument is omitted.
let mut parameter_scope = -1
for index in 0..= 0 && !self.argument_observers.contains(parameter_scope))
}
///|
fn InternalCalls::trim_call(self : InternalCalls, id : Int32) -> Unit {
let program = self.program
let call = program.nodes[id]
guard call.kind is Call else { return }
let callee = program.nodes[program.unwrapped_expression(call.a)]
guard callee.kind is Identifier else { return }
let symbol = program.references[callee.a].symbol
let function_id = self.functions[symbol]
guard function_id >= 0 &&
program.symbols[symbol].use_count == self.direct_uses[symbol] + 1 &&
self.ignores_argument_count(function_id) else {
return
}
for index in 0.. 0 &&
program.is_undefined_value(program.edges[call.b + remaining - 1]) else {
break remaining
}
continue remaining - 1
}
program.nodes[id] = { ..call, c: count, }
}