use crate::{
Context, JsData, JsResult, JsValue,
bytecompiler::ToJsString,
environments::DeclarativeEnvironment,
object::{
JsObject,
internal_methods::{
InternalMethodPropertyContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property,
ordinary_set, ordinary_try_get,
},
},
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
};
use boa_ast::{function::FormalParameterList, operations::bound_names, scope::Scope};
use boa_gc::{Finalize, Gc, Trace};
use boa_interner::Interner;
use rustc_hash::FxHashMap;
use thin_vec::{ThinVec, thin_vec};
#[derive(Debug, Copy, Clone, Trace, Finalize, JsData)]
#[boa_gc(empty_trace)]
pub(crate) struct UnmappedArguments;
impl UnmappedArguments {
#[allow(clippy::new_ret_no_self)]
pub(crate) fn new(arguments_list: &[JsValue], context: &mut Context) -> JsObject {
let len = arguments_list.len();
let values_function = context.intrinsics().objects().array_prototype_values();
let throw_type_error = context.intrinsics().objects().throw_type_error();
let obj = context
.intrinsics()
.templates()
.unmapped_arguments()
.create(
Self,
vec![
len.into(),
values_function.into(),
throw_type_error.clone().into(), throw_type_error.into(), ],
);
obj.borrow_mut()
.properties_mut()
.override_indexed_properties(arguments_list.iter().cloned().collect());
obj
}
}
#[derive(Debug, Clone, Trace, Finalize)]
pub(crate) struct MappedArguments {
#[unsafe_ignore_trace]
binding_indices: Vec<Option<u32>>,
environment: Gc<DeclarativeEnvironment>,
}
impl JsData for MappedArguments {
fn internal_methods(&self) -> &'static InternalObjectMethods {
static METHODS: InternalObjectMethods = InternalObjectMethods {
__get_own_property__: arguments_exotic_get_own_property,
__define_own_property__: arguments_exotic_define_own_property,
__try_get__: arguments_exotic_try_get,
__get__: arguments_exotic_get,
__set__: arguments_exotic_set,
__delete__: arguments_exotic_delete,
..ORDINARY_INTERNAL_METHODS
};
&METHODS
}
}
impl MappedArguments {
pub(crate) fn delete(&mut self, index: u32) {
if let Some(binding) = self.binding_indices.get_mut(index as usize) {
*binding = None;
}
}
pub(crate) fn get(&self, index: u32) -> Option<JsValue> {
let binding_index = self
.binding_indices
.get(index as usize)
.copied()
.flatten()?;
self.environment.get(binding_index)
}
pub(crate) fn set(&self, index: u32, value: &JsValue) {
if let Some(binding_index) = self.binding_indices.get(index as usize).copied().flatten() {
self.environment.set(binding_index, value.clone());
}
}
}
impl MappedArguments {
pub(crate) fn binding_indices(
formals: &FormalParameterList,
scope: &Scope,
interner: &Interner,
) -> ThinVec<Option<u32>> {
let mut bindings = FxHashMap::default();
let mut property_index = 0;
for name in bound_names(formals) {
let binding_index = scope
.get_binding(&name.to_js_string(interner))
.expect("binding must exist")
.binding_index();
let entry = bindings
.entry(name)
.or_insert((binding_index, property_index));
entry.1 = property_index;
property_index += 1;
}
let mut binding_indices = thin_vec![None; property_index];
for (binding_index, property_index) in bindings.values() {
binding_indices[*property_index] = Some(*binding_index);
}
binding_indices
}
#[allow(clippy::new_ret_no_self)]
pub(crate) fn new(
func: &JsObject,
binding_indices: &[Option<u32>],
arguments_list: &[JsValue],
env: &Gc<DeclarativeEnvironment>,
context: &Context,
) -> JsObject {
let len = arguments_list.len();
let range = binding_indices.len().min(len);
let map = MappedArguments {
binding_indices: binding_indices[..range].to_vec(),
environment: env.clone(),
};
let values_function = context.intrinsics().objects().array_prototype_values();
let obj = context.intrinsics().templates().mapped_arguments().create(
map,
vec![
len.into(),
values_function.into(),
func.clone().into(),
],
);
obj.borrow_mut()
.properties_mut()
.override_indexed_properties(arguments_list.iter().cloned().collect());
obj
}
}
pub(crate) fn arguments_exotic_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
let Some(desc) = ordinary_get_own_property(obj, key, context)? else {
return Ok(None);
};
if let PropertyKey::Index(index) = key
&& let Some(value) = obj
.downcast_ref::<MappedArguments>()
.expect("arguments exotic method must only be callable from arguments objects")
.get(index.get())
{
return Ok(Some(
PropertyDescriptor::builder()
.value(value)
.maybe_writable(desc.writable())
.maybe_enumerable(desc.enumerable())
.maybe_configurable(desc.configurable())
.build(),
));
}
Ok(Some(desc))
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn arguments_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
let mapped = if let &PropertyKey::Index(index) = &key {
obj.downcast_ref::<MappedArguments>()
.expect("arguments exotic method must only be callable from arguments objects")
.get(index.get())
.map(|value| (index, value))
} else {
None
};
let new_arg_desc = match desc.kind() {
DescriptorKind::Data {
writable: Some(false),
value: None,
} =>
{
if let Some((_, value)) = &mapped {
PropertyDescriptor::builder()
.value(value.clone())
.writable(false)
.maybe_enumerable(desc.enumerable())
.maybe_configurable(desc.configurable())
.build()
} else {
desc.clone()
}
}
_ => desc.clone(),
};
if !ordinary_define_own_property(obj, key, new_arg_desc, context)? {
return Ok(false);
}
if let Some((index, _)) = mapped {
let mut map = obj
.downcast_mut::<MappedArguments>()
.expect("arguments exotic method must only be callable from arguments objects");
if desc.is_accessor_descriptor() {
map.delete(index.get());
}
else {
if let Some(value) = desc.value() {
map.set(index.get(), value);
}
if desc.writable() == Some(false) {
map.delete(index.get());
}
}
}
Ok(true)
}
pub(crate) fn arguments_exotic_try_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<Option<JsValue>> {
if let PropertyKey::Index(index) = key
&& let Some(value) = obj
.downcast_ref::<MappedArguments>()
.expect("arguments exotic method must only be callable from arguments objects")
.get(index.get())
{
return Ok(Some(value));
}
ordinary_try_get(obj, key, receiver, context)
}
pub(crate) fn arguments_exotic_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<JsValue> {
if let PropertyKey::Index(index) = key
&& let Some(value) = obj
.downcast_ref::<MappedArguments>()
.expect("arguments exotic method must only be callable from arguments objects")
.get(index.get())
{
return Ok(value);
}
ordinary_get(obj, key, receiver, context)
}
pub(crate) fn arguments_exotic_set(
obj: &JsObject,
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
if let PropertyKey::Index(index) = &key
&& JsValue::same_value(&obj.clone().into(), &receiver)
{
obj.downcast_ref::<MappedArguments>()
.expect("arguments exotic method must only be callable from arguments objects")
.set(index.get(), &value);
}
ordinary_set(obj, key, value, receiver, context)
}
pub(crate) fn arguments_exotic_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
let result = ordinary_delete(obj, key, context)?;
if result && let PropertyKey::Index(index) = key {
obj.downcast_mut::<MappedArguments>()
.expect("arguments exotic method must only be callable from arguments objects")
.delete(index.get());
}
Ok(result)
}