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