1use swc_core::{
2 common::Mark,
3 ecma::ast::{Id, Ident},
4};
5
6pub(crate) use self::imports::ImportMap;
7
8pub mod builtin;
9pub mod bump_vec;
10pub mod graph;
11pub mod imports;
12pub mod linker;
13pub mod side_effects;
14pub mod top_level_await;
15pub mod well_known;
16
17mod jsvalue;
18pub use bump_vec::BumpVec;
19pub use bumpalo::Bump;
20pub use jsvalue::*;
21pub use thread_local::ThreadLocal;
22pub use well_known::{kinds::*, require_context::*};
23
24fn is_unresolved(i: &Ident, unresolved_mark: Mark) -> bool {
25 i.ctxt.outer() == unresolved_mark
26}
27
28fn is_unresolved_id(i: &Id, unresolved_mark: Mark) -> bool {
29 i.1.outer() == unresolved_mark
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum Modified {
38 Yes,
39 No,
40}
41
42impl Modified {
43 pub fn is_modified(self) -> bool {
45 matches!(self, Modified::Yes)
46 }
47}
48
49impl From<bool> for Modified {
50 fn from(modified: bool) -> Self {
51 if modified {
52 Modified::Yes
53 } else {
54 Modified::No
55 }
56 }
57}
58
59#[doc(hidden)]
60pub mod test_utils {
61 use anyhow::Result;
62 use turbo_rcstr::rcstr;
63 use turbo_tasks::{FxIndexMap, PrettyPrintError, Vc};
64 use turbopack_core::compile_time_info::CompileTimeInfo;
65
66 use super::{
67 ConstantValue, JsValue, JsValueUrlKind, Modified, ModuleValue, WellKnownFunctionKind,
68 WellKnownObjectKind, builtin::early_replace_builtin, well_known::replace_well_known,
69 };
70 use crate::{
71 analyzer::{
72 Bump, RequireContextValue, ThreadLocal, builtin::replace_builtin,
73 imports::ImportAttributes, parse_require_context,
74 },
75 utils::module_value_to_well_known_object,
76 };
77
78 pub async fn early_visitor<'a>(
79 _arena: &'a ThreadLocal<Bump>,
80 mut v: JsValue<'a>,
81 ) -> Result<(JsValue<'a>, Modified)> {
82 let m = early_replace_builtin(&mut v);
83 Ok((v, m))
84 }
85
86 pub async fn visitor<'a>(
89 arena: &'a ThreadLocal<Bump>,
90 v: JsValue<'a>,
91 compile_time_info: Vc<CompileTimeInfo>,
92 attributes: &ImportAttributes,
93 ) -> Result<(JsValue<'a>, Modified)> {
94 let ImportAttributes { ignore, .. } = *attributes;
95 let mut new_value = match v {
96 JsValue::Call(_, ref call)
97 if matches!(
98 call.callee(),
99 JsValue::WellKnownFunction(WellKnownFunctionKind::Import)
100 ) =>
101 {
102 match &call.args()[0] {
103 JsValue::Constant(ConstantValue::Str(v)) => JsValue::promise(
104 arena.get_or_default(),
105 JsValue::Module(ModuleValue {
106 module: v.as_atom().into_owned().into(),
107 annotations: None,
108 }),
109 ),
110 _ => v.into_unknown(true, rcstr!("import() non constant")),
111 }
112 }
113 JsValue::Call(_, ref call)
114 if matches!(
115 call.callee(),
116 JsValue::WellKnownFunction(WellKnownFunctionKind::CreateRequire)
117 ) =>
118 {
119 if let [JsValue::Member(_, obj, prop)] = call.args()
120 && matches!(
121 &**obj,
122 JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta)
123 )
124 && let JsValue::Constant(ConstantValue::Str(prop)) = &**prop
125 && prop.as_str() == "url"
126 {
127 JsValue::WellKnownFunction(WellKnownFunctionKind::Require)
128 } else {
129 v.into_unknown(true, rcstr!("createRequire() non constant"))
130 }
131 }
132 JsValue::Call(_, ref call)
133 if matches!(
134 call.callee(),
135 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireResolve)
136 ) =>
137 {
138 match &call.args()[0] {
139 JsValue::Constant(v) => (v.to_string() + "/resolved/lib/index.js").into(),
140 _ => v.into_unknown(true, rcstr!("require.resolve non constant")),
141 }
142 }
143 JsValue::Call(_, ref call)
144 if matches!(
145 call.callee(),
146 JsValue::WellKnownFunction(WellKnownFunctionKind::ImportMetaGlob)
147 ) =>
148 {
149 v.into_unknown(false, rcstr!("import.meta.glob()"))
150 }
151 JsValue::Call(_, ref call)
152 if matches!(
153 call.callee(),
154 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContext)
155 ) =>
156 {
157 match parse_require_context(call.args()) {
158 Ok(options) => {
159 let mut map = FxIndexMap::default();
160
161 map.insert(
162 rcstr!("./a"),
163 format!("[context: {}]/a", options.dir).into(),
164 );
165 map.insert(
166 rcstr!("./b"),
167 format!("[context: {}]/b", options.dir).into(),
168 );
169 map.insert(
170 rcstr!("./c"),
171 format!("[context: {}]/c", options.dir).into(),
172 );
173
174 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequire(
175 Box::new(RequireContextValue(map)),
176 ))
177 }
178 Err(err) => v.into_unknown(true, PrettyPrintError(&err).to_string().into()),
179 }
180 }
181 JsValue::New(_, ref call)
182 if matches!(
183 call.callee(),
184 JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor)
185 ) =>
186 {
187 if let [
188 JsValue::Constant(ConstantValue::Str(url)),
189 JsValue::Member(_, obj, prop),
190 ] = call.args()
191 && matches!(
192 &**obj,
193 JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta)
194 )
195 && let JsValue::Constant(ConstantValue::Str(prop)) = &**prop
196 {
197 if prop.as_str() == "url" {
198 JsValue::Url(url.clone(), JsValueUrlKind::Relative)
200 } else {
201 v.into_unknown(true, rcstr!("new non constant"))
202 }
203 } else {
204 v.into_unknown(true, rcstr!("new non constant"))
205 }
206 }
207 JsValue::FreeVar(ref var) => match &**var {
208 "__dirname" => rcstr!("__dirname").into(),
209 "__filename" => rcstr!("__filename").into(),
210
211 "require" => JsValue::unknown_if(
212 ignore,
213 JsValue::WellKnownFunction(WellKnownFunctionKind::Require),
214 true,
215 rcstr!("ignored require"),
216 ),
217 "import" => JsValue::unknown_if(
218 ignore,
219 JsValue::WellKnownFunction(WellKnownFunctionKind::Import),
220 true,
221 rcstr!("ignored import"),
222 ),
223 "Worker" => JsValue::unknown_if(
224 ignore,
225 JsValue::WellKnownFunction(WellKnownFunctionKind::WorkerConstructor),
226 true,
227 rcstr!("ignored Worker constructor"),
228 ),
229 "define" => JsValue::WellKnownFunction(WellKnownFunctionKind::Define),
230 "URL" => JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor),
231 "process" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessModule),
232 "Object" => JsValue::WellKnownObject(WellKnownObjectKind::GlobalObject),
233 "Buffer" => JsValue::WellKnownObject(WellKnownObjectKind::NodeBuffer),
234 _ => v.into_unknown(true, rcstr!("unknown global")),
235 },
236 JsValue::Module(ref mv) => {
237 if let Some(wko) = module_value_to_well_known_object(mv) {
238 wko
239 } else {
240 return Ok((v, Modified::No));
241 }
242 }
243 _ => {
244 let (mut v, m1) = replace_well_known(arena, v, compile_time_info, true).await?;
245 let m2 = replace_builtin(arena.get_or_default(), &mut v);
246 let m = if m1.is_modified() || m2.is_modified() {
247 Modified::Yes
248 } else {
249 Modified::from(v.make_nested_operations_unknown())
250 };
251 return Ok((v, m));
252 }
253 };
254 new_value.normalize_shallow(arena.get_or_default());
255 Ok((new_value, Modified::Yes))
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use std::{mem::take, path::PathBuf, sync::Arc, time::Instant};
262
263 use bumpalo::boxed::Box as BumpBox;
264 use parking_lot::Mutex;
265 use rustc_hash::FxHashMap;
266 use swc_core::{
267 common::{
268 FilePathMapping, GLOBALS, Globals, Mark, SourceMap, comments::SingleThreadedComments,
269 },
270 ecma::{
271 ast::{EsVersion, Id},
272 parser::parse_file_as_program,
273 transforms::base::resolver,
274 visit::VisitMutWith,
275 },
276 testing::{NormalizedOutput, fixture},
277 };
278 use turbo_rcstr::{RcStr, rcstr};
279 use turbo_tasks::{ResolvedVc, TurboTasks, util::FormatDuration};
280 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
281 use turbopack_core::{
282 compile_time_info::CompileTimeInfo,
283 environment::{Environment, ExecutionEnvironment, NodeJsEnvironment, NodeJsVersion},
284 target::{Arch, CompileTarget, Endianness, Libc, Platform},
285 };
286
287 use super::{
288 BumpVec, JsValue,
289 graph::{ConditionalKind, Effect, EffectArg, EvalContext, VarGraph, create_graph},
290 linker::link,
291 };
292 use crate::{
293 AnalyzeMode,
294 analyzer::{Bump, ThreadLocal, graph::AssignmentScopes, imports::ImportAttributes},
295 };
296
297 #[fixture("tests/analyzer/graph/**/input.js")]
298 fn fixture(input: PathBuf) {
299 let input = RcStr::from(input.to_str().unwrap());
300 let rt = tokio::runtime::Builder::new_multi_thread()
301 .worker_threads(2)
302 .enable_all()
303 .build()
304 .unwrap();
305 rt.block_on(async move {
306 let tt = TurboTasks::new(TurboTasksBackend::new(
307 BackendOptions::default(),
308 noop_backing_storage(),
309 ));
310 tt.run_once(async move {
311 fixture_op(input).read_strongly_consistent().await?;
312 anyhow::Ok(())
313 })
314 .await
315 .unwrap();
316 });
317 }
318
319 #[turbo_tasks::function(operation, root)]
320 async fn fixture_op(input: RcStr) -> anyhow::Result<()> {
321 let input = PathBuf::from(input.as_str());
322 let graph_snapshot_path = input.with_file_name("graph.snapshot");
323 let graph_explained_snapshot_path = input.with_file_name("graph-explained.snapshot");
324 let graph_effects_snapshot_path = input.with_file_name("graph-effects.snapshot");
325 let resolved_explained_snapshot_path = input.with_file_name("resolved-explained.snapshot");
326 let resolved_effects_snapshot_path = input.with_file_name("resolved-effects.snapshot");
327 let large_marker = input.with_file_name("large");
328
329 let cm: Arc<SourceMap> = Arc::new(SourceMap::new(FilePathMapping::empty()));
330 let globals = Arc::new(Globals::new());
331 let arena = ThreadLocal::new();
332
333 let (eval_context, mut var_graph) = GLOBALS.set(&globals, || {
337 let fm = cm.load_file(&input).unwrap();
338 let comments = SingleThreadedComments::default();
339 let mut m = parse_file_as_program(
340 &fm,
341 Default::default(),
342 EsVersion::latest(),
343 Some(&comments),
344 &mut vec![],
345 )
346 .map_err(|err| anyhow::anyhow!("parse error: {err:?}"))?;
347
348 let unresolved_mark = Mark::new();
349 let top_level_mark = Mark::new();
350 m.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
351
352 let eval_context = EvalContext::new(
353 Some(&m),
354 unresolved_mark,
355 top_level_mark,
356 Default::default(),
357 Some(&comments),
358 );
359
360 let var_graph = create_graph(
361 arena.get_or_default(),
362 &m,
363 &eval_context,
364 AnalyzeMode::CodeGenerationAndTracing,
365 true,
366 );
367 anyhow::Ok((eval_context, var_graph))
368 })?;
369 let var_cache = Default::default();
370
371 let mut named_values = var_graph
372 .values
373 .iter()
374 .map(|((id, ctx), value)| {
375 let unique = var_graph.values.keys().filter(|(i, _)| id == i).count() == 1;
376 let value = value.clone_in(arena.get_or_default());
377 if unique {
378 (id.to_string(), ((id.clone(), *ctx), value))
379 } else {
380 (format!("{id}{ctx:?}"), ((id.clone(), *ctx), value))
381 }
382 })
383 .collect::<Vec<_>>();
384 named_values.sort_by(|a, b| a.0.cmp(&b.0));
385
386 fn explain_all<'x, 'a: 'x>(
387 values: impl IntoIterator<Item = (&'x String, &'x JsValue<'a>, Option<AssignmentScopes>)>,
388 ) -> String {
389 values
390 .into_iter()
391 .map(|(id, value, assignment_scopes)| {
392 let non_root_assignments = match assignment_scopes {
393 Some(AssignmentScopes::AllInModuleEvalScope) => " (const after eval)",
394 _ => "",
395 };
396 let (explainer, hints) = value.explain(10, 5);
397 format!("{id}{non_root_assignments} = {explainer}{hints}")
398 })
399 .collect::<Vec<_>>()
400 .join("\n\n")
401 }
402
403 {
404 let large = large_marker.exists();
407
408 if !large {
409 NormalizedOutput::from(format!(
410 "{:#?}",
411 named_values
412 .iter()
413 .map(|(name, (_, value))| (name, value))
414 .collect::<Vec<_>>()
415 ))
416 .compare_to_file(&graph_snapshot_path)
417 .unwrap();
418 }
419 NormalizedOutput::from(explain_all(named_values.iter().map(
420 |(name, (id, value))| {
421 (
422 name,
423 value,
424 eval_context.imports.assignment_scopes.get(id).copied(),
425 )
426 },
427 )))
428 .compare_to_file(&graph_explained_snapshot_path)
429 .unwrap();
430 if !large {
431 NormalizedOutput::from(format!("{:#?}", var_graph.effects))
432 .compare_to_file(&graph_effects_snapshot_path)
433 .unwrap();
434 }
435 }
436
437 {
438 let start = Instant::now();
441 let mut resolved = Vec::new();
442 for (name, id) in named_values.iter().map(|(name, (id, _))| (name, id)) {
443 let start = Instant::now();
444 let (res, steps) = resolve(
447 &arena,
448 &var_graph,
449 JsValue::Variable(id.clone()),
450 ImportAttributes::empty_ref(),
451 &var_cache,
452 )
453 .await;
454 let time = start.elapsed();
455 if time.as_millis() > 1 {
456 println!(
457 "linking {} {name} took {} in {} steps",
458 input.display(),
459 FormatDuration(time),
460 steps
461 );
462 }
463
464 resolved.push((name.clone(), res));
465 }
466 let time = start.elapsed();
467 if time.as_millis() > 1 {
468 println!("linking {} took {}", input.display(), FormatDuration(time));
469 }
470
471 let start = Instant::now();
472 let explainer = explain_all(resolved.iter().map(|(name, value)| (name, value, None)));
473 let time = start.elapsed();
474 if time.as_millis() > 1 {
475 println!(
476 "explaining {} took {}",
477 input.display(),
478 FormatDuration(time)
479 );
480 }
481
482 NormalizedOutput::from(explainer)
483 .compare_to_file(&resolved_explained_snapshot_path)
484 .unwrap();
485 }
486
487 {
488 let start = Instant::now();
491 let mut resolved = Vec::new();
492 let mut queue = take(&mut var_graph.effects)
493 .into_iter()
494 .map(|effect| (0, effect))
495 .rev()
496 .collect::<Vec<_>>();
497 let mut i = 0;
498 while let Some((parent, effect)) = queue.pop() {
499 i += 1;
500 let start = Instant::now();
501 async fn handle_args<'a>(
502 arena: &'a ThreadLocal<Bump>,
503 args: BumpVec<'a, EffectArg<'a>>,
504 queue: &mut Vec<(usize, Effect<'a>)>,
505 var_graph: &VarGraph<'a>,
506 var_cache: &Mutex<FxHashMap<Id, JsValue<'a>>>,
507 i: usize,
508 ) -> Vec<JsValue<'a>> {
509 let mut new_args = Vec::with_capacity(args.len());
510 for arg in args {
511 match arg {
512 EffectArg::Value(v) => {
513 new_args.push(
514 resolve(
515 arena,
516 var_graph,
517 v,
518 ImportAttributes::empty_ref(),
519 var_cache,
520 )
521 .await
522 .0,
523 );
524 }
525 EffectArg::Closure(v, effects) => {
526 new_args.push(
527 resolve(
528 arena,
529 var_graph,
530 v,
531 ImportAttributes::empty_ref(),
532 var_cache,
533 )
534 .await
535 .0,
536 );
537 queue.extend(
538 BumpVec::from(BumpBox::into_inner(effects).effects)
539 .into_iter()
540 .rev()
541 .map(|e| (i, e)),
542 );
543 }
544 EffectArg::Spread => {
545 new_args.push(JsValue::unknown_empty(true, rcstr!("spread")));
546 }
547 }
548 }
549 new_args
550 }
551 let steps = match effect {
552 Effect::Conditional {
553 mut condition,
554 kind,
555 ..
556 } => {
557 let (condition, steps) = resolve(
558 &arena,
559 &var_graph,
560 take(&mut *condition),
561 ImportAttributes::empty_ref(),
562 &var_cache,
563 )
564 .await;
565 resolved.push((format!("{parent} -> {i} conditional"), condition));
566 match BumpBox::into_inner(kind) {
567 ConditionalKind::If { then } => {
568 queue.extend(
569 BumpVec::from(then.effects)
570 .into_iter()
571 .rev()
572 .map(|e| (i, e)),
573 );
574 }
575 ConditionalKind::Else { r#else } => {
576 queue.extend(
577 BumpVec::from(r#else.effects)
578 .into_iter()
579 .rev()
580 .map(|e| (i, e)),
581 );
582 }
583 ConditionalKind::IfElse { then, r#else }
584 | ConditionalKind::Ternary { then, r#else } => {
585 queue.extend(
586 BumpVec::from(r#else.effects)
587 .into_iter()
588 .rev()
589 .map(|e| (i, e)),
590 );
591 queue.extend(
592 BumpVec::from(then.effects)
593 .into_iter()
594 .rev()
595 .map(|e| (i, e)),
596 );
597 }
598 ConditionalKind::IfElseMultiple { then, r#else } => {
599 for then in BumpVec::from(then) {
600 queue.extend(
601 BumpVec::from(then.effects)
602 .into_iter()
603 .rev()
604 .map(|e| (i, e)),
605 );
606 }
607 for r#else in BumpVec::from(r#else) {
608 queue.extend(
609 BumpVec::from(r#else.effects)
610 .into_iter()
611 .rev()
612 .map(|e| (i, e)),
613 );
614 }
615 }
616 ConditionalKind::And { expr }
617 | ConditionalKind::Or { expr }
618 | ConditionalKind::NullishCoalescing { expr }
619 | ConditionalKind::Labeled { body: expr } => {
620 queue.extend(
621 BumpVec::from(expr.effects)
622 .into_iter()
623 .rev()
624 .map(|e| (i, e)),
625 );
626 }
627 };
628 steps
629 }
630 Effect::Call {
631 mut func,
632 args,
633 new,
634 span,
635 ..
636 } => {
637 let (func, steps) = resolve(
638 &arena,
639 &var_graph,
640 take(&mut *func),
641 eval_context.imports.get_attributes(span),
642 &var_cache,
643 )
644 .await;
645 let new_args =
646 handle_args(&arena, args, &mut queue, &var_graph, &var_cache, i).await;
647 resolved.push((
648 format!("{parent} -> {i} call"),
649 if new {
650 JsValue::new_from_iter(arena.get_or_default(), func, new_args)
651 } else {
652 JsValue::call_from_iter(arena.get_or_default(), func, new_args)
653 },
654 ));
655 steps
656 }
657 Effect::FreeVar { var, .. } => {
658 resolved.push((format!("{parent} -> {i} free var"), JsValue::FreeVar(var)));
659 0
660 }
661 Effect::TypeOf { mut arg, .. } => {
662 let (arg, steps) = resolve(
663 &arena,
664 &var_graph,
665 take(&mut *arg),
666 ImportAttributes::empty_ref(),
667 &var_cache,
668 )
669 .await;
670 resolved.push((
671 format!("{parent} -> {i} typeof"),
672 JsValue::type_of(arena.get_or_default(), arg),
673 ));
674 steps
675 }
676 Effect::MemberCall {
677 mut obj,
678 mut prop,
679 args,
680 ..
681 } => {
682 let (obj, obj_steps) = resolve(
683 &arena,
684 &var_graph,
685 take(&mut *obj),
686 ImportAttributes::empty_ref(),
687 &var_cache,
688 )
689 .await;
690 let (prop, prop_steps) = resolve(
691 &arena,
692 &var_graph,
693 take(&mut *prop),
694 ImportAttributes::empty_ref(),
695 &var_cache,
696 )
697 .await;
698 let new_args =
699 handle_args(&arena, args, &mut queue, &var_graph, &var_cache, i).await;
700 resolved.push((
701 format!("{parent} -> {i} member call"),
702 JsValue::member_call_from_iter(
703 arena.get_or_default(),
704 obj,
705 prop,
706 new_args,
707 ),
708 ));
709 obj_steps + prop_steps
710 }
711 Effect::DynamicImport { args, .. } => {
712 let new_args =
713 handle_args(&arena, args, &mut queue, &var_graph, &var_cache, i).await;
714 resolved.push((
715 format!("{parent} -> {i} dynamic import"),
716 JsValue::call_from_iter(
717 arena.get_or_default(),
718 JsValue::FreeVar("import".into()),
719 new_args,
720 ),
721 ));
722 0
723 }
724 Effect::Unreachable { .. } => {
725 resolved.push((
726 format!("{parent} -> {i} unreachable"),
727 JsValue::unknown_empty(true, rcstr!("unreachable")),
728 ));
729 0
730 }
731 Effect::ImportMeta { .. }
732 | Effect::ImportedBinding { .. }
733 | Effect::Member { .. }
734 | Effect::In { .. } => 0,
735 };
736 let time = start.elapsed();
737 if time.as_millis() > 1 {
738 println!(
739 "linking effect {} took {} in {} steps",
740 input.display(),
741 FormatDuration(time),
742 steps
743 );
744 }
745 }
746 let time = start.elapsed();
747 if time.as_millis() > 1 {
748 println!(
749 "linking effects {} took {}",
750 input.display(),
751 FormatDuration(time)
752 );
753 }
754
755 let start = Instant::now();
756 let explainer = explain_all(resolved.iter().map(|(name, value)| (name, value, None)));
757 let time = start.elapsed();
758 if time.as_millis() > 1 {
759 println!(
760 "explaining effects {} took {}",
761 input.display(),
762 FormatDuration(time)
763 );
764 }
765
766 NormalizedOutput::from(explainer)
767 .compare_to_file(&resolved_effects_snapshot_path)
768 .unwrap();
769 }
770
771 Ok(())
772 }
773
774 async fn resolve<'a>(
775 arena: &'a ThreadLocal<Bump>,
776 var_graph: &VarGraph<'a>,
777 val: JsValue<'a>,
778 attributes: &ImportAttributes,
779 var_cache: &Mutex<FxHashMap<Id, JsValue<'a>>>,
780 ) -> (JsValue<'a>, u32) {
781 async {
784 let compile_time_info = CompileTimeInfo::builder(
785 Environment::new(ExecutionEnvironment::NodeJsLambda(
786 NodeJsEnvironment {
787 compile_target: CompileTarget {
788 arch: Arch::X64,
789 platform: Platform::Linux,
790 endianness: Endianness::Little,
791 libc: Libc::Glibc,
792 }
793 .resolved_cell(),
794 node_version: NodeJsVersion::default().resolved_cell(),
795 cwd: ResolvedVc::cell(None),
796 }
797 .resolved_cell(),
798 ))
799 .to_resolved()
800 .await?,
801 )
802 .cell()
803 .await?;
804 link(
805 arena,
806 var_graph,
807 val,
808 &(|val| Box::pin(super::test_utils::early_visitor(arena, val))),
809 &(|val| {
810 Box::pin(super::test_utils::visitor(
811 arena,
812 val,
813 compile_time_info,
814 attributes,
815 ))
816 }),
817 &Default::default(),
818 var_cache,
819 )
820 .await
821 }
822 .await
823 .unwrap()
824 }
825}