@@ -47,6 +47,7 @@ pub struct ReactiveFunction {
4747 pub is_async: bool,
4848 pub is_generator: bool,
4949 pub fn_type: ReactFunctionType,
50+ pub enable_forest: bool,
5051}
5152
5253#[derive(Clone)]
@@ -130,6 +131,7 @@ fn function_to_reactive(
130131 is_async: function.is_async,
131132 is_generator: function.is_generator,
132133 fn_type,
134+ enable_forest: false,
133135 }
134136}
135137
@@ -1665,6 +1667,7 @@ fn memoize_reactive_function(reactive: &mut ReactiveFunction) -> (u32, u32, u32,
16651667 let has_destructuring_default_alloc =
16661668 body_contains_destructuring_default_alloc_literal(&reactive.body);
16671669 if reactive.fn_type == ReactFunctionType::Component
1670+ && !reactive.enable_forest
16681671 && !has_identity_sensitive_work
16691672 && !has_destructuring_default_alloc
16701673 {
@@ -1770,7 +1773,10 @@ fn memoize_reactive_function(reactive: &mut ReactiveFunction) -> (u32, u32, u32,
17701773 Some(Stmt::Return(return_stmt)) if return_stmt.arg.is_some()
17711774 )
17721775 );
1773- if !can_memoize_try_tail {
1776+ let can_memoize_if_tail_with_return = stmts.last().is_some_and(|stmt| {
1777+ matches!(stmt, Stmt::If(_)) && contains_return_stmt_in_stmts(std::slice::from_ref(stmt))
1778+ });
1779+ if !can_memoize_try_tail && !can_memoize_if_tail_with_return {
17741780 transformed.extend(stmts);
17751781 reactive.body.stmts = transformed;
17761782 return (0, 0, 0, 0, 0);
@@ -3457,6 +3463,143 @@ fn memoize_reactive_function(reactive: &mut ReactiveFunction) -> (u32, u32, u32,
34573463 }
34583464 }
34593465 }
3466+ } else if matches!(tail.last(), Some(Stmt::If(_))) && contains_return_stmt_in_stmts(&tail) {
3467+ prune_empty_stmts(&mut tail);
3468+ prune_noop_identifier_exprs(&mut tail);
3469+ prune_unused_underscore_jsx_decls(&mut tail);
3470+ promote_immutable_lets_to_const(&mut tail);
3471+ normalize_static_string_members_in_stmts(&mut tail);
3472+ inline_const_literal_indices_in_stmts(&mut tail);
3473+ normalize_compound_assignments_in_stmts(&mut tail);
3474+ normalize_reactive_labels(&mut tail);
3475+ normalize_if_break_blocks(&mut tail);
3476+ normalize_if_return_blocks(&mut tail);
3477+ lower_function_decls_to_const_in_stmts(&mut tail);
3478+ flatten_hoistable_blocks_in_stmts(&mut tail, &mut reserved);
3479+ flatten_hoistable_blocks_in_nested_functions(&mut tail);
3480+ lower_iife_call_args_in_stmts(&mut tail, &mut reserved, &mut next_temp);
3481+ inline_trivial_iifes_in_stmts(&mut tail);
3482+ flatten_hoistable_blocks_in_stmts(&mut tail, &mut reserved);
3483+ flatten_hoistable_blocks_in_nested_functions(&mut tail);
3484+ strip_runtime_call_type_args_in_stmts(&mut tail);
3485+ prune_unused_pure_var_decls(&mut tail);
3486+ prune_unused_function_like_decl_stmts(&mut tail);
3487+
3488+ let mut local_bindings = HashSet::new();
3489+ for stmt in &tail {
3490+ collect_stmt_bindings_including_nested_blocks(stmt, &mut local_bindings);
3491+ }
3492+ let non_optional_member_dep_keys =
3493+ collect_non_optional_member_dependency_keys_from_stmts(
3494+ &tail,
3495+ &known_bindings,
3496+ &local_bindings,
3497+ );
3498+ let mixed_optional_member_dep_keys = collect_mixed_member_dependency_keys_from_stmts(
3499+ &tail,
3500+ &known_bindings,
3501+ &local_bindings,
3502+ );
3503+ let conditional_only_non_optional_member_dep_keys =
3504+ collect_conditional_only_non_optional_member_dependency_keys_from_stmts(
3505+ &tail,
3506+ &known_bindings,
3507+ &local_bindings,
3508+ );
3509+ let mut deps = collect_dependencies_from_stmts(&tail, &known_bindings, &local_bindings);
3510+ let called_fn_deps =
3511+ collect_called_local_function_capture_dependencies(&tail, &known_bindings);
3512+ for dep in called_fn_deps {
3513+ if !deps.iter().any(|existing| existing.key == dep.key) {
3514+ deps.push(dep);
3515+ }
3516+ }
3517+ let inline_fn_capture_deps =
3518+ collect_stmt_function_capture_dependencies(&tail, &known_bindings);
3519+ for dep in inline_fn_capture_deps {
3520+ if !deps.iter().any(|existing| existing.key == dep.key) {
3521+ deps.push(dep);
3522+ }
3523+ }
3524+ deps = reduce_dependencies(deps);
3525+ deps = normalize_optional_member_dependencies(
3526+ deps,
3527+ &non_optional_member_dep_keys,
3528+ &mixed_optional_member_dep_keys,
3529+ &conditional_only_non_optional_member_dep_keys,
3530+ );
3531+ deps = reduce_nested_member_dependencies(deps);
3532+
3533+ let temp = fresh_temp_ident(&mut next_temp, &mut reserved);
3534+ let label = Ident::new_no_ctxt("bb0".into(), DUMMY_SP);
3535+ let (mut rewritten_stmts, has_early_return) =
3536+ rewrite_returns_for_labeled_block(tail, &label, &temp);
3537+
3538+ if has_early_return {
3539+ let nested_slot_start = next_slot + deps.len() as u32 + 1;
3540+ let (nested_slots, nested_blocks, nested_values) = if deps.is_empty() {
3541+ (0, 0, 0)
3542+ } else {
3543+ inject_nested_call_memoization_into_stmts(
3544+ &mut rewritten_stmts,
3545+ &known_bindings,
3546+ &cache_ident,
3547+ nested_slot_start,
3548+ &mut reserved,
3549+ &mut next_temp,
3550+ false,
3551+ )
3552+ };
3553+
3554+ let mut with_header = Vec::with_capacity(rewritten_stmts.len() + 2);
3555+ with_header.push(assign_stmt(
3556+ AssignTarget::from(temp.clone()),
3557+ early_return_sentinel_expr(),
3558+ ));
3559+ with_header.push(Stmt::Labeled(LabeledStmt {
3560+ span: DUMMY_SP,
3561+ label,
3562+ body: Box::new(Stmt::Block(BlockStmt {
3563+ span: DUMMY_SP,
3564+ ctxt: Default::default(),
3565+ stmts: rewritten_stmts,
3566+ })),
3567+ }));
3568+
3569+ let value_slot = next_slot + deps.len() as u32;
3570+ transformed.extend(build_memoized_block(
3571+ &cache_ident,
3572+ next_slot,
3573+ &deps,
3574+ &temp,
3575+ with_header,
3576+ true,
3577+ ));
3578+ transformed.push(Stmt::If(IfStmt {
3579+ span: DUMMY_SP,
3580+ test: Box::new(Expr::Bin(swc_ecma_ast::BinExpr {
3581+ span: DUMMY_SP,
3582+ op: op!("!=="),
3583+ left: Box::new(Expr::Ident(temp.clone())),
3584+ right: early_return_sentinel_expr(),
3585+ })),
3586+ cons: Box::new(Stmt::Block(BlockStmt {
3587+ span: DUMMY_SP,
3588+ ctxt: Default::default(),
3589+ stmts: vec![Stmt::Return(swc_ecma_ast::ReturnStmt {
3590+ span: DUMMY_SP,
3591+ arg: Some(Box::new(Expr::Ident(temp))),
3592+ })],
3593+ })),
3594+ alt: None,
3595+ }));
3596+
3597+ next_slot = value_slot + 1 + nested_slots;
3598+ memo_blocks += 1 + nested_blocks;
3599+ memo_values += 1 + nested_values;
3600+ } else {
3601+ transformed.extend(rewritten_stmts);
3602+ }
34603603 } else if let [Stmt::Try(try_stmt)] = tail.as_mut_slice() {
34613604 if try_stmt.finalizer.is_some() {
34623605 transformed.extend(tail);
@@ -3713,6 +3856,7 @@ fn memoize_reactive_function(reactive: &mut ReactiveFunction) -> (u32, u32, u32,
37133856 }
37143857
37153858 prune_unused_object_pattern_bindings_in_stmts(&mut transformed);
3859+ prune_empty_else_blocks_in_stmts(&mut transformed);
37163860 normalize_empty_jsx_elements_to_self_closing_in_stmts(&mut transformed);
37173861 reactive.body.stmts = transformed;
37183862
@@ -4856,6 +5000,7 @@ fn normalize_non_ident_params_without_memoization(reactive: &mut ReactiveFunctio
48565000 normalize_switch_case_blocks_in_stmts(&mut stmts);
48575001 prune_trivial_do_while_break_stmts(&mut stmts);
48585002 normalize_reactive_labels(&mut stmts);
5003+ prune_empty_else_blocks_in_stmts(&mut stmts);
48595004 if param_prologue.is_empty() {
48605005 reactive.body.stmts = stmts;
48615006 return;
@@ -4869,6 +5014,7 @@ fn normalize_non_ident_params_without_memoization(reactive: &mut ReactiveFunctio
48695014 transformed.extend(stmts.drain(..directive_end));
48705015 transformed.extend(param_prologue);
48715016 transformed.extend(stmts);
5017+ prune_empty_else_blocks_in_stmts(&mut transformed);
48725018 reactive.body.stmts = transformed;
48735019}
48745020
@@ -10416,8 +10562,37 @@ fn inject_nested_call_memoization_into_stmts(
1041610562 && !binding_mutated_via_member_assignment_after(remaining, binding.id.sym.as_ref())
1041710563 && !binding_maybe_mutated_via_alias_after(remaining, binding.id.sym.as_ref())
1041810564 {
10419- let nested_deps = collect_identifier_dependencies_for_nested_expr(init_expr);
10565+ let local_bindings = HashSet::new();
10566+ let nested_deps = collect_dependencies_from_expr(
10567+ init_expr,
10568+ &nested_known_bindings,
10569+ &local_bindings,
10570+ );
10571+ let has_local_member_dep = nested_deps.iter().any(|dep| {
10572+ let Some((base, _)) = dep.key.split_once('.') else {
10573+ return false;
10574+ };
10575+ binding_declared_in_stmts(&out, base)
10576+ });
10577+ let dep_bases_are_stable = nested_deps.iter().all(|dep| {
10578+ let base = dep
10579+ .key
10580+ .split_once('.')
10581+ .map(|(base, _)| base)
10582+ .unwrap_or(dep.key.as_str());
10583+ !binding_reassigned_after(remaining, base)
10584+ && !binding_mutated_via_member_call_after(remaining, base)
10585+ && !binding_mutated_via_member_assignment_after(remaining, base)
10586+ && !binding_maybe_mutated_via_alias_after(remaining, base)
10587+ && !binding_passed_to_potentially_mutating_call_after(remaining, base)
10588+ && !binding_maybe_mutated_in_called_iife_after(remaining, base)
10589+ });
1042010590 if !nested_deps.is_empty() {
10591+ if has_local_member_dep || !dep_bases_are_stable {
10592+ out.push(stmt.clone());
10593+ mark_stmt_bindings_unstable(&stmt, &mut nested_known_bindings);
10594+ continue;
10595+ }
1042110596 let result_temp = fresh_temp_ident(next_temp, reserved);
1042210597 let mut nested_compute = vec![assign_stmt(
1042310598 AssignTarget::from(result_temp.clone()),
@@ -11060,16 +11235,22 @@ fn inject_nested_call_memoization_into_stmt_children(
1106011235 match stmt {
1106111236 Stmt::Block(block) => inject_stmt_list(&mut block.stmts),
1106211237 Stmt::If(if_stmt) => {
11238+ let branch_temp_start = *next_temp;
11239+ let branch_reserved_start = reserved.clone();
11240+ let mut cons_next_temp = branch_temp_start;
11241+ let mut cons_reserved = branch_reserved_start.clone();
1106311242 inject_nested_call_memoization_into_stmt_children(
1106411243 &mut if_stmt.cons,
1106511244 known_bindings,
1106611245 cache_ident,
1106711246 cursor,
1106811247 added_blocks,
1106911248 added_values,
11070- reserved ,
11071- next_temp ,
11249+ &mut cons_reserved ,
11250+ &mut cons_next_temp ,
1107211251 );
11252+ let mut alt_next_temp = branch_temp_start;
11253+ let mut alt_reserved = branch_reserved_start.clone();
1107311254 if let Some(alt) = &mut if_stmt.alt {
1107411255 inject_nested_call_memoization_into_stmt_children(
1107511256 alt,
@@ -11078,10 +11259,12 @@ fn inject_nested_call_memoization_into_stmt_children(
1107811259 cursor,
1107911260 added_blocks,
1108011261 added_values,
11081- reserved ,
11082- next_temp ,
11262+ &mut alt_reserved ,
11263+ &mut alt_next_temp ,
1108311264 );
1108411265 }
11266+ *next_temp = cons_next_temp.max(alt_next_temp);
11267+ *reserved = branch_reserved_start;
1108511268 }
1108611269 Stmt::Labeled(labeled) => inject_nested_call_memoization_into_stmt_children(
1108711270 &mut labeled.body,
@@ -11180,47 +11363,16 @@ fn mark_stmt_bindings_unstable(stmt: &Stmt, known_bindings: &mut HashMap<String,
1118011363 }
1118111364}
1118211365
11183- fn collect_identifier_dependencies_for_nested_expr(expr: &Expr) -> Vec<ReactiveDependency> {
11184- struct Collector {
11185- seen: HashSet<String>,
11186- deps: Vec<ReactiveDependency>,
11187- }
11188-
11189- impl Visit for Collector {
11190- fn visit_arrow_expr(&mut self, _: &ArrowExpr) {
11191- // Skip nested functions.
11192- }
11193-
11194- fn visit_function(&mut self, _: &Function) {
11195- // Skip nested functions.
11196- }
11197-
11198- fn visit_ident(&mut self, ident: &Ident) {
11199- let key = ident.sym.to_string();
11200- if self.seen.insert(key.clone()) {
11201- self.deps.push(ReactiveDependency {
11202- key,
11203- expr: Box::new(Expr::Ident(ident.clone())),
11204- });
11205- }
11206- }
11207- }
11208-
11209- let mut collector = Collector {
11210- seen: HashSet::new(),
11211- deps: Vec::new(),
11212- };
11213- expr.visit_with(&mut collector);
11214- collector
11215- .deps
11216- .sort_by(|left, right| left.key.cmp(&right.key));
11217- collector.deps
11218- }
11219-
1122011366fn is_simple_nested_array_initializer(expr: &Expr) -> bool {
1122111367 let Expr::Array(array) = expr else {
1122211368 return false;
1122311369 };
11370+ if array.elems.len() == 1 {
11371+ let Some(Some(element)) = array.elems.first() else {
11372+ return false;
11373+ };
11374+ return element.spread.is_none() && matches!(&*element.expr, Expr::Member(_));
11375+ }
1122411376 if array.elems.len() < 2 {
1122511377 return false;
1122611378 }
@@ -18234,6 +18386,26 @@ fn normalize_if_return_blocks(stmts: &mut [Stmt]) {
1823418386 }
1823518387}
1823618388
18389+ fn prune_empty_else_blocks_in_stmts(stmts: &mut [Stmt]) {
18390+ struct EmptyElsePruner;
18391+
18392+ impl VisitMut for EmptyElsePruner {
18393+ fn visit_mut_if_stmt(&mut self, if_stmt: &mut IfStmt) {
18394+ if_stmt.visit_mut_children_with(self);
18395+
18396+ if matches!(if_stmt.alt.as_deref(), Some(Stmt::Block(block)) if block.stmts.is_empty())
18397+ {
18398+ if_stmt.alt = None;
18399+ }
18400+ }
18401+ }
18402+
18403+ let mut pruner = EmptyElsePruner;
18404+ for stmt in stmts {
18405+ stmt.visit_mut_with(&mut pruner);
18406+ }
18407+ }
18408+
1823718409fn normalize_switch_case_blocks_in_stmts(stmts: &mut [Stmt]) {
1823818410 struct SwitchCaseNormalizer;
1823918411
0 commit comments