LibJS: Rewrite eval declaration instantiation using metadata
Change eval_declaration_instantiation to take EvalDeclarationData& instead of Program const&. The function body now iterates pre-computed name lists instead of walking the AST. Both callers (perform_eval and perform_shadow_realm_eval) now build EvalDeclarationData before calling eval_declaration_instantiation. This decouples the runtime declaration-instantiation API from AST types, matching the pattern already used by Script for global declaration instantiation.
This commit is contained in:
parent
87b9795d75
commit
680487fa9a
3 changed files with 65 additions and 113 deletions
|
|
@ -723,7 +723,8 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
|
|||
// NOTE: Spec steps are rearranged in order to compute number of registers+constants+locals before construction of the execution context.
|
||||
|
||||
// 30. Let result be Completion(EvalDeclarationInstantiation(body, varEnv, lexEnv, privateEnv, strictEval)).
|
||||
TRY(eval_declaration_instantiation(vm, program, variable_environment, lexical_environment, private_environment, strict_eval));
|
||||
auto eval_declaration_data = EvalDeclarationData::create(vm, program, strict_eval);
|
||||
TRY(eval_declaration_instantiation(vm, eval_declaration_data, variable_environment, lexical_environment, private_environment, strict_eval));
|
||||
|
||||
// 31. If result.[[Type]] is normal, then
|
||||
// a. Set result to the result of evaluating body.
|
||||
|
|
@ -827,7 +828,7 @@ EvalDeclarationData EvalDeclarationData::create(VM& vm, Program const& program,
|
|||
|
||||
// 19.2.1.3 EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict ), https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
|
||||
// 9.1.1.1 EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict ), https://tc39.es/proposal-explicit-resource-management/#sec-evaldeclarationinstantiation
|
||||
ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& program, Environment* variable_environment, Environment* lexical_environment, PrivateEnvironment* private_environment, bool strict)
|
||||
ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, EvalDeclarationData& data, Environment* variable_environment, Environment* lexical_environment, PrivateEnvironment* private_environment, bool strict)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
GlobalEnvironment* global_var_environment = variable_environment->is_global_environment() ? static_cast<GlobalEnvironment*>(variable_environment) : nullptr;
|
||||
|
|
@ -839,16 +840,13 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
// a. If varEnv is a global Environment Record, then
|
||||
if (global_var_environment) {
|
||||
// i. For each element name of varNames, do
|
||||
TRY(program.for_each_var_declared_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
|
||||
auto const& name = identifier.string();
|
||||
|
||||
for (auto const& name : data.var_names) {
|
||||
// 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
|
||||
if (global_var_environment->has_lexical_declaration(name))
|
||||
return vm.throw_completion<SyntaxError>(ErrorType::TopLevelVariableAlreadyDeclared, identifier.string());
|
||||
return vm.throw_completion<SyntaxError>(ErrorType::TopLevelVariableAlreadyDeclared, name);
|
||||
|
||||
// 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration.
|
||||
return {};
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// b. Let thisEnv be lexEnv.
|
||||
|
|
@ -861,9 +859,7 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
if (!is<ObjectEnvironment>(*this_environment)) {
|
||||
// 1. NOTE: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts.
|
||||
// 2. For each element name of varNames, do
|
||||
TRY(program.for_each_var_declared_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
|
||||
auto const& name = identifier.string();
|
||||
|
||||
for (auto const& name : data.var_names) {
|
||||
// a. If ! thisEnv.HasBinding(name) is true, then
|
||||
if (MUST(this_environment->has_binding(name))) {
|
||||
// i. Throw a SyntaxError exception.
|
||||
|
|
@ -873,8 +869,7 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
// In particular it only throw the syntax error if it is not an environment from a catchclause.
|
||||
}
|
||||
// b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration.
|
||||
return {};
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// ii. Set thisEnv to thisEnv.[[OuterEnv]].
|
||||
|
|
@ -893,44 +888,19 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
// FIXME: Add Private identifiers check here.
|
||||
|
||||
// 8. Let functionsToInitialize be a new empty List.
|
||||
Vector<FunctionDeclaration const&> functions_to_initialize;
|
||||
|
||||
// 9. Let declaredFunctionNames be a new empty List.
|
||||
HashTable<Utf16FlyString> declared_function_names;
|
||||
|
||||
// 10. For each element d of varDeclarations, in reverse List order, do
|
||||
TRY(program.for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) -> ThrowCompletionOr<void> {
|
||||
auto function_name = function.name();
|
||||
|
||||
// a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then
|
||||
// i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
|
||||
// Note: This is done by for_each_var_function_declaration_in_reverse_order.
|
||||
|
||||
// ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
|
||||
// iii. Let fn be the sole element of the BoundNames of d.
|
||||
// iv. If fn is not an element of declaredFunctionNames, then
|
||||
if (declared_function_names.set(function_name) != AK::HashSetResult::InsertedNewEntry)
|
||||
return {};
|
||||
|
||||
for (auto const& function : data.functions_to_initialize) {
|
||||
// 1. If varEnv is a global Environment Record, then
|
||||
if (global_var_environment) {
|
||||
// a. Let fnDefinable be ? varEnv.CanDeclareGlobalFunction(fn).
|
||||
auto function_definable = TRY(global_var_environment->can_declare_global_function(function_name));
|
||||
auto function_definable = TRY(global_var_environment->can_declare_global_function(function.name));
|
||||
|
||||
// b. If fnDefinable is false, throw a TypeError exception.
|
||||
if (!function_definable)
|
||||
return vm.throw_completion<TypeError>(ErrorType::CannotDeclareGlobalFunction, function_name);
|
||||
return vm.throw_completion<TypeError>(ErrorType::CannotDeclareGlobalFunction, function.name);
|
||||
}
|
||||
|
||||
// 2. Append fn to declaredFunctionNames.
|
||||
// Note: Already done in step iv.
|
||||
|
||||
// 3. Insert d as the first element of functionsToInitialize.
|
||||
// NOTE: Since prepending is much slower, we just append
|
||||
// and iterate in reverse order in step 17 below.
|
||||
functions_to_initialize.append(function);
|
||||
return {};
|
||||
}));
|
||||
}
|
||||
|
||||
// 11. NOTE: Annex B.3.2.3 adds additional steps at this point.
|
||||
// B.3.2.3 Changes to EvalDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-evaldeclarationinstantiation
|
||||
|
|
@ -941,9 +911,9 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
HashTable<Utf16FlyString> hoisted_functions;
|
||||
|
||||
// b. For each FunctionDeclaration f that is directly contained in the StatementList of a Block, CaseClause, or DefaultClause Contained within body, do
|
||||
TRY(program.for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) -> ThrowCompletionOr<void> {
|
||||
for (auto& function_declaration : data.annex_b_candidates) {
|
||||
// i. Let F be StringValue of the BindingIdentifier of f.
|
||||
auto function_name = function_declaration.name();
|
||||
auto function_name = function_declaration->name();
|
||||
|
||||
// ii. If replacing the FunctionDeclaration f with a VariableStatement that has F as a BindingIdentifier would not produce any Early Errors for body, then
|
||||
// Note: This is checked during parsing and for_each_function_hoistable_with_annexB_extension so it always passes here.
|
||||
|
|
@ -955,14 +925,15 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
// 3. Assert: The following loop will terminate.
|
||||
|
||||
// 4. Repeat, while thisEnv is not the same as varEnv,
|
||||
bool binding_exists = false;
|
||||
while (this_environment != variable_environment) {
|
||||
// a. If thisEnv is not an object Environment Record, then
|
||||
if (!is<ObjectEnvironment>(*this_environment)) {
|
||||
// i. If ! thisEnv.HasBinding(F) is true, then
|
||||
if (MUST(this_environment->has_binding(function_name))) {
|
||||
// i. Let bindingExists be true.
|
||||
// Note: When bindingExists is true we skip all the other steps.
|
||||
return {};
|
||||
binding_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -971,6 +942,9 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
VERIFY(this_environment);
|
||||
}
|
||||
|
||||
if (binding_exists)
|
||||
continue;
|
||||
|
||||
// Note: At this point bindingExists is false.
|
||||
// 5. If bindingExists is false and varEnv is a global Environment Record, then
|
||||
if (global_var_environment) {
|
||||
|
|
@ -978,12 +952,12 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
if (!global_var_environment->has_lexical_declaration(function_name)) {
|
||||
// i. Let fnDefinable be ? varEnv.CanDeclareGlobalVar(F).
|
||||
if (!TRY(global_var_environment->can_declare_global_var(function_name)))
|
||||
return {};
|
||||
continue;
|
||||
}
|
||||
// b. Else,
|
||||
else {
|
||||
// i. Let fnDefinable be false.
|
||||
return {};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 6. Else,
|
||||
|
|
@ -993,7 +967,7 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
// 7. If bindingExists is false and fnDefinable is true, then
|
||||
|
||||
// a. If declaredFunctionOrVarNames does not contain F, then
|
||||
if (!declared_function_names.contains(function_name) && !hoisted_functions.contains(function_name)) {
|
||||
if (!data.declared_function_names.contains(function_name) && !hoisted_functions.contains(function_name)) {
|
||||
// i. If varEnv is a global Environment Record, then
|
||||
if (global_var_environment) {
|
||||
// i. Perform ? varEnv.CreateGlobalVarBinding(F, true).
|
||||
|
|
@ -1001,7 +975,6 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
}
|
||||
// ii. Else,
|
||||
else {
|
||||
|
||||
// i. Let bindingExists be ! varEnv.HasBinding(F).
|
||||
// ii. If bindingExists is false, then
|
||||
if (!MUST(variable_environment->has_binding(function_name))) {
|
||||
|
|
@ -1022,107 +995,85 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
|
|||
// iii. Let fobj be ! benv.GetBindingValue(F, false).
|
||||
// iv. Perform ? genv.SetMutableBinding(F, fobj, false).
|
||||
// v. Return unused.
|
||||
function_declaration.set_should_do_additional_annexB_steps();
|
||||
|
||||
return {};
|
||||
}));
|
||||
function_declaration->set_should_do_additional_annexB_steps();
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Let declaredVarNames be a new empty List.
|
||||
HashTable<Utf16FlyString> declared_var_names;
|
||||
|
||||
// 13. For each element d of varDeclarations, do
|
||||
TRY(program.for_each_var_scoped_variable_declaration([&](VariableDeclaration const& declaration) {
|
||||
// a. If d is a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
|
||||
// Note: This is handled by for_each_var_scoped_variable_declaration.
|
||||
for (auto const& name : data.var_scoped_names) {
|
||||
// 1. If vn is not an element of declaredFunctionNames, then
|
||||
if (!data.declared_function_names.contains(name)) {
|
||||
// a. If varEnv is a global Environment Record, then
|
||||
if (global_var_environment) {
|
||||
// i. Let vnDefinable be ? varEnv.CanDeclareGlobalVar(vn).
|
||||
auto variable_definable = TRY(global_var_environment->can_declare_global_var(name));
|
||||
|
||||
// i. For each String vn of the BoundNames of d, do
|
||||
return declaration.for_each_bound_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
|
||||
auto const& name = identifier.string();
|
||||
|
||||
// 1. If vn is not an element of declaredFunctionNames, then
|
||||
if (!declared_function_names.contains(name)) {
|
||||
// a. If varEnv is a global Environment Record, then
|
||||
if (global_var_environment) {
|
||||
// i. Let vnDefinable be ? varEnv.CanDeclareGlobalVar(vn).
|
||||
auto variable_definable = TRY(global_var_environment->can_declare_global_var(name));
|
||||
|
||||
// ii. If vnDefinable is false, throw a TypeError exception.
|
||||
if (!variable_definable)
|
||||
return vm.throw_completion<TypeError>(ErrorType::CannotDeclareGlobalVariable, name);
|
||||
}
|
||||
|
||||
// b. If vn is not an element of declaredVarNames, then
|
||||
// i. Append vn to declaredVarNames.
|
||||
declared_var_names.set(name);
|
||||
// ii. If vnDefinable is false, throw a TypeError exception.
|
||||
if (!variable_definable)
|
||||
return vm.throw_completion<TypeError>(ErrorType::CannotDeclareGlobalVariable, name);
|
||||
}
|
||||
return {};
|
||||
});
|
||||
}));
|
||||
|
||||
// b. If vn is not an element of declaredVarNames, then
|
||||
// i. Append vn to declaredVarNames.
|
||||
declared_var_names.set(name);
|
||||
}
|
||||
}
|
||||
|
||||
// 14. NOTE: No abnormal terminations occur after this algorithm step unless varEnv is a global Environment Record and the global object is a Proxy exotic object.
|
||||
|
||||
// 15. Let lexDeclarations be the LexicallyScopedDeclarations of body.
|
||||
// 16. For each element d of lexDeclarations, do
|
||||
TRY(program.for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
|
||||
// a. NOTE: Lexically declared names are only instantiated here but not initialized.
|
||||
|
||||
// b. For each element dn of the BoundNames of d, do
|
||||
return declaration.for_each_bound_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
|
||||
auto const& name = identifier.string();
|
||||
|
||||
// i. If IsConstantDeclaration of d is true, then
|
||||
if (declaration.is_constant_declaration()) {
|
||||
// 1. Perform ? lexEnv.CreateImmutableBinding(dn, true).
|
||||
TRY(lexical_environment->create_immutable_binding(vm, name, true));
|
||||
}
|
||||
// ii. Else,
|
||||
else {
|
||||
// 1. Perform ? lexEnv.CreateMutableBinding(dn, false).
|
||||
TRY(lexical_environment->create_mutable_binding(vm, name, false));
|
||||
}
|
||||
return {};
|
||||
});
|
||||
}));
|
||||
for (auto const& binding : data.lexical_bindings) {
|
||||
// i. If IsConstantDeclaration of d is true, then
|
||||
if (binding.is_constant) {
|
||||
// 1. Perform ? lexEnv.CreateImmutableBinding(dn, true).
|
||||
TRY(lexical_environment->create_immutable_binding(vm, binding.name, true));
|
||||
}
|
||||
// ii. Else,
|
||||
else {
|
||||
// 1. Perform ? lexEnv.CreateMutableBinding(dn, false).
|
||||
TRY(lexical_environment->create_mutable_binding(vm, binding.name, false));
|
||||
}
|
||||
}
|
||||
|
||||
// 17. For each Parse Node f of functionsToInitialize, do
|
||||
// NOTE: We iterate in reverse order since we appended the functions
|
||||
// instead of prepending. We append because prepending is much slower
|
||||
// and we only use the created vector here.
|
||||
for (auto const& declaration : functions_to_initialize.in_reverse()) {
|
||||
auto declaration_name = declaration.name();
|
||||
|
||||
// NB: We iterate in reverse order since we appended the functions
|
||||
// instead of prepending during pre-computation.
|
||||
for (auto const& function_to_initialize : data.functions_to_initialize.in_reverse()) {
|
||||
// a. Let fn be the sole element of the BoundNames of f.
|
||||
// b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv.
|
||||
auto function = ECMAScriptFunctionObject::create_from_function_data(
|
||||
realm,
|
||||
declaration.ensure_shared_data(vm),
|
||||
function_to_initialize.shared_data,
|
||||
lexical_environment,
|
||||
private_environment);
|
||||
|
||||
// c. If varEnv is a global Environment Record, then
|
||||
if (global_var_environment) {
|
||||
// i. Perform ? varEnv.CreateGlobalFunctionBinding(fn, fo, true).
|
||||
TRY(global_var_environment->create_global_function_binding(declaration_name, function, true));
|
||||
TRY(global_var_environment->create_global_function_binding(function_to_initialize.name, function, true));
|
||||
}
|
||||
// d. Else,
|
||||
else {
|
||||
// i. Let bindingExists be ! varEnv.HasBinding(fn).
|
||||
auto binding_exists = MUST(variable_environment->has_binding(declaration_name));
|
||||
auto binding_exists = MUST(variable_environment->has_binding(function_to_initialize.name));
|
||||
|
||||
// ii. If bindingExists is false, then
|
||||
if (!binding_exists) {
|
||||
// 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14.
|
||||
// 2. Perform ! varEnv.CreateMutableBinding(fn, true).
|
||||
MUST(variable_environment->create_mutable_binding(vm, declaration_name, true));
|
||||
MUST(variable_environment->create_mutable_binding(vm, function_to_initialize.name, true));
|
||||
|
||||
// 3. Perform ! varEnv.InitializeBinding(fn, fo, normal).
|
||||
MUST(variable_environment->initialize_binding(vm, declaration_name, function, Environment::InitializeBindingHint::Normal));
|
||||
MUST(variable_environment->initialize_binding(vm, function_to_initialize.name, function, Environment::InitializeBindingHint::Normal));
|
||||
}
|
||||
// iii. Else,
|
||||
else {
|
||||
// 1. Perform ! varEnv.SetMutableBinding(fn, fo, false).
|
||||
MUST(variable_environment->set_mutable_binding(vm, declaration_name, function, false));
|
||||
MUST(variable_environment->set_mutable_binding(vm, function_to_initialize.name, function, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ struct EvalDeclarationData {
|
|||
static EvalDeclarationData create(VM&, Program const&, bool strict);
|
||||
};
|
||||
|
||||
ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& program, Environment* variable_environment, Environment* lexical_environment, PrivateEnvironment* private_environment, bool strict);
|
||||
ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, EvalDeclarationData&, Environment* variable_environment, Environment* lexical_environment, PrivateEnvironment* private_environment, bool strict);
|
||||
|
||||
// 7.3.14 Call ( F, V [ , argumentsList ] ), https://tc39.es/ecma262/#sec-call
|
||||
ALWAYS_INLINE ThrowCompletionOr<Value> call(VM& vm, Value function, Value this_value, ReadonlySpan<Value> arguments_list)
|
||||
|
|
|
|||
|
|
@ -167,7 +167,8 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(VM& vm, Value source, Realm&
|
|||
TRY(vm.push_execution_context(*eval_context, {}));
|
||||
|
||||
// 10. Let result be Completion(EvalDeclarationInstantiation(body, varEnv, lexEnv, null, strictEval)).
|
||||
auto eval_result = eval_declaration_instantiation(vm, program, variable_environment, lexical_environment, nullptr, strict_eval);
|
||||
auto eval_declaration_data = EvalDeclarationData::create(vm, program, strict_eval);
|
||||
auto eval_result = eval_declaration_instantiation(vm, eval_declaration_data, variable_environment, lexical_environment, nullptr, strict_eval);
|
||||
|
||||
Completion result;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue