use boa_gc::{custom_trace, Finalize, GcRefCell, Trace};
use crate::{JsNativeError, JsObject, JsResult, JsValue};
use super::PoisonableEnvironment;
#[derive(Debug, Trace, Finalize)]
pub(crate) struct FunctionEnvironment {
inner: PoisonableEnvironment,
slots: FunctionSlots,
}
impl FunctionEnvironment {
pub(crate) fn new(bindings: u32, poisoned: bool, with: bool, slots: FunctionSlots) -> Self {
Self {
inner: PoisonableEnvironment::new(bindings, poisoned, with),
slots,
}
}
pub(crate) const fn slots(&self) -> &FunctionSlots {
&self.slots
}
pub(crate) const fn poisonable_environment(&self) -> &PoisonableEnvironment {
&self.inner
}
#[track_caller]
pub(crate) fn get(&self, index: u32) -> Option<JsValue> {
self.inner.get(index)
}
#[track_caller]
pub(crate) fn set(&self, index: u32, value: JsValue) {
self.inner.set(index, value);
}
pub(crate) fn bind_this_value(&self, value: JsObject) -> JsResult<()> {
let mut this = self.slots.this.borrow_mut();
match &*this {
ThisBindingStatus::Lexical => {
unreachable!("1. Assert: envRec.[[ThisBindingStatus]] is not lexical.")
}
ThisBindingStatus::Initialized(_) => {
return Err(JsNativeError::reference()
.with_message("cannot reinitialize `this` binding")
.into());
}
ThisBindingStatus::Uninitialized => {
*this = ThisBindingStatus::Initialized(value.into());
}
}
Ok(())
}
pub(crate) fn has_super_binding(&self) -> bool {
if matches!(&*self.slots.this.borrow(), ThisBindingStatus::Lexical) {
return false;
}
self.slots
.function_object
.borrow()
.as_function()
.expect("function object must be function")
.get_home_object()
.is_some()
}
pub(crate) fn has_this_binding(&self) -> bool {
!matches!(&*self.slots.this.borrow(), ThisBindingStatus::Lexical)
}
pub(crate) fn get_this_binding(&self) -> JsResult<Option<JsValue>> {
match &*self.slots.this.borrow() {
ThisBindingStatus::Lexical => Ok(None),
ThisBindingStatus::Uninitialized => Err(JsNativeError::reference()
.with_message(
"Must call super constructor in derived \
class before accessing 'this' or returning from derived constructor",
)
.into()),
ThisBindingStatus::Initialized(this) => Ok(Some(this.clone())),
}
}
}
#[derive(Clone, Debug, Finalize)]
pub(crate) enum ThisBindingStatus {
Lexical,
Uninitialized,
Initialized(JsValue),
}
unsafe impl Trace for ThisBindingStatus {
custom_trace!(this, {
match this {
Self::Initialized(obj) => mark(obj),
Self::Lexical | Self::Uninitialized => {}
}
});
}
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) struct FunctionSlots {
this: GcRefCell<ThisBindingStatus>,
function_object: JsObject,
new_target: Option<JsObject>,
}
impl FunctionSlots {
pub(crate) fn new(
this: ThisBindingStatus,
function_object: JsObject,
new_target: Option<JsObject>,
) -> Self {
Self {
this: GcRefCell::new(this),
function_object,
new_target,
}
}
pub(crate) const fn function_object(&self) -> &JsObject {
&self.function_object
}
pub(crate) const fn new_target(&self) -> Option<&JsObject> {
self.new_target.as_ref()
}
}