1use std::hash::BuildHasherDefault;
2
3use indexmap::IndexSet;
4use rustc_hash::{FxHashMap, FxHasher};
5
6use boa_gc::{Finalize, Trace};
7
8use crate::object::internal_methods::immutable_prototype::immutable_prototype_exotic_set_prototype_of;
9use crate::object::internal_methods::{
10 InternalMethodPropertyContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
11 ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property,
12 ordinary_has_property, ordinary_own_property_keys, ordinary_try_get,
13};
14use crate::object::{JsData, JsPrototype};
15use crate::property::{PropertyDescriptor, PropertyKey};
16use crate::{Context, JsExpect, JsResult, JsString, JsValue, js_string, object::JsObject};
17use crate::{JsNativeError, Module};
18
19use super::{BindingName, ResolvedBinding};
20
21#[derive(Debug, Trace, Finalize)]
25pub struct ModuleNamespace {
26 module: Module,
27 #[unsafe_ignore_trace]
28 exports: IndexSet<JsString, BuildHasherDefault<FxHasher>>,
29 #[unsafe_ignore_trace]
40 resolved_bindings: FxHashMap<JsString, ResolvedBinding>,
41}
42
43impl JsData for ModuleNamespace {
44 fn internal_methods(&self) -> &'static InternalObjectMethods {
45 static METHODS: InternalObjectMethods = InternalObjectMethods {
46 __get_prototype_of__: module_namespace_exotic_get_prototype_of,
47 __set_prototype_of__: module_namespace_exotic_set_prototype_of,
48 __is_extensible__: module_namespace_exotic_is_extensible,
49 __prevent_extensions__: module_namespace_exotic_prevent_extensions,
50 __get_own_property__: module_namespace_exotic_get_own_property,
51 __define_own_property__: module_namespace_exotic_define_own_property,
52 __has_property__: module_namespace_exotic_has_property,
53 __try_get__: module_namespace_exotic_try_get,
54 __get__: module_namespace_exotic_get,
55 __set__: module_namespace_exotic_set,
56 __delete__: module_namespace_exotic_delete,
57 __own_property_keys__: module_namespace_exotic_own_property_keys,
58 ..ORDINARY_INTERNAL_METHODS
59 };
60
61 &METHODS
62 }
63}
64
65impl ModuleNamespace {
66 pub(crate) fn create(module: Module, names: Vec<JsString>, context: &mut Context) -> JsObject {
70 let mut exports = names.into_iter().collect::<IndexSet<_, _>>();
75 exports.sort();
76
77 let mut resolved_bindings = FxHashMap::default();
81 for name in &exports {
82 if let Ok(binding) = module.resolve_export(
83 name,
84 &mut rustc_hash::FxHashSet::default(),
85 context.interner(),
86 ) {
87 resolved_bindings.insert(name.clone(), binding);
88 }
89 }
90
91 context.intrinsics().templates().namespace().create(
103 Self {
104 module,
105 exports,
106 resolved_bindings,
107 },
108 vec![js_string!("Module").into()],
109 )
110 }
111
112 pub(crate) const fn exports(&self) -> &IndexSet<JsString, BuildHasherDefault<FxHasher>> {
114 &self.exports
115 }
116
117 pub(crate) const fn module(&self) -> &Module {
119 &self.module
120 }
121
122 pub(crate) fn get_resolved_binding(&self, name: &JsString) -> Option<&ResolvedBinding> {
124 self.resolved_bindings.get(name)
125 }
126}
127
128#[allow(clippy::unnecessary_wraps)]
132fn module_namespace_exotic_get_prototype_of(
133 _: &JsObject,
134 _: &mut Context,
135) -> JsResult<JsPrototype> {
136 Ok(None)
138}
139
140#[allow(clippy::unnecessary_wraps)]
144fn module_namespace_exotic_set_prototype_of(
145 obj: &JsObject,
146 val: JsPrototype,
147 context: &mut Context,
148) -> JsResult<bool> {
149 Ok(
151 immutable_prototype_exotic_set_prototype_of(obj, val, context)
152 .js_expect("this must not fail per the spec")?,
153 )
154}
155
156#[allow(clippy::unnecessary_wraps)]
160fn module_namespace_exotic_is_extensible(_: &JsObject, _: &mut Context) -> JsResult<bool> {
161 Ok(false)
163}
164
165#[allow(clippy::unnecessary_wraps)]
169fn module_namespace_exotic_prevent_extensions(_: &JsObject, _: &mut Context) -> JsResult<bool> {
170 Ok(true)
171}
172
173fn module_namespace_exotic_get_own_property(
177 obj: &JsObject,
178 key: &PropertyKey,
179 context: &mut InternalMethodPropertyContext<'_>,
180) -> JsResult<Option<PropertyDescriptor>> {
181 let key = match key {
183 PropertyKey::Symbol(_) => return ordinary_get_own_property(obj, key, context),
184 PropertyKey::Index(idx) => js_string!(format!("{}", idx.get())),
185 PropertyKey::String(s) => s.clone(),
186 };
187
188 {
189 let obj = obj
190 .downcast_ref::<ModuleNamespace>()
191 .js_expect("internal method can only be called on module namespace objects")?;
192 let exports = obj.exports();
194
195 if !exports.contains(&key) {
197 return Ok(None);
198 }
199 }
200
201 let value = obj.get(key, context)?;
203
204 Ok(Some(
206 PropertyDescriptor::builder()
207 .value(value)
208 .writable(true)
209 .enumerable(true)
210 .configurable(false)
211 .build(),
212 ))
213}
214
215fn module_namespace_exotic_define_own_property(
219 obj: &JsObject,
220 key: &PropertyKey,
221 desc: PropertyDescriptor,
222 context: &mut InternalMethodPropertyContext<'_>,
223) -> JsResult<bool> {
224 if let PropertyKey::Symbol(_) = key {
226 return ordinary_define_own_property(obj, key, desc, context);
227 }
228
229 let Some(current) = obj.__get_own_property__(key, context)? else {
231 return Ok(false);
233 };
234
235 if desc.configurable() == Some(true)
240 || desc.enumerable() == Some(false)
241 || desc.is_accessor_descriptor()
242 || desc.writable() == Some(false)
243 {
244 return Ok(false);
245 }
246
247 Ok(desc.value().is_none_or(|v| v == current.expect_value()))
250}
251
252fn module_namespace_exotic_has_property(
256 obj: &JsObject,
257 key: &PropertyKey,
258 context: &mut InternalMethodPropertyContext<'_>,
259) -> JsResult<bool> {
260 let key = match key {
262 PropertyKey::Symbol(_) => return ordinary_has_property(obj, key, context),
263 PropertyKey::Index(idx) => js_string!(format!("{}", idx.get())),
264 PropertyKey::String(s) => s.clone(),
265 };
266
267 let obj = obj
268 .downcast_ref::<ModuleNamespace>()
269 .js_expect("internal method can only be called on module namespace objects")?;
270
271 let exports = obj.exports();
273
274 Ok(exports.contains(&key))
277}
278
279fn module_namespace_exotic_try_get(
290 obj: &JsObject,
291 key: &PropertyKey,
292 receiver: JsValue,
293 context: &mut InternalMethodPropertyContext<'_>,
294) -> JsResult<Option<JsValue>> {
295 let key = match key {
298 PropertyKey::Symbol(_) => return ordinary_try_get(obj, key, receiver, context),
299 PropertyKey::Index(idx) => js_string!(format!("{}", idx.get())),
300 PropertyKey::String(s) => s.clone(),
301 };
302
303 let obj = obj
304 .downcast_ref::<ModuleNamespace>()
305 .js_expect("internal method can only be called on module namespace objects")?;
306
307 let exports = obj.exports();
309
310 let Some(export_name) = exports.get(&key).cloned() else {
312 return Ok(None);
313 };
314
315 let module = obj.module();
317
318 let fallback;
323 let binding = if let Some(b) = obj.get_resolved_binding(&export_name) {
324 b
325 } else {
326 fallback = module
327 .resolve_export(
328 &export_name,
329 &mut rustc_hash::FxHashSet::default(),
330 context.interner(),
331 )
332 .expect("6. Assert: binding is a ResolvedBinding Record.");
333 &fallback
334 };
335
336 let target_module = binding.module();
339
340 if let BindingName::Name(name) = binding.binding_name() {
341 let Some(env) = target_module.environment() else {
343 let import = export_name.to_std_string_escaped();
345 return Err(JsNativeError::reference()
346 .with_message(format!(
347 "cannot get import `{import}` from an uninitialized module"
348 ))
349 .into());
350 };
351
352 let locator = env
353 .kind()
354 .as_module()
355 .js_expect("must be module environment")?
356 .compile()
357 .get_binding(name)
358 .js_expect("checked before that the name was reachable")?;
359
360 env.get(locator.binding_index()).map(Some).ok_or_else(|| {
362 let import = export_name.to_std_string_escaped();
363
364 JsNativeError::reference()
365 .with_message(format!("cannot get uninitialized import `{import}`"))
366 .into()
367 })
368 } else {
369 Ok(Some(target_module.namespace(context).into()))
372 }
373}
374
375fn module_namespace_exotic_get(
379 obj: &JsObject,
380 key: &PropertyKey,
381 receiver: JsValue,
382 context: &mut InternalMethodPropertyContext<'_>,
383) -> JsResult<JsValue> {
384 let key = match key {
387 PropertyKey::Symbol(_) => return ordinary_get(obj, key, receiver, context),
388 PropertyKey::Index(idx) => js_string!(format!("{}", idx.get())),
389 PropertyKey::String(s) => s.clone(),
390 };
391
392 let obj = obj
393 .downcast_ref::<ModuleNamespace>()
394 .js_expect("internal method can only be called on module namespace objects")?;
395
396 let exports = obj.exports();
398 let Some(export_name) = exports.get(&key).cloned() else {
400 return Ok(JsValue::undefined());
401 };
402
403 let module = obj.module();
405
406 let fallback;
411 let binding = if let Some(b) = obj.get_resolved_binding(&export_name) {
412 b
413 } else {
414 fallback = module
415 .resolve_export(
416 &export_name,
417 &mut rustc_hash::FxHashSet::default(),
418 context.interner(),
419 )
420 .expect("6. Assert: binding is a ResolvedBinding Record.");
421 &fallback
422 };
423
424 let target_module = binding.module();
427
428 if let BindingName::Name(name) = binding.binding_name() {
429 let Some(env) = target_module.environment() else {
431 let import = export_name.to_std_string_escaped();
433 return Err(JsNativeError::reference()
434 .with_message(format!(
435 "cannot get import `{import}` from an uninitialized module"
436 ))
437 .into());
438 };
439
440 let locator = env
441 .kind()
442 .as_module()
443 .js_expect("must be module environment")?
444 .compile()
445 .get_binding(name)
446 .js_expect("checked before that the name was reachable")?;
447
448 env.get(locator.binding_index()).ok_or_else(|| {
450 let import = export_name.to_std_string_escaped();
451
452 JsNativeError::reference()
453 .with_message(format!("cannot get uninitialized import `{import}`"))
454 .into()
455 })
456 } else {
457 Ok(target_module.namespace(context).into())
460 }
461}
462
463#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
467fn module_namespace_exotic_set(
468 _obj: &JsObject,
469 _key: PropertyKey,
470 _value: JsValue,
471 _receiver: JsValue,
472 _context: &mut InternalMethodPropertyContext<'_>,
473) -> JsResult<bool> {
474 Ok(false)
476}
477
478fn module_namespace_exotic_delete(
482 obj: &JsObject,
483 key: &PropertyKey,
484 context: &mut InternalMethodPropertyContext<'_>,
485) -> JsResult<bool> {
486 let key = match key {
489 PropertyKey::Symbol(_) => return ordinary_delete(obj, key, context),
490 PropertyKey::Index(idx) => js_string!(format!("{}", idx.get())),
491 PropertyKey::String(s) => s.clone(),
492 };
493
494 let obj = obj
495 .downcast_ref::<ModuleNamespace>()
496 .js_expect("internal method can only be called on module namespace objects")?;
497
498 let exports = obj.exports();
500
501 Ok(!exports.contains(&key))
504}
505
506fn module_namespace_exotic_own_property_keys(
510 obj: &JsObject,
511 context: &mut Context,
512) -> JsResult<Vec<PropertyKey>> {
513 let symbol_keys = ordinary_own_property_keys(obj, context)?;
515
516 let obj = obj
517 .downcast_ref::<ModuleNamespace>()
518 .js_expect("internal method can only be called on module namespace objects")?;
519
520 let exports = obj.exports();
522
523 Ok(exports
525 .iter()
526 .map(|k| PropertyKey::String(k.clone()))
527 .chain(symbol_keys)
528 .collect())
529}