From 0f7d7cbdfa113e4fdf844ffc5157459800096e49 Mon Sep 17 00:00:00 2001 From: Sisyphus Date: Mon, 6 Jul 2026 09:52:09 +0800 Subject: [PATCH] fix(decorators): evaluate member decorator expressions before class decorator expressions Per the ES decorator proposal (tc39/proposal-decorators), member decorator expressions must be evaluated before class decorator expressions. Bun was evaluating class decorator arrays first, causing to call d1()/d2() factories before d3(). Fix: reorder Phase 8 output assembly to emit member decorator array declarations (pre_eval_stmts) before the class decorator array (class_dec_stmt/class_dec_assign_expr). --- src/js_parser/lower/lower_decorators.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/js_parser/lower/lower_decorators.rs b/src/js_parser/lower/lower_decorators.rs index 8b6cafc2a786..aeb6710bdecf 100644 --- a/src/js_parser/lower/lower_decorators.rs +++ b/src/js_parser/lower/lower_decorators.rs @@ -2437,16 +2437,13 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // ── Phase 8: Assemble output ───────────────────── if is_expr { let mut comma_parts = BumpVec::::new_in(bump); - if let Some(cda) = class_dec_assign_expr { - comma_parts.push(cda); - } if let Some(ba) = base_assign_expr { comma_parts.push(ba); } - // Can't capture `&mut self` in a closure while also calling - // `p.method()`, so inline both call sites against a `&[Stmt]` - // slice array. + // Member decorator arrays first (per ES spec), then class + // decorator arrays, so member decorator expressions are + // evaluated before class decorator expressions. for stmts_list in [&pre_eval_stmts[..], &prefix_stmts[..]] { for pstmt in stmts_list.iter() { match &pstmt.data { @@ -2484,6 +2481,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } } + // Class decorator array after member decorator arrays + if let Some(cda) = class_dec_assign_expr { + comma_parts.push(cda); + } // _init = __decoratorStart(...) comma_parts.push(p.assign_to(init_ref, init_start_expr, loc)); @@ -2539,14 +2540,16 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O return; } - // Statement mode - if !matches!(class_dec_stmt.data, js_ast::StmtData::SEmpty(_)) { - out.push(class_dec_stmt); - } + // Statement mode: member decorator arrays first (per ES spec), + // then class decorator arrays, so member decorator expressions + // are evaluated before class decorator expressions. if !matches!(base_decl_stmt.data, js_ast::StmtData::SEmpty(_)) { out.push(base_decl_stmt); } out.extend_from_slice(&pre_eval_stmts); + if !matches!(class_dec_stmt.data, js_ast::StmtData::SEmpty(_)) { + out.push(class_dec_stmt); + } out.extend_from_slice(&prefix_stmts); out.push(init_decl_stmt); out.push(original_stmt.unwrap());