use ahash::AHashMap;
use crate::{
ecmascript::{
AbstractModule, AbstractModuleSlots, Agent, ExceptionType, JsError, JsResult, String,
TryResult, Value,
},
engine::{Bindable, NoGcScope},
heap::{CompactionLists, HeapMarkAndSweep, WorkQueues},
};
use super::{
DeclarativeEnvironment, DeclarativeEnvironmentRecord, Environments, ModuleEnvironment, OuterEnv,
};
#[derive(Debug)]
pub(crate) struct ModuleEnvironmentRecord {
declarative_environment: DeclarativeEnvironment<'static>,
indirect_bindings: AHashMap<String<'static>, IndirectBinding<'static>>,
}
#[derive(Debug)]
struct IndirectBinding<'a> {
m: AbstractModule<'a>,
n2: String<'a>,
}
impl ModuleEnvironmentRecord {
fn new(dcl_env: DeclarativeEnvironment) -> Self {
Self {
declarative_environment: dcl_env.unbind(),
indirect_bindings: Default::default(),
}
}
fn has_indirect_binding(&self, name: String) -> bool {
self.indirect_bindings.contains_key(&name.unbind())
}
fn get_indirect_binding(&self, name: String) -> Option<&IndirectBinding<'static>> {
self.indirect_bindings.get(&name.unbind())
}
}
pub(crate) fn new_module_environment<'a>(
agent: &mut Agent,
outer_env: OuterEnv,
gc: NoGcScope<'a, '_>,
) -> ModuleEnvironment<'a> {
agent.heap.alloc_counter += core::mem::size_of::<Option<DeclarativeEnvironmentRecord>>()
+ core::mem::size_of::<Option<ModuleEnvironmentRecord>>();
let declarative_environment = agent
.heap
.environments
.push_declarative_environment(DeclarativeEnvironmentRecord::new(outer_env), gc);
agent
.heap
.environments
.push_module_environment(ModuleEnvironmentRecord::new(declarative_environment), gc)
}
impl<'e> ModuleEnvironment<'e> {
fn get_declarative_env(self, agent: &impl AsRef<Environments>) -> DeclarativeEnvironment<'e> {
agent
.as_ref()
.get_module_environment(self)
.declarative_environment
}
pub(crate) fn get_outer_env(self, agent: &Agent) -> OuterEnv<'e> {
self.get_declarative_env(agent).get_outer_env(agent)
}
pub(crate) fn has_binding(self, agent: &impl AsRef<Environments>, name: String) -> bool {
let env = agent.as_ref().get_module_environment(self);
env.has_indirect_binding(name) || env.declarative_environment.has_binding(agent, name)
}
pub(crate) fn create_mutable_binding(
self,
agent: &mut Agent,
name: String,
is_deletable: bool,
) {
self.get_declarative_env(agent)
.create_mutable_binding(agent, name, is_deletable);
}
pub(crate) fn create_immutable_binding(
self,
envs: &mut impl AsMut<Environments>,
name: String,
) {
let envs = envs.as_mut();
self.inner_create_immutable_binding(envs, name);
}
fn inner_create_immutable_binding(self, envs: &mut Environments, name: String) {
envs.get_declarative_environment_mut(self.get_declarative_env(envs))
.create_immutable_binding(name, true);
}
pub(crate) fn initialize_binding(
self,
envs: &mut impl AsMut<Environments>,
name: String,
value: Value,
) {
let envs = envs.as_mut();
self.inner_initialize_binding(envs, name, value);
}
fn inner_initialize_binding(self, envs: &mut Environments, name: String, value: Value) {
envs.get_declarative_environment_mut(self.get_declarative_env(envs))
.initialize_binding(name, value);
}
pub(crate) fn set_mutable_binding<'a>(
self,
agent: &mut Agent,
name: String,
value: Value,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, ()> {
let env_rec = agent.heap.environments.get_module_environment(self);
if env_rec.has_indirect_binding(name) {
let error_message = format!(
"Cannot assign to immutable binding '{}'.",
name.to_string_lossy_(agent)
);
return Err(agent.throw_exception(ExceptionType::TypeError, error_message, gc));
}
env_rec
.declarative_environment
.set_mutable_binding(agent, name, value, true, gc)
}
pub(crate) fn try_get_binding_value(
self,
agent: &mut Agent,
name: String,
is_strict: bool,
gc: NoGcScope<'e, '_>,
) -> TryResult<'e, Value<'e>> {
let Some(value) = self.get_binding_value(agent, name, is_strict, gc) else {
return throw_uninitialized_binding(agent, name, gc).into();
};
TryResult::Continue(value)
}
pub(crate) fn env_get_binding_value(
self,
agent: &mut Agent,
name: String,
is_strict: bool,
gc: NoGcScope<'e, '_>,
) -> JsResult<'e, Value<'e>> {
let Some(value) = self.get_binding_value(agent, name, is_strict, gc) else {
return Err(throw_uninitialized_binding(agent, name, gc));
};
Ok(value)
}
pub(crate) fn get_binding_value(
self,
agent: &Agent,
name: String,
is_strict: bool,
gc: NoGcScope<'e, '_>,
) -> Option<Value<'e>> {
debug_assert!(is_strict);
debug_assert!(self.has_binding(agent, name));
let env_rec = agent.heap.environments.get_module_environment(self);
if let Some(IndirectBinding { m, n2 }) = env_rec.get_indirect_binding(name) {
let target_env = m.environment(agent, gc)?;
return target_env.get_binding_value(agent, *n2, true, gc);
}
let decl_env = env_rec.declarative_environment;
let binding = agent
.heap
.environments
.get_declarative_environment(decl_env)
.get_binding(name)
.unwrap();
let value = binding.value?;
Some(value.bind(gc))
}
}
pub(crate) fn throw_uninitialized_binding<'a>(
agent: &mut Agent,
name: String,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
let name = name.to_string_lossy_(agent);
agent.throw_exception(
ExceptionType::ReferenceError,
format!("attempted to access uninitialized binding {name}"),
gc,
)
}
pub(crate) fn create_import_binding(
agent: &mut Agent,
env_rec: ModuleEnvironment,
n: String,
m: AbstractModule,
n2: String,
gc: NoGcScope,
) {
debug_assert!(!env_rec.has_binding(agent, n));
let m_environment = m.environment(agent, gc);
let envs = &mut agent.heap.environments;
let can_be_direct_binding = if let Some(m_environment) = m_environment {
let m_decl_env = m_environment.get_declarative_env(envs);
let m_decl_env = envs.get_declarative_environment_mut(m_decl_env);
let m_direct_binding = m_decl_env.get_binding(n2);
let Some(m_direct_binding) = m_direct_binding else {
unreachable!();
};
!m_direct_binding.mutable
} else {
false
};
if can_be_direct_binding {
env_rec.create_immutable_binding(envs, n);
} else {
let created_new = envs
.get_module_environment_mut(env_rec)
.indirect_bindings
.insert(
n.unbind(),
IndirectBinding {
m: m.unbind(),
n2: n2.unbind(),
},
)
.is_none();
debug_assert!(created_new);
}
}
pub(crate) fn create_indirect_import_binding(
agent: &mut Agent,
env_rec: ModuleEnvironment,
n: String,
m: AbstractModule,
n2: String,
) {
debug_assert!(!env_rec.has_binding(agent, n));
let envs = &mut agent.heap.environments;
let created_new = envs
.get_module_environment_mut(env_rec)
.indirect_bindings
.insert(
n.unbind(),
IndirectBinding {
m: m.unbind(),
n2: n2.unbind(),
},
)
.is_none();
debug_assert!(created_new);
}
pub(crate) fn initialize_import_binding(
agent: &mut Agent,
env_rec: ModuleEnvironment,
n: String,
m: AbstractModule,
n2: String,
gc: NoGcScope,
) {
let direct_binding = env_rec.get_declarative_env(agent).get_binding_mut(agent, n);
let Some(direct_binding) = direct_binding else {
return;
};
debug_assert!(!direct_binding.mutable);
debug_assert!(direct_binding.strict);
let direct_binding_value = &mut direct_binding.value as *mut Option<Value<'static>>;
let value = m
.environment(agent, gc)
.expect("Attempted to access unlinked module's environment")
.get_declarative_env(agent)
.get_binding(agent, n2)
.expect("Direct binding target did not exist")
.value
.expect("Attempted to access uninitialized binding");
unsafe { *direct_binding_value = Some(value) };
}
impl HeapMarkAndSweep for ModuleEnvironment<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
queues.module_environments.push(*self);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
compactions.module_environments.shift_index(&mut self.0);
}
}
impl HeapMarkAndSweep for ModuleEnvironmentRecord {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
declarative_environment,
indirect_bindings,
} = self;
declarative_environment.mark_values(queues);
indirect_bindings.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
declarative_environment,
indirect_bindings,
} = self;
declarative_environment.sweep_values(compactions);
indirect_bindings.sweep_values(compactions);
}
}
impl HeapMarkAndSweep for IndirectBinding<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self { m, n2 } = self;
m.mark_values(queues);
n2.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self { m, n2 } = self;
m.sweep_values(compactions);
n2.sweep_values(compactions);
}
}