LibJS/Rust: Cargo fmt on all source files

This commit is contained in:
xnacly 2026-02-24 10:40:18 +01:00 committed by Andreas Kling
parent 809f81704c
commit e897b77e83
13 changed files with 4046 additions and 1650 deletions

View file

@ -160,7 +160,11 @@ impl FunctionTable {
self.collect_from_statement(child, result);
}
}
StatementKind::If { test, consequent, alternate } => {
StatementKind::If {
test,
consequent,
alternate,
} => {
self.collect_from_expression(test, result);
self.collect_from_statement(consequent, result);
if let Some(alt) = alternate {
@ -175,15 +179,24 @@ impl FunctionTable {
self.collect_from_statement(body, result);
self.collect_from_expression(test, result);
}
StatementKind::For { init, test, update, body } => {
StatementKind::For {
init,
test,
update,
body,
} => {
if let Some(init) = init {
match init {
ForInit::Expression(expr) => self.collect_from_expression(expr, result),
ForInit::Declaration(decl) => self.collect_from_statement(decl, result),
}
}
if let Some(test) = test { self.collect_from_expression(test, result); }
if let Some(update) = update { self.collect_from_expression(update, result); }
if let Some(test) = test {
self.collect_from_expression(test, result);
}
if let Some(update) = update {
self.collect_from_expression(update, result);
}
self.collect_from_statement(body, result);
}
StatementKind::ForInOf { lhs, rhs, body, .. } => {
@ -214,7 +227,9 @@ impl FunctionTable {
self.collect_from_statement(item, result);
}
StatementKind::Return(arg) => {
if let Some(expr) = arg { self.collect_from_expression(expr, result); }
if let Some(expr) = arg {
self.collect_from_expression(expr, result);
}
}
StatementKind::Throw(expr) => {
self.collect_from_expression(expr, result);
@ -276,8 +291,7 @@ impl FunctionTable {
ExpressionKind::Class(class_data) => {
self.collect_from_class(class_data, result);
}
ExpressionKind::Binary { lhs, rhs, .. }
| ExpressionKind::Logical { lhs, rhs, .. } => {
ExpressionKind::Binary { lhs, rhs, .. } | ExpressionKind::Logical { lhs, rhs, .. } => {
self.collect_from_expression(lhs, result);
self.collect_from_expression(rhs, result);
}
@ -294,15 +308,23 @@ impl FunctionTable {
}
self.collect_from_expression(rhs, result);
}
ExpressionKind::Conditional { test, consequent, alternate } => {
ExpressionKind::Conditional {
test,
consequent,
alternate,
} => {
self.collect_from_expression(test, result);
self.collect_from_expression(consequent, result);
self.collect_from_expression(alternate, result);
}
ExpressionKind::Sequence(exprs) => {
for expr in exprs { self.collect_from_expression(expr, result); }
for expr in exprs {
self.collect_from_expression(expr, result);
}
}
ExpressionKind::Member { object, property, .. } => {
ExpressionKind::Member {
object, property, ..
} => {
self.collect_from_expression(object, result);
self.collect_from_expression(property, result);
}
@ -311,7 +333,9 @@ impl FunctionTable {
for reference in references {
match reference {
OptionalChainReference::Call { arguments, .. } => {
for arg in arguments { self.collect_from_expression(&arg.value, result); }
for arg in arguments {
self.collect_from_expression(&arg.value, result);
}
}
OptionalChainReference::ComputedReference { expression, .. } => {
self.collect_from_expression(expression, result);
@ -323,10 +347,14 @@ impl FunctionTable {
}
ExpressionKind::Call(data) | ExpressionKind::New(data) => {
self.collect_from_expression(&data.callee, result);
for arg in &data.arguments { self.collect_from_expression(&arg.value, result); }
for arg in &data.arguments {
self.collect_from_expression(&arg.value, result);
}
}
ExpressionKind::SuperCall(data) => {
for arg in &data.arguments { self.collect_from_expression(&arg.value, result); }
for arg in &data.arguments {
self.collect_from_expression(&arg.value, result);
}
}
ExpressionKind::Spread(expr) | ExpressionKind::Await(expr) => {
self.collect_from_expression(expr, result);
@ -345,18 +373,27 @@ impl FunctionTable {
}
}
ExpressionKind::TemplateLiteral(data) => {
for expr in &data.expressions { self.collect_from_expression(expr, result); }
for expr in &data.expressions {
self.collect_from_expression(expr, result);
}
}
ExpressionKind::TaggedTemplateLiteral { tag, template_literal } => {
ExpressionKind::TaggedTemplateLiteral {
tag,
template_literal,
} => {
self.collect_from_expression(tag, result);
self.collect_from_expression(template_literal, result);
}
ExpressionKind::Yield { argument, .. } => {
if let Some(expr) = argument { self.collect_from_expression(expr, result); }
if let Some(expr) = argument {
self.collect_from_expression(expr, result);
}
}
ExpressionKind::ImportCall { specifier, options } => {
self.collect_from_expression(specifier, result);
if let Some(opts) = options { self.collect_from_expression(opts, result); }
if let Some(opts) = options {
self.collect_from_expression(opts, result);
}
}
ExpressionKind::NumericLiteral(_)
| ExpressionKind::StringLiteral(_)
@ -386,7 +423,9 @@ impl FunctionTable {
self.collect_from_expression(key, result);
self.collect_from_expression(function, result);
}
ClassElement::Field { key, initializer, .. } => {
ClassElement::Field {
key, initializer, ..
} => {
self.collect_from_expression(key, result);
if let Some(init) = initializer {
self.collect_from_expression(init, result);
@ -406,8 +445,12 @@ impl FunctionTable {
}
if let Some(ref alias) = entry.alias {
match alias {
BindingEntryAlias::BindingPattern(sub) => self.collect_from_pattern(sub, result),
BindingEntryAlias::MemberExpression(expr) => self.collect_from_expression(expr, result),
BindingEntryAlias::BindingPattern(sub) => {
self.collect_from_pattern(sub, result)
}
BindingEntryAlias::MemberExpression(expr) => {
self.collect_from_expression(expr, result)
}
BindingEntryAlias::Identifier(_) => {}
}
}
@ -417,7 +460,11 @@ impl FunctionTable {
}
}
fn collect_from_target(&mut self, target: &VariableDeclaratorTarget, result: &mut FunctionTable) {
fn collect_from_target(
&mut self,
target: &VariableDeclaratorTarget,
result: &mut FunctionTable,
) {
if let VariableDeclaratorTarget::BindingPattern(pat) = target {
self.collect_from_pattern(pat, result);
}

View file

@ -80,7 +80,11 @@ fn print_node(state: &DumpState, text: &str) {
let line = if state.is_root {
text.to_string()
} else {
let connector = if state.is_last { "\u{2514}\u{2500} " } else { "\u{251c}\u{2500} " };
let connector = if state.is_last {
"\u{2514}\u{2500} "
} else {
"\u{251c}\u{2500} "
};
if state.use_color {
format!("{}{}{}{}{}", state.prefix, DIM, connector, RESET, text)
} else {
@ -98,11 +102,7 @@ fn child_prefix(state: &DumpState) -> String {
if state.is_root {
return String::new();
}
let branch = if state.is_last {
" "
} else {
"\u{2502} "
};
let branch = if state.is_last { " " } else { "\u{2502} " };
if state.use_color {
format!("{}{}{}{}", state.prefix, DIM, branch, RESET)
} else {
@ -146,7 +146,7 @@ fn color_string(state: &DumpState, value: &str) -> String {
if !state.use_color {
return format!("\"{}\"", value);
}
format!("{}\"{}\"{}",GREEN, value, RESET)
format!("{}\"{}\"{}", GREEN, value, RESET)
}
fn color_string_utf16(state: &DumpState, value: &[u16]) -> String {
@ -240,7 +240,9 @@ fn format_f64(value: f64) -> String {
}
let mut buffer = [0u8; 128];
let length = unsafe { rust_format_double(value, buffer.as_mut_ptr(), buffer.len()) };
std::str::from_utf8(&buffer[..length]).expect("C++ produced invalid UTF-8").to_string()
std::str::from_utf8(&buffer[..length])
.expect("C++ produced invalid UTF-8")
.to_string()
}
op_to_string!(binary_op_to_string, BinaryOp, {
@ -357,12 +359,12 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
if s.children.len() == 1
&& matches!(
s.children[0].inner,
StatementKind::For { .. }
| StatementKind::ForInOf { .. }
) {
dump_statement(&s.children[0], state);
return;
}
StatementKind::For { .. } | StatementKind::ForInOf { .. }
)
{
dump_statement(&s.children[0], state);
return;
}
dump_scope_node("BlockStatement", &s, &statement.range, state);
}
@ -431,8 +433,12 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
let init_state = child_state(state, false);
print_node(&init_state, &color_label(state, "init"));
match init {
ForInit::Expression(expr) => dump_expression(expr, &child_state(&init_state, true)),
ForInit::Declaration(decl) => dump_statement(decl, &child_state(&init_state, true)),
ForInit::Expression(expr) => {
dump_expression(expr, &child_state(&init_state, true))
}
ForInit::Declaration(decl) => {
dump_statement(decl, &child_state(&init_state, true))
}
}
}
if let Some(test) = test {
@ -444,7 +450,12 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
dump_labeled_statement("body", body, true, state);
}
StatementKind::ForInOf { kind, lhs, rhs, body } => {
StatementKind::ForInOf {
kind,
lhs,
rhs,
body,
} => {
let name = match kind {
ForInOfKind::ForIn => "ForInStatement",
ForInOfKind::ForOf => "ForOfStatement",
@ -460,7 +471,12 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
StatementKind::Switch(data) => {
dump_node!(state, "SwitchStatement", &statement.range);
dump_labeled_expression("discriminant", &data.discriminant, data.cases.is_empty(), state);
dump_labeled_expression(
"discriminant",
&data.discriminant,
data.cases.is_empty(),
state,
);
for (i, case) in data.cases.iter().enumerate() {
dump_switch_case(case, &child_state(state, i == data.cases.len() - 1), state);
}
@ -473,7 +489,12 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
}
StatementKind::Labelled { label, item } => {
dump_node!(state, "LabelledStatement", &statement.range, color_string_utf16(state, label));
dump_node!(
state,
"LabelledStatement",
&statement.range,
color_string_utf16(state, label)
);
dump_statement(item, &child_state(state, true));
}
@ -513,34 +534,65 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
}
StatementKind::VariableDeclaration { kind, declarations } => {
dump_node!(state, "VariableDeclaration", &statement.range, color_op(state, declaration_kind_to_string(*kind)));
dump_node!(
state,
"VariableDeclaration",
&statement.range,
color_op(state, declaration_kind_to_string(*kind))
);
for (i, declaration) in declarations.iter().enumerate() {
dump_variable_declarator(declaration, &child_state(state, i == declarations.len() - 1), state);
dump_variable_declarator(
declaration,
&child_state(state, i == declarations.len() - 1),
state,
);
}
}
StatementKind::UsingDeclaration { declarations } => {
dump_node!(state, "UsingDeclaration", &statement.range);
for (i, declaration) in declarations.iter().enumerate() {
dump_variable_declarator(declaration, &child_state(state, i == declarations.len() - 1), state);
dump_variable_declarator(
declaration,
&child_state(state, i == declarations.len() - 1),
state,
);
}
}
StatementKind::FunctionDeclaration { function_id, .. } => {
let function_data = state.function_table().get(*function_id);
dump_function(function_data, "FunctionDeclaration", &statement.range, state);
dump_function(
function_data,
"FunctionDeclaration",
&statement.range,
state,
);
}
StatementKind::ClassDeclaration(class_data) => {
dump_node!(state, "ClassDeclaration", &statement.range);
dump_class(class_data, &statement.range, &child_state(state, true), state);
dump_class(
class_data,
&statement.range,
&child_state(state, true),
state,
);
}
StatementKind::Import(data) => {
let module_spec = utf16_to_string(&data.module_request.module_specifier);
let assert_clauses = format_assert_clauses(&data.module_request);
dump_node!(state, "ImportStatement", &statement.range,
format!("from {}{}", color_string(state, &module_spec), assert_clauses));
dump_node!(
state,
"ImportStatement",
&statement.range,
format!(
"from {}{}",
color_string(state, &module_spec),
assert_clauses
)
);
if !data.entries.is_empty() {
for (i, entry) in data.entries.iter().enumerate() {
let import_name = match &entry.import_name {
@ -583,10 +635,8 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
None => "null".to_string(),
}
};
let mut desc = format!(
"ExportName: {}, LocalName: {}",
export_name, local_name
);
let mut desc =
format!("ExportName: {}, LocalName: {}", export_name, local_name);
if let Some(ref module_request) = data.module_request {
desc.push_str(&format!(
", ModuleRequest: {}{}",
@ -602,10 +652,7 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
}
if let Some(ref statement) = data.statement {
print_node(
&child_state(state, true),
&color_label(state, "statement"),
);
print_node(&child_state(state, true), &color_label(state, "statement"));
let inner_state = &child_state(&child_state(state, true), true);
// For `export default <expression>`, the C++ AST stores the
// expression directly without an ExpressionStatement wrapper.
@ -635,15 +682,30 @@ fn dump_statement(statement: &Statement, state: &DumpState) {
fn dump_expression(expression: &Expression, state: &DumpState) {
match &expression.inner {
ExpressionKind::NumericLiteral(value) => {
dump_node!(state, "NumericLiteral", &expression.range, color_number_f64(state, *value));
dump_node!(
state,
"NumericLiteral",
&expression.range,
color_number_f64(state, *value)
);
}
ExpressionKind::StringLiteral(value) => {
dump_node!(state, "StringLiteral", &expression.range, color_string_utf16(state, value));
dump_node!(
state,
"StringLiteral",
&expression.range,
color_string_utf16(state, value)
);
}
ExpressionKind::BooleanLiteral(value) => {
dump_node!(state, "BooleanLiteral", &expression.range, color_number_bool(state, *value));
dump_node!(
state,
"BooleanLiteral",
&expression.range,
color_number_bool(state, *value)
);
}
ExpressionKind::NullLiteral => {
@ -651,13 +713,23 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
}
ExpressionKind::BigIntLiteral(value) => {
dump_node!(state, "BigIntLiteral", &expression.range, color_number_str(state, value));
dump_node!(
state,
"BigIntLiteral",
&expression.range,
color_number_str(state, value)
);
}
ExpressionKind::RegExpLiteral(data) => {
let pattern = utf16_to_string(&data.pattern);
let flags = utf16_to_string(&data.flags);
dump_node!(state, "RegExpLiteral", &expression.range, format!("/{}/{}", pattern, flags));
dump_node!(
state,
"RegExpLiteral",
&expression.range,
format!("/{}/{}", pattern, flags)
);
}
ExpressionKind::Identifier(ident) => {
@ -665,23 +737,43 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
}
ExpressionKind::PrivateIdentifier(ident) => {
dump_node!(state, "PrivateIdentifier", &expression.range, color_string_utf16(state, &ident.name));
dump_node!(
state,
"PrivateIdentifier",
&expression.range,
color_string_utf16(state, &ident.name)
);
}
ExpressionKind::Binary { op, lhs, rhs } => {
dump_node!(state, "BinaryExpression", &expression.range, color_op(state, binary_op_to_string(*op)));
dump_node!(
state,
"BinaryExpression",
&expression.range,
color_op(state, binary_op_to_string(*op))
);
dump_expression(lhs, &child_state(state, false));
dump_expression(rhs, &child_state(state, true));
}
ExpressionKind::Logical { op, lhs, rhs } => {
dump_node!(state, "LogicalExpression", &expression.range, color_op(state, logical_op_to_string(*op)));
dump_node!(
state,
"LogicalExpression",
&expression.range,
color_op(state, logical_op_to_string(*op))
);
dump_expression(lhs, &child_state(state, false));
dump_expression(rhs, &child_state(state, true));
}
ExpressionKind::Unary { op, operand } => {
dump_node!(state, "UnaryExpression", &expression.range, color_op(state, unary_op_to_string(*op)));
dump_node!(
state,
"UnaryExpression",
&expression.range,
color_op(state, unary_op_to_string(*op))
);
dump_expression(operand, &child_state(state, true));
}
@ -691,12 +783,22 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
prefixed,
} => {
let prefix_str = if *prefixed { "prefix" } else { "postfix" };
dump_node!(state, "UpdateExpression", &expression.range, format!("({}, {})", update_op_to_string(*op), prefix_str));
dump_node!(
state,
"UpdateExpression",
&expression.range,
format!("({}, {})", update_op_to_string(*op), prefix_str)
);
dump_expression(argument, &child_state(state, true));
}
ExpressionKind::Assignment { op, lhs, rhs } => {
dump_node!(state, "AssignmentExpression", &expression.range, color_op(state, assignment_op_to_string(*op)));
dump_node!(
state,
"AssignmentExpression",
&expression.range,
color_op(state, assignment_op_to_string(*op))
);
match lhs {
AssignmentLhs::Expression(expression) => {
dump_expression(expression, &child_state(state, false));
@ -731,7 +833,11 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
property,
computed,
} => {
let name = if *computed { "MemberExpression [computed]" } else { "MemberExpression" };
let name = if *computed {
"MemberExpression [computed]"
} else {
"MemberExpression"
};
dump_node!(state, name, &expression.range);
dump_expression(object, &child_state(state, false));
dump_expression(property, &child_state(state, true));
@ -764,7 +870,11 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
&ref_state,
&format!("MemberReference({})", optional_mode_str(*mode)),
);
dump_identifier(identifier, &identifier.range, &child_state(&ref_state, true));
dump_identifier(
identifier,
&identifier.range,
&child_state(&ref_state, true),
);
}
OptionalChainReference::PrivateMemberReference {
private_identifier,
@ -792,7 +902,10 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
dump_node!(state, "CallExpression", &expression.range);
dump_expression(&data.callee, &child_state(state, data.arguments.is_empty()));
for (i, argument) in data.arguments.iter().enumerate() {
dump_expression(&argument.value, &child_state(state, i == data.arguments.len() - 1));
dump_expression(
&argument.value,
&child_state(state, i == data.arguments.len() - 1),
);
}
}
@ -800,14 +913,20 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
dump_node!(state, "NewExpression", &expression.range);
dump_expression(&data.callee, &child_state(state, data.arguments.is_empty()));
for (i, argument) in data.arguments.iter().enumerate() {
dump_expression(&argument.value, &child_state(state, i == data.arguments.len() - 1));
dump_expression(
&argument.value,
&child_state(state, i == data.arguments.len() - 1),
);
}
}
ExpressionKind::SuperCall(data) => {
dump_node!(state, "SuperCall", &expression.range);
for (i, argument) in data.arguments.iter().enumerate() {
dump_expression(&argument.value, &child_state(state, i == data.arguments.len() - 1));
dump_expression(
&argument.value,
&child_state(state, i == data.arguments.len() - 1),
);
}
}
@ -826,7 +945,12 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
ExpressionKind::Function(function_id) => {
let function_data = state.function_table().get(*function_id);
dump_function(function_data, "FunctionExpression", &expression.range, state);
dump_function(
function_data,
"FunctionExpression",
&expression.range,
state,
);
}
ExpressionKind::Class(class_data) => {
@ -927,7 +1051,10 @@ fn dump_identifier(ident: &Identifier, range: &SourceRange, state: &DumpState) {
} else {
"variable"
};
desc.push_str(&format!(" {}", color_local(state, kind, ident.local_index.get())));
desc.push_str(&format!(
" {}",
color_local(state, kind, ident.local_index.get())
));
} else if ident.is_global.get() {
desc.push_str(&format!(" {}", color_global(state)));
}
@ -948,12 +1075,7 @@ fn dump_identifier(ident: &Identifier, range: &SourceRange, state: &DumpState) {
// Helper dumpers
// ============================================================================
fn dump_scope_node(
class_name: &str,
scope: &ScopeData,
range: &SourceRange,
state: &DumpState,
) {
fn dump_scope_node(class_name: &str, scope: &ScopeData, range: &SourceRange, state: &DumpState) {
dump_node!(state, class_name, range);
for (i, child) in scope.children.iter().enumerate() {
dump_statement(child, &child_state(state, i == scope.children.len() - 1));
@ -1001,10 +1123,7 @@ fn dump_function(
));
}
if function_data.parsing_insights.might_need_arguments_object {
desc.push_str(&format!(
" {}",
color_flag(state, "might-need-arguments")
));
desc.push_str(&format!(" {}", color_flag(state, "might-need-arguments")));
}
desc.push_str(&format_position(state, range));
print_node(state, &desc);
@ -1016,13 +1135,18 @@ fn dump_function(
);
let parameters_state = child_state(state, false);
for (i, parameter) in function_data.parameters.iter().enumerate() {
let parameter_state = child_state(&parameters_state, i == function_data.parameters.len() - 1);
let parameter_state =
child_state(&parameters_state, i == function_data.parameters.len() - 1);
let has_default = parameter.default_value.is_some();
if parameter.is_rest {
print_node(&parameter_state, &color_label(state, "rest"));
match &parameter.binding {
FunctionParameterBinding::Identifier(ident) => {
dump_identifier(ident, &ident.range, &child_state(&parameter_state, !has_default));
dump_identifier(
ident,
&ident.range,
&child_state(&parameter_state, !has_default),
);
}
FunctionParameterBinding::BindingPattern(pattern) => {
dump_binding_pattern(
@ -1038,7 +1162,10 @@ fn dump_function(
dump_identifier(
ident,
&ident.range,
&child_state(&parameters_state, i == function_data.parameters.len() - 1),
&child_state(
&parameters_state,
i == function_data.parameters.len() - 1,
),
);
}
FunctionParameterBinding::BindingPattern(pattern) => {
@ -1059,21 +1186,29 @@ fn dump_function(
&color_label(state, "default"),
);
dump_expression(
parameter.default_value.as_ref().expect("guarded by is_some check"),
parameter
.default_value
.as_ref()
.expect("guarded by is_some check"),
&child_state(&child_state(&parameter_state, true), true),
);
}
}
}
print_node(
&child_state(state, true),
&color_label(state, "body"),
print_node(&child_state(state, true), &color_label(state, "body"));
dump_statement(
&function_data.body,
&child_state(&child_state(state, true), true),
);
dump_statement(&function_data.body, &child_state(&child_state(state, true), true));
}
fn dump_class(class_data: &ClassData, range: &SourceRange, state: &DumpState, root_state: &DumpState) {
fn dump_class(
class_data: &ClassData,
range: &SourceRange,
state: &DumpState,
root_state: &DumpState,
) {
let name_str = match &class_data.name {
Some(ident) => utf16_to_string(&ident.name),
None => String::new(),
@ -1096,7 +1231,10 @@ fn dump_class(class_data: &ClassData, range: &SourceRange, state: &DumpState, ro
&color_label(root_state, "super class"),
);
dump_expression(
class_data.super_class.as_ref().expect("guarded by has_super_class check"),
class_data
.super_class
.as_ref()
.expect("guarded by has_super_class check"),
&child_state(&child_state(state, false), true),
);
}
@ -1121,7 +1259,10 @@ fn dump_class(class_data: &ClassData, range: &SourceRange, state: &DumpState, ro
dump_class_element(
&element.inner,
&element.range,
&child_state(&child_state(state, true), i == class_data.elements.len() - 1),
&child_state(
&child_state(state, true),
i == class_data.elements.len() - 1,
),
root_state,
);
}
@ -1190,11 +1331,7 @@ fn dump_class_element(
}
}
fn dump_binding_pattern(
pattern: &BindingPattern,
state: &DumpState,
root_state: &DumpState,
) {
fn dump_binding_pattern(pattern: &BindingPattern, state: &DumpState, root_state: &DumpState) {
let kind_str = match pattern.kind {
BindingPatternKind::Array => "array",
BindingPatternKind::Object => "object",
@ -1268,29 +1405,20 @@ fn dump_binding_pattern(
dump_identifier(
ident,
&ident.range,
&child_state(
&child_state(&entry_state, !has_initializer),
true,
),
&child_state(&child_state(&entry_state, !has_initializer), true),
);
}
BindingEntryAlias::BindingPattern(sub) => {
dump_binding_pattern(
sub,
&child_state(
&child_state(&entry_state, !has_initializer),
true,
),
&child_state(&child_state(&entry_state, !has_initializer), true),
root_state,
);
}
BindingEntryAlias::MemberExpression(expression) => {
dump_expression(
expression,
&child_state(
&child_state(&entry_state, !has_initializer),
true,
),
&child_state(&child_state(&entry_state, !has_initializer), true),
);
}
}
@ -1302,7 +1430,10 @@ fn dump_binding_pattern(
&color_label(root_state, "initializer"),
);
dump_expression(
entry.initializer.as_ref().expect("guarded by is_some check"),
entry
.initializer
.as_ref()
.expect("guarded by is_some check"),
&child_state(&child_state(&entry_state, true), true),
);
}
@ -1310,10 +1441,7 @@ fn dump_binding_pattern(
}
fn is_elision(entry: &BindingEntry) -> bool {
entry.name.is_none()
&& entry.alias.is_none()
&& entry.initializer.is_none()
&& !entry.is_rest
entry.name.is_none() && entry.alias.is_none() && entry.initializer.is_none() && !entry.is_rest
}
fn dump_variable_declarator(
@ -1343,11 +1471,7 @@ fn dump_variable_declarator(
}
}
fn dump_object_property(
property: &ObjectProperty,
state: &DumpState,
root_state: &DumpState,
) {
fn dump_object_property(property: &ObjectProperty, state: &DumpState, root_state: &DumpState) {
if property.property_type == ObjectPropertyType::Spread {
print_node(
state,
@ -1377,11 +1501,7 @@ fn dump_object_property(
}
}
fn dump_catch_clause(
clause: &CatchClause,
state: &DumpState,
root_state: &DumpState,
) {
fn dump_catch_clause(clause: &CatchClause, state: &DumpState, root_state: &DumpState) {
print_node(
state,
&format!(
@ -1419,11 +1539,7 @@ fn dump_catch_clause(
dump_statement(&clause.body, &child_state(state, true));
}
fn dump_switch_case(
case: &SwitchCase,
state: &DumpState,
root_state: &DumpState,
) {
fn dump_switch_case(case: &SwitchCase, state: &DumpState, root_state: &DumpState) {
if let Some(ref test) = case.test {
print_node(
state,
@ -1433,10 +1549,7 @@ fn dump_switch_case(
format_position(root_state, &case.range)
),
);
print_node(
&child_state(state, false),
&color_label(root_state, "test"),
);
print_node(&child_state(state, false), &color_label(root_state, "test"));
dump_expression(test, &child_state(&child_state(state, false), true));
} else {
print_node(
@ -1457,7 +1570,10 @@ fn dump_switch_case(
let scope = case.scope.borrow();
let children = &scope.children;
for (i, child) in children.iter().enumerate() {
dump_statement(child, &child_state(&consequent_state, i == children.len() - 1));
dump_statement(
child,
&child_state(&consequent_state, i == children.len() - 1),
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -60,13 +60,19 @@ pub struct FFIUtf16Slice {
impl From<&[u16]> for FFIUtf16Slice {
fn from(slice: &[u16]) -> Self {
Self { data: slice.as_ptr(), length: slice.len() }
Self {
data: slice.as_ptr(),
length: slice.len(),
}
}
}
impl From<&Utf16String> for FFIUtf16Slice {
fn from(s: &Utf16String) -> Self {
Self { data: s.as_ptr(), length: s.len() }
Self {
data: s.as_ptr(),
length: s.len(),
}
}
}
@ -80,11 +86,17 @@ pub struct FFIOptionalU32 {
impl FFIOptionalU32 {
pub fn none() -> Self {
Self { value: 0, has_value: false }
Self {
value: 0,
has_value: false,
}
}
pub fn some(value: u32) -> Self {
Self { value, has_value: true }
Self {
value,
has_value: true,
}
}
}
@ -210,10 +222,20 @@ extern "C" {
// Callbacks for populating Script GDI data from Rust.
pub fn script_gdi_push_lexical_name(ctx: *mut c_void, name: *const u16, len: usize);
pub fn script_gdi_push_var_name(ctx: *mut c_void, name: *const u16, len: usize);
pub fn script_gdi_push_function(ctx: *mut c_void, sfd: *mut c_void, name: *const u16, len: usize);
pub fn script_gdi_push_function(
ctx: *mut c_void,
sfd: *mut c_void,
name: *const u16,
len: usize,
);
pub fn script_gdi_push_var_scoped_name(ctx: *mut c_void, name: *const u16, len: usize);
pub fn script_gdi_push_annex_b_name(ctx: *mut c_void, name: *const u16, len: usize);
pub fn script_gdi_push_lexical_binding(ctx: *mut c_void, name: *const u16, len: usize, is_constant: bool);
pub fn script_gdi_push_lexical_binding(
ctx: *mut c_void,
name: *const u16,
len: usize,
is_constant: bool,
);
// Callbacks for populating eval EDI data from Rust.
pub fn eval_gdi_set_strict(ctx: *mut c_void, is_strict: bool);
@ -221,7 +243,12 @@ extern "C" {
pub fn eval_gdi_push_function(ctx: *mut c_void, sfd: *mut c_void, name: *const u16, len: usize);
pub fn eval_gdi_push_var_scoped_name(ctx: *mut c_void, name: *const u16, len: usize);
pub fn eval_gdi_push_annex_b_name(ctx: *mut c_void, name: *const u16, len: usize);
pub fn eval_gdi_push_lexical_binding(ctx: *mut c_void, name: *const u16, len: usize, is_constant: bool);
pub fn eval_gdi_push_lexical_binding(
ctx: *mut c_void,
name: *const u16,
len: usize,
is_constant: bool,
);
pub fn rust_compile_regex(
pattern_data: *const u16,
@ -241,7 +268,11 @@ extern "C" {
// Get an intrinsic abstract operation function as an opaque Value.
// name/name_len is the function name (e.g. "GetMethod").
pub fn get_abstract_operation_function(vm_ptr: *mut c_void, name: *const u16, name_len: usize) -> u64;
pub fn get_abstract_operation_function(
vm_ptr: *mut c_void,
name: *const u16,
name_len: usize,
) -> u64;
}
/// Create a SharedFunctionInstanceData from a FunctionData.
@ -293,7 +324,9 @@ pub unsafe fn create_shared_function_data(
if let FunctionParameterBinding::Identifier(ref id) = p.binding {
FFIUtf16Slice::from(id.name.as_ref())
} else {
unreachable!("has_simple_parameter_list guarantees all bindings are identifiers")
unreachable!(
"has_simple_parameter_list guarantees all bindings are identifiers"
)
}
})
.collect()
@ -335,7 +368,10 @@ pub unsafe fn create_shared_function_data(
let sfd_ptr = rust_create_sfd(vm_ptr, source_code_ptr, &ffi_data);
assert!(!sfd_ptr.is_null(), "create_shared_function_data: rust_create_sfd returned null");
assert!(
!sfd_ptr.is_null(),
"create_shared_function_data: rust_create_sfd returned null"
);
sfd_ptr
}
@ -350,7 +386,14 @@ pub unsafe fn create_sfd_for_gdi(
source_code_ptr: *const c_void,
is_strict: bool,
) -> *mut c_void {
create_shared_function_data(function_data, subtable, vm_ptr, source_code_ptr, is_strict, None)
create_shared_function_data(
function_data,
subtable,
vm_ptr,
source_code_ptr,
is_strict,
None,
)
}
/// Constant tags for the FFI constant buffer (ABI-compatible with BytecodeFactory).
@ -530,14 +573,18 @@ pub fn compile_regex(pattern: &[u16], flags: &[u16]) -> Result<*mut c_void, Stri
unsafe {
let mut error: *const std::os::raw::c_char = std::ptr::null();
let handle = rust_compile_regex(
pattern.as_ptr(), pattern.len(),
flags.as_ptr(), flags.len(),
pattern.as_ptr(),
pattern.len(),
flags.as_ptr(),
flags.len(),
&mut error,
);
if error.is_null() {
Ok(handle)
} else {
let msg = std::ffi::CStr::from_ptr(error).to_string_lossy().into_owned();
let msg = std::ffi::CStr::from_ptr(error)
.to_string_lossy()
.into_owned();
rust_free_error_string(error);
Err(msg)
}

View file

@ -44,7 +44,9 @@ impl std::fmt::Debug for ScopedOperandInner {
impl Drop for ScopedOperandInner {
fn drop(&mut self) {
if self.operand.is_register() && self.operand.index() >= Register::RESERVED_COUNT {
self.free_register_pool.borrow_mut().push(Register(self.operand.index()));
self.free_register_pool
.borrow_mut()
.push(Register(self.operand.index()));
}
}
}
@ -351,11 +353,17 @@ impl Generator {
// --- Function kind queries ---
pub fn is_in_generator_function(&self) -> bool {
matches!(self.enclosing_function_kind, FunctionKind::Generator | FunctionKind::AsyncGenerator)
matches!(
self.enclosing_function_kind,
FunctionKind::Generator | FunctionKind::AsyncGenerator
)
}
pub fn is_in_async_function(&self) -> bool {
matches!(self.enclosing_function_kind, FunctionKind::Async | FunctionKind::AsyncGenerator)
matches!(
self.enclosing_function_kind,
FunctionKind::Async | FunctionKind::AsyncGenerator
)
}
pub fn is_in_async_generator_function(&self) -> bool {
@ -383,12 +391,7 @@ impl Generator {
self.next_register += 1;
r
} else {
let min_index = pool
.iter()
.enumerate()
.min_by_key(|(_, r)| r.0)
.unwrap()
.0;
let min_index = pool.iter().enumerate().min_by_key(|(_, r)| r.0).unwrap().0;
pool.remove(min_index)
}
};
@ -427,7 +430,10 @@ impl Generator {
/// Copy a local variable into a fresh register to prevent later
/// side effects from changing its value. Returns the operand unchanged
/// if it is not a local.
pub fn copy_if_needed_to_preserve_evaluation_order(&mut self, operand: &ScopedOperand) -> ScopedOperand {
pub fn copy_if_needed_to_preserve_evaluation_order(
&mut self,
operand: &ScopedOperand,
) -> ScopedOperand {
if operand.operand().is_local() {
let reg = self.allocate_register();
self.emit_mov(&reg, operand);
@ -520,9 +526,24 @@ impl Generator {
// --- Table interning ---
define_intern_method!(intern_string, StringTableIndex, string_table, string_table_index);
define_intern_method!(intern_identifier, IdentifierTableIndex, identifier_table, identifier_table_index);
define_intern_method!(intern_property_key, PropertyKeyTableIndex, property_key_table, property_key_table_index);
define_intern_method!(
intern_string,
StringTableIndex,
string_table,
string_table_index
);
define_intern_method!(
intern_identifier,
IdentifierTableIndex,
identifier_table,
identifier_table_index
);
define_intern_method!(
intern_property_key,
PropertyKeyTableIndex,
property_key_table,
property_key_table_index
);
/// If `operand` is a constant string that is not an array index, intern it
/// as a property key and return the index. Uses split borrows to avoid
@ -681,58 +702,82 @@ impl Generator {
// OPTIMIZATION: If the condition is a register with ref_count == 1 and the last
// instruction is a comparison whose dst matches condition, fuse into a JumpXxx.
if condition.operand().is_register()
&& std::rc::Rc::strong_count(&condition.inner) == 1
{
if condition.operand().is_register() && std::rc::Rc::strong_count(&condition.inner) == 1 {
let block = &mut self.basic_blocks[self.current_block_index.basic_block_index()];
if let Some((last_instruction, _)) = block.instructions.last() {
let fused = match last_instruction {
Instruction::LessThan { dst, lhs, rhs } if *dst == condition.operand() => {
Some(Instruction::JumpLessThan {
lhs: *lhs, rhs: *rhs,
true_target, false_target,
lhs: *lhs,
rhs: *rhs,
true_target,
false_target,
})
}
Instruction::LessThanEquals { dst, lhs, rhs } if *dst == condition.operand() => {
Instruction::LessThanEquals { dst, lhs, rhs }
if *dst == condition.operand() =>
{
Some(Instruction::JumpLessThanEquals {
lhs: *lhs, rhs: *rhs,
true_target, false_target,
lhs: *lhs,
rhs: *rhs,
true_target,
false_target,
})
}
Instruction::GreaterThan { dst, lhs, rhs } if *dst == condition.operand() => {
Some(Instruction::JumpGreaterThan {
lhs: *lhs, rhs: *rhs,
true_target, false_target,
lhs: *lhs,
rhs: *rhs,
true_target,
false_target,
})
}
Instruction::GreaterThanEquals { dst, lhs, rhs } if *dst == condition.operand() => {
Instruction::GreaterThanEquals { dst, lhs, rhs }
if *dst == condition.operand() =>
{
Some(Instruction::JumpGreaterThanEquals {
lhs: *lhs, rhs: *rhs,
true_target, false_target,
lhs: *lhs,
rhs: *rhs,
true_target,
false_target,
})
}
Instruction::LooselyEquals { dst, lhs, rhs } if *dst == condition.operand() => {
Some(Instruction::JumpLooselyEquals {
lhs: *lhs, rhs: *rhs,
true_target, false_target,
lhs: *lhs,
rhs: *rhs,
true_target,
false_target,
})
}
Instruction::LooselyInequals { dst, lhs, rhs } if *dst == condition.operand() => {
Instruction::LooselyInequals { dst, lhs, rhs }
if *dst == condition.operand() =>
{
Some(Instruction::JumpLooselyInequals {
lhs: *lhs, rhs: *rhs,
true_target, false_target,
lhs: *lhs,
rhs: *rhs,
true_target,
false_target,
})
}
Instruction::StrictlyEquals { dst, lhs, rhs } if *dst == condition.operand() => {
Instruction::StrictlyEquals { dst, lhs, rhs }
if *dst == condition.operand() =>
{
Some(Instruction::JumpStrictlyEquals {
lhs: *lhs, rhs: *rhs,
true_target, false_target,
lhs: *lhs,
rhs: *rhs,
true_target,
false_target,
})
}
Instruction::StrictlyInequals { dst, lhs, rhs } if *dst == condition.operand() => {
Instruction::StrictlyInequals { dst, lhs, rhs }
if *dst == condition.operand() =>
{
Some(Instruction::JumpStrictlyInequals {
lhs: *lhs, rhs: *rhs,
true_target, false_target,
lhs: *lhs,
rhs: *rhs,
true_target,
false_target,
})
}
_ => None,
@ -763,8 +808,12 @@ impl Generator {
// --- Lexical environment helpers ---
pub fn current_lexical_environment(&mut self) -> ScopedOperand {
self.lexical_environment_register_stack.last().cloned()
.unwrap_or_else(|| self.scoped_operand(Operand::register(Register::SAVED_LEXICAL_ENVIRONMENT)))
self.lexical_environment_register_stack
.last()
.cloned()
.unwrap_or_else(|| {
self.scoped_operand(Operand::register(Register::SAVED_LEXICAL_ENVIRONMENT))
})
}
pub fn end_variable_scope(&mut self) {
@ -797,7 +846,8 @@ impl Generator {
parent: parent.operand(),
capacity,
});
self.lexical_environment_register_stack.push(new_env.clone());
self.lexical_environment_register_stack
.push(new_env.clone());
new_env
}
@ -814,7 +864,12 @@ impl Generator {
// --- Break/continue scope management ---
pub fn begin_breakable_scope(&mut self, target: Label, label_set: Vec<Utf16String>, completion: Option<ScopedOperand>) {
pub fn begin_breakable_scope(
&mut self,
target: Label,
label_set: Vec<Utf16String>,
completion: Option<ScopedOperand>,
) {
self.breakable_scopes.push(LabelableScope {
bytecode_target: target,
language_label_set: label_set,
@ -828,7 +883,12 @@ impl Generator {
self.breakable_scopes.pop();
}
pub fn begin_continuable_scope(&mut self, target: Label, label_set: Vec<Utf16String>, completion: Option<ScopedOperand>) {
pub fn begin_continuable_scope(
&mut self,
target: Label,
label_set: Vec<Utf16String>,
completion: Option<ScopedOperand>,
) {
self.continuable_scopes.push(LabelableScope {
bytecode_target: target,
language_label_set: label_set,
@ -843,7 +903,10 @@ impl Generator {
}
pub fn set_current_breakable_scope_completion_register(&mut self, completion: ScopedOperand) {
self.breakable_scopes.last_mut().expect("no active breakable scope").completion_register = Some(completion);
self.breakable_scopes
.last_mut()
.expect("no active breakable scope")
.completion_register = Some(completion);
}
pub fn find_breakable_scope(&self, label: Option<&[u16]>) -> Option<&LabelableScope> {
@ -898,7 +961,9 @@ impl Generator {
/// Register a jump target with the current FinallyContext.
/// Assigns a unique completion_type index and emits code to set it and jump to finally.
pub fn register_jump_in_finally_context(&mut self, target: Label) {
let index = self.current_finally_context.expect("no active finally context");
let index = self
.current_finally_context
.expect("no active finally context");
let ctx = &mut self.finally_contexts[index];
let jump_index = ctx.next_jump_index;
ctx.next_jump_index += 1;
@ -921,7 +986,9 @@ impl Generator {
self.register_jump_in_finally_context(trampoline_block);
self.switch_to_basic_block(trampoline_block);
// Pop to the parent FinallyContext (simulating the inner finally completing).
let index = self.current_finally_context.expect("no active finally context");
let index = self
.current_finally_context
.expect("no active finally context");
self.current_finally_context = self.finally_contexts[index].parent_index;
}
@ -955,10 +1022,15 @@ impl Generator {
let boundary = self.boundaries[i];
match boundary {
BlockBoundaryType::Break if is_break => {
let target_scope = self.breakable_scopes.last().expect("no active breakable scope");
let target_scope = self
.breakable_scopes
.last()
.expect("no active breakable scope");
let target = target_scope.bytecode_target;
let completion = target_scope.completion_register.clone();
if let (Some(cur), Some(tgt)) = (self.current_completion_register.clone(), completion) {
if let (Some(cur), Some(tgt)) =
(self.current_completion_register.clone(), completion)
{
if cur != tgt {
self.emit_mov(&tgt, &cur);
}
@ -968,10 +1040,15 @@ impl Generator {
return;
}
BlockBoundaryType::Continue if !is_break => {
let target_scope = self.continuable_scopes.last().expect("no active continuable scope");
let target_scope = self
.continuable_scopes
.last()
.expect("no active continuable scope");
let target = target_scope.bytecode_target;
let completion = target_scope.completion_register.clone();
if let (Some(cur), Some(tgt)) = (self.current_completion_register.clone(), completion) {
if let (Some(cur), Some(tgt)) =
(self.current_completion_register.clone(), completion)
{
if cur != tgt {
self.emit_mov(&tgt, &cur);
}
@ -990,13 +1067,19 @@ impl Generator {
BlockBoundaryType::ReturnToFinally => {
if !self.has_outer_finally_before_target(is_break, i + 1) {
let target_scope = if is_break {
self.breakable_scopes.last().expect("no active breakable scope")
self.breakable_scopes
.last()
.expect("no active breakable scope")
} else {
self.continuable_scopes.last().expect("no active continuable scope")
self.continuable_scopes
.last()
.expect("no active continuable scope")
};
let target = target_scope.bytecode_target;
let completion = target_scope.completion_register.clone();
if let (Some(cur), Some(tgt)) = (self.current_completion_register.clone(), completion) {
if let (Some(cur), Some(tgt)) =
(self.current_completion_register.clone(), completion)
{
if cur != tgt {
self.emit_mov(&tgt, &cur);
}
@ -1023,13 +1106,25 @@ impl Generator {
self.breakable_scopes
.iter()
.rev()
.map(|s| (s.bytecode_target, s.language_label_set.clone(), s.completion_register.clone()))
.map(|s| {
(
s.bytecode_target,
s.language_label_set.clone(),
s.completion_register.clone(),
)
})
.collect()
} else {
self.continuable_scopes
.iter()
.rev()
.map(|s| (s.bytecode_target, s.language_label_set.clone(), s.completion_register.clone()))
.map(|s| {
(
s.bytecode_target,
s.language_label_set.clone(),
s.completion_register.clone(),
)
})
.collect()
};
@ -1051,7 +1146,9 @@ impl Generator {
if !self.has_outer_finally_before_target(is_break, current_boundary + 1)
&& label_set.iter().any(|l| l == label)
{
if let (Some(cur), Some(tgt)) = (self.current_completion_register.clone(), completion.clone()) {
if let (Some(cur), Some(tgt)) =
(self.current_completion_register.clone(), completion.clone())
{
if cur != tgt {
self.emit_mov(&tgt, &cur);
}
@ -1072,14 +1169,14 @@ impl Generator {
}
if label_set.iter().any(|l| l == label) {
if let (Some(cur), Some(tgt)) = (self.current_completion_register.clone(), completion.clone()) {
if let (Some(cur), Some(tgt)) =
(self.current_completion_register.clone(), completion.clone())
{
if cur != tgt {
self.emit_mov(&tgt, &cur);
}
}
self.emit(Instruction::Jump {
target: *target,
});
self.emit(Instruction::Jump { target: *target });
self.current_finally_context = saved_ctx;
return;
}
@ -1097,7 +1194,8 @@ impl Generator {
match self.boundaries[i] {
BlockBoundaryType::LeaveLexicalEnvironment => {
env_stack_offset -= 1;
let parent_env = self.lexical_environment_register_stack[env_stack_offset - 1].clone();
let parent_env =
self.lexical_environment_register_stack[env_stack_offset - 1].clone();
self.emit(Instruction::SetLexicalEnvironment {
environment: parent_env.operand(),
});
@ -1213,11 +1311,9 @@ impl Generator {
OperandType::Constant => {
op.offset_index_by(number_of_registers + number_of_locals)
}
OperandType::Argument => {
op.offset_index_by(
number_of_registers + number_of_locals + number_of_constants,
)
}
OperandType::Argument => op.offset_index_by(
number_of_registers + number_of_locals + number_of_constants,
),
}
});
}
@ -1282,7 +1378,11 @@ impl Generator {
block_actions.push(InstAction::Emit);
offset += instruction.encoded_size();
}
Instruction::JumpIf { condition, true_target, false_target } => {
Instruction::JumpIf {
condition,
true_target,
false_target,
} => {
let true_block = true_target.0 as usize;
let false_block = false_target.0 as usize;
// OPTIMIZATION: Replace JumpIf where one target is next block
@ -1322,7 +1422,9 @@ impl Generator {
}
// Unterminated blocks get an implicit End(undefined) appended.
if !block.terminated {
let dummy_end = Instruction::End { value: Operand::constant(0) };
let dummy_end = Instruction::End {
value: Operand::constant(0),
};
offset += dummy_end.encoded_size();
}
actions.push(block_actions);
@ -1396,7 +1498,10 @@ impl Generator {
let replacement = Instruction::End { value };
replacement.encode(self.strict, &mut bytecode);
}
InstAction::EmitJumpFalse { condition, mut target } => {
InstAction::EmitJumpFalse {
condition,
mut target,
} => {
// Patch label for the target
let target_block = target.0 as usize;
target.0 = u32_from_usize(block_offsets[target_block]);
@ -1409,7 +1514,10 @@ impl Generator {
let replacement = Instruction::JumpFalse { condition, target };
replacement.encode(self.strict, &mut bytecode);
}
InstAction::EmitJumpTrue { condition, mut target } => {
InstAction::EmitJumpTrue {
condition,
mut target,
} => {
let target_block = target.0 as usize;
target.0 = u32_from_usize(block_offsets[target_block]);
let instruction_offset = bytecode.len();
@ -1426,9 +1534,12 @@ impl Generator {
// Unterminated blocks get an implicit End(undefined).
if !block.terminated {
let mut undef_rewritten = undefined_constant_operand.expect("undefined constant must exist");
let mut undef_rewritten =
undefined_constant_operand.expect("undefined constant must exist");
undef_rewritten.offset_index_by(number_of_registers + number_of_locals);
let end_instruction = Instruction::End { value: undef_rewritten };
let end_instruction = Instruction::End {
value: undef_rewritten,
};
let instruction_offset = bytecode.len();
source_map.push(SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
@ -1443,7 +1554,9 @@ impl Generator {
exception_handlers.push(ExceptionHandler {
start_offset: u32_from_usize(block_start),
end_offset: u32_from_usize(bytecode.len()),
handler_offset: u32_from_usize(block_offsets[handler_label.basic_block_index()]),
handler_offset: u32_from_usize(
block_offsets[handler_label.basic_block_index()],
),
});
}
}
@ -1539,10 +1652,12 @@ pub fn parse_bigint(s: &str) -> Option<num_bigint::BigInt> {
}
/// Use `preferred_dst` if available, otherwise allocate a fresh register.
pub fn choose_dst(generator: &mut Generator, preferred_dst: Option<&ScopedOperand>) -> ScopedOperand {
pub fn choose_dst(
generator: &mut Generator,
preferred_dst: Option<&ScopedOperand>,
) -> ScopedOperand {
match preferred_dst {
Some(dst) => dst.clone(),
None => generator.allocate_register(),
}
}

View file

@ -78,7 +78,10 @@ impl Operand {
}
pub fn operand_type(self) -> OperandType {
assert!(!self.is_invalid(), "operand_type() called on INVALID operand");
assert!(
!self.is_invalid(),
"operand_type() called on INVALID operand"
);
match (self.0 >> Self::TYPE_SHIFT) & 0x7 {
0 => OperandType::Register,
1 => OperandType::Local,

View file

@ -138,7 +138,10 @@ fn decode_code_point(source: &[u16], pos: usize) -> (u32, usize) {
return (0xFFFD, 1);
}
let cu = source[pos];
if is_utf16_high_surrogate(cu) && pos + 1 < source.len() && is_utf16_low_surrogate(source[pos + 1]) {
if is_utf16_high_surrogate(cu)
&& pos + 1 < source.len()
&& is_utf16_low_surrogate(source[pos + 1])
{
let hi = cu as u32;
let lo = source[pos + 1] as u32;
let cp = 0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00);
@ -151,7 +154,10 @@ fn decode_code_point(source: &[u16], pos: usize) -> (u32, usize) {
// https://tc39.es/ecma262/#sec-line-terminators
// LineTerminator :: <LF> | <CR> | <LS> | <PS>
fn is_line_terminator_cp(cp: u32) -> bool {
cp == '\n' as u32 || cp == '\r' as u32 || cp == LINE_SEPARATOR as u32 || cp == PARAGRAPH_SEPARATOR as u32
cp == '\n' as u32
|| cp == '\r' as u32
|| cp == LINE_SEPARATOR as u32
|| cp == PARAGRAPH_SEPARATOR as u32
}
// https://tc39.es/ecma262/#sec-white-space
@ -165,10 +171,7 @@ fn is_whitespace_cp(cp: u32) -> bool {
return true;
}
// Unicode General Category "Space_Separator" (Zs)
matches!(
cp,
0x1680 | 0x2000..=0x200A | 0x202F | 0x205F | 0x3000
)
matches!(cp, 0x1680 | 0x2000..=0x200A | 0x202F | 0x205F | 0x3000)
}
// https://tc39.es/ecma262/#sec-identifier-names
@ -186,7 +189,12 @@ fn is_identifier_start_cp(cp: u32) -> bool {
// https://tc39.es/ecma262/#sec-identifier-names
// IdentifierPartChar :: UnicodeIDContinue | $ | <ZWNJ> | <ZWJ>
fn is_identifier_continue_cp(cp: u32) -> bool {
if is_ascii_alphanumeric(cp) || cp == '$' as u32 || cp == '_' as u32 || cp == ZERO_WIDTH_NON_JOINER || cp == ZERO_WIDTH_JOINER {
if is_ascii_alphanumeric(cp)
|| cp == '$' as u32
|| cp == '_' as u32
|| cp == ZERO_WIDTH_NON_JOINER
|| cp == ZERO_WIDTH_JOINER
{
return true;
}
if cp < 128 {
@ -213,62 +221,142 @@ fn keyword_from_str(s: &[u16]) -> Option<TokenType> {
// against compile-time UTF-16 constants (zero allocation).
match s.len() {
2 => {
if s == utf16!("do") { return Some(TokenType::Do); }
if s == utf16!("if") { return Some(TokenType::If); }
if s == utf16!("in") { return Some(TokenType::In); }
if s == utf16!("do") {
return Some(TokenType::Do);
}
if s == utf16!("if") {
return Some(TokenType::If);
}
if s == utf16!("in") {
return Some(TokenType::In);
}
}
3 => {
if s == utf16!("for") { return Some(TokenType::For); }
if s == utf16!("let") { return Some(TokenType::Let); }
if s == utf16!("new") { return Some(TokenType::New); }
if s == utf16!("try") { return Some(TokenType::Try); }
if s == utf16!("var") { return Some(TokenType::Var); }
if s == utf16!("for") {
return Some(TokenType::For);
}
if s == utf16!("let") {
return Some(TokenType::Let);
}
if s == utf16!("new") {
return Some(TokenType::New);
}
if s == utf16!("try") {
return Some(TokenType::Try);
}
if s == utf16!("var") {
return Some(TokenType::Var);
}
}
4 => {
if s == utf16!("case") { return Some(TokenType::Case); }
if s == utf16!("else") { return Some(TokenType::Else); }
if s == utf16!("enum") { return Some(TokenType::Enum); }
if s == utf16!("null") { return Some(TokenType::NullLiteral); }
if s == utf16!("this") { return Some(TokenType::This); }
if s == utf16!("true") { return Some(TokenType::BoolLiteral); }
if s == utf16!("void") { return Some(TokenType::Void); }
if s == utf16!("with") { return Some(TokenType::With); }
if s == utf16!("case") {
return Some(TokenType::Case);
}
if s == utf16!("else") {
return Some(TokenType::Else);
}
if s == utf16!("enum") {
return Some(TokenType::Enum);
}
if s == utf16!("null") {
return Some(TokenType::NullLiteral);
}
if s == utf16!("this") {
return Some(TokenType::This);
}
if s == utf16!("true") {
return Some(TokenType::BoolLiteral);
}
if s == utf16!("void") {
return Some(TokenType::Void);
}
if s == utf16!("with") {
return Some(TokenType::With);
}
}
5 => {
if s == utf16!("async") { return Some(TokenType::Async); }
if s == utf16!("await") { return Some(TokenType::Await); }
if s == utf16!("break") { return Some(TokenType::Break); }
if s == utf16!("catch") { return Some(TokenType::Catch); }
if s == utf16!("class") { return Some(TokenType::Class); }
if s == utf16!("const") { return Some(TokenType::Const); }
if s == utf16!("false") { return Some(TokenType::BoolLiteral); }
if s == utf16!("super") { return Some(TokenType::Super); }
if s == utf16!("throw") { return Some(TokenType::Throw); }
if s == utf16!("while") { return Some(TokenType::While); }
if s == utf16!("yield") { return Some(TokenType::Yield); }
if s == utf16!("async") {
return Some(TokenType::Async);
}
if s == utf16!("await") {
return Some(TokenType::Await);
}
if s == utf16!("break") {
return Some(TokenType::Break);
}
if s == utf16!("catch") {
return Some(TokenType::Catch);
}
if s == utf16!("class") {
return Some(TokenType::Class);
}
if s == utf16!("const") {
return Some(TokenType::Const);
}
if s == utf16!("false") {
return Some(TokenType::BoolLiteral);
}
if s == utf16!("super") {
return Some(TokenType::Super);
}
if s == utf16!("throw") {
return Some(TokenType::Throw);
}
if s == utf16!("while") {
return Some(TokenType::While);
}
if s == utf16!("yield") {
return Some(TokenType::Yield);
}
}
6 => {
if s == utf16!("delete") { return Some(TokenType::Delete); }
if s == utf16!("export") { return Some(TokenType::Export); }
if s == utf16!("import") { return Some(TokenType::Import); }
if s == utf16!("return") { return Some(TokenType::Return); }
if s == utf16!("delete") {
return Some(TokenType::Delete);
}
if s == utf16!("export") {
return Some(TokenType::Export);
}
if s == utf16!("import") {
return Some(TokenType::Import);
}
if s == utf16!("return") {
return Some(TokenType::Return);
}
// NB: "static" is intentionally NOT lexed as TokenType::Static.
// C++ lexes it as Identifier and handles it contextually in class parsing.
if s == utf16!("switch") { return Some(TokenType::Switch); }
if s == utf16!("typeof") { return Some(TokenType::Typeof); }
if s == utf16!("switch") {
return Some(TokenType::Switch);
}
if s == utf16!("typeof") {
return Some(TokenType::Typeof);
}
}
7 => {
if s == utf16!("default") { return Some(TokenType::Default); }
if s == utf16!("extends") { return Some(TokenType::Extends); }
if s == utf16!("finally") { return Some(TokenType::Finally); }
if s == utf16!("default") {
return Some(TokenType::Default);
}
if s == utf16!("extends") {
return Some(TokenType::Extends);
}
if s == utf16!("finally") {
return Some(TokenType::Finally);
}
}
8 => {
if s == utf16!("continue") { return Some(TokenType::Continue); }
if s == utf16!("debugger") { return Some(TokenType::Debugger); }
if s == utf16!("function") { return Some(TokenType::Function); }
if s == utf16!("continue") {
return Some(TokenType::Continue);
}
if s == utf16!("debugger") {
return Some(TokenType::Debugger);
}
if s == utf16!("function") {
return Some(TokenType::Function);
}
}
10 => {
if s == utf16!("instanceof") { return Some(TokenType::Instanceof); }
if s == utf16!("instanceof") {
return Some(TokenType::Instanceof);
}
}
_ => {}
}
@ -375,7 +463,12 @@ impl<'a> Lexer<'a> {
lexer
}
pub fn new_at_offset(source: &'a [u16], offset: usize, line_number: u32, line_column: u32) -> Self {
pub fn new_at_offset(
source: &'a [u16],
offset: usize,
line_number: u32,
line_column: u32,
) -> Self {
let mut lexer = Lexer {
source,
position: offset,
@ -394,11 +487,15 @@ impl<'a> Lexer<'a> {
}
fn current_template_state(&self) -> &TemplateState {
self.template_states.last().expect("template_states must not be empty")
self.template_states
.last()
.expect("template_states must not be empty")
}
fn current_template_state_mut(&mut self) -> &mut TemplateState {
self.template_states.last_mut().expect("template_states must not be empty")
self.template_states
.last_mut()
.expect("template_states must not be empty")
}
// https://tc39.es/ecma262/#sec-html-like-comments
@ -589,7 +686,7 @@ impl<'a> Lexer<'a> {
/// Re-scan the source from `scan_start` (1-based position) to `self.position`
/// and build a decoded identifier value. Only called when escapes are present.
fn build_identifier_value(&self, scan_start: usize) -> Utf16String {
let raw = &self.source[scan_start - 1 .. self.position - 1];
let raw = &self.source[scan_start - 1..self.position - 1];
let mut result = Utf16String(Vec::with_capacity(raw.len()));
let mut i = 0;
while i < raw.len() {
@ -680,7 +777,9 @@ impl<'a> Lexer<'a> {
if self.position + 1 >= self.source_len() {
return false;
}
self.current_code_unit == a && self.source[self.position] == b && self.source[self.position + 1] == c
self.current_code_unit == a
&& self.source[self.position] == b
&& self.source[self.position + 1] == c
}
fn match4(&self, a: u16, b: u16, c: u16, d: u16) -> bool {
@ -713,7 +812,9 @@ impl<'a> Lexer<'a> {
fn is_line_comment_start(&self, line_has_token_yet: bool) -> bool {
self.match2(ch(b'/'), ch(b'/'))
|| (self.allow_html_comments && self.match4(ch(b'<'), ch(b'!'), ch(b'-'), ch(b'-')))
|| (self.allow_html_comments && !line_has_token_yet && self.match3(ch(b'-'), ch(b'-'), ch(b'>')))
|| (self.allow_html_comments
&& !line_has_token_yet
&& self.match3(ch(b'-'), ch(b'-'), ch(b'>')))
|| (self.match2(ch(b'#'), ch(b'!')) && self.position == 1)
}
@ -757,7 +858,9 @@ impl<'a> Lexer<'a> {
if !is_ascii_digit(self.current_code_unit) {
return false;
}
while is_ascii_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_ascii_digit) {
while is_ascii_digit(self.current_code_unit)
|| self.match_numeric_literal_separator_followed_by(is_ascii_digit)
{
self.consume();
}
true
@ -779,7 +882,9 @@ impl<'a> Lexer<'a> {
if !is_octal_digit(self.current_code_unit) {
return false;
}
while is_octal_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_octal_digit) {
while is_octal_digit(self.current_code_unit)
|| self.match_numeric_literal_separator_followed_by(is_octal_digit)
{
self.consume();
}
true
@ -790,7 +895,9 @@ impl<'a> Lexer<'a> {
if !is_ascii_hex_digit(self.current_code_unit) {
return false;
}
while is_ascii_hex_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_ascii_hex_digit) {
while is_ascii_hex_digit(self.current_code_unit)
|| self.match_numeric_literal_separator_followed_by(is_ascii_hex_digit)
{
self.consume();
}
true
@ -808,7 +915,9 @@ impl<'a> Lexer<'a> {
if !is_binary_digit(self.current_code_unit) {
return false;
}
while is_binary_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_binary_digit) {
while is_binary_digit(self.current_code_unit)
|| self.match_numeric_literal_separator_followed_by(is_binary_digit)
{
self.consume();
}
true
@ -817,7 +926,9 @@ impl<'a> Lexer<'a> {
fn consume_regex_literal(&mut self) -> TokenType {
self.regex_is_in_character_class = false;
while !self.is_eof() {
if self.is_line_terminator() || (!self.regex_is_in_character_class && self.current_code_unit == ch(b'/')) {
if self.is_line_terminator()
|| (!self.regex_is_in_character_class && self.current_code_unit == ch(b'/'))
{
break;
}
@ -915,11 +1026,15 @@ impl<'a> Lexer<'a> {
if self.current_token_type == TokenType::RegexLiteral
&& !self.is_eof()
&& (self.current_code_unit < 128 && (self.current_code_unit as u8 as char).is_ascii_alphabetic())
&& (self.current_code_unit < 128
&& (self.current_code_unit as u8 as char).is_ascii_alphabetic())
&& !did_consume_whitespace_or_comments
{
token_type = TokenType::RegexFlags;
while !self.is_eof() && self.current_code_unit < 128 && (self.current_code_unit as u8 as char).is_ascii_alphabetic() {
while !self.is_eof()
&& self.current_code_unit < 128
&& (self.current_code_unit as u8 as char).is_ascii_alphabetic()
{
self.consume();
}
} else if self.current_code_unit == ch(b'`') {
@ -958,7 +1073,10 @@ impl<'a> Lexer<'a> {
self.consume();
self.current_template_state_mut().in_expression = true;
} else {
while !self.match2(ch(b'$'), ch(b'{')) && self.current_code_unit != ch(b'`') && !self.is_eof() {
while !self.match2(ch(b'$'), ch(b'{'))
&& self.current_code_unit != ch(b'`')
&& !self.is_eof()
{
if self.match2(ch(b'\\'), ch(b'$'))
|| self.match2(ch(b'\\'), ch(b'`'))
|| self.match2(ch(b'\\'), ch(b'\\'))
@ -983,7 +1101,9 @@ impl<'a> Lexer<'a> {
token_type = TokenType::PrivateIdentifier;
} else {
token_type = TokenType::Invalid;
token_message = Some("Start of private name '#' but not followed by valid identifier".to_string());
token_message = Some(
"Start of private name '#' but not followed by valid identifier".to_string(),
);
}
} else if let Some((_cp, len)) = self.is_identifier_start() {
let has_escape = self.scan_identifier_body(len);
@ -1001,7 +1121,7 @@ impl<'a> Lexer<'a> {
}
identifier_value = Some(decoded);
} else {
let source_slice = &self.source[value_start - 1 .. self.position - 1];
let source_slice = &self.source[value_start - 1..self.position - 1];
if let Some(kw) = keyword_from_str(source_slice) {
token_type = kw;
} else {
@ -1044,7 +1164,9 @@ impl<'a> Lexer<'a> {
}
}
} else {
while is_ascii_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_ascii_digit) {
while is_ascii_digit(self.current_code_unit)
|| self.match_numeric_literal_separator_followed_by(is_ascii_digit)
{
self.consume();
}
if self.current_code_unit == ch(b'n') {
@ -1056,7 +1178,9 @@ impl<'a> Lexer<'a> {
if self.current_code_unit == ch(b'_') {
is_invalid = true;
}
while is_ascii_digit(self.current_code_unit) || self.match_numeric_literal_separator_followed_by(is_ascii_digit) {
while is_ascii_digit(self.current_code_unit)
|| self.match_numeric_literal_separator_followed_by(is_ascii_digit)
{
self.consume();
}
}
@ -1171,8 +1295,10 @@ impl<'a> Lexer<'a> {
if token_type == TokenType::CurlyOpen {
self.current_template_state_mut().open_bracket_count += 1;
} else if token_type == TokenType::CurlyClose {
self.current_template_state_mut().open_bracket_count =
self.current_template_state().open_bracket_count.saturating_sub(1);
self.current_template_state_mut().open_bracket_count = self
.current_template_state()
.open_bracket_count
.saturating_sub(1);
}
}
@ -1181,7 +1307,12 @@ impl<'a> Lexer<'a> {
let trivia_has_line_terminator = if trivia_start > 0 && value_start > trivia_start {
self.source[trivia_start - 1..value_start - 1]
.iter()
.any(|&cu| cu == ch(b'\n') || cu == ch(b'\r') || cu == LINE_SEPARATOR || cu == PARAGRAPH_SEPARATOR)
.any(|&cu| {
cu == ch(b'\n')
|| cu == ch(b'\r')
|| cu == LINE_SEPARATOR
|| cu == PARAGRAPH_SEPARATOR
})
} else {
false
};

View file

@ -164,8 +164,13 @@ unsafe fn source_from_raw<'a>(source: *const u16, len: usize) -> Option<&'a [u16
}
/// Callback type for reporting parse errors to C++.
type ParseErrorCallback =
unsafe extern "C" fn(ctx: *mut c_void, message: *const u8, message_len: usize, line: u32, column: u32);
type ParseErrorCallback = unsafe extern "C" fn(
ctx: *mut c_void,
message: *const u8,
message_len: usize,
line: u32,
column: u32,
);
/// Log parser and scope collector errors, returning true if any were found.
fn check_errors(parser: &mut Parser) -> bool {
@ -205,13 +210,15 @@ fn check_errors_with_callback(
/// Convert scope local variables to generator LocalVariable format.
fn convert_local_variables(scope: &ast::ScopeData) -> Vec<bytecode::generator::LocalVariable> {
scope.local_variables.iter().map(|lv| {
bytecode::generator::LocalVariable {
scope
.local_variables
.iter()
.map(|lv| bytecode::generator::LocalVariable {
name: lv.name.clone(),
is_lexically_declared: lv.kind == ast::LocalVarKind::LetOrConst,
is_initialized_during_declaration_instantiation: false,
}
}).collect()
})
.collect()
}
/// Create a Generator configured for program-level compilation.
@ -333,7 +340,8 @@ pub unsafe extern "C" fn rust_compile_program(
return std::ptr::null_mut();
};
let mut gen = new_program_generator(starts_in_strict_mode, vm_ptr, source_code_ptr, source_len);
let mut gen =
new_program_generator(starts_in_strict_mode, vm_ptr, source_code_ptr, source_len);
gen.function_table = std::mem::take(&mut parser.function_table);
compile_program_body(&mut gen, &program, &scope_ref, vm_ptr, source_code_ptr)
})
@ -374,7 +382,11 @@ pub unsafe extern "C" fn rust_compile_script(
let Some(source_slice) = source_from_raw(source, source_len) else {
return std::ptr::null_mut();
};
let mut parser = Parser::new_with_line_offset(source_slice, ProgramType::Script, u32_from_usize(initial_line_number));
let mut parser = Parser::new_with_line_offset(
source_slice,
ProgramType::Script,
u32_from_usize(initial_line_number),
);
let program = parser.parse_program(false);
@ -389,7 +401,12 @@ pub unsafe extern "C" fn rust_compile_script(
ast_dump::dump_program(&program, use_color, &parser.function_table);
}
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
write_ast_dump_output(
&program,
&parser.function_table,
ast_dump_output,
ast_dump_output_len,
);
let (scope_ref, is_strict) = if let StatementKind::Program(ref data) = program.inner {
(data.scope.clone(), data.is_strict_mode)
@ -399,12 +416,20 @@ pub unsafe extern "C" fn rust_compile_script(
let mut gen = new_program_generator(is_strict, vm_ptr, source_code_ptr, source_len);
gen.function_table = std::mem::take(&mut parser.function_table);
let exec_ptr = compile_program_body(&mut gen, &program, &scope_ref, vm_ptr, source_code_ptr);
let exec_ptr =
compile_program_body(&mut gen, &program, &scope_ref, vm_ptr, source_code_ptr);
if exec_ptr.is_null() {
return std::ptr::null_mut();
}
extract_script_gdi(&scope_ref.borrow(), is_strict, vm_ptr, source_code_ptr, gdi_context, &mut gen.function_table);
extract_script_gdi(
&scope_ref.borrow(),
is_strict,
vm_ptr,
source_code_ptr,
gdi_context,
&mut gen.function_table,
);
exec_ptr
})
@ -462,7 +487,12 @@ pub unsafe extern "C" fn rust_compile_eval(
parser.scope_collector.analyze(true);
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
write_ast_dump_output(
&program,
&parser.function_table,
ast_dump_output,
ast_dump_output_len,
);
let (scope_ref, is_strict) = if let StatementKind::Program(ref data) = program.inner {
(data.scope.clone(), data.is_strict_mode)
@ -472,12 +502,20 @@ pub unsafe extern "C" fn rust_compile_eval(
let mut gen = new_program_generator(is_strict, vm_ptr, source_code_ptr, source_len);
gen.function_table = std::mem::take(&mut parser.function_table);
let exec_ptr = compile_program_body(&mut gen, &program, &scope_ref, vm_ptr, source_code_ptr);
let exec_ptr =
compile_program_body(&mut gen, &program, &scope_ref, vm_ptr, source_code_ptr);
if exec_ptr.is_null() {
return std::ptr::null_mut();
}
extract_eval_gdi(&scope_ref.borrow(), is_strict, vm_ptr, source_code_ptr, gdi_context, &mut gen.function_table);
extract_eval_gdi(
&scope_ref.borrow(),
is_strict,
vm_ptr,
source_code_ptr,
gdi_context,
&mut gen.function_table,
);
exec_ptr
})
@ -530,7 +568,8 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
// Validate parameters standalone.
// First lex independently to catch lexer errors (e.g. unterminated comments)
// with correct line/column positions relative to the parameter string.
let Some(parameters_slice) = source_from_raw(parameters_source, parameters_source_len) else {
let Some(parameters_slice) = source_from_raw(parameters_source, parameters_source_len)
else {
return std::ptr::null_mut();
};
{
@ -541,10 +580,18 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
break;
}
if token.token_type == token::TokenType::Invalid {
let msg = token.message.unwrap_or_else(|| format!("Unexpected token {}", token.token_type.name()));
let msg = token
.message
.unwrap_or_else(|| format!("Unexpected token {}", token.token_type.name()));
if let Some(cb) = error_callback {
unsafe {
cb(error_context, msg.as_ptr(), msg.len(), token.line_number, token.line_column);
cb(
error_context,
msg.as_ptr(),
msg.len(),
token.line_number,
token.line_column,
);
}
}
return std::ptr::null_mut();
@ -555,10 +602,18 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
{
let mut validate_src: Vec<u16> = Vec::new();
match kind {
ast::FunctionKind::Generator => validate_src.extend_from_slice(utf16!("function* test(")),
ast::FunctionKind::Async => validate_src.extend_from_slice(utf16!("async function test(")),
ast::FunctionKind::AsyncGenerator => validate_src.extend_from_slice(utf16!("async function* test(")),
ast::FunctionKind::Normal => validate_src.extend_from_slice(utf16!("function test(")),
ast::FunctionKind::Generator => {
validate_src.extend_from_slice(utf16!("function* test("))
}
ast::FunctionKind::Async => {
validate_src.extend_from_slice(utf16!("async function test("))
}
ast::FunctionKind::AsyncGenerator => {
validate_src.extend_from_slice(utf16!("async function* test("))
}
ast::FunctionKind::Normal => {
validate_src.extend_from_slice(utf16!("function test("))
}
}
validate_src.extend_from_slice(parameters_slice);
validate_src.extend_from_slice(utf16!("\n) {}"));
@ -625,7 +680,12 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
return std::ptr::null_mut();
}
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
write_ast_dump_output(
&program,
&parser.function_table,
ast_dump_output,
ast_dump_output_len,
);
// Extract the FunctionExpression from the program.
// The program should contain a single ExpressionStatement wrapping a FunctionExpression.
@ -665,7 +725,13 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
let is_strict = function_data.is_strict_mode;
let subtable = parser.function_table.extract_reachable(&function_data);
bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, is_strict)
bytecode::ffi::create_sfd_for_gdi(
function_data,
subtable,
vm_ptr,
source_code_ptr,
is_strict,
)
})
}
@ -708,15 +774,22 @@ pub unsafe extern "C" fn rust_compile_builtin_file(
let program = parser.parse_program(true); // strict mode
if parser.has_errors() {
let errors: Vec<String> = parser.errors().iter().map(|e| {
format!("{}:{}: {}", e.line, e.column, e.message)
}).collect();
let errors: Vec<String> = parser
.errors()
.iter()
.map(|e| format!("{}:{}: {}", e.line, e.column, e.message))
.collect();
panic!("Parse errors in builtin file: {}", errors.join("; "));
}
parser.scope_collector.analyze(false);
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
write_ast_dump_output(
&program,
&parser.function_table,
ast_dump_output,
ast_dump_output_len,
);
let scope_ref = if let StatementKind::Program(ref data) = program.inner {
data.scope.clone()
@ -726,7 +799,12 @@ pub unsafe extern "C" fn rust_compile_builtin_file(
let scope = scope_ref.borrow();
for child in &scope.children {
if let StatementKind::FunctionDeclaration { function_id, ref name, .. } = child.inner {
if let StatementKind::FunctionDeclaration {
function_id,
ref name,
..
} = child.inner
{
let function_data = parser.function_table.take(function_id);
let subtable = parser.function_table.extract_reachable(&function_data);
let sfd_ptr = bytecode::ffi::create_sfd_for_gdi(
@ -760,9 +838,13 @@ type ModuleBoolCallback = unsafe extern "C" fn(ctx: *mut c_void, value: bool);
type ModuleNameCallback = unsafe extern "C" fn(ctx: *mut c_void, name: *const u16, name_len: usize);
type ModuleImportEntryCallback = unsafe extern "C" fn(
ctx: *mut c_void,
import_name: *const u16, import_name_len: usize, is_namespace: bool,
local_name: *const u16, local_name_len: usize,
module_specifier: *const u16, specifier_len: usize,
import_name: *const u16,
import_name_len: usize,
is_namespace: bool,
local_name: *const u16,
local_name_len: usize,
module_specifier: *const u16,
specifier_len: usize,
attribute_keys: *const bytecode::ffi::FFIUtf16Slice,
attribute_values: *const bytecode::ffi::FFIUtf16Slice,
attribute_count: usize,
@ -770,25 +852,32 @@ type ModuleImportEntryCallback = unsafe extern "C" fn(
type ModuleExportEntryCallback = unsafe extern "C" fn(
ctx: *mut c_void,
kind: u8,
export_name: *const u16, export_name_len: usize,
local_or_import_name: *const u16, local_or_import_name_len: usize,
module_specifier: *const u16, specifier_len: usize,
export_name: *const u16,
export_name_len: usize,
local_or_import_name: *const u16,
local_or_import_name_len: usize,
module_specifier: *const u16,
specifier_len: usize,
attribute_keys: *const bytecode::ffi::FFIUtf16Slice,
attribute_values: *const bytecode::ffi::FFIUtf16Slice,
attribute_count: usize,
);
type ModuleRequestedModuleCallback = unsafe extern "C" fn(
ctx: *mut c_void,
specifier: *const u16, specifier_len: usize,
specifier: *const u16,
specifier_len: usize,
attribute_keys: *const bytecode::ffi::FFIUtf16Slice,
attribute_values: *const bytecode::ffi::FFIUtf16Slice,
attribute_count: usize,
);
type ModuleFunctionCallback = unsafe extern "C" fn(
ctx: *mut c_void, sfd_ptr: *mut c_void, name: *const u16, name_len: usize,
);
type ModuleFunctionCallback =
unsafe extern "C" fn(ctx: *mut c_void, sfd_ptr: *mut c_void, name: *const u16, name_len: usize);
type ModuleLexicalBindingCallback = unsafe extern "C" fn(
ctx: *mut c_void, name: *const u16, name_len: usize, is_constant: bool, function_index: i32,
ctx: *mut c_void,
name: *const u16,
name_len: usize,
is_constant: bool,
function_index: i32,
);
/// Module callback table passed from C++ to avoid many function pointer parameters.
@ -809,13 +898,18 @@ pub struct ModuleCallbacks {
/// Helper to build FFI attribute arrays from a ModuleRequest.
fn build_attribute_slices(
attributes: &[ast::ImportAttribute],
) -> (Vec<bytecode::ffi::FFIUtf16Slice>, Vec<bytecode::ffi::FFIUtf16Slice>) {
) -> (
Vec<bytecode::ffi::FFIUtf16Slice>,
Vec<bytecode::ffi::FFIUtf16Slice>,
) {
attributes
.iter()
.map(|a| (
bytecode::ffi::FFIUtf16Slice::from(a.key.as_ref()),
bytecode::ffi::FFIUtf16Slice::from(a.value.as_ref()),
))
.map(|a| {
(
bytecode::ffi::FFIUtf16Slice::from(a.key.as_ref()),
bytecode::ffi::FFIUtf16Slice::from(a.value.as_ref()),
)
})
.unzip()
}
@ -838,14 +932,31 @@ unsafe fn call_export_callback(
if let Some(mr) = module_request {
let (keys, values) = build_attribute_slices(&mr.attributes);
callback(
ctx, kind, en_ptr, en_len, lin_ptr, lin_len,
mr.module_specifier.as_ptr(), mr.module_specifier.len(),
keys.as_ptr(), values.as_ptr(), keys.len(),
ctx,
kind,
en_ptr,
en_len,
lin_ptr,
lin_len,
mr.module_specifier.as_ptr(),
mr.module_specifier.len(),
keys.as_ptr(),
values.as_ptr(),
keys.len(),
);
} else {
callback(
ctx, kind, en_ptr, en_len, lin_ptr, lin_len,
std::ptr::null(), 0, std::ptr::null(), std::ptr::null(), 0,
ctx,
kind,
en_ptr,
en_len,
lin_ptr,
lin_len,
std::ptr::null(),
0,
std::ptr::null(),
std::ptr::null(),
0,
);
}
}
@ -903,7 +1014,12 @@ pub unsafe extern "C" fn rust_compile_module(
ast_dump::dump_program(&program, use_color, &parser.function_table);
}
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
write_ast_dump_output(
&program,
&parser.function_table,
ast_dump_output,
ast_dump_output_len,
);
let program_data = if let StatementKind::Program(ref data) = program.inner {
data
@ -924,7 +1040,12 @@ pub unsafe extern "C" fn rust_compile_module(
// 4. Extract var declared names and lexical bindings.
extract_module_declarations(
&scope_ref.borrow(), vm_ptr, source_code_ptr, module_context, cb, &mut function_table,
&scope_ref.borrow(),
vm_ptr,
source_code_ptr,
module_context,
cb,
&mut function_table,
);
// 5. Compute requested modules (sorted by source offset).
@ -934,7 +1055,13 @@ pub unsafe extern "C" fn rust_compile_module(
if has_top_level_await {
// Compile as an async wrapper function.
let exec_ptr = compile_module_as_async(
&program, &scope_ref, vm_ptr, source_code_ptr, source, source_len, function_table,
&program,
&scope_ref,
vm_ptr,
source_code_ptr,
source,
source_len,
function_table,
);
if !tla_executable_out.is_null() {
*tla_executable_out = exec_ptr;
@ -953,11 +1080,7 @@ pub unsafe extern "C" fn rust_compile_module(
}
/// Extract import/export metadata from a module's scope and call C++ callbacks.
unsafe fn extract_module_metadata(
scope: &ast::ScopeData,
ctx: *mut c_void,
cb: &ModuleCallbacks,
) {
unsafe fn extract_module_metadata(scope: &ast::ScopeData, ctx: *mut c_void, cb: &ModuleCallbacks) {
use ast::{ExportEntryKind, StatementKind};
// Collect all import entries with their module requests.
@ -975,15 +1098,22 @@ unsafe fn extract_module_metadata(
let (in_ptr, in_len, is_ns) = entry
.import_name
.as_ref()
.map_or((std::ptr::null(), 0, true), |n| (n.as_ptr(), n.len(), false));
.map_or((std::ptr::null(), 0, true), |n| {
(n.as_ptr(), n.len(), false)
});
let (keys, values) = build_attribute_slices(&import_data.module_request.attributes);
(cb.push_import_entry)(
ctx,
in_ptr, in_len, is_ns,
entry.local_name.as_ptr(), entry.local_name.len(),
in_ptr,
in_len,
is_ns,
entry.local_name.as_ptr(),
entry.local_name.len(),
import_data.module_request.module_specifier.as_ptr(),
import_data.module_request.module_specifier.len(),
keys.as_ptr(), values.as_ptr(), keys.len(),
keys.as_ptr(),
values.as_ptr(),
keys.len(),
);
all_import_entries.push(ImportEntryWithRequest {
@ -1004,9 +1134,7 @@ unsafe fn extract_module_metadata(
};
// Handle default export binding name.
if export_data.is_default_export
&& export_data.entries.len() == 1
{
if export_data.is_default_export && export_data.entries.len() == 1 {
let entry = &export_data.entries[0];
// If the default export is not a declaration (function/class/etc.),
// its binding name is the local_or_import_name.
@ -1032,22 +1160,26 @@ unsafe fn extract_module_metadata(
if !has_module_request {
// No module request: check against import entries.
let matching_import = all_import_entries.iter().find(|ie| {
entry.local_or_import_name.as_ref() == Some(&ie.local_name)
});
let matching_import = all_import_entries
.iter()
.find(|ie| entry.local_or_import_name.as_ref() == Some(&ie.local_name));
if let Some(import_entry) = matching_import {
if import_entry.import_name.is_none() {
// Namespace re-export → local export.
call_export_callback(
cb.push_local_export, ctx,
entry.kind as u8, &entry.export_name, &entry.local_or_import_name,
cb.push_local_export,
ctx,
entry.kind as u8,
&entry.export_name,
&entry.local_or_import_name,
None,
);
} else {
// Re-export of a specific binding → indirect export.
call_export_callback(
cb.push_indirect_export, ctx,
cb.push_indirect_export,
ctx,
ExportEntryKind::NamedExport as u8,
&entry.export_name,
&import_entry.import_name,
@ -1057,23 +1189,32 @@ unsafe fn extract_module_metadata(
} else {
// Direct local export.
call_export_callback(
cb.push_local_export, ctx,
entry.kind as u8, &entry.export_name, &entry.local_or_import_name,
cb.push_local_export,
ctx,
entry.kind as u8,
&entry.export_name,
&entry.local_or_import_name,
None,
);
}
} else if entry.kind == ExportEntryKind::ModuleRequestAllButDefault {
// export * from "module"
call_export_callback(
cb.push_star_export, ctx,
entry.kind as u8, &entry.export_name, &entry.local_or_import_name,
cb.push_star_export,
ctx,
entry.kind as u8,
&entry.export_name,
&entry.local_or_import_name,
export_data.module_request.as_ref(),
);
} else {
// export { x } from "module" or export { x as y } from "module"
call_export_callback(
cb.push_indirect_export, ctx,
entry.kind as u8, &entry.export_name, &entry.local_or_import_name,
cb.push_indirect_export,
ctx,
entry.kind as u8,
&entry.export_name,
&entry.local_or_import_name,
export_data.module_request.as_ref(),
);
}
@ -1114,14 +1255,22 @@ unsafe fn extract_module_declarations(
};
match declaration {
StatementKind::FunctionDeclaration { function_id, ref name, .. } => {
let is_default = is_exported
&& name.as_ref().is_some_and(|n| n.name == default_name);
StatementKind::FunctionDeclaration {
function_id,
ref name,
..
} => {
let is_default =
is_exported && name.as_ref().is_some_and(|n| n.name == default_name);
let function_data = function_table.take(*function_id);
let subtable = function_table.extract_reachable(&function_data);
let sfd_ptr = bytecode::ffi::create_sfd_for_gdi(
function_data, subtable, vm_ptr, source_code_ptr, true,
function_data,
subtable,
vm_ptr,
source_code_ptr,
true,
);
if sfd_ptr.is_null() {
continue;
@ -1149,7 +1298,13 @@ unsafe fn extract_module_declarations(
function_count += 1;
// Lexical binding uses the AST name (e.g., "*default*").
(cb.push_lexical_binding)(ctx, binding_name.as_ptr(), binding_name.len(), false, function_index);
(cb.push_lexical_binding)(
ctx,
binding_name.as_ptr(),
binding_name.len(),
false,
function_index,
);
}
StatementKind::ClassDeclaration(class_data) => {
if let Some(ref name_ident) = class_data.name {
@ -1347,10 +1502,7 @@ extern "C" {
/// Recursively collect var-declared names from a statement and all nested
/// statements, excluding function/class bodies (which create new var scopes).
fn collect_var_names_recursive(
statement: &ast::StatementKind,
push_name: &mut dyn FnMut(&[u16]),
) {
fn collect_var_names_recursive(statement: &ast::StatementKind, push_name: &mut dyn FnMut(&[u16])) {
match statement {
ast::StatementKind::VariableDeclaration {
kind: ast::DeclarationKind::Var,
@ -1391,7 +1543,11 @@ fn extract_gdi_common(
// Var names (var declarations at any nesting level + top-level function declarations)
for child in &scope.children {
collect_var_names_recursive(&child.inner, push_var_name);
if let StatementKind::FunctionDeclaration { name: Some(ref name_ident), .. } = child.inner {
if let StatementKind::FunctionDeclaration {
name: Some(ref name_ident),
..
} = child.inner
{
push_var_name(&name_ident.name);
}
}
@ -1400,7 +1556,12 @@ fn extract_gdi_common(
let mut seen_names: HashSet<ast::Utf16String> = HashSet::new();
let mut functions_to_init: Vec<(ast::FunctionId, ast::Utf16String)> = Vec::new();
for child in scope.children.iter().rev() {
if let StatementKind::FunctionDeclaration { function_id, name: Some(ref name_ident), .. } = child.inner {
if let StatementKind::FunctionDeclaration {
function_id,
name: Some(ref name_ident),
..
} = child.inner
{
if seen_names.insert(name_ident.name.clone()) {
functions_to_init.push((function_id, name_ident.name.clone()));
}
@ -1410,7 +1571,13 @@ fn extract_gdi_common(
let function_data = function_table.take(*function_id);
let subtable = function_table.extract_reachable(&function_data);
let sfd_ptr = unsafe {
bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, is_strict)
bytecode::ffi::create_sfd_for_gdi(
function_data,
subtable,
vm_ptr,
source_code_ptr,
is_strict,
)
};
assert!(!sfd_ptr.is_null(), "create_sfd_for_gdi returned null");
push_function(sfd_ptr, name);
@ -1472,12 +1639,17 @@ unsafe fn extract_eval_gdi(
eval_gdi_set_strict(ctx, is_strict);
extract_gdi_common(
scope, vm_ptr, source_code_ptr, is_strict,
scope,
vm_ptr,
source_code_ptr,
is_strict,
&mut |name| eval_gdi_push_var_name(ctx, name.as_ptr(), name.len()),
&mut |sfd_ptr, name| eval_gdi_push_function(ctx, sfd_ptr, name.as_ptr(), name.len()),
&mut |name| eval_gdi_push_var_scoped_name(ctx, name.as_ptr(), name.len()),
&mut |name| eval_gdi_push_annex_b_name(ctx, name.as_ptr(), name.len()),
&mut |name, is_const| eval_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const),
&mut |name, is_const| {
eval_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const)
},
function_table,
);
}
@ -1527,19 +1699,27 @@ unsafe fn extract_script_gdi(
}
extract_gdi_common(
scope, vm_ptr, source_code_ptr, is_strict,
scope,
vm_ptr,
source_code_ptr,
is_strict,
&mut |name| script_gdi_push_var_name(ctx, name.as_ptr(), name.len()),
&mut |sfd_ptr, name| script_gdi_push_function(ctx, sfd_ptr, name.as_ptr(), name.len()),
&mut |name| script_gdi_push_var_scoped_name(ctx, name.as_ptr(), name.len()),
&mut |name| script_gdi_push_annex_b_name(ctx, name.as_ptr(), name.len()),
&mut |name, is_const| script_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const),
&mut |name, is_const| {
script_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const)
},
function_table,
);
}
/// Visit each child statement of a statement, excluding function/class bodies
/// (which create new var scopes). This enables recursive var-declaration walking.
fn for_each_child_statement(statement: &ast::StatementKind, f: &mut dyn FnMut(&ast::StatementKind)) {
fn for_each_child_statement(
statement: &ast::StatementKind,
f: &mut dyn FnMut(&ast::StatementKind),
) {
use ast::StatementKind;
match statement {
@ -1548,7 +1728,11 @@ fn for_each_child_statement(statement: &ast::StatementKind, f: &mut dyn FnMut(&a
f(&child.inner);
}
}
StatementKind::If { consequent, alternate, .. } => {
StatementKind::If {
consequent,
alternate,
..
} => {
f(&consequent.inner);
if let Some(alt) = alternate {
f(&alt.inner);
@ -1676,110 +1860,113 @@ pub unsafe extern "C" fn rust_compile_function(
builtin_abstract_operations_enabled: bool,
) -> *mut c_void {
abort_on_panic(|| {
if rust_function_ast.is_null() {
return std::ptr::null_mut();
}
let payload = Box::from_raw(rust_function_ast as *mut ast::FunctionPayload);
let function_data = Box::new(payload.data);
if rust_function_ast.is_null() {
return std::ptr::null_mut();
}
let payload = Box::from_raw(rust_function_ast as *mut ast::FunctionPayload);
let function_data = Box::new(payload.data);
let body_scope = match &function_data.body.inner {
StatementKind::FunctionBody { ref scope, .. } => Some(scope),
StatementKind::Block(ref scope) => Some(scope),
_ => None,
};
let body_scope = match &function_data.body.inner {
StatementKind::FunctionBody { ref scope, .. } => Some(scope),
StatementKind::Block(ref scope) => Some(scope),
_ => None,
};
// Compute SFD metadata before codegen so the generator can use
// function_environment_needed to optimize `this` access.
let sfd_metadata = compute_sfd_metadata(&function_data);
// Compute SFD metadata before codegen so the generator can use
// function_environment_needed to optimize `this` access.
let sfd_metadata = compute_sfd_metadata(&function_data);
let mut gen = bytecode::generator::Generator::new();
gen.strict = function_data.is_strict_mode;
gen.function_environment_needed = sfd_metadata.function_environment_needed;
gen.builtin_abstract_operations_enabled = builtin_abstract_operations_enabled;
gen.function_table = payload.function_table;
gen.vm_ptr = vm_ptr;
gen.source_code_ptr = source_code_ptr;
gen.source_len = source_len;
gen.enclosing_function_kind = function_data.kind;
let mut gen = bytecode::generator::Generator::new();
gen.strict = function_data.is_strict_mode;
gen.function_environment_needed = sfd_metadata.function_environment_needed;
gen.builtin_abstract_operations_enabled = builtin_abstract_operations_enabled;
gen.function_table = payload.function_table;
gen.vm_ptr = vm_ptr;
gen.source_code_ptr = source_code_ptr;
gen.source_len = source_len;
gen.enclosing_function_kind = function_data.kind;
if let Some(scope) = body_scope {
gen.local_variables = convert_local_variables(&scope.borrow());
}
if let Some(scope) = body_scope {
gen.local_variables = convert_local_variables(&scope.borrow());
}
let entry_block = gen.make_block();
gen.switch_to_basic_block(entry_block);
let entry_block = gen.make_block();
gen.switch_to_basic_block(entry_block);
// https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
// For async (non-generator) functions, emit the initial Yield BEFORE
// GetLexicalEnvironment so that parameter evaluation errors are caught
// by the async promise wrapper. This matches C++ ordering.
if gen.is_in_async_function() && !gen.is_in_generator_function() {
let start_block = gen.make_block();
let undef = gen.add_constant_undefined();
gen.emit(bytecode::instruction::Instruction::Yield {
continuation_label: Some(start_block),
value: undef.operand(),
});
gen.switch_to_basic_block(start_block);
}
{
use bytecode::operand::{Operand, Register};
let env_reg = gen.scoped_operand(Operand::register(Register::SAVED_LEXICAL_ENVIRONMENT));
gen.emit(bytecode::instruction::Instruction::GetLexicalEnvironment {
dst: env_reg.operand(),
});
gen.lexical_environment_register_stack.push(env_reg);
}
if let Some(scope) = body_scope {
bytecode::codegen::emit_function_declaration_instantiation(
&mut gen, &function_data, &scope.borrow(),
);
}
// https://tc39.es/ecma262/#sec-generatorstart
// For generator functions (including async generators), emit the initial Yield
// AFTER FDI. Parameter evaluation happens synchronously before the generator starts.
if gen.is_in_generator_function() {
let start_block = gen.make_block();
let undef = gen.add_constant_undefined();
gen.emit(bytecode::instruction::Instruction::Yield {
continuation_label: Some(start_block),
value: undef.operand(),
});
gen.switch_to_basic_block(start_block);
}
let result = bytecode::codegen::generate_statement(&function_data.body, &mut gen, None);
if !gen.is_current_block_terminated() {
if gen.is_in_generator_or_async_function() {
// Generator/async functions end with Yield (no continuation = done).
// https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
// For async (non-generator) functions, emit the initial Yield BEFORE
// GetLexicalEnvironment so that parameter evaluation errors are caught
// by the async promise wrapper. This matches C++ ordering.
if gen.is_in_async_function() && !gen.is_in_generator_function() {
let start_block = gen.make_block();
let undef = gen.add_constant_undefined();
gen.emit(bytecode::instruction::Instruction::Yield {
continuation_label: None,
continuation_label: Some(start_block),
value: undef.operand(),
});
} else if let Some(value) = result {
gen.emit(bytecode::instruction::Instruction::End {
value: value.operand(),
});
gen.switch_to_basic_block(start_block);
}
// If result is None, the assembler will add End(undefined) as a
// fallthrough for unterminated blocks, matching C++ compile().
}
// For generator/async functions, terminate all unterminated blocks with Yield.
if gen.is_in_generator_or_async_function() {
gen.terminate_unterminated_blocks_with_yield();
}
{
use bytecode::operand::{Operand, Register};
let env_reg =
gen.scoped_operand(Operand::register(Register::SAVED_LEXICAL_ENVIRONMENT));
gen.emit(bytecode::instruction::Instruction::GetLexicalEnvironment {
dst: env_reg.operand(),
});
gen.lexical_environment_register_stack.push(env_reg);
}
let assembled = gen.assemble();
if let Some(scope) = body_scope {
bytecode::codegen::emit_function_declaration_instantiation(
&mut gen,
&function_data,
&scope.borrow(),
);
}
write_sfd_metadata(sfd_ptr, &sfd_metadata);
// https://tc39.es/ecma262/#sec-generatorstart
// For generator functions (including async generators), emit the initial Yield
// AFTER FDI. Parameter evaluation happens synchronously before the generator starts.
if gen.is_in_generator_function() {
let start_block = gen.make_block();
let undef = gen.add_constant_undefined();
gen.emit(bytecode::instruction::Instruction::Yield {
continuation_label: Some(start_block),
value: undef.operand(),
});
gen.switch_to_basic_block(start_block);
}
bytecode::ffi::create_executable(&gen, &assembled, vm_ptr, source_code_ptr)
let result = bytecode::codegen::generate_statement(&function_data.body, &mut gen, None);
if !gen.is_current_block_terminated() {
if gen.is_in_generator_or_async_function() {
// Generator/async functions end with Yield (no continuation = done).
let undef = gen.add_constant_undefined();
gen.emit(bytecode::instruction::Instruction::Yield {
continuation_label: None,
value: undef.operand(),
});
} else if let Some(value) = result {
gen.emit(bytecode::instruction::Instruction::End {
value: value.operand(),
});
}
// If result is None, the assembler will add End(undefined) as a
// fallthrough for unterminated blocks, matching C++ compile().
}
// For generator/async functions, terminate all unterminated blocks with Yield.
if gen.is_in_generator_or_async_function() {
gen.terminate_unterminated_blocks_with_yield();
}
let assembled = gen.assemble();
write_sfd_metadata(sfd_ptr, &sfd_metadata);
bytecode::ffi::create_executable(&gen, &assembled, vm_ptr, source_code_ptr)
})
}
@ -1833,12 +2020,17 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> SfdMetadata {
|| function_data.parsing_insights.uses_this_from_environment,
might_need_arguments: function_data.parsing_insights.might_need_arguments_object,
has_function_named_arguments: fsd.is_some_and(|f| f.has_function_named_arguments),
has_lexically_declared_arguments: fsd.is_some_and(|f| f.has_lexically_declared_arguments),
has_lexically_declared_arguments: fsd
.is_some_and(|f| f.has_lexically_declared_arguments),
non_local_var_count: fsd.map_or(0, |f| f.non_local_var_count),
non_local_var_count_for_parameter_expressions: fsd.map_or(0, |f| f.non_local_var_count_for_parameter_expressions),
non_local_var_count_for_parameter_expressions: fsd
.map_or(0, |f| f.non_local_var_count_for_parameter_expressions),
var_names: fsd.map(|f| &f.var_names).cloned().unwrap_or_default(),
annexb_function_names: sd.annexb_function_names.clone(),
has_arguments_object_local: sd.local_variables.iter().any(|lv| lv.kind == ast::LocalVarKind::ArgumentsObject),
has_arguments_object_local: sd
.local_variables
.iter()
.any(|lv| lv.kind == ast::LocalVarKind::ArgumentsObject),
}
} else {
BodyScopeInfo {
@ -1868,17 +2060,15 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> SfdMetadata {
for parameter in &function_data.parameters {
match &parameter.binding {
ast::FunctionParameterBinding::Identifier(ident) => {
if parameter_names.insert(ident.name.clone())
&& !ident.is_local() {
parameters_in_environment += 1;
}
if parameter_names.insert(ident.name.clone()) && !ident.is_local() {
parameters_in_environment += 1;
}
}
ast::FunctionParameterBinding::BindingPattern(pattern) => {
for_each_binding_pattern_identifier(pattern, &mut |ident| {
if parameter_names.insert(ident.name.clone())
&& !ident.is_local() {
parameters_in_environment += 1;
}
if parameter_names.insert(ident.name.clone()) && !ident.is_local() {
parameters_in_environment += 1;
}
});
}
}
@ -1893,8 +2083,7 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> SfdMetadata {
&& (has_parameter_expressions || !bsi.has_lexically_declared_arguments);
// Arguments object needs an environment binding if it's not a local variable.
let arguments_object_needs_binding =
arguments_object_needed && !bsi.has_arguments_object_local;
let arguments_object_needs_binding = arguments_object_needed && !bsi.has_arguments_object_local;
let mut function_environment_bindings_count: usize = 0;
let mut var_environment_bindings_count: usize = 0;
@ -2039,10 +2228,7 @@ fn count_non_local_names_in_target(target: &ast::VariableDeclaratorTarget, count
}
}
fn count_non_local_names_in_binding_pattern(
pattern: &ast::BindingPattern,
count: &mut usize,
) {
fn count_non_local_names_in_binding_pattern(pattern: &ast::BindingPattern, count: &mut usize) {
for entry in &pattern.entries {
match &entry.alias {
Some(ast::BindingEntryAlias::Identifier(ident)) => {

View file

@ -38,7 +38,7 @@ use std::rc::Rc;
use crate::ast::{
BindingPattern, Expression, ExpressionKind, FunctionParameter, FunctionTable, Identifier,
PrivateIdentifier, SourceRange, Statement, StatementKind, ScopeData, ProgramData, Utf16String,
PrivateIdentifier, ProgramData, ScopeData, SourceRange, Statement, StatementKind, Utf16String,
};
use crate::lexer::{ch, Lexer};
use crate::scope_collector::{ScopeCollector, ScopeCollectorState};
@ -48,11 +48,11 @@ mod declarations;
mod expressions;
mod statements;
pub use crate::ast::Position;
pub use crate::ast::DeclarationKind;
pub use crate::ast::FunctionKind;
pub use crate::ast::ProgramType;
pub use crate::ast::FunctionParsingInsights;
pub use crate::ast::Position;
pub use crate::ast::ProgramType;
// Named precedence levels for parse_expression().
// These correspond to the operator precedence table in ECMA-262.
@ -126,7 +126,10 @@ impl ForbiddenTokens {
}
pub fn with_in() -> Self {
Self { forbid_in: true, ..Self::default() }
Self {
forbid_in: true,
..Self::default()
}
}
pub fn allows(&self, token: TokenType) -> bool {
@ -147,7 +150,8 @@ impl ForbiddenTokens {
forbid_logical: self.forbid_logical || other.forbid_logical,
forbid_coalesce: self.forbid_coalesce || other.forbid_coalesce,
forbid_paren_open: self.forbid_paren_open || other.forbid_paren_open,
forbid_question_mark_period: self.forbid_question_mark_period || other.forbid_question_mark_period,
forbid_question_mark_period: self.forbid_question_mark_period
|| other.forbid_question_mark_period,
forbid_equals: self.forbid_equals || other.forbid_equals,
}
}
@ -287,7 +291,11 @@ impl<'a> Parser<'a> {
Self::new_with_line_offset(source, program_type, 1)
}
pub fn new_with_line_offset(source: &'a [u16], program_type: ProgramType, initial_line_number: u32) -> Self {
pub fn new_with_line_offset(
source: &'a [u16],
program_type: ProgramType,
initial_line_number: u32,
) -> Self {
let mut lexer = Lexer::new(source, initial_line_number, 0);
if program_type == ProgramType::Module {
lexer.disallow_html_comments();
@ -327,7 +335,10 @@ impl<'a> Parser<'a> {
// === AST construction helpers ===
pub(crate) fn range_from(&self, start: Position) -> SourceRange {
SourceRange { start, end: self.position() }
SourceRange {
start,
end: self.position(),
}
}
pub(crate) fn expression(&self, start: Position, expression: ExpressionKind) -> Expression {
@ -338,7 +349,11 @@ impl<'a> Parser<'a> {
Statement::new(self.range_from(start), statement)
}
pub(crate) fn make_identifier(&self, start: Position, name: impl Into<Utf16String>) -> Rc<Identifier> {
pub(crate) fn make_identifier(
&self,
start: Position,
name: impl Into<Utf16String>,
) -> Rc<Identifier> {
Rc::new(Identifier::new(self.range_from(start), name.into()))
}
@ -365,7 +380,13 @@ impl<'a> Parser<'a> {
} else {
(id.name.clone(), parameter.is_rest, false)
};
entries.push(ParameterEntry { name, identifier: Some(id.clone()), is_rest, is_from_pattern, is_first_from_pattern: false });
entries.push(ParameterEntry {
name,
identifier: Some(id.clone()),
is_rest,
is_from_pattern,
is_first_from_pattern: false,
});
}
FunctionParameterBinding::BindingPattern(pattern) => {
if pattern.contains_expression() {
@ -373,17 +394,32 @@ impl<'a> Parser<'a> {
}
// Push a placeholder entry for the pattern parameter itself
// so subsequent parameters get correct positional indices.
entries.push(ParameterEntry { name: Utf16String::default(), identifier: None, is_rest: false, is_from_pattern: true, is_first_from_pattern: true });
entries.push(ParameterEntry {
name: Utf16String::default(),
identifier: None,
is_rest: false,
is_from_pattern: true,
is_first_from_pattern: true,
});
// Then push bound names from this pattern.
while info_index < parameter_info.len() && parameter_info[info_index].is_from_pattern {
while info_index < parameter_info.len()
&& parameter_info[info_index].is_from_pattern
{
let pi = &parameter_info[info_index];
entries.push(ParameterEntry { name: pi.name.clone(), identifier: pi.identifier.clone(), is_rest: pi.is_rest, is_from_pattern: true, is_first_from_pattern: false });
entries.push(ParameterEntry {
name: pi.name.clone(),
identifier: pi.identifier.clone(),
is_rest: pi.is_rest,
is_from_pattern: true,
is_first_from_pattern: false,
});
info_index += 1;
}
}
}
}
self.scope_collector.set_function_parameters(&entries, has_parameter_expressions);
self.scope_collector
.set_function_parameters(&entries, has_parameter_expressions);
}
// === Token access ===
@ -456,7 +492,11 @@ impl<'a> Parser<'a> {
} else {
let start = token.value_start as usize;
let end = start + token.value_len as usize;
if end <= self.source.len() { &self.source[start..end] } else { &[] }
if end <= self.source.len() {
&self.source[start..end]
} else {
&[]
}
};
if value == utf16!("arguments") {
if self.flags.in_class_field_initializer {
@ -488,8 +528,10 @@ impl<'a> Parser<'a> {
// https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
// In strict mode, legacy octal literals (0-prefixed) are not permitted.
let value = self.token_value(&token);
if value.len() > 1 && value[0] == ch(b'0')
&& value[1] >= ch(b'0') && value[1] <= ch(b'9')
if value.len() > 1
&& value[0] == ch(b'0')
&& value[1] >= ch(b'0')
&& value[1] <= ch(b'9')
{
self.syntax_error("Unprefixed octal number not allowed in strict mode");
}
@ -614,7 +656,11 @@ impl<'a> Parser<'a> {
/// Compile a regex pattern+flags and return the opaque compiled handle.
/// On error, reports a syntax error and returns null.
pub(crate) fn compile_regex_pattern(&mut self, pattern: &[u16], flags: &[u16]) -> *mut std::ffi::c_void {
pub(crate) fn compile_regex_pattern(
&mut self,
pattern: &[u16],
flags: &[u16],
) -> *mut std::ffi::c_void {
match crate::bytecode::ffi::compile_regex(pattern, flags) {
Ok(handle) => handle,
Err(msg) => {
@ -625,15 +671,30 @@ impl<'a> Parser<'a> {
}
pub(crate) fn validate_regex_flags(&mut self, flags: &[u16]) {
let valid_flags: &[u16] = &[ch(b'd'), ch(b'g'), ch(b'i'), ch(b'm'), ch(b's'), ch(b'u'), ch(b'v'), ch(b'y')];
let valid_flags: &[u16] = &[
ch(b'd'),
ch(b'g'),
ch(b'i'),
ch(b'm'),
ch(b's'),
ch(b'u'),
ch(b'v'),
ch(b'y'),
];
let mut seen = [false; 128];
for &flag in flags {
if flag >= 128 || !valid_flags.contains(&flag) {
self.syntax_error(&format!("Invalid RegExp flag '{}'", char::from_u32(flag as u32).unwrap_or('?')));
self.syntax_error(&format!(
"Invalid RegExp flag '{}'",
char::from_u32(flag as u32).unwrap_or('?')
));
return;
}
if seen[flag as usize] {
self.syntax_error(&format!("Repeated RegExp flag '{}'", char::from_u32(flag as u32).unwrap_or('?')));
self.syntax_error(&format!(
"Repeated RegExp flag '{}'",
char::from_u32(flag as u32).unwrap_or('?')
));
return;
}
seen[flag as usize] = true;
@ -692,14 +753,18 @@ impl<'a> Parser<'a> {
tt == TokenType::Identifier
|| (tt == TokenType::EscapedKeyword && !self.match_invalid_escaped_keyword())
|| (tt == TokenType::Let && !self.flags.strict_mode)
|| (tt == TokenType::Yield && !self.flags.strict_mode && !self.flags.in_generator_function_context)
|| (tt == TokenType::Await && !self.flags.await_expression_is_valid && self.program_type != ProgramType::Module && !self.flags.in_class_static_init_block)
|| (tt == TokenType::Yield
&& !self.flags.strict_mode
&& !self.flags.in_generator_function_context)
|| (tt == TokenType::Await
&& !self.flags.await_expression_is_valid
&& self.program_type != ProgramType::Module
&& !self.flags.in_class_static_init_block)
|| tt == TokenType::Async
}
pub(crate) fn match_identifier_name(&self) -> bool {
self.current_token.token_type.is_identifier_name()
|| self.match_identifier()
self.current_token.token_type.is_identifier_name() || self.match_identifier()
}
pub(crate) fn match_invalid_escaped_keyword(&self) -> bool {
@ -708,7 +773,9 @@ impl<'a> Parser<'a> {
}
let value = self.token_value(&self.current_token);
if value == utf16!("await") {
return self.program_type == ProgramType::Module || self.flags.await_expression_is_valid || self.flags.in_class_static_init_block;
return self.program_type == ProgramType::Module
|| self.flags.await_expression_is_valid
|| self.flags.in_class_static_init_block;
}
if value == utf16!("async") {
return false;
@ -725,13 +792,22 @@ impl<'a> Parser<'a> {
value != utf16!("let") && value != utf16!("static")
}
pub(crate) fn check_identifier_name_for_assignment_validity(&mut self, name: &[u16], force_strict: bool) {
pub(crate) fn check_identifier_name_for_assignment_validity(
&mut self,
name: &[u16],
force_strict: bool,
) {
if self.flags.strict_mode || force_strict {
if name == utf16!("arguments") || name == utf16!("eval") {
self.syntax_error("Binding pattern target may not be called 'arguments' or 'eval' in strict mode");
self.syntax_error(
"Binding pattern target may not be called 'arguments' or 'eval' in strict mode",
);
} else if is_strict_reserved_word(name) {
let name_str = String::from_utf16_lossy(name);
self.syntax_error(&format!("Identifier must not be a reserved word in strict mode ('{}')", name_str));
self.syntax_error(&format!(
"Identifier must not be a reserved word in strict mode ('{}')",
name_str
));
}
}
}
@ -838,10 +914,13 @@ impl<'a> Parser<'a> {
Some(pattern)
}
pub(crate) fn is_simple_assignment_target(expression: &Expression, allow_call_expression: bool) -> bool {
matches!(&expression.inner,
ExpressionKind::Identifier(_)
| ExpressionKind::Member { .. }
pub(crate) fn is_simple_assignment_target(
expression: &Expression,
allow_call_expression: bool,
) -> bool {
matches!(
&expression.inner,
ExpressionKind::Identifier(_) | ExpressionKind::Member { .. }
) || (allow_call_expression && matches!(&expression.inner, ExpressionKind::Call(_)))
}
@ -881,23 +960,29 @@ impl<'a> Parser<'a> {
// Now close it after children are set.
self.scope_collector.set_scope_node(scope.clone());
self.scope_collector.close_scope();
self.statement(start, StatementKind::Program(ProgramData {
scope,
program_type: ProgramType::Script,
is_strict_mode: is_strict,
has_top_level_await: false,
}))
self.statement(
start,
StatementKind::Program(ProgramData {
scope,
program_type: ProgramType::Script,
is_strict_mode: is_strict,
has_top_level_await: false,
}),
)
} else {
let (children, has_top_level_await) = self.parse_module();
let scope = ScopeData::shared_with_children(children);
self.scope_collector.set_scope_node(scope.clone());
self.scope_collector.close_scope();
self.statement(start, StatementKind::Program(ProgramData {
scope,
program_type: ProgramType::Module,
is_strict_mode: true,
has_top_level_await,
}))
self.statement(
start,
StatementKind::Program(ProgramData {
scope,
program_type: ProgramType::Module,
is_strict_mode: true,
has_top_level_await,
}),
)
}
}
@ -984,7 +1069,10 @@ impl<'a> Parser<'a> {
collect_binding_names(&decl.target, &mut declared_names);
}
}
StatementKind::FunctionDeclaration { name: Some(ref name), .. } => {
StatementKind::FunctionDeclaration {
name: Some(ref name),
..
} => {
declared_names.insert(name.name.clone());
}
StatementKind::ClassDeclaration(data) => {
@ -1005,7 +1093,10 @@ impl<'a> Parser<'a> {
collect_binding_names(&decl.target, &mut declared_names);
}
}
StatementKind::FunctionDeclaration { name: Some(ref name), .. } => {
StatementKind::FunctionDeclaration {
name: Some(ref name),
..
} => {
declared_names.insert(name.name.clone());
}
StatementKind::ClassDeclaration(class_data) => {
@ -1066,7 +1157,9 @@ impl<'a> Parser<'a> {
if is_use_strict(raw_value) {
found_use_strict = true;
if self.flags.string_legacy_octal_escape_sequence_in_scope {
self.syntax_error("Octal escape sequence in string literal not allowed in strict mode");
self.syntax_error(
"Octal escape sequence in string literal not allowed in strict mode",
);
}
break;
}
@ -1075,7 +1168,10 @@ impl<'a> Parser<'a> {
(found_use_strict, statements)
}
pub(crate) fn parse_statement_list(&mut self, allow_labelled_functions: bool) -> Vec<Statement> {
pub(crate) fn parse_statement_list(
&mut self,
allow_labelled_functions: bool,
) -> Vec<Statement> {
let mut statements = Vec::new();
while !self.done() {
if self.match_export_or_import() {
@ -1153,8 +1249,10 @@ impl<'a> Parser<'a> {
}
fn match_iteration_start(&self) -> bool {
matches!(self.current_token_type(),
TokenType::For | TokenType::While | TokenType::Do)
matches!(
self.current_token_type(),
TokenType::For | TokenType::While | TokenType::Do
)
}
pub(crate) fn match_export_or_import(&mut self) -> bool {
@ -1163,8 +1261,7 @@ impl<'a> Parser<'a> {
}
if self.match_token(TokenType::Import) {
let next = self.next_token();
return next.token_type != TokenType::ParenOpen
&& next.token_type != TokenType::Period;
return next.token_type != TokenType::ParenOpen && next.token_type != TokenType::Period;
}
false
}
@ -1173,16 +1270,32 @@ impl<'a> Parser<'a> {
pub(crate) fn operator_precedence(tt: TokenType) -> i32 {
match tt {
TokenType::Period | TokenType::BracketOpen | TokenType::ParenOpen | TokenType::QuestionMarkPeriod => 20,
TokenType::Period
| TokenType::BracketOpen
| TokenType::ParenOpen
| TokenType::QuestionMarkPeriod => 20,
TokenType::New => 19,
TokenType::PlusPlus | TokenType::MinusMinus => 18,
TokenType::ExclamationMark | TokenType::Tilde | TokenType::Typeof | TokenType::Void | TokenType::Delete | TokenType::Await => 17,
TokenType::ExclamationMark
| TokenType::Tilde
| TokenType::Typeof
| TokenType::Void
| TokenType::Delete
| TokenType::Await => 17,
TokenType::DoubleAsterisk => 16,
TokenType::Asterisk | TokenType::Slash | TokenType::Percent => 15,
TokenType::Plus | TokenType::Minus => 14,
TokenType::ShiftLeft | TokenType::ShiftRight | TokenType::UnsignedShiftRight => 13,
TokenType::LessThan | TokenType::LessThanEquals | TokenType::GreaterThan | TokenType::GreaterThanEquals | TokenType::In | TokenType::Instanceof => 12,
TokenType::EqualsEquals | TokenType::ExclamationMarkEquals | TokenType::EqualsEqualsEquals | TokenType::ExclamationMarkEqualsEquals => 11,
TokenType::LessThan
| TokenType::LessThanEquals
| TokenType::GreaterThan
| TokenType::GreaterThanEquals
| TokenType::In
| TokenType::Instanceof => 12,
TokenType::EqualsEquals
| TokenType::ExclamationMarkEquals
| TokenType::EqualsEqualsEquals
| TokenType::ExclamationMarkEqualsEquals => 11,
TokenType::Ampersand => 10,
TokenType::Caret => 9,
TokenType::Pipe => 8,
@ -1190,11 +1303,21 @@ impl<'a> Parser<'a> {
TokenType::DoubleAmpersand => 6,
TokenType::DoublePipe => 5,
TokenType::QuestionMark => 4,
TokenType::Equals | TokenType::PlusEquals | TokenType::MinusEquals
| TokenType::DoubleAsteriskEquals | TokenType::AsteriskEquals | TokenType::SlashEquals
| TokenType::PercentEquals | TokenType::ShiftLeftEquals | TokenType::ShiftRightEquals
| TokenType::UnsignedShiftRightEquals | TokenType::AmpersandEquals | TokenType::CaretEquals
| TokenType::PipeEquals | TokenType::DoubleAmpersandEquals | TokenType::DoublePipeEquals
TokenType::Equals
| TokenType::PlusEquals
| TokenType::MinusEquals
| TokenType::DoubleAsteriskEquals
| TokenType::AsteriskEquals
| TokenType::SlashEquals
| TokenType::PercentEquals
| TokenType::ShiftLeftEquals
| TokenType::ShiftRightEquals
| TokenType::UnsignedShiftRightEquals
| TokenType::AmpersandEquals
| TokenType::CaretEquals
| TokenType::PipeEquals
| TokenType::DoubleAmpersandEquals
| TokenType::DoublePipeEquals
| TokenType::DoubleQuestionMarkEquals => 3,
TokenType::Yield => 2,
TokenType::Comma => 1,
@ -1204,16 +1327,38 @@ impl<'a> Parser<'a> {
pub(crate) fn operator_associativity(tt: TokenType) -> Associativity {
match tt {
TokenType::Period | TokenType::BracketOpen | TokenType::ParenOpen | TokenType::QuestionMarkPeriod
| TokenType::Asterisk | TokenType::Slash | TokenType::Percent
| TokenType::Plus | TokenType::Minus
| TokenType::ShiftLeft | TokenType::ShiftRight | TokenType::UnsignedShiftRight
| TokenType::LessThan | TokenType::LessThanEquals | TokenType::GreaterThan | TokenType::GreaterThanEquals
| TokenType::In | TokenType::Instanceof
| TokenType::EqualsEquals | TokenType::ExclamationMarkEquals | TokenType::EqualsEqualsEquals | TokenType::ExclamationMarkEqualsEquals
| TokenType::Typeof | TokenType::Void | TokenType::Delete | TokenType::Await
| TokenType::Ampersand | TokenType::Caret | TokenType::Pipe
| TokenType::DoubleQuestionMark | TokenType::DoubleAmpersand | TokenType::DoublePipe
TokenType::Period
| TokenType::BracketOpen
| TokenType::ParenOpen
| TokenType::QuestionMarkPeriod
| TokenType::Asterisk
| TokenType::Slash
| TokenType::Percent
| TokenType::Plus
| TokenType::Minus
| TokenType::ShiftLeft
| TokenType::ShiftRight
| TokenType::UnsignedShiftRight
| TokenType::LessThan
| TokenType::LessThanEquals
| TokenType::GreaterThan
| TokenType::GreaterThanEquals
| TokenType::In
| TokenType::Instanceof
| TokenType::EqualsEquals
| TokenType::ExclamationMarkEquals
| TokenType::EqualsEqualsEquals
| TokenType::ExclamationMarkEqualsEquals
| TokenType::Typeof
| TokenType::Void
| TokenType::Delete
| TokenType::Await
| TokenType::Ampersand
| TokenType::Caret
| TokenType::Pipe
| TokenType::DoubleQuestionMark
| TokenType::DoubleAmpersand
| TokenType::DoublePipe
| TokenType::Comma => Associativity::Left,
_ => Associativity::Right,
}
@ -1227,7 +1372,10 @@ fn is_use_strict(raw: &[u16]) -> bool {
}
/// Collect all binding names introduced by a variable declarator target.
fn collect_binding_names(target: &crate::ast::VariableDeclaratorTarget, names: &mut HashSet<Utf16String>) {
fn collect_binding_names(
target: &crate::ast::VariableDeclaratorTarget,
names: &mut HashSet<Utf16String>,
) {
match target {
crate::ast::VariableDeclaratorTarget::Identifier(identifier) => {
names.insert(identifier.name.clone());
@ -1239,7 +1387,10 @@ fn collect_binding_names(target: &crate::ast::VariableDeclaratorTarget, names: &
}
/// Collect all binding names from a binding pattern (object or array destructuring).
fn collect_binding_pattern_names(pattern: &crate::ast::BindingPattern, names: &mut HashSet<Utf16String>) {
fn collect_binding_pattern_names(
pattern: &crate::ast::BindingPattern,
names: &mut HashSet<Utf16String>,
) {
for entry in &pattern.entries {
if let Some(ref alias) = entry.alias {
match alias {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -57,7 +57,9 @@ impl<'a> Parser<'a> {
self.syntax_error("Keyword must not contain escaped characters");
}
if self.match_identifier_name() {
if let Some(labelled) = self.try_parse_labelled_statement(allow_labelled_function) {
if let Some(labelled) =
self.try_parse_labelled_statement(allow_labelled_function)
{
return labelled;
}
}
@ -100,13 +102,21 @@ impl<'a> Parser<'a> {
if self.match_token(TokenType::Async) {
let lookahead = self.next_token();
if lookahead.token_type == TokenType::Function && !lookahead.trivia_has_line_terminator {
self.syntax_error("Async function declaration not allowed in single-statement context");
if lookahead.token_type == TokenType::Function && !lookahead.trivia_has_line_terminator
{
self.syntax_error(
"Async function declaration not allowed in single-statement context",
);
}
} else if self.match_token(TokenType::Function) || self.match_token(TokenType::Class) {
let name = self.current_token.token_type.name();
self.syntax_error(&format!("{} declaration not allowed in single-statement context", name));
} else if self.match_token(TokenType::Let) && self.next_token().token_type == TokenType::BracketOpen {
self.syntax_error(&format!(
"{} declaration not allowed in single-statement context",
name
));
} else if self.match_token(TokenType::Let)
&& self.next_token().token_type == TokenType::BracketOpen
{
self.syntax_error("let followed by [ is not allowed in single-statement context");
}
@ -191,10 +201,17 @@ impl<'a> Parser<'a> {
};
if label.is_none() && !self.flags.in_break_context {
self.syntax_error("Unlabeled 'break' not allowed outside of a loop or switch statement");
self.syntax_error(
"Unlabeled 'break' not allowed outside of a loop or switch statement",
);
}
self.statement(start, StatementKind::Break { target_label: label })
self.statement(
start,
StatementKind::Break {
target_label: label,
},
)
}
// https://tc39.es/ecma262/#sec-continue-statement
@ -232,7 +249,12 @@ impl<'a> Parser<'a> {
self.consume_or_insert_semicolon();
self.statement(start, StatementKind::Continue { target_label: label })
self.statement(
start,
StatementKind::Continue {
target_label: label,
},
)
}
fn parse_debugger_statement(&mut self) -> Statement {
@ -258,7 +280,9 @@ impl<'a> Parser<'a> {
let alternate = if self.match_token(TokenType::Else) {
self.consume();
if !self.flags.strict_mode && self.match_token(TokenType::Function) {
Some(Box::new(self.parse_function_declaration_as_block_statement(start)))
Some(Box::new(
self.parse_function_declaration_as_block_statement(start),
))
} else {
Some(Box::new(self.parse_statement(false)))
}
@ -266,11 +290,14 @@ impl<'a> Parser<'a> {
None
};
self.statement(start, StatementKind::If {
test: Box::new(predicate),
consequent: Box::new(consequent),
alternate,
})
self.statement(
start,
StatementKind::If {
test: Box::new(predicate),
consequent: Box::new(consequent),
alternate,
},
)
}
/// Annex B: Parse a function declaration as if wrapped in a synthetic block.
@ -307,10 +334,13 @@ impl<'a> Parser<'a> {
let body = self.parse_loop_body();
self.statement(start, StatementKind::While {
test: Box::new(test),
body: Box::new(body),
})
self.statement(
start,
StatementKind::While {
test: Box::new(test),
body: Box::new(body),
},
)
}
fn parse_do_while_statement(&mut self) -> Statement {
@ -328,10 +358,13 @@ impl<'a> Parser<'a> {
// the regular ASI rules not applying.
self.eat(TokenType::Semicolon);
self.statement(start, StatementKind::DoWhile {
test: Box::new(test),
body: Box::new(body),
})
self.statement(
start,
StatementKind::DoWhile {
test: Box::new(test),
body: Box::new(body),
},
)
}
// https://tc39.es/ecma262/#sec-for-statement
@ -365,15 +398,21 @@ impl<'a> Parser<'a> {
let init_start = self.position();
let is_var_init = self.match_token(TokenType::Var);
let is_using = self.match_for_using_declaration();
let is_let = self.match_token(TokenType::Let) && (self.flags.strict_mode || self.try_match_let_declaration());
let is_declaration = is_var_init || is_using || is_let || self.match_token(TokenType::Const);
let is_let = self.match_token(TokenType::Let)
&& (self.flags.strict_mode || self.try_match_let_declaration());
let is_declaration =
is_var_init || is_using || is_let || self.match_token(TokenType::Const);
let init = if is_using {
LocalForInit::Declaration(self.parse_using_declaration(true))
} else if is_declaration {
LocalForInit::Declaration(self.parse_variable_declaration(true))
} else {
let forbidden = ForbiddenTokens::with_in();
LocalForInit::Expression(self.parse_expression(PRECEDENCE_COMMA, Associativity::Right, forbidden))
LocalForInit::Expression(self.parse_expression(
PRECEDENCE_COMMA,
Associativity::Right,
forbidden,
))
};
// Check for in
@ -393,7 +432,10 @@ impl<'a> Parser<'a> {
if self.for_loop_declaration_has_init {
// https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads
// Annex B: In sloppy mode, a single `var` with an initializer is permitted.
if !(self.for_loop_declaration_is_var && self.for_loop_declaration_count == 1 && !self.flags.strict_mode) {
if !(self.for_loop_declaration_is_var
&& self.for_loop_declaration_count == 1
&& !self.flags.strict_mode)
{
self.syntax_error("Variable initializer not allowed in for..in/of");
}
}
@ -407,12 +449,15 @@ impl<'a> Parser<'a> {
let body = self.parse_loop_body();
let lhs = self.synthesize_for_in_of_lhs(init, init_start);
let result = self.statement(forin_start, StatementKind::ForInOf {
kind: ForInOfKind::ForIn,
lhs,
rhs: Box::new(rhs),
body: Box::new(body),
});
let result = self.statement(
forin_start,
StatementKind::ForInOf {
kind: ForInOfKind::ForIn,
lhs,
rhs: Box::new(rhs),
body: Box::new(body),
},
);
return self.close_for_loop_scope(start, result);
}
@ -449,20 +494,31 @@ impl<'a> Parser<'a> {
let body = self.parse_loop_body();
let lhs = self.synthesize_for_in_of_lhs(init, init_start);
let for_of_kind = if is_await { ForInOfKind::ForAwaitOf } else { ForInOfKind::ForOf };
let result = self.statement(forof_start, StatementKind::ForInOf {
kind: for_of_kind,
lhs,
rhs: Box::new(rhs),
body: Box::new(body),
});
let for_of_kind = if is_await {
ForInOfKind::ForAwaitOf
} else {
ForInOfKind::ForOf
};
let result = self.statement(
forof_start,
StatementKind::ForInOf {
kind: for_of_kind,
lhs,
rhs: Box::new(rhs),
body: Box::new(body),
},
);
return self.close_for_loop_scope(start, result);
}
}
// Standard for loop — const requires initializer.
if let LocalForInit::Declaration(ref declaration) = init {
if let StatementKind::VariableDeclaration { kind: DeclarationKind::Const, ref declarations } = declaration.inner {
if let StatementKind::VariableDeclaration {
kind: DeclarationKind::Const,
ref declarations,
} = declaration.inner
{
for d in declarations {
if d.init.is_none() {
self.syntax_error("Missing initializer in const declaration");
@ -472,7 +528,9 @@ impl<'a> Parser<'a> {
}
self.consume_token(TokenType::Semicolon);
let for_init = match init {
LocalForInit::Declaration(declaration) => Some(ForInit::Declaration(Box::new(declaration))),
LocalForInit::Declaration(declaration) => {
Some(ForInit::Declaration(Box::new(declaration)))
}
LocalForInit::Expression(expression) => Some(ForInit::Expression(Box::new(expression))),
};
let result = self.parse_standard_for_loop(start, for_init);
@ -505,12 +563,15 @@ impl<'a> Parser<'a> {
let body = self.parse_loop_body();
self.statement(start, StatementKind::For {
init,
test,
update,
body: Box::new(body),
})
self.statement(
start,
StatementKind::For {
init,
test,
update,
body: Box::new(body),
},
)
}
// https://tc39.es/ecma262/#sec-with-statement
@ -524,10 +585,13 @@ impl<'a> Parser<'a> {
self.scope_collector.open_with_scope(None);
let body = self.parse_statement(false);
self.scope_collector.close_scope();
self.statement(start, StatementKind::With {
object: Box::new(object),
body: Box::new(body),
})
self.statement(
start,
StatementKind::With {
object: Box::new(object),
body: Box::new(body),
},
)
}
// https://tc39.es/ecma262/#sec-switch-statement
@ -567,11 +631,14 @@ impl<'a> Parser<'a> {
self.scope_collector.set_scope_node(scope.clone());
self.scope_collector.close_scope();
self.statement(start, StatementKind::Switch(SwitchStatementData {
scope,
discriminant: Box::new(discriminant),
cases,
}))
self.statement(
start,
StatementKind::Switch(SwitchStatementData {
scope,
discriminant: Box::new(discriminant),
cases,
}),
)
}
fn parse_switch_case(&mut self) -> SwitchCase {
@ -637,11 +704,14 @@ impl<'a> Parser<'a> {
self.syntax_error("try statement must have a catch or finally clause");
}
self.statement(start, StatementKind::Try(TryStatementData {
block: Box::new(block),
handler,
finalizer,
}))
self.statement(
start,
StatementKind::Try(TryStatementData {
block: Box::new(block),
handler,
finalizer,
}),
)
}
// https://tc39.es/ecma262/#sec-try-statement
@ -656,19 +726,31 @@ impl<'a> Parser<'a> {
let parameter = if self.match_token(TokenType::ParenOpen) {
self.consume();
let parameter = if self.match_token(TokenType::CurlyOpen) || self.match_token(TokenType::BracketOpen) {
let parameter = if self.match_token(TokenType::CurlyOpen)
|| self.match_token(TokenType::BracketOpen)
{
self.pattern_bound_names.clear();
let pattern = self.parse_binding_pattern();
let names_to_check: Vec<Utf16String> = self.pattern_bound_names.iter().map(|(n, _)| n.clone()).collect();
let names_to_check: Vec<Utf16String> = self
.pattern_bound_names
.iter()
.map(|(n, _)| n.clone())
.collect();
for name in &names_to_check {
self.check_identifier_name_for_assignment_validity(name, false);
}
let bound_names: Vec<&[u16]> = self.pattern_bound_names.iter().map(|(n, _)| n.as_slice()).collect();
self.scope_collector.add_catch_parameter_pattern(&bound_names);
let bound_names: Vec<&[u16]> = self
.pattern_bound_names
.iter()
.map(|(n, _)| n.as_slice())
.collect();
self.scope_collector
.add_catch_parameter_pattern(&bound_names);
// Register each binding pattern identifier for scope analysis
// so they get is_local() annotations (matching variable declarations).
for (name, id) in &self.pattern_bound_names {
self.scope_collector.register_identifier(id.clone(), name, None);
self.scope_collector
.register_identifier(id.clone(), name, None);
}
Some(CatchBinding::BindingPattern(pattern))
} else if self.match_identifier() {
@ -676,9 +758,14 @@ impl<'a> Parser<'a> {
let token = self.consume();
let value = self.token_value(&token).to_vec();
self.check_identifier_name_for_assignment_validity(&value, false);
let id = Rc::new(Identifier::new(self.range_from(parameter_start), value.clone().into()));
self.scope_collector.register_identifier(id.clone(), &value, None);
self.scope_collector.add_catch_parameter_identifier(&value, id.clone());
let id = Rc::new(Identifier::new(
self.range_from(parameter_start),
value.clone().into(),
));
self.scope_collector
.register_identifier(id.clone(), &value, None);
self.scope_collector
.add_catch_parameter_identifier(&value, id.clone());
Some(CatchBinding::Identifier(id))
} else {
self.expected("catch parameter");
@ -723,7 +810,9 @@ impl<'a> Parser<'a> {
self.discard_saved_state();
self.consume(); // consume :
if self.flags.strict_mode && (label == utf16!("let") || crate::parser::is_strict_reserved_word(&label)) {
if self.flags.strict_mode
&& (label == utf16!("let") || crate::parser::is_strict_reserved_word(&label))
{
self.syntax_error("Strict mode reserved word is not allowed in label");
}
if self.flags.in_generator_function_context && label == utf16!("yield") {
@ -738,7 +827,9 @@ impl<'a> Parser<'a> {
self.syntax_error(&format!("Label '{}' has already been declared", label_str));
}
if self.match_token(TokenType::Function) && (!allow_labelled_function || self.flags.strict_mode) {
if self.match_token(TokenType::Function)
&& (!allow_labelled_function || self.flags.strict_mode)
{
self.syntax_error("Not allowed to declare a function here");
}
if self.match_token(TokenType::Async) {
@ -760,10 +851,14 @@ impl<'a> Parser<'a> {
if let StatementKind::FunctionDeclaration { kind, .. } = fn_decl.inner {
match kind {
FunctionKind::Generator | FunctionKind::AsyncGenerator => {
self.syntax_error("Generator functions cannot be defined in labelled statements");
self.syntax_error(
"Generator functions cannot be defined in labelled statements",
);
}
FunctionKind::Async => {
self.syntax_error("Async functions cannot be defined in labelled statements");
self.syntax_error(
"Async functions cannot be defined in labelled statements",
);
}
_ => {}
}
@ -778,7 +873,9 @@ impl<'a> Parser<'a> {
if let Some(Some((line, col))) = self.labels_in_scope.get(label.as_slice()) {
self.syntax_error_at(
"labelled continue statement cannot use non iterating statement",
*line, *col);
*line,
*col,
);
}
}
@ -786,10 +883,13 @@ impl<'a> Parser<'a> {
self.flags.in_break_context = break_before;
self.last_inner_label_is_iteration = is_iteration;
Some(self.statement(start, StatementKind::Labelled {
label,
item: Box::new(body),
}))
Some(self.statement(
start,
StatementKind::Labelled {
label,
item: Box::new(body),
},
))
}
fn match_for_using_declaration(&mut self) -> bool {
@ -817,9 +917,12 @@ impl<'a> Parser<'a> {
/// Validate that an expression-form LHS is valid for for-in/for-of.
fn validate_for_in_of_lhs(&mut self, init: &LocalForInit) {
if let LocalForInit::Expression(ref expression) = *init {
if !Self::is_identifier(expression) && !Self::is_member_expression(expression)
if !Self::is_identifier(expression)
&& !Self::is_member_expression(expression)
&& !Self::is_call_expression(expression)
&& !Self::is_object_expression(expression) && !Self::is_array_expression(expression) {
&& !Self::is_object_expression(expression)
&& !Self::is_array_expression(expression)
{
self.syntax_error("Invalid left-hand side in for-loop");
}
}
@ -829,9 +932,12 @@ impl<'a> Parser<'a> {
/// pattern when the LHS is an array or object expression.
fn synthesize_for_in_of_lhs(&mut self, init: LocalForInit, init_start: Position) -> ForInOfLhs {
match init {
LocalForInit::Declaration(declaration) => ForInOfLhs::Declaration(Box::new(declaration)),
LocalForInit::Declaration(declaration) => {
ForInOfLhs::Declaration(Box::new(declaration))
}
LocalForInit::Expression(expression) => {
if Self::is_array_expression(&expression) || Self::is_object_expression(&expression) {
if Self::is_array_expression(&expression) || Self::is_object_expression(&expression)
{
if let Some(pattern) = self.synthesize_binding_pattern(init_start) {
for (name, id) in self.pattern_bound_names.drain(..) {
self.scope_collector.register_identifier(id, &name, None);

View file

@ -54,8 +54,8 @@ use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use crate::ast::{
FunctionScopeData, Identifier, LocalBinding, LocalVarKind,
LocalVariable, ScopeData, Utf16String, VarToInit,
FunctionScopeData, Identifier, LocalBinding, LocalVarKind, LocalVariable, ScopeData,
Utf16String, VarToInit,
};
use crate::parser::{DeclarationKind, FunctionKind, ProgramType};
use crate::u32_from_usize;
@ -216,7 +216,11 @@ struct ScopeRecord {
}
impl ScopeRecord {
fn new(scope_type: ScopeType, scope_level: ScopeLevel, scope_data: Option<Rc<RefCell<ScopeData>>>) -> Self {
fn new(
scope_type: ScopeType,
scope_level: ScopeLevel,
scope_data: Option<Rc<RefCell<ScopeData>>>,
) -> Self {
Self {
scope_type,
scope_level,
@ -251,7 +255,9 @@ impl ScopeRecord {
}
fn has_flag(&self, name: &[u16], flags: VarFlags) -> bool {
self.variables.get(name).is_some_and(|v| v.flags.intersects(flags))
self.variables
.get(name)
.is_some_and(|v| v.flags.intersects(flags))
}
fn get_parameter_index(&self, name: &[u16]) -> Option<u32> {
@ -266,7 +272,9 @@ impl ScopeRecord {
}
fn has_rest_parameter_with_name(&self, name: &[u16]) -> bool {
self.parameter_names.iter().any(|param| param.is_rest && param.name == name)
self.parameter_names
.iter()
.any(|param| param.is_rest && param.name == name)
}
fn has_hoistable_function_named(&self, name: &[u16]) -> bool {
@ -281,7 +289,8 @@ fn ancestor_scopes(start: usize, records: &[ScopeRecord]) -> impl Iterator<Item
fn last_function_scope(index: usize, records: &[ScopeRecord]) -> Option<usize> {
ancestor_scopes(index, records).find(|&i| {
records[i].scope_type == ScopeType::Function || records[i].scope_type == ScopeType::ClassStaticInit
records[i].scope_type == ScopeType::Function
|| records[i].scope_type == ScopeType::ClassStaticInit
})
}
@ -339,7 +348,10 @@ impl ScopeCollector {
fn already_declared_error(&mut self, name: &[u16], line: u32, column: u32) {
self.errors.push(ScopeError {
message: format!("Identifier '{}' already declared", String::from_utf16_lossy(name)),
message: format!(
"Identifier '{}' already declared",
String::from_utf16_lossy(name)
),
line,
column,
});
@ -383,7 +395,9 @@ impl ScopeCollector {
self.errors.truncate(state.errors_len);
// Remove any child indices that pointed to now-truncated records.
if let Some(current_index) = self.current {
self.records[current_index].children.retain(|&c| c < saved_len);
self.records[current_index]
.children
.retain(|&c| c < saved_len);
}
// Restore flags on ancestor function scopes that may have been
// modified by set_uses_this() or set_uses_new_target() during
@ -391,14 +405,20 @@ impl ScopeCollector {
for saved in &state.saved_flags {
if saved.index < self.records.len() {
self.records[saved.index].uses_this = saved.uses_this;
self.records[saved.index].uses_this_from_environment = saved.uses_this_from_environment;
self.records[saved.index].uses_this_from_environment =
saved.uses_this_from_environment;
}
}
}
// === Open/close scopes ===
fn open_scope(&mut self, scope_type: ScopeType, scope_data: Option<Rc<RefCell<ScopeData>>>, scope_level: ScopeLevel) {
fn open_scope(
&mut self,
scope_type: ScopeType,
scope_data: Option<Rc<RefCell<ScopeData>>>,
scope_level: ScopeLevel,
) {
let index = self.records.len();
let mut record = ScopeRecord::new(scope_type, scope_level, scope_data);
record.parent = self.current;
@ -433,7 +453,8 @@ impl ScopeCollector {
let arguments = c.contains_access_to_arguments_object_in_non_strict_mode;
let eval = c.contains_direct_call_to_eval;
let contains_await = c.contains_await_expression;
self.records[parent_index].contains_access_to_arguments_object_in_non_strict_mode |= arguments;
self.records[parent_index]
.contains_access_to_arguments_object_in_non_strict_mode |= arguments;
self.records[parent_index].contains_direct_call_to_eval |= eval;
self.records[parent_index].contains_await_expression |= contains_await;
}
@ -480,7 +501,11 @@ impl ScopeCollector {
}
pub fn open_static_init_scope(&mut self, scope_data: Option<Rc<RefCell<ScopeData>>>) {
self.open_scope(ScopeType::ClassStaticInit, scope_data, ScopeLevel::StaticInitTopLevel);
self.open_scope(
ScopeType::ClassStaticInit,
scope_data,
ScopeLevel::StaticInitTopLevel,
);
}
pub fn open_class_field_scope(&mut self, scope_data: Option<Rc<RefCell<ScopeData>>>) {
@ -511,7 +536,12 @@ impl ScopeCollector {
for name in bound_names {
let flags = self.records[index].variable(name).flags;
if flags.intersects(VarFlags::VAR | VarFlags::FORBIDDEN_LEXICAL | VarFlags::FUNCTION | VarFlags::LEXICAL) {
if flags.intersects(
VarFlags::VAR
| VarFlags::FORBIDDEN_LEXICAL
| VarFlags::FUNCTION
| VarFlags::LEXICAL,
) {
self.already_declared_error(name, declaration_line, declaration_column);
}
self.records[index].variable(name).flags |= VarFlags::LEXICAL;
@ -541,7 +571,9 @@ impl ScopeCollector {
let mut scope_index = index;
loop {
let existing_flags = self.records[scope_index].variable(name).flags;
if existing_flags.intersects(VarFlags::LEXICAL | VarFlags::FUNCTION | VarFlags::FORBIDDEN_VAR) {
if existing_flags
.intersects(VarFlags::LEXICAL | VarFlags::FUNCTION | VarFlags::FORBIDDEN_VAR)
{
self.already_declared_error(name, declaration_line, declaration_column);
}
let var = self.records[scope_index].variable(name);
@ -550,7 +582,9 @@ impl ScopeCollector {
if self.records[scope_index].is_top_level() {
break;
}
scope_index = self.records[scope_index].parent.expect("scope has no parent");
scope_index = self.records[scope_index]
.parent
.expect("scope has no parent");
}
}
}
@ -586,7 +620,10 @@ impl ScopeCollector {
} else {
// Check flags first, then modify. This avoids borrow checker issues
// since we need to access both variables and functions_to_hoist.
let existing_flags = self.records[index].variables.get(name).map_or(VarFlags::EMPTY, |v| v.flags);
let existing_flags = self.records[index]
.variables
.get(name)
.map_or(VarFlags::EMPTY, |v| v.flags);
if existing_flags.intersects(VarFlags::VAR | VarFlags::LEXICAL) {
self.already_declared_error(name, declaration_line, declaration_column);
@ -602,10 +639,12 @@ impl ScopeCollector {
if !existing_flags.intersects(VarFlags::LEXICAL) {
let block_scope = self.records[index].scope_data.clone();
self.records[index].functions_to_hoist.push(HoistableFunction {
name: Utf16String::from(name),
block_scope_data: block_scope,
});
self.records[index]
.functions_to_hoist
.push(HoistableFunction {
name: Utf16String::from(name),
block_scope_data: block_scope,
});
}
let var = self.records[index].variable(name);
@ -634,9 +673,15 @@ impl ScopeCollector {
// === Identifier registration ===
pub fn register_identifier(&mut self, id: Rc<Identifier>, name: &[u16], declaration_kind: Option<DeclarationKind>) {
pub fn register_identifier(
&mut self,
id: Rc<Identifier>,
name: &[u16],
declaration_kind: Option<DeclarationKind>,
) {
let index = self.current.expect("no current scope");
self.records[index].identifier_groups
self.records[index]
.identifier_groups
.entry(Utf16String::from(name))
.and_modify(|group| {
group.identifiers.push(id.clone());
@ -669,16 +714,25 @@ impl ScopeCollector {
// Placeholder for a pattern parameter — push an empty
// entry so subsequent parameters get the correct
// positional index. Don't register anything else.
self.records[index].parameter_names.push(ParameterName { name: Utf16String::default(), is_rest: false });
self.records[index].parameter_names.push(ParameterName {
name: Utf16String::default(),
is_rest: false,
});
continue;
}
} else {
self.records[index].parameter_names.push(ParameterName { name: entry.name.clone(), is_rest: entry.is_rest });
self.records[index].parameter_names.push(ParameterName {
name: entry.name.clone(),
is_rest: entry.is_rest,
});
}
if let Some(ref id) = entry.identifier {
self.register_identifier(id.clone(), &entry.name, None);
}
let var = self.records[index].variables.entry(entry.name.clone()).or_default();
let var = self.records[index]
.variables
.entry(entry.name.clone())
.or_default();
var.flags |= VarFlags::PARAMETER_CANDIDATE | VarFlags::FORBIDDEN_LEXICAL;
}
@ -687,12 +741,15 @@ impl ScopeCollector {
// declares the same name, it must not be optimized to a local, since the
// default expression needs to resolve it from the outer scope.
if has_parameter_expressions {
let names_to_mark: Vec<Utf16String> = self.records[index].identifier_groups.keys()
let names_to_mark: Vec<Utf16String> = self.records[index]
.identifier_groups
.keys()
.filter(|name| !self.records[index].has_flag(name, VarFlags::FORBIDDEN_LEXICAL))
.cloned()
.collect();
for name in names_to_mark {
self.records[index].variable(&name).flags |= VarFlags::REFERENCED_IN_FORMAL_PARAMETERS;
self.records[index].variable(&name).flags |=
VarFlags::REFERENCED_IN_FORMAL_PARAMETERS;
}
}
}
@ -778,19 +835,23 @@ impl ScopeCollector {
// === Getters ===
pub fn contains_direct_call_to_eval(&self) -> bool {
self.current.is_some_and(|index| self.records[index].contains_direct_call_to_eval)
self.current
.is_some_and(|index| self.records[index].contains_direct_call_to_eval)
}
pub fn uses_this_from_environment(&self) -> bool {
self.current.is_some_and(|index| self.records[index].uses_this_from_environment)
self.current
.is_some_and(|index| self.records[index].uses_this_from_environment)
}
pub fn uses_this(&self) -> bool {
self.current.is_some_and(|index| self.records[index].uses_this)
self.current
.is_some_and(|index| self.records[index].uses_this)
}
pub fn contains_await_expression(&self) -> bool {
self.current.is_some_and(|index| self.records[index].contains_await_expression)
self.current
.is_some_and(|index| self.records[index].contains_await_expression)
}
pub fn scope_type(&self) -> Option<ScopeType> {
@ -798,7 +859,8 @@ impl ScopeCollector {
}
pub fn can_have_using_declaration(&self) -> bool {
self.current.is_some_and(|index| self.records[index].scope_level != ScopeLevel::ScriptTopLevel)
self.current
.is_some_and(|index| self.records[index].scope_level != ScopeLevel::ScriptTopLevel)
}
pub fn has_declaration(&self, name: &[u16]) -> bool {
@ -812,14 +874,19 @@ impl ScopeCollector {
}
pub fn has_declaration_in_current_function(&self, name: &[u16]) -> bool {
let Some(index) = self.current else { return false };
let Some(index) = self.current else {
return false;
};
let fn_scope = last_function_scope(index, &self.records);
let stop = fn_scope.and_then(|fi| self.records[fi].parent);
for si in ancestor_scopes(index, &self.records) {
if Some(si) == stop {
break;
}
if self.records[si].has_flag(name, VarFlags::LEXICAL | VarFlags::VAR | VarFlags::PARAMETER_CANDIDATE) {
if self.records[si].has_flag(
name,
VarFlags::LEXICAL | VarFlags::VAR | VarFlags::PARAMETER_CANDIDATE,
) {
return true;
}
if self.records[si].has_hoistable_function_named(name) {
@ -867,7 +934,12 @@ impl ScopeCollector {
// 1. Propagate eval() flags from children to parent.
Self::propagate_eval_poisoning(&mut self.records, index);
// 2. Match identifier references to declarations; optimize as locals.
Self::resolve_identifiers(&mut self.records, index, initiated_by_eval, suppress_globals);
Self::resolve_identifiers(
&mut self.records,
index,
initiated_by_eval,
suppress_globals,
);
// 3. Annex B: hoist block-scoped functions to enclosing function scope.
Self::hoist_functions(&mut self.records, index);
@ -875,7 +947,8 @@ impl ScopeCollector {
// the bytecode generator uses to initialize function-scoped variables.
if self.records[index].scope_data.is_some() {
let st = self.records[index].scope_type;
let needs_fsd = (st == ScopeType::Function && self.records[index].has_function_parameters)
let needs_fsd = (st == ScopeType::Function
&& self.records[index].has_function_parameters)
|| st == ScopeType::ClassStaticInit
|| st == ScopeType::ClassField;
if needs_fsd {
@ -894,12 +967,16 @@ impl ScopeCollector {
/// function (propagates through blocks but stops at function boundaries)
fn propagate_eval_poisoning(records: &mut [ScopeRecord], index: usize) {
if let Some(parent_index) = records[index].parent {
if records[index].contains_direct_call_to_eval || records[index].poisoned_by_eval_in_scope_chain {
if records[index].contains_direct_call_to_eval
|| records[index].poisoned_by_eval_in_scope_chain
{
records[parent_index].poisoned_by_eval_in_scope_chain = true;
}
// eval_in_current_function propagates upward through blocks but
// stops at function boundaries (each function is independent).
if records[index].eval_in_current_function && records[index].scope_type != ScopeType::Function {
if records[index].eval_in_current_function
&& records[index].scope_type != ScopeType::Function
{
records[parent_index].eval_in_current_function = true;
}
}
@ -917,7 +994,12 @@ impl ScopeCollector {
/// - It's NOT captured by a nested function
/// - It's NOT used inside a `with` statement
/// - The scope chain is NOT poisoned by `eval()`
fn resolve_identifiers(records: &mut [ScopeRecord], index: usize, initiated_by_eval: bool, suppress_globals: bool) {
fn resolve_identifiers(
records: &mut [ScopeRecord],
index: usize,
initiated_by_eval: bool,
suppress_globals: bool,
) {
let groups = std::mem::take(&mut records[index].identifier_groups);
// Sort groups by name for deterministic local variable indices
// (HashMap iteration order is arbitrary).
@ -933,7 +1015,10 @@ impl ScopeCollector {
}
}
let var_flags = records[index].variables.get(&name).map_or(VarFlags::EMPTY, |v| v.flags);
let var_flags = records[index]
.variables
.get(&name)
.map_or(VarFlags::EMPTY, |v| v.flags);
// Determine what kind of local variable this is (if any).
// Priority: var (at top-level) > let/const > function declaration.
@ -978,7 +1063,8 @@ impl ScopeCollector {
&& !var_flags.intersects(VarFlags::FORBIDDEN_LEXICAL)
{
if let Some(parent_index) = records[index].parent {
records[parent_index].identifier_groups
records[parent_index]
.identifier_groups
.entry(name.clone())
.or_insert_with(|| IdentifierGroup {
captured_by_nested_function: false,
@ -1031,7 +1117,8 @@ impl ScopeCollector {
}
if records[index].scope_type == ScopeType::Program {
let can_use_global = !(suppress_globals || group.used_inside_with_statement || initiated_by_eval);
let can_use_global =
!(suppress_globals || group.used_inside_with_statement || initiated_by_eval);
if can_use_global {
for id in &group.identifiers {
if !id.is_inside_scope_with_eval.get() {
@ -1054,7 +1141,8 @@ impl ScopeCollector {
&& !var_flags.intersects(VarFlags::FORBIDDEN_LEXICAL)
{
if let Some(parent_index) = records[index].parent {
records[parent_index].identifier_groups
records[parent_index]
.identifier_groups
.entry(name.clone())
.or_insert_with(|| IdentifierGroup {
captured_by_nested_function: false,
@ -1102,7 +1190,8 @@ impl ScopeCollector {
}
}
} else {
let kind = local_var_kind.expect("local_var_kind must be set for local variables");
let kind = local_var_kind
.expect("local_var_kind must be set for local variables");
let lvi = u32_from_usize(sd.local_variables.len());
sd.local_variables.push(LocalVariable {
name: name.clone(),
@ -1167,7 +1256,9 @@ impl ScopeCollector {
None => return,
};
let has_argument_parameter = record.variables.get(utf16!("arguments") as &[u16])
let has_argument_parameter = record
.variables
.get(utf16!("arguments") as &[u16])
.is_some_and(|v| v.flags.intersects(VarFlags::FORBIDDEN_LEXICAL));
let mut vars_to_initialize = Vec::new();
@ -1184,11 +1275,13 @@ impl ScopeCollector {
{
let sd = scope_data.borrow();
for i in (0..sd.children.len()).rev() {
if let crate::ast::StatementKind::FunctionDeclaration { name: Some(ref name_ident), .. } = sd.children[i].inner {
if let crate::ast::StatementKind::FunctionDeclaration {
name: Some(ref name_ident),
..
} = sd.children[i].inner
{
if seen_function_names.insert(name_ident.name.clone()) {
functions_to_initialize.push(crate::ast::FunctionToInit {
child_index: i,
});
functions_to_initialize.push(crate::ast::FunctionToInit { child_index: i });
}
}
}
@ -1207,7 +1300,10 @@ impl ScopeCollector {
let local_info = if let Some(ref ident) = var.var_identifier {
if ident.is_local() {
Some(LocalBinding {
local_type: ident.local_type.get().expect("is_local() implies local_type is Some"),
local_type: ident
.local_type
.get()
.expect("is_local() implies local_type is Some"),
index: ident.local_index.get(),
})
} else {
@ -1230,7 +1326,6 @@ impl ScopeCollector {
is_function_name,
local: local_info,
});
}
// Sort by name for deterministic output (HashMap iteration order is arbitrary).
@ -1241,7 +1336,9 @@ impl ScopeCollector {
has_function_named_arguments = true;
}
if record.variables.get(utf16!("arguments") as &[u16])
if record
.variables
.get(utf16!("arguments") as &[u16])
.is_some_and(|v| v.flags.intersects(VarFlags::LEXICAL))
{
has_lexically_declared_arguments = true;
@ -1266,8 +1363,8 @@ impl ScopeCollector {
// during lazy compilation (write_sfd_metadata, FDI emission).
sd.uses_this = record.uses_this;
sd.uses_this_from_environment = record.uses_this_from_environment;
sd.contains_direct_call_to_eval = record.contains_direct_call_to_eval
|| record.poisoned_by_eval_in_scope_chain;
sd.contains_direct_call_to_eval =
record.contains_direct_call_to_eval || record.poisoned_by_eval_in_scope_chain;
sd.contains_access_to_arguments_object =
record.contains_access_to_arguments_object_in_non_strict_mode;
}
@ -1294,7 +1391,8 @@ impl ScopeCollector {
for function in functions {
// A let/const or forbidden var with the same name blocks hoisting.
if records[index].has_flag(&function.name, VarFlags::LEXICAL | VarFlags::FORBIDDEN_VAR) {
if records[index].has_flag(&function.name, VarFlags::LEXICAL | VarFlags::FORBIDDEN_VAR)
{
continue;
}
@ -1324,7 +1422,9 @@ impl ScopeCollector {
let bs = block_scope.borrow();
for child in &bs.children {
if let crate::ast::StatementKind::FunctionDeclaration {
ref name, ref is_hoisted, ..
ref name,
ref is_hoisted,
..
} = child.inner
{
if name.as_ref().is_some_and(|n| n.name == function.name) {
@ -1335,7 +1435,9 @@ impl ScopeCollector {
}
} else if let Some(parent_index) = records[index].parent {
// Not yet at top level — keep propagating upward unless blocked.
if !records[parent_index].has_flag(&function.name, VarFlags::LEXICAL | VarFlags::FUNCTION) {
if !records[parent_index]
.has_flag(&function.name, VarFlags::LEXICAL | VarFlags::FUNCTION)
{
records[parent_index].functions_to_hoist.push(function);
}
}