///|
/// Move a single-use function value only within the execution frame that
/// originally creates it. A nested function or loop could allocate it again.
fn ValueBindings::inline_function_values(self : ValueBindings) -> Unit {
let program = self.program
let scopes : @hashset.HashSet[Int32] = @hashset.HashSet([])
for id in self.function_value_declarations {
let declaration = program.nodes[id]
guard declaration.kind is FunctionDeclaration else { continue }
let name = program.functions[declaration.a].name
guard name >= 0 else { continue }
let symbol = program.references[name].symbol
let entry = program.symbols[symbol]
if self.functions[symbol] &&
!self.has_writes[symbol] &&
entry.flags == 0 &&
entry.link < 0 &&
entry.use_count == 2 &&
!program.preserved_names.contains(entry.name) {
scopes.add(entry.scope)
}
}
guard scopes.length() > 0 else { return }
if scopes.contains(0) {
self.inline_function_body(program.root)
}
// Function bodies record their execution scope. Most contain no candidate
// declarations, so their expressions need no additional traversal.
for function in program.functions {
let body = program.nodes[function.body]
if body.kind is Block && scopes.contains(body.c) {
self.inline_function_body(function.body)
}
}
}
///|
fn ValueBindings::inline_function_body(
self : ValueBindings,
id : Int32,
) -> Unit {
let program = self.program
let body = program.nodes[id]
for index in 0.. Unit {
guard id >= 0 else { return }
let program = self.program
let statement = program.nodes[id]
match statement.kind {
VariableDeclaration =>
for index in 0..
self.inline_function_expression(statement.a, false)
If => {
self.inline_function_expression(statement.a, false)
self.inline_function_statement(statement.b)
self.inline_function_statement(statement.c)
}
_ => ()
}
}
///|
fn ValueBindings::inline_function_expression(
self : ValueBindings,
id : Int32,
primary_context : Bool,
) -> Unit {
guard id >= 0 else { return }
let program = self.program
let node = program.nodes[id]
match node.kind {
Identifier => {
let reference = program.references[node.a]
let symbol = reference.symbol
guard symbol >= 0 && self.functions[symbol] && !self.has_writes[symbol] else {
return
}
let entry = program.symbols[symbol]
guard entry.scope == reference.scope &&
entry.flags == 0 &&
entry.link < 0 &&
entry.use_count == 2 &&
!program.preserved_names.contains(entry.name) else {
return
}
let declaration_id = self.declarations[symbol]
guard declaration_id >= 0 else { return }
let declaration = program.nodes[declaration_id]
guard declaration.kind is FunctionDeclaration else { return }
let function = program.functions[declaration.a]
program.functions[declaration.a] = { ..function, name: -1, }
program.nodes[id] = if primary_context {
let value = program.node(FunctionExpression, declaration.a, 0, 0, 0)
Node::new(Parenthesized, value, 0, 0, 0)
} else {
Node::new(FunctionExpression, declaration.a, 0, 0, 0)
}
program.symbols[symbol] = { ..entry, flags: entry.flags | SYMBOL_ELIDED, }
program.nodes[declaration_id] = Node::new(Elided, 0, 0, 0, 0)
}
Parenthesized | EvaluatedValue | Spread =>
self.inline_function_expression(node.a, false)
Member | Index => self.inline_function_expression(node.a, true)
Unary if !(program.operators[node.a] is Delete) =>
self.inline_function_expression(node.b, false)
Binary | Logical => {
self.inline_function_expression(node.b, false)
self.inline_function_expression(node.c, false)
}
Assignment => self.inline_function_expression(node.c, false)
Conditional => {
self.inline_function_expression(node.a, false)
self.inline_function_expression(node.b, false)
self.inline_function_expression(node.c, false)
}
Call | New => {
self.inline_function_expression(node.a, true)
if node.b >= 0 {
self.inline_function_list(node.b, node.c)
}
}
TaggedTemplate => self.inline_function_expression(node.a, true)
ArrayLiteral | Sequence => self.inline_function_list(node.a, node.b)
ObjectLiteral =>
for index in 0.. ()
}
}
///|
fn ValueBindings::inline_function_list(
self : ValueBindings,
offset : Int32,
count : Int32,
) -> Unit {
for index in 0.. Unit {
let program = self.program
let call = program.nodes[id]
guard call.c <= 1 else { return }
let callee = program.nodes[program.unwrapped_expression(call.a)]
guard callee.kind is Identifier else { return }
let reference = program.references[callee.a]
let symbol = reference.symbol
guard symbol >= 0 && !self.has_writes[symbol] else { return }
guard program.symbols[symbol].scope > 0 || rename_top_level else { return }
let declaration_id = self.declarations[symbol]
guard declaration_id >= 0 else { return }
let declaration = program.nodes[declaration_id]
let function_id = match declaration.kind {
FunctionDeclaration if self.functions[symbol] => declaration.a
VariableDeclaration if declaration.a == DECLARATION_CONST &&
declaration.c == 1 &&
self.stable_prefix(call.a, id, reference.scope) => {
let binding = program.nodes[program.edges[declaration.b]]
guard binding.b >= 0 else { return }
let value = program.nodes[program.unwrapped_expression(binding.b)]
guard value.kind is (ArrowExpression | FunctionExpression) else { return }
value.a
}
_ => return
}
let function = program.functions[function_id]
guard (function.flags & (FUNCTION_ASYNC | FUNCTION_GENERATOR)) == 0 else {
return
}
for index in 0..= 0 else { return }
statement.a
}
let value = program.nodes[program.unwrapped_expression(result)]
// A sloppy call used as a write target is parsed successfully and throws
// only when evaluated. Replacing that Call can turn it into a syntax error.
if self.call_write_targets.length() > 0 {
for target in self.call_write_targets {
guard target != id else { return }
}
}
let replacement = match value.kind {
NumberLiteral | BooleanLiteral | NullLiteral => {
guard call.c == 0 else { return }
value
}
StringLiteral => {
guard call.c == 0 else { return }
// The call could have been the statement that ended a directive prefix.
// Give it its own literal node instead of sharing parenthesized nodes
// from the function body with later transformations at the call site.
let literal = program.node(
StringLiteral,
value.a,
value.b,
value.c,
value.d,
)
Node::new(Parenthesized, literal, 0, 0, 0)
}
ArrayLiteral if value.b == 0 => {
guard call.c == 0 else { return }
value
}
Identifier => {
guard function.parameters_count > 0 && call.c == 1 else { return }
let parameter = program.identifier_symbol(
program.edges[function.parameters_offset],
)
guard parameter >= 0 && program.references[value.a].symbol == parameter else {
return
}
// Sloppy functions can repeat a parameter name. A later occurrence
// overwrites the first argument, even when that later argument is absent.
for index in 1.. return
}
program.nodes[id] = replacement
let entry = program.symbols[symbol]
let remaining = entry.use_count - 1
let flags = if remaining == 1 {
entry.flags | SYMBOL_ELIDED
} else {
entry.flags
}
program.symbols[symbol] = { ..entry, use_count: remaining, flags, }
if remaining == 1 {
program.nodes[declaration_id] = Node::new(Elided, 0, 0, 0, 0)
}
}