Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 24 additions & 2 deletions compiler/frontend/bs_builtin_ppx.ml
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,35 @@ let expr_mapper ~async_context ~in_function_def (self : mapper)
| Pexp_newtype (s, body) ->
let res = self.expr self body in
{e with pexp_desc = Pexp_newtype (s, res)}
| Pexp_fun {arg_label = label; lhs = pat; rhs = body; async} -> (
| Pexp_fun {arg_label = label; lhs = pat; rhs = body; async; arity; default}
-> (
match Ast_attributes.process_attributes_rev e.pexp_attributes with
| Nothing, _ ->
(* Handle @async x => y => ... is in async context *)
async_context := (old_in_function_def && !async_context) || async;
(* The default mapper would descend into nested [Pexp_fun] nodes (used for
additional parameters) before visiting the function body. Those
nested calls see [async = false] and would reset [async_context] to
false, so by the time we translate the body we incorrectly think we are
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need all this? why not just save !in_function_def and restore it later? Does it already fix the issue.
If not, go with this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately it seems it is needed.

• I tried the “just save and restore in_function_def” tweak you suggested in compiler/frontend/bs_builtin_ppx.ml:102. Even with that in place, the
nested Pexp_fun that represents the second parameter still executes this branch with async = false, so we compute async_context := (true && !
true) || false, i.e. it flips back to false before we ever reach the real body. The result is the same error as before—node cli/bsc.js -dparsetree
tests/tests/src/function_directives_async.res still reports “Await on expression not in an async context”.

The manual rebuild keeps the outer async flag alive while we thread through the extra parameter wrappers: we map the attributes and parameters
ourselves, recurse into the body, then reassemble the function with the original ~async and pass that to Ast_async.make_function_async. With that
code back in place, the same CLI run succeeds and the generated JS contains the 'use cache'; directive. So we do already restore in_function_def,
but the heavier change is what stops the nested parameter visit from clearing async_context, which is the root of the regression.

outside of an async function. This shows up with function-level
[@directive] (GH #7974): the directive attribute lives on the outer
async lambda, while extra parameters are represented as nested
functions. Rebuild the function manually to keep the async flag alive
until the body is processed. *)
let attrs = self.attributes self e.pexp_attributes in
let default = Option.map (self.expr self) default in
let lhs = self.pat self pat in
let saved_in_function_def = !in_function_def in
in_function_def := true;
Ast_async.make_function_async ~async (default_expr_mapper self e)
(* Keep reporting nested parameters as part of a function definition so
they propagate async context exactly like the original mapper. *)
let rhs = self.expr self body in
in_function_def := saved_in_function_def;
let mapped =
Ast_helper.Exp.fun_ ~loc:e.pexp_loc ~attrs ~arity ~async label default
lhs rhs
in
Ast_async.make_function_async ~async mapped
| Meth_callback _, pexp_attributes ->
(* FIXME: does it make sense to have a label for [this] ? *)
async_context := false;
Expand Down
19 changes: 19 additions & 0 deletions tests/tests/src/function_directives_async.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Generated by ReScript, PLEASE EDIT WITH CARE


async function f(p1, p2, p3) {
'use cache';
return await new Promise((resolve, _reject) => resolve([
p1,
p2,
p3
]));
}

let result = f(1, 2, 3);

export {
f,
result,
}
/* result Not a pure module */
7 changes: 7 additions & 0 deletions tests/tests/src/function_directives_async.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
let f =
@directive("'use cache'")
async (p1, ~p2, ~p3) => {
await Promise.make((resolve, _reject) => resolve((p1, p2, p3)))
}

let result = f(1, ~p2=2, ~p3=3)
Loading