use boa_ast::scope::Scope;
use boa_gc::{Finalize, GcRefCell, Trace, custom_trace};
use crate::{JsNativeError, JsObject, JsResult, JsValue, builtins::function::OrdinaryFunction};
use super::PoisonableEnvironment;
#[derive(Debug, Trace, Finalize)]
pub(crate) struct FunctionEnvironment {
inner: PoisonableEnvironment,
slots: Box<FunctionSlots>,
#[unsafe_ignore_trace]
scope: Scope,
}
impl FunctionEnvironment {
pub(crate) fn new(
bindings: u32,
poisoned: bool,
with: bool,
slots: FunctionSlots,
scope: Scope,
) -> Self {
Self {
inner: PoisonableEnvironment::new(bindings, poisoned, with),
slots: Box::new(slots),
scope,
}
}
pub(crate) const fn slots(&self) -> &FunctionSlots {
&self.slots
}
pub(crate) const fn compile(&self) -> &Scope {
&self.scope
}
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(())
}
#[track_caller]
pub(crate) fn has_super_binding(&self) -> bool {
if matches!(&*self.slots.this.borrow(), ThisBindingStatus::Lexical) {
return false;
}
self.slots
.function_object
.downcast_ref::<OrdinaryFunction>()
.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, mark, {
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()
}
}