use std::collections::HashSet;
use crate::{
js_string,
module::BindingName,
object::{JsObject, JsPrototype},
property::{PropertyDescriptor, PropertyKey},
Context, JsNativeError, JsResult, JsValue,
};
use super::{
immutable_prototype, ordinary_define_own_property, ordinary_delete, ordinary_get,
ordinary_get_own_property, ordinary_has_property, ordinary_own_property_keys,
InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
};
pub(crate) static MODULE_NAMESPACE_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
InternalObjectMethods {
__get_prototype_of__: module_namespace_exotic_get_prototype_of,
__set_prototype_of__: module_namespace_exotic_set_prototype_of,
__is_extensible__: module_namespace_exotic_is_extensible,
__prevent_extensions__: module_namespace_exotic_prevent_extensions,
__get_own_property__: module_namespace_exotic_get_own_property,
__define_own_property__: module_namespace_exotic_define_own_property,
__has_property__: module_namespace_exotic_has_property,
__get__: module_namespace_exotic_get,
__set__: module_namespace_exotic_set,
__delete__: module_namespace_exotic_delete,
__own_property_keys__: module_namespace_exotic_own_property_keys,
..ORDINARY_INTERNAL_METHODS
};
#[allow(clippy::unnecessary_wraps)]
fn module_namespace_exotic_get_prototype_of(
_: &JsObject,
_: &mut Context<'_>,
) -> JsResult<JsPrototype> {
Ok(None)
}
#[allow(clippy::unnecessary_wraps)]
fn module_namespace_exotic_set_prototype_of(
obj: &JsObject,
val: JsPrototype,
context: &mut Context<'_>,
) -> JsResult<bool> {
Ok(
immutable_prototype::immutable_prototype_exotic_set_prototype_of(obj, val, context)
.expect("this must not fail per the spec"),
)
}
#[allow(clippy::unnecessary_wraps)]
fn module_namespace_exotic_is_extensible(_: &JsObject, _: &mut Context<'_>) -> JsResult<bool> {
Ok(false)
}
#[allow(clippy::unnecessary_wraps)]
fn module_namespace_exotic_prevent_extensions(_: &JsObject, _: &mut Context<'_>) -> JsResult<bool> {
Ok(true)
}
fn module_namespace_exotic_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
let key = match key {
PropertyKey::Symbol(_) => return ordinary_get_own_property(obj, key, context),
PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s.clone(),
};
{
let obj = obj.borrow();
let obj = obj
.as_module_namespace()
.expect("internal method can only be called on module namespace objects");
let exports = obj.exports();
if !exports.contains_key(&key) {
return Ok(None);
}
}
let value = obj.get(key, context)?;
Ok(Some(
PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(true)
.configurable(false)
.build(),
))
}
fn module_namespace_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context<'_>,
) -> JsResult<bool> {
if let PropertyKey::Symbol(_) = key {
return ordinary_define_own_property(obj, key, desc, context);
}
let Some(current) = obj.__get_own_property__(key, context)? else {
return Ok(false);
};
if desc.configurable() == Some(true)
|| desc.enumerable() == Some(false)
|| desc.is_accessor_descriptor()
|| desc.writable() == Some(false)
{
return Ok(false);
}
Ok(desc.value().map_or(true, |v| v == current.expect_value()))
}
fn module_namespace_exotic_has_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context<'_>,
) -> JsResult<bool> {
let key = match key {
PropertyKey::Symbol(_) => return ordinary_has_property(obj, key, context),
PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s.clone(),
};
let obj = obj.borrow();
let obj = obj
.as_module_namespace()
.expect("internal method can only be called on module namespace objects");
let exports = obj.exports();
Ok(exports.contains_key(&key))
}
fn module_namespace_exotic_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let key = match key {
PropertyKey::Symbol(_) => return ordinary_get(obj, key, receiver, context),
PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s.clone(),
};
let obj = obj.borrow();
let obj = obj
.as_module_namespace()
.expect("internal method can only be called on module namespace objects");
let exports = obj.exports();
let Some(export_name) = exports.get(&key).copied() else {
return Ok(JsValue::undefined());
};
let m = obj.module();
let binding = m
.resolve_export(export_name, &mut HashSet::default())
.expect("6. Assert: binding is a ResolvedBinding Record.");
let target_module = binding.module();
if let BindingName::Name(name) = binding.binding_name() {
let Some(env) = target_module.environment() else {
let import = context.interner().resolve_expect(export_name);
return Err(JsNativeError::reference().with_message(
format!("cannot get import `{import}` from an uninitialized module")
).into());
};
let locator = env
.compile_env()
.borrow()
.get_binding(name)
.expect("checked before that the name was reachable");
env.get(locator.binding_index()).ok_or_else(|| {
let import = context.interner().resolve_expect(export_name);
JsNativeError::reference()
.with_message(format!("cannot get uninitialized import `{import}`"))
.into()
})
} else {
Ok(target_module.namespace(context).into())
}
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn module_namespace_exotic_set(
_obj: &JsObject,
_key: PropertyKey,
_value: JsValue,
_receiver: JsValue,
_context: &mut Context<'_>,
) -> JsResult<bool> {
Ok(false)
}
fn module_namespace_exotic_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context<'_>,
) -> JsResult<bool> {
let key = match key {
PropertyKey::Symbol(_) => return ordinary_delete(obj, key, context),
PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s.clone(),
};
let obj = obj.borrow();
let obj = obj
.as_module_namespace()
.expect("internal method can only be called on module namespace objects");
let exports = obj.exports();
Ok(!exports.contains_key(&key))
}
fn module_namespace_exotic_own_property_keys(
obj: &JsObject,
context: &mut Context<'_>,
) -> JsResult<Vec<PropertyKey>> {
let symbol_keys = ordinary_own_property_keys(obj, context)?;
let obj = obj.borrow();
let obj = obj
.as_module_namespace()
.expect("internal method can only be called on module namespace objects");
let exports = obj.exports();
Ok(exports
.keys()
.map(|k| PropertyKey::String(k.clone()))
.chain(symbol_keys)
.collect())
}