Skip to content

Commit c3dc0dc

Browse files
authored
Fix @directive on function level with async and multiple parameters (#7977)
* Fix @directive on function level with async and multiple parameters * CHANGELOG
1 parent 737c1b4 commit c3dc0dc

File tree

4 files changed

+52
-2
lines changed

4 files changed

+52
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
#### :bug: Bug fix
2222

23+
- Fix @directive on function level with async and multiple parameters. https://github.com/rescript-lang/rescript/pull/7977
24+
2325
#### :memo: Documentation
2426

2527
#### :nail_care: Polish

compiler/frontend/bs_builtin_ppx.ml

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,35 @@ let expr_mapper ~async_context ~in_function_def (self : mapper)
9595
| Pexp_newtype (s, body) ->
9696
let res = self.expr self body in
9797
{e with pexp_desc = Pexp_newtype (s, res)}
98-
| Pexp_fun {arg_label = label; lhs = pat; rhs = body; async} -> (
98+
| Pexp_fun {arg_label = label; lhs = pat; rhs = body; async; arity; default}
99+
-> (
99100
match Ast_attributes.process_attributes_rev e.pexp_attributes with
100101
| Nothing, _ ->
101102
(* Handle @async x => y => ... is in async context *)
102103
async_context := (old_in_function_def && !async_context) || async;
104+
(* The default mapper would descend into nested [Pexp_fun] nodes (used for
105+
additional parameters) before visiting the function body. Those
106+
nested calls see [async = false] and would reset [async_context] to
107+
false, so by the time we translate the body we incorrectly think we are
108+
outside of an async function. This shows up with function-level
109+
[@directive] (GH #7974): the directive attribute lives on the outer
110+
async lambda, while extra parameters are represented as nested
111+
functions. Rebuild the function manually to keep the async flag alive
112+
until the body is processed. *)
113+
let attrs = self.attributes self e.pexp_attributes in
114+
let default = Option.map (self.expr self) default in
115+
let lhs = self.pat self pat in
116+
let saved_in_function_def = !in_function_def in
103117
in_function_def := true;
104-
Ast_async.make_function_async ~async (default_expr_mapper self e)
118+
(* Keep reporting nested parameters as part of a function definition so
119+
they propagate async context exactly like the original mapper. *)
120+
let rhs = self.expr self body in
121+
in_function_def := saved_in_function_def;
122+
let mapped =
123+
Ast_helper.Exp.fun_ ~loc:e.pexp_loc ~attrs ~arity ~async label default
124+
lhs rhs
125+
in
126+
Ast_async.make_function_async ~async mapped
105127
| Meth_callback _, pexp_attributes ->
106128
(* FIXME: does it make sense to have a label for [this] ? *)
107129
async_context := false;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Generated by ReScript, PLEASE EDIT WITH CARE
2+
3+
4+
async function f(p1, p2, p3) {
5+
'use cache';
6+
return await new Promise((resolve, _reject) => resolve([
7+
p1,
8+
p2,
9+
p3
10+
]));
11+
}
12+
13+
let result = f(1, 2, 3);
14+
15+
export {
16+
f,
17+
result,
18+
}
19+
/* result Not a pure module */
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let f =
2+
@directive("'use cache'")
3+
async (p1, ~p2, ~p3) => {
4+
await Promise.make((resolve, _reject) => resolve((p1, p2, p3)))
5+
}
6+
7+
let result = f(1, ~p2=2, ~p3=3)

0 commit comments

Comments
 (0)