Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ecmascript): ClassStaticBlocks #412

Merged
merged 4 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions nova_vm/src/ecmascript/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ mod realm;
pub use agent::{Agent, JsResult};
pub use default_host_hooks::DefaultHostHooks;
pub(crate) use environments::{
get_this_environment, new_declarative_environment, new_function_environment,
DeclarativeEnvironmentIndex, EnvironmentIndex, Environments, FunctionEnvironmentIndex,
GlobalEnvironment, GlobalEnvironmentIndex, ModuleEnvironmentIndex, ObjectEnvironmentIndex,
PrivateEnvironmentIndex, ThisBindingStatus,
get_this_environment, new_class_static_element_environment, new_declarative_environment,
new_function_environment, DeclarativeEnvironmentIndex, EnvironmentIndex, Environments,
FunctionEnvironmentIndex, GlobalEnvironment, GlobalEnvironmentIndex, ModuleEnvironmentIndex,
ObjectEnvironmentIndex, PrivateEnvironmentIndex, ThisBindingStatus,
};
pub(crate) use execution_context::*;
#[cfg(test)]
Expand Down
3 changes: 2 additions & 1 deletion nova_vm/src/ecmascript/execution/environments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ mod private_environment;

pub(crate) use declarative_environment::{new_declarative_environment, DeclarativeEnvironment};
pub(crate) use function_environment::{
new_function_environment, FunctionEnvironment, ThisBindingStatus,
new_class_static_element_environment, new_function_environment, FunctionEnvironment,
ThisBindingStatus,
};
pub(crate) use global_environment::GlobalEnvironment;
pub(crate) use object_environment::ObjectEnvironment;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
ecmascript::{
builtins::{ECMAScriptFunction, ThisMode},
execution::{agent::ExceptionType, Agent, JsResult},
types::{Function, InternalMethods, IntoFunction, Object, String, Value},
types::{Function, InternalMethods, IntoFunction, IntoValue, Object, String, Value},
},
heap::{CompactionLists, HeapMarkAndSweep, WorkQueues},
};
Expand Down Expand Up @@ -125,6 +125,48 @@ pub(crate) fn new_function_environment(
agent.heap.environments.push_function_environment(env)
}

/// ### NewClassStaticElementEnvironment ( classConstructor )
///
/// This is a non-standard abstract operation that performs the same steps as
/// NewFunctionEnvironment, but for a class static element's evaluation
/// function. These functions are never visible to ECMAScript code and thus we
/// avoid creating them entirely. The only parameter is the class constructor,
/// which is used as both the this value and the \[\[FunctionObject]] of the
/// new function environment.
pub(crate) fn new_class_static_element_environment(
agent: &mut Agent,
class_constructor: Function,
) -> FunctionEnvironmentIndex {
// 1. Let env be a new Function Environment Record containing no bindings.
let dcl_env = DeclarativeEnvironment::new(Some(
agent
.running_execution_context()
.ecmascript_code
.as_ref()
.unwrap()
.lexical_environment,
));
agent.heap.environments.declarative.push(Some(dcl_env));
let declarative_environment =
DeclarativeEnvironmentIndex::last(&agent.heap.environments.declarative);

let env = FunctionEnvironment {
this_value: Some(class_constructor.into_value()),

function_object: class_constructor,

this_binding_status: ThisBindingStatus::Initialized,

// 5. Set env.[[NewTarget]] to newTarget.
new_target: None,

// 6. Set env.[[OuterEnv]] to F.[[Environment]].
declarative_environment,
};
// 7. Return env.
agent.heap.environments.push_function_environment(env)
}

impl FunctionEnvironmentIndex {
pub(crate) fn get_this_binding_status(self, agent: &Agent) -> ThisBindingStatus {
agent[self].this_binding_status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,22 @@ pub(crate) fn function_body_lexically_scoped_declarations<'body>(
lexically_scoped_declarations
}

pub(crate) fn class_static_block_lexically_scoped_declarations() {
pub(crate) fn class_static_block_lexically_scoped_declarations<'body>(
static_block: &'body StaticBlock<'body>,
) -> Vec<LexicallyScopedDeclaration<'body>> {
let mut lexically_scoped_declarations = vec![];
// ClassStaticBlockStatementList : [empty]
// 1. Return a new empty List.

// ClassStaticBlockStatementList : StatementList
// 1. Return the TopLevelLexicallyScopedDeclarations of StatementList.
static_block
.body
.top_level_lexically_scoped_declarations(&mut |decl| {
lexically_scoped_declarations.push(decl);
});

lexically_scoped_declarations
}

pub(crate) fn script_lexically_scoped_declarations<'body>(
Expand Down Expand Up @@ -811,6 +821,22 @@ pub(crate) fn function_body_var_scoped_declarations<'a>(
// AsyncConciseBody : ExpressionBody
// 1. Return a new empty List.

pub(crate) fn class_static_block_var_scoped_declarations<'a>(
static_block: &'a StaticBlock<'a>,
) -> Vec<VarScopedDeclaration<'a>> {
let mut var_scoped_declarations = vec![];
// ClassStaticBlockStatementList : [empty]
// 1. Return a new empty List.
// ClassStaticBlockStatementList : StatementList
// 1. Return the TopLevelVarScopedDeclarations of StatementList.
static_block
.body
.top_level_var_scoped_declarations(&mut |declarator| {
var_scoped_declarations.push(declarator);
});
var_scoped_declarations
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum VarScopedDeclaration<'a> {
Variable(&'a VariableDeclarator<'a>),
Expand Down
144 changes: 137 additions & 7 deletions nova_vm/src/engine/bytecode/executable/class_definition_evaluation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@
use crate::{
ecmascript::{
execution::agent::ExceptionType,
syntax_directed_operations::scope_analysis::{
class_static_block_lexically_scoped_declarations,
class_static_block_var_declared_names, class_static_block_var_scoped_declarations,
LexicallyScopedDeclaration, VarScopedDeclaration,
},
types::{String, Value, BUILTIN_STRING_MEMORY},
},
engine::{
is_reference, CompileContext, CompileEvaluation, FunctionExpression, Instruction,
NamedEvaluationParameter, SendableRef,
},
};
use ahash::{AHashMap, AHashSet};
use oxc_ast::{
ast::{self, MethodDefinitionKind},
syntax_directed_operations::{PrivateBoundIdentifiers, PropName},
syntax_directed_operations::{BoundNames, PrivateBoundIdentifiers, PropName},
};

impl CompileEvaluation for ast::Class<'_> {
Expand Down Expand Up @@ -393,8 +399,13 @@ impl CompileEvaluation for ast::Class<'_> {
// stack: [constructor]

// 26. Set the running execution context's LexicalEnvironment to env.
// 27. If classBinding is not undefined, then
// Note: We do not exit classEnv here. First, classBinding is
// initialized in classEnv. Second, the static elements are "functions"
// that were "created" in the classEnv, and they are "evaluated" below.
// The evaluation is done inline so we need the classEnv to be active,
// and the "function environments" to be created in it.

// 27. If classBinding is not undefined, then
// Note: The classBinding needs to be initialized in classEnv, as any
// class method calls access the classBinding through the classEnv.
if let Some(class_binding) = class_identifier {
Expand All @@ -406,30 +417,33 @@ impl CompileEvaluation for ast::Class<'_> {
.add_instruction(Instruction::InitializeReferencedBinding);
}

ctx.exe
.add_instruction(Instruction::ExitDeclarativeEnvironment);
// 28. Set F.[[PrivateMethods]] to instancePrivateMethods.
// 29. Set F.[[Fields]] to instanceFields.
// 30. For each PrivateElement method of staticPrivateMethods, do
// a. Perform ! PrivateMethodOrAccessorAdd(F, method).
// 31. For each element elementRecord of staticElements, do
for _element_record in static_elements.iter() {
for element_record in static_elements.iter() {
// a. If elementRecord is a ClassFieldDefinition Record, then
// i. Let result be Completion(DefineField(F, elementRecord)).
// b. Else,
// i. Assert: elementRecord is a ClassStaticBlockDefinition Record.
// ii. Let result be Completion(Call(elementRecord.[[BodyFunction]], F)).
element_record.compile(ctx);
// c. If result is an abrupt completion, then
// i. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
// ii. Return ? result.
todo!();
}
// Note: We finally leave classEnv here. See step 26.
ctx.exe
.add_instruction(Instruction::ExitDeclarativeEnvironment);

// 32. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
// 33. Return F.

// 15.7.15 Runtime Semantics: BindingClassDeclarationEvaluation
// ClassDeclaration: class BindingIdentifier ClassTail
if let Some(class_identifier) = class_identifier {
if self.is_declaration() {
let class_identifier = class_identifier.unwrap();
// 4. Let env be the running execution context's LexicalEnvironment.
// 5. Perform ? InitializeBoundName(className, value, env).
// => a. Perform ! environment.InitializeBinding(name, value).
Expand Down Expand Up @@ -556,3 +570,119 @@ fn define_method(class_element: &ast::MethodDefinition, ctx: &mut CompileContext

// 9. Return the Record { [[Key]]: propKey, [[Closure]]: closure }.
}

impl CompileEvaluation for ast::StaticBlock<'_> {
fn compile(&self, ctx: &mut CompileContext) {
// 12. Let functionNames be a new empty List.
// 13. Let functionsToInitialize be a new empty List.
// NOTE: the keys of `functions` will be `functionNames`, its values will be
// `functionsToInitialize`.
let mut functions = AHashMap::new();
for d in class_static_block_var_scoped_declarations(self) {
// a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then
if let VarScopedDeclaration::Function(d) = d {
// i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
// ii. Let fn be the sole element of the BoundNames of d.
let f_name = d.id.as_ref().unwrap().name.clone();
// iii. If functionNames does not contain fn, then
// 1. Insert fn as the first element of functionNames.
// 2. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
// 3. Insert d as the first element of functionsToInitialize.
functions.insert(f_name, d);
}
}

// 27. If hasParameterExpressions is false, then
// a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars.
// b. Let instantiatedVarNames be a copy of the List parameterBindings.
let mut instantiated_var_names = AHashSet::new();
// c. For each element n of varNames, do
ctx.exe
.add_instruction(Instruction::EnterClassStaticElementEnvironment);
for n in class_static_block_var_declared_names(self) {
// i. If instantiatedVarNames does not contain n, then
if instantiated_var_names.contains(&n) {
continue;
}
// 1. Append n to instantiatedVarNames.
let n_string = String::from_str(ctx.agent, &n);
instantiated_var_names.insert(n);
// 2. Perform ! env.CreateMutableBinding(n, false).
ctx.exe
.add_instruction_with_identifier(Instruction::CreateMutableBinding, n_string);
// 3. Perform ! env.InitializeBinding(n, undefined).
ctx.exe
.add_instruction_with_identifier(Instruction::ResolveBinding, n_string);
ctx.exe
.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined);
ctx.exe
.add_instruction(Instruction::InitializeReferencedBinding);
}

// 34. For each element d of lexDeclarations, do
for d in class_static_block_lexically_scoped_declarations(self) {
// a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do
match d {
// i. If IsConstantDeclaration of d is true, then
LexicallyScopedDeclaration::Variable(decl) if decl.kind.is_const() => {
{
decl.id.bound_names(&mut |identifier| {
let dn = String::from_str(ctx.agent, &identifier.name);
// 1. Perform ! lexEnv.CreateImmutableBinding(dn, true).
ctx.exe.add_instruction_with_identifier(
Instruction::CreateImmutableBinding,
dn,
);
})
}
}
// ii. Else,
// 1. Perform ! lexEnv.CreateMutableBinding(dn, false).
LexicallyScopedDeclaration::Variable(decl) => {
decl.id.bound_names(&mut |identifier| {
let dn = String::from_str(ctx.agent, &identifier.name);
ctx.exe
.add_instruction_with_identifier(Instruction::CreateMutableBinding, dn);
})
}
LexicallyScopedDeclaration::Function(decl) => {
let dn = String::from_str(ctx.agent, &decl.id.as_ref().unwrap().name);
ctx.exe
.add_instruction_with_identifier(Instruction::CreateMutableBinding, dn);
}
LexicallyScopedDeclaration::Class(decl) => {
let dn = String::from_str(ctx.agent, &decl.id.as_ref().unwrap().name);
ctx.exe
.add_instruction_with_identifier(Instruction::CreateMutableBinding, dn);
}
LexicallyScopedDeclaration::DefaultExport => {
let dn = BUILTIN_STRING_MEMORY._default_;
ctx.exe
.add_instruction_with_identifier(Instruction::CreateMutableBinding, dn);
}
}
}

// 36. For each Parse Node f of functionsToInitialize, do
for f in functions.values() {
// b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv.
f.compile(ctx);
// a. Let fn be the sole element of the BoundNames of f.
let f_name = String::from_str(ctx.agent, &f.id.as_ref().unwrap().name);
// c. Perform ! varEnv.SetMutableBinding(fn, fo, false).
// TODO: This compilation is incorrect if !strict, when varEnv != lexEnv.
ctx.exe
.add_instruction_with_identifier(Instruction::ResolveBinding, f_name);
ctx.exe.add_instruction(Instruction::PutValue);
}

for statement in self.body.iter() {
statement.compile(ctx);
}
ctx.exe
.add_instruction(Instruction::ExitDeclarativeEnvironment);
ctx.exe
.add_instruction(Instruction::ExitVariableEnvironment);
}
}
9 changes: 5 additions & 4 deletions nova_vm/src/engine/bytecode/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,14 @@ pub enum Instruction {
/// spec requires that creation of bindings in the environment is done
/// first. This is immaterial because creating the bindings cannot fail.
EnterDeclarativeEnvironment,
/// Perform NewDeclarativeEnvironment with the running execution context's
/// LexicalEnvironment as the only parameter, and set it as the running
/// execution context's VariableEnvironment.
EnterDeclarativeVariableEnvironment,
/// Enter a new FunctionEnvironment with the top of the stack as the this
/// binding and \[\[FunctionObject]]. This is used for class static
/// initializers.
EnterClassStaticElementEnvironment,
/// Reset the running execution context's LexicalEnvironment to its current
/// value's \[\[OuterEnv]].
ExitDeclarativeEnvironment,
ExitVariableEnvironment,
/// Begin binding values using destructuring
BeginSimpleObjectBindingPattern,
/// Begin binding values using a sync iterator for known repetitions
Expand Down
Loading