Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 46 additions & 23 deletions src/js_parser/lower/lower_decorators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,25 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
)
}

/// `(() => { ...stmts })()`
fn iife(&mut self, stmts: js_ast::StmtNodeList, l: bun_ast::Loc) -> Expr {
let target = self.new_expr(
E::Arrow {
body: G::FnBody { loc: l, stmts },
..Default::default()
},
l,
);
self.new_expr(
E::Call {
target,
args: bun_alloc::AstAlloc::vec(),
..Default::default()
},
l,
)
}

/// Create a static block property from a single expression.
fn make_static_block(&mut self, expr: Expr, l: bun_ast::Loc) -> Property {
let bump = self.arena;
Expand Down Expand Up @@ -2127,27 +2146,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}
}
} else {
// Wrap in IIFE
let stmts_ptr = bun_ast::StoreSlice::new_mut(stmts_slice);
let iife_body = p.new_expr(
E::Arrow {
body: G::FnBody {
loc,
stmts: stmts_ptr,
},
is_async: false,
..Default::default()
},
loc,
);
suffix_exprs.push(p.new_expr(
E::Call {
target: iife_body,
args: bun_alloc::AstAlloc::vec(),
..Default::default()
},
loc,
));
suffix_exprs.push(p.iife(stmts_ptr, loc));
}
}
StaticElementKind::FieldOrAccessor => {
Expand Down Expand Up @@ -2518,7 +2518,15 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
);
}

// Emit var declarations
// The class keeps reading the temporaries after it is created
// (`_init` in the constructor, the accessor storage WeakMaps), so
// each evaluation of the expression needs its own set. Hoisting
// them into the nearest statement list is only right when that list
// runs once per evaluation; otherwise (a parameter default value, an
// instance field initializer, no list at all) they are declared in
// an arrow wrapped around the chain, which keeps `this`, `super`,
// `arguments` and `new.target` of the enclosing code. Neither of
// those positions can contain `await` or `yield`.
Comment on lines +2521 to +2529

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

if !expr_var_decls.is_empty() {
let decls = DeclList::from_bump_vec(expr_var_decls);
let var_decl_stmt = p.s(
Expand All @@ -2528,8 +2536,23 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
},
loc,
);
if let Some(stmt_list) = p.nearest_stmt_list_mut() {
stmt_list.push(var_decl_stmt);
let hoisted = !p.nearest_stmt_list_is_shared
&& match p.nearest_stmt_list_mut() {
Some(stmt_list) => {
stmt_list.push(var_decl_stmt);
true
}
None => false,
};
if !hoisted {
let return_stmt = p.s(
S::Return {
value: Some(result),
},
loc,
);
let body = bump.alloc_slice_copy(&[var_decl_stmt, return_stmt]);
result = p.iife(bun_ast::StoreSlice::new_mut(body), loc);
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/js_parser/p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,12 @@ pub struct P<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> {
/// Name from assignment context for anonymous decorated class expressions.
/// Set before visitExpr, consumed by lowerStandardDecoratorsImpl.
pub(crate) decorator_class_name: Option<&'a [u8]>,
/// Set while visiting an expression that runs once per call or per
/// construction (a parameter default value, an instance field initializer)
/// although `nearest_stmt_list` runs once: a declaration hoisted there would
/// be shared by every evaluation of the expression. `visit_stmts` clears it
/// for the statements of any body nested inside such an expression.
Comment on lines +607 to +611

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

pub(crate) nearest_stmt_list_is_shared: bool,
}

// `binding::ToExprWrapper` type-erases `*P` (which is generic over
Expand Down Expand Up @@ -8847,6 +8853,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
will_wrap_module_in_try_catch_for_using: false,
nearest_stmt_list: None,
decorator_class_name: None,
nearest_stmt_list_is_shared: false,

jsx_transform,

Expand Down
20 changes: 19 additions & 1 deletion src/js_parser/visit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
duplicate_args_check = Some(StringVoidMap::get());
}

// Default values (including those nested in destructuring patterns) run
// once per call, while the statement list enclosing the function runs
// once; the function's own body installs its list later.
Comment on lines +221 to +223

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

let old_nearest_stmt_list_is_shared = self.nearest_stmt_list_is_shared;
self.nearest_stmt_list_is_shared = true;

for arg in args.iter_mut() {
if arg.ts_decorators.len_u32() > 0 {
self.visit_ts_decorators(&mut arg.ts_decorators);
Expand All @@ -230,6 +236,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
self.visit_expr(default);
}
}

self.nearest_stmt_list_is_shared = old_nearest_stmt_list_is_shared;
}

// `Vec<Expr>` is not `Copy`; mutate in place.
Expand Down Expand Up @@ -984,7 +992,12 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}

if let Some(val) = property.initializer {
// if (property.flags.is_static and )
// An instance initializer runs once per construction; a
// static one runs with the class itself.
Comment on lines +995 to +996

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

let old_nearest_stmt_list_is_shared = self.nearest_stmt_list_is_shared;
if !property.flags.contains(flags::Property::IsStatic) {
self.nearest_stmt_list_is_shared = true;
}
if let Some(name) = name_to_keep {
let was_anon = val.is_anonymous_named();
let prev_dcn2 = self.decorator_class_name;
Expand All @@ -1003,6 +1016,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
} else {
self.visit_expr(property.initializer.as_mut().unwrap());
}
self.nearest_stmt_list_is_shared = old_nearest_stmt_list_is_shared;
}

// manual restore for the three `defer`s above
Expand Down Expand Up @@ -1341,11 +1355,14 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
ListManaged::with_capacity_in(stmts.len(), p.arena);

let prev_nearest_stmt_list = p.nearest_stmt_list;
let prev_nearest_stmt_list_is_shared = p.nearest_stmt_list_is_shared;
// BACKREF — `before` outlives this block; raw NonNull avoids
// the `&'a mut` borrow conflict. Derive via `addr_of_mut!` (no intermediate
// `&mut`) so the pointer shares the local's base tag and survives the
// direct `&mut before` reborrows in the loop below (Stacked Borrows).
p.nearest_stmt_list = NonNull::new(core::ptr::addr_of_mut!(before));
// `before` runs every time the statements it is emitted ahead of run.
p.nearest_stmt_list_is_shared = false;

let mut preprocessed_enum_i: usize = 0;

Expand Down Expand Up @@ -1538,6 +1555,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O

// manual restore for the block-level `defer`s
p.nearest_stmt_list = prev_nearest_stmt_list;
p.nearest_stmt_list_is_shared = prev_nearest_stmt_list_is_shared;
p.is_control_flow_dead = old_is_control_flow_dead;
}

Expand Down
Loading
Loading