1use boa_ast::scope::Scope;
2use boa_gc::{Finalize, Gc, GcRefCell, Trace};
3use rustc_hash::FxHashSet;
4
5use super::{BindingName, ResolveExportError, ResolvedBinding};
6use crate::{
7 Context, JsExpect, JsNativeError, JsResult, JsString, JsValue, Module, SpannedSourceText,
8 builtins::promise::ResolvingFunctions,
9 bytecompiler::ByteCompiler,
10 class::{Class, ClassBuilder},
11 environments::{DeclarativeEnvironment, EnvironmentStack},
12 js_string,
13 object::JsPromise,
14 vm::{ActiveRunnable, CallFrame, CodeBlock, source_info::SourcePath},
15};
16
17trait TraceableCallback: Trace {
18 fn call(&self, module: &SyntheticModule, context: &mut Context) -> JsResult<()>;
19}
20
21#[derive(Trace, Finalize)]
22struct Callback<F, T>
23where
24 F: Fn(&SyntheticModule, &T, &mut Context) -> JsResult<()>,
25 T: Trace,
26{
27 #[unsafe_ignore_trace]
31 f: F,
32 captures: T,
33}
34
35impl<F, T> TraceableCallback for Callback<F, T>
36where
37 F: Fn(&SyntheticModule, &T, &mut Context) -> JsResult<()>,
38 T: Trace,
39{
40 fn call(&self, module: &SyntheticModule, context: &mut Context) -> JsResult<()> {
41 (self.f)(module, &self.captures, context)
42 }
43}
44
45#[derive(Clone, Trace, Finalize)]
55pub struct SyntheticModuleInitializer {
56 inner: Gc<dyn TraceableCallback>,
57}
58
59impl std::fmt::Debug for SyntheticModuleInitializer {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("SyntheticModuleInitializer")
62 .finish_non_exhaustive()
63 }
64}
65
66impl SyntheticModuleInitializer {
67 pub fn from_copy_closure<F>(closure: F) -> Self
69 where
70 F: Fn(&SyntheticModule, &mut Context) -> JsResult<()> + Copy + 'static,
71 {
72 unsafe { Self::from_closure(closure) }
74 }
75
76 pub fn from_copy_closure_with_captures<F, T>(closure: F, captures: T) -> Self
78 where
79 F: Fn(&SyntheticModule, &T, &mut Context) -> JsResult<()> + Copy + 'static,
80 T: Trace + 'static,
81 {
82 unsafe { Self::from_closure_with_captures(closure, captures) }
84 }
85
86 pub unsafe fn from_closure<F>(closure: F) -> Self
95 where
96 F: Fn(&SyntheticModule, &mut Context) -> JsResult<()> + 'static,
97 {
98 unsafe {
100 Self::from_closure_with_captures(
101 move |module, (), context| closure(module, context),
102 (),
103 )
104 }
105 }
106
107 pub unsafe fn from_closure_with_captures<F, T>(closure: F, captures: T) -> Self
116 where
117 F: Fn(&SyntheticModule, &T, &mut Context) -> JsResult<()> + 'static,
118 T: Trace + 'static,
119 {
120 let ptr = Gc::into_raw(Gc::new(Callback {
123 f: closure,
124 captures,
125 }));
126
127 unsafe {
130 Self {
131 inner: Gc::from_raw(ptr),
132 }
133 }
134 }
135
136 #[inline]
138 pub(crate) fn call(&self, module: &SyntheticModule, context: &mut Context) -> JsResult<()> {
139 self.inner.call(module, context)
140 }
141}
142
143#[derive(Debug, Trace, Finalize, Default)]
145#[boa_gc(unsafe_no_drop)]
146enum ModuleStatus {
147 #[default]
148 Unlinked,
149 Linked {
150 environment: Gc<DeclarativeEnvironment>,
151 eval_context: (EnvironmentStack, Gc<CodeBlock>),
152 },
153 Evaluated {
154 environment: Gc<DeclarativeEnvironment>,
155 promise: JsPromise,
156 },
157}
158
159impl ModuleStatus {
160 fn transition<F>(&mut self, f: F)
163 where
164 F: FnOnce(Self) -> Self,
165 {
166 *self = f(std::mem::take(self));
167 }
168}
169
170#[derive(Trace, Finalize)]
174pub struct SyntheticModule {
175 #[unsafe_ignore_trace]
176 export_names: FxHashSet<JsString>,
177 eval_steps: SyntheticModuleInitializer,
178 state: GcRefCell<ModuleStatus>,
179}
180
181impl std::fmt::Debug for SyntheticModule {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 f.debug_struct("SyntheticModule")
184 .field("export_names", &self.export_names)
185 .field("eval_steps", &self.eval_steps)
186 .finish_non_exhaustive()
187 }
188}
189
190impl SyntheticModule {
191 pub fn set_export(&self, export_name: &JsString, export_value: JsValue) -> JsResult<()> {
202 let env = self.environment().ok_or_else(|| {
203 JsNativeError::typ().with_message(format!(
204 "cannot set name `{}` in an unlinked synthetic module",
205 export_name.to_std_string_escaped()
206 ))
207 })?;
208 let locator = env
209 .kind()
210 .as_module()
211 .js_expect("must be module environment")?
212 .compile()
213 .get_binding(export_name)
214 .ok_or_else(|| {
215 JsNativeError::reference().with_message(format!(
216 "cannot set name `{}` which was not included in the list of exports",
217 export_name.to_std_string_escaped()
218 ))
219 })?;
220 env.set(locator.binding_index(), export_value);
221
222 Ok(())
223 }
224
225 pub fn export_class<C: Class>(&self, context: &mut Context) -> JsResult<()> {
228 self.export_named_class::<C>(&JsString::from(C::NAME), context)
229 }
230
231 pub fn export_named_class<C: Class>(
234 &self,
235 export_name: &JsString,
236 context: &mut Context,
237 ) -> JsResult<()> {
238 let mut class_builder = ClassBuilder::new::<C>(context);
239 C::init(&mut class_builder)?;
240
241 let class = class_builder.build();
242
243 self.set_export(export_name, class.constructor().into())?;
244 Ok(())
245 }
246
247 pub(super) fn new(names: FxHashSet<JsString>, eval_steps: SyntheticModuleInitializer) -> Self {
249 Self {
250 export_names: names,
251 eval_steps,
252 state: GcRefCell::default(),
253 }
254 }
255
256 pub(super) fn load(context: &mut Context) -> JsPromise {
260 JsPromise::resolve(JsValue::undefined(), context)
261 .expect("default resolve functions cannot throw and must return a promise")
262 }
263
264 pub(super) fn get_exported_names(&self) -> FxHashSet<JsString> {
268 self.export_names.clone()
270 }
271
272 #[allow(clippy::mutable_key_type)]
276 pub(super) fn resolve_export(
277 &self,
278 module_self: &Module,
279 export_name: &JsString,
280 ) -> Result<ResolvedBinding, ResolveExportError> {
281 if self.export_names.contains(export_name) {
282 Ok(ResolvedBinding {
284 module: module_self.clone(),
285 binding_name: BindingName::Name(export_name.clone()),
286 })
287 } else {
288 Err(ResolveExportError::NotFound)
290 }
291 }
292
293 pub(super) fn link(&self, module_self: &Module, context: &mut Context) {
297 if !matches!(&*self.state.borrow(), ModuleStatus::Unlinked) {
298 return;
300 }
301
302 let global_env = module_self.realm().environment().clone();
306 let global_scope = module_self.realm().scope().clone();
307 let module_scope = Scope::new(global_scope, true);
308
309 let compiler = ByteCompiler::new(
312 js_string!("<synthetic>"),
313 true,
314 false,
315 module_scope.clone(),
316 module_scope.clone(),
317 false,
318 false,
319 context.interner_mut(),
320 false,
321 SpannedSourceText::new_empty(),
323 SourcePath::None,
324 );
325
326 let exports = self
328 .export_names
329 .iter()
330 .map(|name| {
331 module_scope.create_mutable_binding(name.clone(), false)
333 })
334 .collect::<Vec<_>>();
335
336 module_scope.escape_all_bindings();
337
338 let cb = Gc::new(compiler.finish());
339
340 let mut envs = EnvironmentStack::new();
341 envs.push_module(module_scope);
342
343 for locator in exports {
344 envs.put_lexical_value(
346 locator.scope(),
347 locator.binding_index(),
348 JsValue::undefined(),
349 &global_env,
350 );
351 }
352
353 let env = envs
354 .current_declarative_ref(&global_env)
355 .cloned()
356 .expect("should have the module environment");
357
358 self.state
359 .borrow_mut()
360 .transition(|_| ModuleStatus::Linked {
361 environment: env,
362 eval_context: (envs, cb),
363 });
364
365 }
367
368 pub(super) fn evaluate(
372 &self,
373 module_self: &Module,
374 context: &mut Context,
375 ) -> JsResult<JsPromise> {
376 let (environments, codeblock) = match &*self.state.borrow() {
377 ModuleStatus::Unlinked => {
378 let (promise, ResolvingFunctions { reject, .. }) = JsPromise::new_pending(context);
379 reject
380 .call(
381 &JsValue::undefined(),
382 &[JsNativeError::typ()
383 .with_message("cannot evaluate unlinked synthetic module")
384 .into_opaque(context)
385 .into()],
386 context,
387 )
388 .js_expect("native resolving functions cannot throw")?;
389 return Ok(promise);
390 }
391 ModuleStatus::Linked { eval_context, .. } => eval_context.clone(),
392 ModuleStatus::Evaluated { promise, .. } => return Ok(promise.clone()),
393 };
394 let realm = module_self.realm().clone();
397
398 let env_fp = environments.len() as u32;
399 let callframe = CallFrame::new(
400 codeblock,
401 Some(ActiveRunnable::Module(module_self.clone())),
403 environments,
406 realm,
408 )
409 .with_env_fp(env_fp);
410
411 context
415 .vm
416 .push_frame_with_stack(callframe, JsValue::undefined(), JsValue::null());
417
418 let result = self.eval_steps.call(self, context);
421
422 let frame = context
425 .vm
426 .pop_frame()
427 .js_expect("there should be a frame")?;
428 context.vm.stack.truncate_to_frame(&frame);
429
430 let (promise, ResolvingFunctions { resolve, reject }) = JsPromise::new_pending(context);
432
433 match result {
434 Ok(()) => resolve.call(&JsValue::undefined(), &[], context),
436 Err(err) => reject.call(&JsValue::undefined(), &[err.into_opaque(context)?], context),
438 }
439 .js_expect("default resolving functions cannot throw")?;
440
441 self.state.borrow_mut().transition(|state| match state {
442 ModuleStatus::Linked { environment, .. } => ModuleStatus::Evaluated {
443 environment,
444 promise: promise.clone(),
445 },
446 _ => unreachable!("checks above ensure the module is linked"),
447 });
448
449 Ok(promise)
451 }
452
453 pub(crate) fn environment(&self) -> Option<Gc<DeclarativeEnvironment>> {
454 match &*self.state.borrow() {
455 ModuleStatus::Unlinked => None,
456 ModuleStatus::Linked { environment, .. }
457 | ModuleStatus::Evaluated { environment, .. } => Some(environment.clone()),
458 }
459 }
460}