Skip to content

Commit 08bfac0

Browse files
committed
improve return value analysis and fix a bug in arrow function handling
1 parent 9519f14 commit 08bfac0

File tree

1 file changed

+67
-156
lines changed
  • turbopack/crates/turbopack-ecmascript/src/analyzer

1 file changed

+67
-156
lines changed

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

Lines changed: 67 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -937,19 +937,25 @@ mod analyzer_state {
937937

938938
/// Runs `func` with the current function identifier and return values initialized for the
939939
/// block.
940-
pub(super) fn enter_fn(
941-
&mut self,
942-
fn_id: u32,
943-
initial_return_values: Vec<JsValue>,
944-
func: impl FnOnce(&mut Self),
945-
) -> JsValue {
940+
pub(super) fn enter_fn(&mut self, fn_id: u32, func: impl FnOnce(&mut Self)) -> JsValue {
946941
let prev_fn_id = self.state.cur_fn_ident.replace(fn_id);
947-
948-
let old_return_values = self
949-
.state
950-
.cur_fn_return_values
951-
.replace(initial_return_values);
942+
let old_return_values = self.state.cur_fn_return_values.replace(vec![]);
943+
let early_return_stack = take(&mut self.early_return_stack);
944+
let mut effects = take(&mut self.effects);
945+
let hoisted_effects = take(&mut self.hoisted_effects);
952946
func(self);
947+
if !self.end_early_return_block() {
948+
// If the block doesn't end in a return there is an implicit `undefined` return
949+
self.get_mut_cur_fn_return_values()
950+
.unwrap()
951+
.push(JsValue::Constant(ConstantValue::Undefined))
952+
}
953+
self.effects.append(&mut self.hoisted_effects);
954+
effects.append(&mut self.effects);
955+
self.hoisted_effects = hoisted_effects;
956+
self.effects = effects;
957+
self.early_return_stack = early_return_stack;
958+
953959
let return_values = self.take_return_values();
954960
self.state.cur_fn_ident = prev_fn_id;
955961
self.state.cur_fn_return_values = old_return_values;
@@ -1672,14 +1678,10 @@ impl VisitAstPath for Analyzer<'_> {
16721678
decl: &'ast FnDecl,
16731679
ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
16741680
) {
1675-
let fn_value = self.enter_fn(
1676-
decl.function.span.lo.0,
1677-
get_fn_init_return_vals(decl.function.body.as_ref()),
1678-
|this| {
1679-
decl.visit_children_with_ast_path(this, ast_path);
1680-
this.hoisted_effects.append(&mut this.effects);
1681-
},
1682-
);
1681+
let fn_value = self.enter_fn(decl.function.span.lo.0, |this| {
1682+
decl.visit_children_with_ast_path(this, ast_path);
1683+
this.hoisted_effects.append(&mut this.effects);
1684+
});
16831685
self.add_value(decl.ident.to_id(), fn_value);
16841686
}
16851687

@@ -1688,13 +1690,9 @@ impl VisitAstPath for Analyzer<'_> {
16881690
expr: &'ast FnExpr,
16891691
ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
16901692
) {
1691-
let fn_value = self.enter_fn(
1692-
expr.function.span.lo.0,
1693-
get_fn_init_return_vals(expr.function.body.as_ref()),
1694-
|this| {
1695-
expr.visit_children_with_ast_path(this, ast_path);
1696-
},
1697-
);
1693+
let fn_value = self.enter_fn(expr.function.span.lo.0, |this| {
1694+
expr.visit_children_with_ast_path(this, ast_path);
1695+
});
16981696
if let Some(ident) = &expr.ident {
16991697
self.add_value(ident.to_id(), fn_value);
17001698
} else {
@@ -1713,11 +1711,7 @@ impl VisitAstPath for Analyzer<'_> {
17131711
expr: &'ast ArrowExpr,
17141712
ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
17151713
) {
1716-
let init_return_values = match &*expr.body {
1717-
BlockStmtOrExpr::BlockStmt(block) => get_fn_init_return_vals(Some(block)),
1718-
BlockStmtOrExpr::Expr(inner_expr) => vec![self.eval_context.eval(inner_expr)],
1719-
};
1720-
let fn_value = self.enter_fn(expr.span.lo.0, init_return_values, |this| {
1714+
let fn_value = self.enter_fn(expr.span.lo.0, |this| {
17211715
for (index, p) in expr.params.iter().enumerate() {
17221716
this.with_pat_value(
17231717
Some(JsValue::Argument(this.cur_fn_ident(), index)),
@@ -1735,6 +1729,17 @@ impl VisitAstPath for Analyzer<'_> {
17351729
let mut ast_path =
17361730
ast_path.with_guard(AstParentNodeRef::ArrowExpr(expr, ArrowExprField::Body));
17371731
expr.body.visit_with_ast_path(this, &mut ast_path);
1732+
// If body is a single expression treat it as a Block with an return statement
1733+
if let BlockStmtOrExpr::Expr(inner_expr) = &*expr.body {
1734+
let implicit_return_value = self.eval_context.eval(inner_expr);
1735+
this.get_mut_cur_fn_return_values()
1736+
.unwrap()
1737+
.push(implicit_return_value);
1738+
this.early_return_stack.push(EarlyReturn::Always {
1739+
prev_effects: take(&mut this.effects),
1740+
start_ast_path: as_parent_path(&ast_path),
1741+
});
1742+
}
17381743
}
17391744
});
17401745
self.add_value(
@@ -1784,99 +1789,39 @@ impl VisitAstPath for Analyzer<'_> {
17841789
expr.visit_children_with_ast_path(self, ast_path);
17851790
}
17861791

1787-
// N.B. ClassMethod covers both methods and getters/setters.
1788-
1789-
fn visit_class_method<'ast: 'r, 'r>(
1790-
&mut self,
1791-
node: &'ast ClassMethod,
1792-
ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
1793-
) {
1794-
self.enter_fn(
1795-
node.function.span.lo.0,
1796-
get_fn_init_return_vals(node.function.body.as_ref()),
1797-
|this| {
1798-
node.visit_children_with_ast_path(this, ast_path);
1799-
// TODO: do we want to track the value of the class method?
1800-
this.hoisted_effects.append(&mut this.effects);
1801-
},
1802-
);
1803-
}
1804-
fn visit_method_prop<'ast: 'r, 'r>(
1805-
&mut self,
1806-
node: &'ast MethodProp,
1807-
ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
1808-
) {
1809-
self.enter_fn(
1810-
node.function.span.lo.0,
1811-
get_fn_init_return_vals(node.function.body.as_ref()),
1812-
|this| {
1813-
node.visit_children_with_ast_path(this, ast_path);
1814-
// TODO: do we want to track the value of the object method?
1815-
},
1816-
);
1817-
}
1818-
18191792
fn visit_getter_prop<'ast: 'r, 'r>(
18201793
&mut self,
18211794
node: &'ast GetterProp,
18221795
ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
18231796
) {
1824-
self.enter_fn(
1825-
node.span.lo.0,
1826-
get_fn_init_return_vals(node.body.as_ref()),
1827-
|this| {
1828-
node.visit_children_with_ast_path(this, ast_path);
1829-
this.hoisted_effects.append(&mut this.effects);
1830-
},
1831-
);
1797+
self.enter_fn(node.span.lo.0, |this| {
1798+
node.visit_children_with_ast_path(this, ast_path);
1799+
this.hoisted_effects.append(&mut this.effects);
1800+
});
18321801
}
18331802

18341803
fn visit_setter_prop<'ast: 'r, 'r>(
18351804
&mut self,
18361805
node: &'ast SetterProp,
18371806
ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
18381807
) {
1839-
self.enter_fn(
1840-
node.span.lo.0,
1841-
get_fn_init_return_vals(node.body.as_ref()),
1842-
|this| {
1843-
node.visit_children_with_ast_path(this, ast_path);
1844-
// TODO: do we want to track the value of the object method?
1845-
this.hoisted_effects.append(&mut this.effects);
1846-
},
1847-
);
1848-
}
1849-
1850-
fn visit_private_method<'ast: 'r, 'r>(
1851-
&mut self,
1852-
node: &'ast PrivateMethod,
1853-
ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
1854-
) {
1855-
self.enter_fn(
1856-
node.function.span.lo.0,
1857-
get_fn_init_return_vals(node.function.body.as_ref()),
1858-
|this| {
1859-
node.visit_children_with_ast_path(this, ast_path);
1860-
// TODO: do we want to track the value of the class method?
1861-
this.hoisted_effects.append(&mut this.effects);
1862-
},
1863-
);
1808+
self.enter_fn(node.span.lo.0, |this| {
1809+
node.visit_children_with_ast_path(this, ast_path);
1810+
// TODO: do we want to track the value of the object method?
1811+
this.hoisted_effects.append(&mut this.effects);
1812+
});
18641813
}
18651814

18661815
fn visit_constructor<'ast: 'r, 'r>(
18671816
&mut self,
18681817
node: &'ast Constructor,
18691818
ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
18701819
) {
1871-
self.enter_fn(
1872-
node.span.lo.0,
1873-
get_fn_init_return_vals(node.body.as_ref()),
1874-
|this| {
1875-
node.visit_children_with_ast_path(this, ast_path);
1876-
// TODO: do we want to track the value of the constructor?
1877-
this.hoisted_effects.append(&mut this.effects);
1878-
},
1879-
);
1820+
self.enter_fn(node.span.lo.0, |this| {
1821+
node.visit_children_with_ast_path(this, ast_path);
1822+
// TODO: do we want to track the value of the constructor?
1823+
this.hoisted_effects.append(&mut this.effects);
1824+
});
18801825
}
18811826

18821827
fn visit_var_decl<'ast: 'r, 'r>(
@@ -2422,10 +2367,9 @@ impl VisitAstPath for Analyzer<'_> {
24222367
n: &'ast BlockStmt,
24232368
ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
24242369
) {
2425-
let block_type = if ast_path.len() < 2 {
2426-
Some(false)
2427-
} else if matches!(
2428-
&ast_path[ast_path.len() - 2..],
2370+
let path_suffix = &ast_path[ast_path.len().saturating_sub(2)..];
2371+
if matches!(
2372+
path_suffix,
24292373
[
24302374
AstParentNodeRef::IfStmt(_, IfStmtField::Cons),
24312375
AstParentNodeRef::Stmt(_, StmtField::Block)
@@ -2442,9 +2386,12 @@ impl VisitAstPath for Analyzer<'_> {
24422386
AstParentNodeRef::Stmt(_, StmtField::Block)
24432387
]
24442388
) {
2445-
None
2389+
// Just visit children, we already handled stack manipulation in the corresponding
2390+
// handlers.
2391+
n.visit_children_with_ast_path(self, ast_path);
24462392
} else if matches!(
2447-
&ast_path[ast_path.len() - 2..],
2393+
path_suffix,
2394+
// NOTE: class and object methods have 'functions' as children so they match here
24482395
[_, AstParentNodeRef::Function(_, FunctionField::Body)]
24492396
| [
24502397
AstParentNodeRef::ArrowExpr(_, ArrowExprField::Body),
@@ -2454,34 +2401,16 @@ impl VisitAstPath for Analyzer<'_> {
24542401
| [_, AstParentNodeRef::SetterProp(_, SetterPropField::Body)]
24552402
| [_, AstParentNodeRef::Constructor(_, ConstructorField::Body)]
24562403
) {
2457-
Some(true)
2404+
// Just visit children, we already handled everything in `enter_fn`
2405+
n.visit_children_with_ast_path(self, ast_path);
24582406
} else {
2459-
Some(false)
2460-
};
2461-
match block_type {
2462-
Some(true) => {
2463-
let early_return_stack = take(&mut self.early_return_stack);
2464-
let mut effects = take(&mut self.effects);
2465-
let hoisted_effects = take(&mut self.hoisted_effects);
2466-
n.visit_children_with_ast_path(self, ast_path);
2467-
self.end_early_return_block();
2468-
self.effects.append(&mut self.hoisted_effects);
2469-
effects.append(&mut self.effects);
2470-
self.hoisted_effects = hoisted_effects;
2471-
self.effects = effects;
2472-
self.early_return_stack = early_return_stack;
2473-
}
2474-
Some(false) => {
2475-
n.visit_children_with_ast_path(self, ast_path);
2476-
if self.end_early_return_block() {
2477-
self.early_return_stack.push(EarlyReturn::Always {
2478-
prev_effects: take(&mut self.effects),
2479-
start_ast_path: as_parent_path(ast_path),
2480-
});
2481-
}
2482-
}
2483-
None => {
2484-
n.visit_children_with_ast_path(self, ast_path);
2407+
// These are anonymous blocks
2408+
n.visit_children_with_ast_path(self, ast_path);
2409+
if self.end_early_return_block() {
2410+
self.early_return_stack.push(EarlyReturn::Always {
2411+
prev_effects: take(&mut self.effects),
2412+
start_ast_path: as_parent_path(ast_path),
2413+
});
24852414
}
24862415
}
24872416
}
@@ -2843,21 +2772,3 @@ fn extract_var_from_umd_factory(callee: &Expr, args: &[ExprOrSpread]) -> Option<
28432772

28442773
None
28452774
}
2846-
2847-
fn get_fn_init_return_vals(fn_body_stmts: Option<&BlockStmt>) -> Vec<JsValue> {
2848-
let has_final_return_val = match fn_body_stmts {
2849-
Some(fn_body_stmts) => {
2850-
matches!(
2851-
fn_body_stmts.stmts.last(),
2852-
Some(Stmt::Return(ReturnStmt { arg: Some(_), .. }))
2853-
)
2854-
}
2855-
None => false,
2856-
};
2857-
2858-
if has_final_return_val {
2859-
vec![]
2860-
} else {
2861-
vec![JsValue::Constant(ConstantValue::Undefined)]
2862-
}
2863-
}

0 commit comments

Comments
 (0)