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