use crate::{
ecmascript::{
Agent, DeclarativeEnvironment, DeclarativeEnvironmentRecord, ECMAScriptFunction,
Environment, ExceptionType, Function, FunctionEnvironment, InternalMethods, JsResult,
Object, String, ThisMode, Value, unwrap_try,
},
engine::{Bindable, NoGcScope},
heap::{ArenaAccess, ArenaAccessMut, CompactionLists, HeapMarkAndSweep, WorkQueues},
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum ThisBindingStatus {
Lexical,
Initialized,
Uninitialized,
}
#[derive(Debug)]
pub(crate) struct FunctionEnvironmentRecord {
this_value: Option<Value<'static>>,
this_binding_status: ThisBindingStatus,
function_object: Function<'static>,
new_target: Option<Object<'static>>,
declarative_environment: DeclarativeEnvironment<'static>,
}
impl HeapMarkAndSweep for FunctionEnvironmentRecord {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
this_value,
this_binding_status: _,
function_object,
new_target,
declarative_environment,
} = self;
declarative_environment.mark_values(queues);
function_object.mark_values(queues);
new_target.mark_values(queues);
this_value.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
this_value,
this_binding_status: _,
function_object,
new_target,
declarative_environment,
} = self;
declarative_environment.sweep_values(compactions);
function_object.sweep_values(compactions);
new_target.sweep_values(compactions);
this_value.sweep_values(compactions);
}
}
pub(crate) fn new_function_environment<'a>(
agent: &mut Agent,
f: ECMAScriptFunction,
new_target: Option<Object>,
gc: NoGcScope<'a, '_>,
) -> FunctionEnvironment<'a> {
let ecmascript_function_object = &f.get(agent).ecmascript_function;
let this_mode = ecmascript_function_object.this_mode;
let dcl_env = DeclarativeEnvironmentRecord::new(Some(ecmascript_function_object.environment));
let declarative_environment = agent
.heap
.environments
.push_declarative_environment(dcl_env, gc);
let function_object = f.unbind().into();
let this_binding_status = if this_mode == ThisMode::Lexical {
ThisBindingStatus::Lexical
} else {
ThisBindingStatus::Uninitialized
};
let env = FunctionEnvironmentRecord {
this_value: None,
function_object,
this_binding_status,
new_target: new_target.unbind(),
declarative_environment: declarative_environment.unbind(),
};
agent.heap.alloc_counter += core::mem::size_of::<Option<FunctionEnvironmentRecord>>()
+ core::mem::size_of::<Option<DeclarativeEnvironmentRecord>>();
agent.heap.environments.push_function_environment(env, gc)
}
pub(crate) fn new_class_static_element_environment<'a>(
agent: &mut Agent,
class_constructor: Function,
gc: NoGcScope<'a, '_>,
) -> FunctionEnvironment<'a> {
let dcl_env = DeclarativeEnvironmentRecord::new(Some(agent.current_lexical_environment(gc)));
let declarative_environment = agent
.heap
.environments
.push_declarative_environment(dcl_env, gc);
let env = FunctionEnvironmentRecord {
this_value: Some(class_constructor.unbind().into()),
function_object: class_constructor.unbind(),
this_binding_status: ThisBindingStatus::Initialized,
new_target: None,
declarative_environment: declarative_environment.unbind(),
};
agent.heap.alloc_counter += core::mem::size_of::<Option<FunctionEnvironmentRecord>>()
+ core::mem::size_of::<Option<DeclarativeEnvironmentRecord>>();
agent.heap.environments.push_function_environment(env, gc)
}
pub(crate) fn new_class_field_initializer_environment<'a>(
agent: &mut Agent,
class_constructor: Function,
class_instance: Object,
outer_env: Environment,
gc: NoGcScope<'a, '_>,
) -> FunctionEnvironment<'a> {
let declarative_environment = agent
.heap
.environments
.push_declarative_environment(DeclarativeEnvironmentRecord::new(Some(outer_env)), gc);
agent.heap.alloc_counter += core::mem::size_of::<Option<FunctionEnvironmentRecord>>()
+ core::mem::size_of::<Option<DeclarativeEnvironmentRecord>>();
agent.heap.environments.push_function_environment(
FunctionEnvironmentRecord {
this_value: Some(class_instance.unbind().into()),
this_binding_status: ThisBindingStatus::Initialized,
function_object: class_constructor.unbind(),
new_target: None,
declarative_environment: declarative_environment.unbind(),
},
gc,
)
}
impl<'e> FunctionEnvironment<'e> {
pub(crate) fn get_function_object(self, agent: &Agent) -> Function<'e> {
self.get(agent).function_object
}
pub(crate) fn get_new_target(self, agent: &Agent) -> Option<Object<'e>> {
self.get(agent).new_target
}
pub(crate) fn get_outer_env(self, agent: &Agent) -> Option<Environment<'e>> {
self.get(agent).declarative_environment.get_outer_env(agent)
}
pub(crate) fn get_this_binding_status(self, agent: &Agent) -> ThisBindingStatus {
self.get(agent).this_binding_status
}
pub(crate) fn get_this_binding<'a>(
self,
agent: &mut Agent,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, Value<'a>> {
let env_rec = &self.get(agent);
match env_rec.this_binding_status {
ThisBindingStatus::Lexical => unreachable!(),
ThisBindingStatus::Initialized => Ok(env_rec.this_value.unwrap()),
ThisBindingStatus::Uninitialized => Err(agent.throw_exception_with_static_message(
ExceptionType::ReferenceError,
"Uninitialized this binding",
gc,
)),
}
}
pub(crate) fn has_binding(self, agent: &Agent, name: String) -> bool {
self.get(agent)
.declarative_environment
.has_binding(agent, name)
}
pub(crate) fn create_mutable_binding(
self,
agent: &mut Agent,
name: String,
is_deletable: bool,
) {
self.get(agent)
.declarative_environment
.create_mutable_binding(agent, name, is_deletable)
}
pub(crate) fn create_immutable_binding(self, agent: &mut Agent, name: String, is_strict: bool) {
self.get(agent)
.declarative_environment
.create_immutable_binding(agent, name, is_strict)
}
pub(crate) fn initialize_binding(self, agent: &mut Agent, name: String, value: Value) {
self.get(agent)
.declarative_environment
.initialize_binding(agent, name, value)
}
pub(crate) fn set_mutable_binding<'a>(
self,
agent: &mut Agent,
name: String,
value: Value,
mut is_strict: bool,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, ()> {
let env_rec = &self.get(agent);
let dcl_rec = env_rec.declarative_environment;
if !dcl_rec.has_binding(agent, name) {
if is_strict {
let error_message = format!(
"Could not set mutable binding '{}'.",
name.to_string_lossy_(agent)
);
return Err(agent.throw_exception(
ExceptionType::ReferenceError,
error_message,
gc,
));
}
dcl_rec.create_mutable_binding(agent, name, true);
dcl_rec.initialize_binding(agent, name, value);
return Ok(());
};
let binding = dcl_rec.get_binding_mut(agent, name).unwrap();
if binding.strict {
is_strict = true;
}
if binding.value.is_none() {
let error_message = format!(
"Identifier '{}' has not been initialized.",
name.to_string_lossy_(agent)
);
return Err(agent.throw_exception(ExceptionType::ReferenceError, error_message, gc));
}
if binding.mutable {
binding.value = Some(value.unbind());
}
else {
debug_assert!(!binding.mutable);
if is_strict {
let error_message = format!(
"invalid assignment to const '{}'",
name.to_string_lossy_(agent)
);
return Err(agent.throw_exception(ExceptionType::TypeError, error_message, gc));
}
}
Ok(())
}
pub(crate) fn get_binding_value(
self,
agent: &mut Agent,
name: String,
is_strict: bool,
gc: NoGcScope<'e, '_>,
) -> JsResult<'e, Value<'e>> {
self.get(agent)
.declarative_environment
.get_binding_value(agent, name, is_strict, gc)
}
pub(crate) fn delete_binding(self, agent: &mut Agent, name: String) -> bool {
self.get(agent)
.declarative_environment
.delete_binding(agent, name)
}
pub(crate) fn bind_this_value<'a>(
self,
agent: &mut Agent,
value: Value,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, Value<'a>> {
let env_rec = self.get_mut(agent);
debug_assert!(env_rec.this_binding_status != ThisBindingStatus::Lexical);
if env_rec.this_binding_status == ThisBindingStatus::Initialized {
return Err(agent.throw_exception_with_static_message(
ExceptionType::ReferenceError,
"[[ThisBindingStatus]] is INITIALIZED",
gc,
));
}
env_rec.this_value = Some(value.unbind());
env_rec.this_binding_status = ThisBindingStatus::Initialized;
Ok(value.bind(gc))
}
pub(crate) fn has_this_binding(self, agent: &Agent) -> bool {
let env_rec = &self.get(agent);
env_rec.this_binding_status != ThisBindingStatus::Lexical
}
pub(crate) fn has_super_binding(self, agent: &Agent) -> bool {
let env_rec = &self.get(agent);
if env_rec.this_binding_status == ThisBindingStatus::Lexical {
return false;
}
match env_rec.function_object {
Function::ECMAScriptFunction(func) => {
func.get(agent).ecmascript_function.home_object.is_some()
}
_ => false,
}
}
pub(crate) fn get_super_base<'a>(self, agent: &mut Agent, gc: NoGcScope<'a, '_>) -> Value<'a> {
let env_rec: &FunctionEnvironmentRecord = self.get(agent);
let home = match env_rec.function_object {
Function::ECMAScriptFunction(func) => func.get(agent).ecmascript_function.home_object,
_ => None,
};
let Some(home) = home else {
return Value::Undefined;
};
unwrap_try(home.try_get_prototype_of(agent, gc)).map_or(Value::Undefined, |o| o.into())
}
}
impl HeapMarkAndSweep for FunctionEnvironment<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
queues.function_environments.push(*self);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
compactions.function_environments.shift_index(&mut self.0);
}
}