Skip to content

Commit 1adef15

Browse files
authored
Turbopack: correctly track await import("path") in static analysis (#77811)
1. There was a problem where the module name was `"path"` in the static analyzer (and not `path`), so the lookup to the Node builtin modules list silently failed as `"\"path\"" != "path"` 2. Then, to make this actually correct, introduce `JsValue::Promise` and `JsValue::Awaited` so that `import("path")` and `await import("path")` actually are different JsValues This is correctly tracked now and includes the Wasm asset in the NFT list: ```js const { readFileSync } = await import('fs') const { join } = await import('path') const wasm = readFileSync( join(process.cwd(), '../web/lib/results/parser/biome_wasm_bg_opt.wasm') ) ``` Closes PACK-4269
1 parent 8c405dc commit 1adef15

19 files changed

+781
-137
lines changed

turbopack/crates/turbopack-ecmascript/src/analyzer/builtin.rs

+10
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,16 @@ pub fn replace_builtin(value: &mut JsValue) -> bool {
662662
}
663663
}
664664

665+
JsValue::Awaited(_, operand) => {
666+
if let JsValue::Promise(_, inner) = &mut **operand {
667+
*value = take(inner);
668+
true
669+
} else {
670+
*value = take(operand);
671+
true
672+
}
673+
}
674+
665675
_ => false,
666676
}
667677
}

turbopack/crates/turbopack-ecmascript/src/analyzer/graph.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ impl EvalContext {
543543
SyntaxContext::empty(),
544544
)),
545545

546-
Expr::Await(AwaitExpr { arg, .. }) => self.eval(arg),
546+
Expr::Await(AwaitExpr { arg, .. }) => JsValue::awaited(Box::new(self.eval(arg))),
547547

548548
Expr::Seq(e) => {
549549
let mut seq = e.exprs.iter().map(|e| self.eval(e)).peekable();

turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs

+82-31
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ impl ConstantString {
111111
}
112112
}
113113

114+
pub fn as_atom(&self) -> Cow<Atom> {
115+
match self {
116+
Self::Atom(s) => Cow::Borrowed(s),
117+
Self::RcStr(s) => Cow::Owned(s.as_str().into()),
118+
}
119+
}
120+
114121
pub fn is_empty(&self) -> bool {
115122
self.as_str().is_empty()
116123
}
@@ -482,6 +489,12 @@ pub enum JsValue {
482489
/// A tenary operator `test ? cons : alt`
483490
/// `(total_node_count, test, cons, alt)`
484491
Tenary(u32, Box<JsValue>, Box<JsValue>, Box<JsValue>),
492+
/// A promise resolving to some value
493+
/// `(total_node_count, value)`
494+
Promise(u32, Box<JsValue>),
495+
/// An await call (potentially) unwrapping a promise.
496+
/// `(total_node_count, value)`
497+
Awaited(u32, Box<JsValue>),
485498

486499
/// A for-of loop
487500
///
@@ -734,6 +747,8 @@ impl Display for JsValue {
734747
}
735748
JsValue::Iterated(_, iterable) => write!(f, "Iterated({})", iterable),
736749
JsValue::TypeOf(_, operand) => write!(f, "typeof({})", operand),
750+
JsValue::Promise(_, operand) => write!(f, "Promise<{}>", operand),
751+
JsValue::Awaited(_, operand) => write!(f, "await({})", operand),
737752
}
738753
}
739754
}
@@ -795,6 +810,7 @@ impl JsValue {
795810
| JsValue::Object { .. }
796811
| JsValue::Alternatives { .. }
797812
| JsValue::Function(..)
813+
| JsValue::Promise(..)
798814
| JsValue::Member(..) => JsValueMetaKind::Nested,
799815
JsValue::Concat(..)
800816
| JsValue::Add(..)
@@ -807,6 +823,7 @@ impl JsValue {
807823
| JsValue::Tenary(..)
808824
| JsValue::MemberCall(..)
809825
| JsValue::Iterated(..)
826+
| JsValue::Awaited(..)
810827
| JsValue::TypeOf(..) => JsValueMetaKind::Operation,
811828
JsValue::Variable(..)
812829
| JsValue::Argument(..)
@@ -991,6 +1008,14 @@ impl JsValue {
9911008
Self::Member(1 + o.total_nodes() + p.total_nodes(), o, p)
9921009
}
9931010

1011+
pub fn promise(operand: Box<JsValue>) -> Self {
1012+
Self::Promise(1 + operand.total_nodes(), operand)
1013+
}
1014+
1015+
pub fn awaited(operand: Box<JsValue>) -> Self {
1016+
Self::Awaited(1 + operand.total_nodes(), operand)
1017+
}
1018+
9941019
pub fn unknown(
9951020
value: impl Into<Arc<JsValue>>,
9961021
side_effects: bool,
@@ -1063,6 +1088,8 @@ impl JsValue {
10631088
| JsValue::Member(c, _, _)
10641089
| JsValue::Function(c, _, _)
10651090
| JsValue::Iterated(c, ..)
1091+
| JsValue::Promise(c, ..)
1092+
| JsValue::Awaited(c, ..)
10661093
| JsValue::TypeOf(c, ..) => *c,
10671094
}
10681095
}
@@ -1104,6 +1131,12 @@ impl JsValue {
11041131
JsValue::Not(c, r) => {
11051132
*c = 1 + r.total_nodes();
11061133
}
1134+
JsValue::Promise(c, r) => {
1135+
*c = 1 + r.total_nodes();
1136+
}
1137+
JsValue::Awaited(c, r) => {
1138+
*c = 1 + r.total_nodes();
1139+
}
11071140

11081141
JsValue::Object {
11091142
total_nodes: c,
@@ -1252,6 +1285,12 @@ impl JsValue {
12521285
JsValue::TypeOf(_, operand) => {
12531286
operand.make_unknown_without_content(false, "node limit reached");
12541287
}
1288+
JsValue::Awaited(_, operand) => {
1289+
operand.make_unknown_without_content(false, "node limit reached");
1290+
}
1291+
JsValue::Promise(_, operand) => {
1292+
operand.make_unknown_without_content(false, "node limit reached");
1293+
}
12551294
JsValue::Member(_, o, p) => {
12561295
make_max_unknown([&mut **o, &mut **p].into_iter());
12571296
self.update_total_nodes();
@@ -1494,6 +1533,18 @@ impl JsValue {
14941533
operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
14951534
)
14961535
}
1536+
JsValue::Promise(_, operand) => {
1537+
format!(
1538+
"Promise<{}>",
1539+
operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
1540+
)
1541+
}
1542+
JsValue::Awaited(_, operand) => {
1543+
format!(
1544+
"await({})",
1545+
operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
1546+
)
1547+
}
14971548
JsValue::New(_, callee, list) => {
14981549
format!(
14991550
"new {}({})",
@@ -2111,6 +2162,8 @@ impl JsValue {
21112162
JsValue::Argument(_, _) => false,
21122163
JsValue::Iterated(_, iterable) => iterable.has_side_effects(),
21132164
JsValue::TypeOf(_, operand) => operand.has_side_effects(),
2165+
JsValue::Promise(_, operand) => operand.has_side_effects(),
2166+
JsValue::Awaited(_, operand) => operand.has_side_effects(),
21142167
}
21152168
}
21162169

@@ -2307,7 +2360,8 @@ impl JsValue {
23072360
| JsValue::Module(..)
23082361
| JsValue::Function(..)
23092362
| JsValue::WellKnownObject(_)
2310-
| JsValue::WellKnownFunction(_) => Some(false),
2363+
| JsValue::WellKnownFunction(_)
2364+
| JsValue::Promise(_, _) => Some(false),
23112365

23122366
// Booleans are not strings
23132367
JsValue::Not(..) | JsValue::Binary(..) => Some(false),
@@ -2346,6 +2400,11 @@ impl JsValue {
23462400
_,
23472401
) => Some(true),
23482402

2403+
JsValue::Awaited(_, operand) => match &**operand {
2404+
JsValue::Promise(_, v) => v.is_string(),
2405+
v => v.is_string(),
2406+
},
2407+
23492408
JsValue::FreeVar(..)
23502409
| JsValue::Variable(_)
23512410
| JsValue::Unknown { .. }
@@ -2650,14 +2709,10 @@ macro_rules! for_each_children_async {
26502709
$value.update_total_nodes();
26512710
($value, m1 || m2)
26522711
}
2653-
JsValue::Iterated(_, box iterable) => {
2654-
let (new_iterable, modified) = $visit_fn(take(iterable), $($args),+).await?;
2655-
*iterable = new_iterable;
2656-
2657-
$value.update_total_nodes();
2658-
($value, modified)
2659-
}
2660-
JsValue::TypeOf(_, box operand) => {
2712+
JsValue::Iterated(_, box operand)
2713+
| JsValue::TypeOf(_, box operand)
2714+
| JsValue::Promise(_, box operand)
2715+
| JsValue::Awaited(_, box operand) => {
26612716
let (new_operand, modified) = $visit_fn(take(operand), $($args),+).await?;
26622717
*operand = new_operand;
26632718

@@ -2962,15 +3017,10 @@ impl JsValue {
29623017
modified
29633018
}
29643019

2965-
JsValue::Iterated(_, iterable) => {
2966-
let modified = visitor(iterable);
2967-
if modified {
2968-
self.update_total_nodes();
2969-
}
2970-
modified
2971-
}
2972-
2973-
JsValue::TypeOf(_, operand) => {
3020+
JsValue::Iterated(_, operand)
3021+
| JsValue::TypeOf(_, operand)
3022+
| JsValue::Promise(_, operand)
3023+
| JsValue::Awaited(_, operand) => {
29743024
let modified = visitor(operand);
29753025
if modified {
29763026
self.update_total_nodes();
@@ -3164,11 +3214,10 @@ impl JsValue {
31643214
visitor(alt);
31653215
}
31663216

3167-
JsValue::Iterated(_, iterable) => {
3168-
visitor(iterable);
3169-
}
3170-
3171-
JsValue::TypeOf(_, operand) => {
3217+
JsValue::Iterated(_, operand)
3218+
| JsValue::TypeOf(_, operand)
3219+
| JsValue::Promise(_, operand)
3220+
| JsValue::Awaited(_, operand) => {
31723221
visitor(operand);
31733222
}
31743223

@@ -3566,10 +3615,10 @@ impl JsValue {
35663615
cons.similar_hash(state, depth - 1);
35673616
alt.similar_hash(state, depth - 1);
35683617
}
3569-
JsValue::Iterated(_, iterable) => {
3570-
iterable.similar_hash(state, depth - 1);
3571-
}
3572-
JsValue::TypeOf(_, operand) => {
3618+
JsValue::Iterated(_, operand)
3619+
| JsValue::TypeOf(_, operand)
3620+
| JsValue::Promise(_, operand)
3621+
| JsValue::Awaited(_, operand) => {
35733622
operand.similar_hash(state, depth - 1);
35743623
}
35753624
JsValue::Module(ModuleValue {
@@ -3879,10 +3928,12 @@ pub mod test_utils {
38793928
box JsValue::WellKnownFunction(WellKnownFunctionKind::Import),
38803929
ref args,
38813930
) => match &args[0] {
3882-
JsValue::Constant(v) => JsValue::Module(ModuleValue {
3883-
module: v.to_string().into(),
3884-
annotations: ImportAnnotations::default(),
3885-
}),
3931+
JsValue::Constant(ConstantValue::Str(v)) => {
3932+
JsValue::promise(Box::new(JsValue::Module(ModuleValue {
3933+
module: v.as_atom().into_owned(),
3934+
annotations: ImportAnnotations::default(),
3935+
})))
3936+
}
38863937
_ => v.into_unknown(true, "import() non constant"),
38873938
},
38883939
JsValue::Call(

turbopack/crates/turbopack-ecmascript/src/analyzer/well_known.rs

+22-5
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,7 @@ pub async fn well_known_function_call(
5757
WellKnownFunctionKind::PathJoin => path_join(args),
5858
WellKnownFunctionKind::PathDirname => path_dirname(args),
5959
WellKnownFunctionKind::PathResolve(cwd) => path_resolve(*cwd, args),
60-
WellKnownFunctionKind::Import => JsValue::unknown(
61-
JsValue::call(Box::new(JsValue::WellKnownFunction(kind)), args),
62-
true,
63-
"import() is not supported",
64-
),
60+
WellKnownFunctionKind::Import => import(args),
6561
WellKnownFunctionKind::Require => require(args),
6662
WellKnownFunctionKind::RequireContextRequire(value) => {
6763
require_context_require(value, args).await?
@@ -322,6 +318,27 @@ pub fn path_dirname(mut args: Vec<JsValue>) -> JsValue {
322318
)
323319
}
324320

321+
/// Resolve the contents of an import call, throwing errors
322+
/// if we come across any unsupported syntax.
323+
pub fn import(args: Vec<JsValue>) -> JsValue {
324+
match &args[..] {
325+
[JsValue::Constant(ConstantValue::Str(v))] => {
326+
JsValue::promise(Box::new(JsValue::Module(ModuleValue {
327+
module: v.as_atom().into_owned(),
328+
annotations: ImportAnnotations::default(),
329+
})))
330+
}
331+
_ => JsValue::unknown(
332+
JsValue::call(
333+
Box::new(JsValue::WellKnownFunction(WellKnownFunctionKind::Import)),
334+
args,
335+
),
336+
true,
337+
"only a single constant argument is supported",
338+
),
339+
}
340+
}
341+
325342
/// Resolve the contents of a require call, throwing errors
326343
/// if we come across any unsupported syntax.
327344
pub fn require(args: Vec<JsValue>) -> JsValue {

turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/conditional-import/resolved-explained.snapshot

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
a = module<"a", {}>
1+
a = Promise<module<a, {}>>
22

3-
b = (???*0* | module<"a", {}> | module<"b", {}>)
3+
b = (???*0* | Promise<module<a, {}>> | Promise<module<b, {}>>)
44
- *0* b
55
⚠️ pattern without value
66

turbopack/crates/turbopack-ecmascript/tests/analyzer/graph/imports/resolved-effects.snapshot

Whitespace-only changes.

0 commit comments

Comments
 (0)