Skip to content

@directives in functions #6756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

# 12.0.0-alpha.1 (Unreleased)

#### :rocket: New Feature

- Allow `@directive` on functions for emitting function level directive code (`let serverAction = @directive("'use server'") (~name) => {...}`). https://github.com/rescript-lang/rescript-compiler/pull/6756

#### :boom: Breaking Change

- `lazy` syntax is no longer supported. If you're using it, use `Lazy` module or `React.lazy_` instead. https://github.com/rescript-lang/rescript-compiler/pull/6342
Expand Down
1 change: 1 addition & 0 deletions jscomp/core/j.ml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ and expression_desc =
env : Js_fun_env.t;
return_unit : bool;
async : bool;
directive : string option;
}
| Str of { delim : delim; txt : string }
(* A string is UTF-8 encoded, and may contain
Expand Down
30 changes: 19 additions & 11 deletions jscomp/core/js_dump.ml
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ let rec try_optimize_curry cxt f len function_id =
Curry_gen.pp_optimize_curry f len;
P.paren_group f 1 (fun _ -> expression ~level:1 cxt f function_id)

and pp_function ~return_unit ~async ~is_method cxt (f : P.t) ~fn_state
and pp_function ~return_unit ~async ~is_method ?directive cxt (f : P.t) ~fn_state
(l : Ident.t list) (b : J.block) (env : Js_fun_env.t) : cxt =
match b with
| [
Expand Down Expand Up @@ -363,13 +363,13 @@ and pp_function ~return_unit ~async ~is_method cxt (f : P.t) ~fn_state
if Js_fun_env.get_unused env 0 then cxt
else pp_var_assign_this cxt f this
in
function_body ~return_unit cxt f b))
function_body ?directive ~return_unit cxt f b))
else
let cxt =
P.paren_group f 1 (fun _ -> formal_parameter_list inner_cxt f l)
in
P.space f;
P.brace_vgroup f 1 (fun _ -> function_body ~return_unit cxt f b)
P.brace_vgroup f 1 (fun _ -> function_body ?directive ~return_unit cxt f b)
in
let enclose () =
let handle () =
Expand Down Expand Up @@ -483,9 +483,9 @@ and expression_desc cxt ~(level : int) f x : cxt =
let cxt = expression ~level:0 cxt f e1 in
comma_sp f;
expression ~level:0 cxt f e2)
| Fun { is_method; params; body; env; return_unit; async } ->
| Fun { is_method; params; body; env; return_unit; async; directive } ->
(* TODO: dump for comments *)
pp_function ~is_method cxt f ~fn_state:default_fn_exp_state params body
pp_function ?directive ~is_method cxt f ~fn_state:default_fn_exp_state params body
env ~return_unit ~async
(* TODO:
when [e] is [Js_raw_code] with arity
Expand Down Expand Up @@ -515,10 +515,11 @@ and expression_desc cxt ~(level : int) f x : cxt =
env;
return_unit;
async;
directive;
};
};
] ->
pp_function ~is_method ~return_unit ~async cxt f
pp_function ?directive ~is_method ~return_unit ~async cxt f
~fn_state:(No_name { single_arg = true })
params body env
| _ ->
Expand Down Expand Up @@ -920,8 +921,8 @@ and variable_declaration top cxt f (variable : J.variable_declaration) : cxt =
statement_desc top cxt f (J.Exp e)
| _ -> (
match e.expression_desc with
| Fun { is_method; params; body; env; return_unit; async } ->
pp_function ~is_method cxt f ~return_unit ~async
| Fun { is_method; params; body; env; return_unit; async; directive } ->
pp_function ?directive ~is_method cxt f ~return_unit ~async
~fn_state:(if top then Name_top name else Name_non_top name)
params body env
| _ ->
Expand Down Expand Up @@ -1124,9 +1125,9 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt =
cxt
| Return e -> (
match e.expression_desc with
| Fun { is_method; params; body; env; return_unit; async } ->
| Fun { is_method; params; body; env; return_unit; async; directive } ->
let cxt =
pp_function ~return_unit ~is_method ~async cxt f ~fn_state:Is_return
pp_function ?directive ~return_unit ~is_method ~async cxt f ~fn_state:Is_return
params body env
in
semi f;
Expand Down Expand Up @@ -1216,8 +1217,15 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt =
P.string f L.finally;
P.space f;
brace_block cxt f b))


and function_body (cxt : cxt) f ~return_unit (b : J.block) : unit =
and function_body ?directive (cxt : cxt) f ~return_unit (b : J.block) : unit =
(match directive with
| None -> ()
| Some directive ->
P.newline f;
P.string f directive; P.string f ";";
P.newline f);
match b with
| [] -> ()
| [ s ] -> (
Expand Down
5 changes: 4 additions & 1 deletion jscomp/core/js_exp_make.ml
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ let unit : t = { expression_desc = Undefined {isUnit = true}; comment = None }
[Js_fun_env.empty] is a mutable state ..
*)

let ocaml_fun ?comment ?immutable_mask ~return_unit ~async ~oneUnitArg params body : t =
let ocaml_fun ?comment ?immutable_mask ~return_unit ~async ~oneUnitArg ?directive params body : t =
let params = if oneUnitArg then [] else params in
let len = List.length params in
{
Expand All @@ -220,6 +220,7 @@ let ocaml_fun ?comment ?immutable_mask ~return_unit ~async ~oneUnitArg params bo
env = Js_fun_env.make ?immutable_mask len;
return_unit;
async;
directive;
};
comment;
}
Expand All @@ -236,6 +237,7 @@ let method_ ?comment ?immutable_mask ~return_unit params body : t =
env = Js_fun_env.make ?immutable_mask len;
return_unit;
async = false;
directive = None;
};
comment;
}
Expand Down Expand Up @@ -1301,6 +1303,7 @@ let of_block ?comment ?e block : t =
env = Js_fun_env.make 0;
return_unit;
async = false;
directive = None;
};
}
[]
Expand Down
1 change: 1 addition & 0 deletions jscomp/core/js_exp_make.mli
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ val ocaml_fun :
return_unit:bool ->
async:bool ->
oneUnitArg:bool ->
?directive:string ->
J.ident list ->
J.block ->
t
Expand Down
4 changes: 2 additions & 2 deletions jscomp/core/js_pass_tailcall_inline.ml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ let subst (export_set : Set_ident.t) stats =
Some
{
expression_desc =
Fun {is_method=false; params; body; env; async=false};
Fun {is_method=false; params; body; env; async=false; directive=None};
comment = _;
};
(*TODO: don't inline method tail call yet,
Expand Down Expand Up @@ -200,7 +200,7 @@ let subst (export_set : Set_ident.t) stats =
Call
( {
expression_desc =
Fun {is_method=false; params; body; env; async=false};
Fun {is_method=false; params; body; env; async=false; directive=None};
},
args,
_info );
Expand Down
4 changes: 2 additions & 2 deletions jscomp/core/lam_analysis.ml
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ let destruct_pattern (body : Lam.t) params args =
| _ -> false

(* Async functions cannot be beta reduced *)
let lfunction_can_be_beta_reduced (lfunction : Lam.lfunction) =
not lfunction.attr.async
let lfunction_can_be_inlined (lfunction : Lam.lfunction) =
not lfunction.attr.async && lfunction.attr.directive = None

(** Hints to inlining *)
let ok_to_inline_fun_when_app (m : Lam.lfunction) (args : Lam.t list) =
Expand Down
2 changes: 1 addition & 1 deletion jscomp/core/lam_analysis.mli
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ val no_side_effects : Lam.t -> bool

val size : Lam.t -> int

val lfunction_can_be_beta_reduced : Lam.lfunction -> bool
val lfunction_can_be_inlined : Lam.lfunction -> bool

val ok_to_inline_fun_when_app : Lam.lfunction -> Lam.t list -> bool

Expand Down
12 changes: 6 additions & 6 deletions jscomp/core/lam_compile.ml
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ and compile_external_field_apply ?(dynamic_import = false) (appinfo : Lam.apply)
let ap_args = appinfo.ap_args in
match ident_info.persistent_closed_lambda with
| Some (Lfunction ({ params; body; _ } as lfunction))
when Ext_list.same_length params ap_args && Lam_analysis.lfunction_can_be_beta_reduced lfunction ->
when Ext_list.same_length params ap_args && Lam_analysis.lfunction_can_be_inlined lfunction ->
(* TODO: serialize it when exporting to save compile time *)
let _, param_map =
Lam_closure.is_closed_with_map Set_ident.empty params body
Expand Down Expand Up @@ -319,7 +319,7 @@ and compile_external_field_apply ?(dynamic_import = false) (appinfo : Lam.apply)
and compile_recursive_let ~all_bindings (cxt : Lam_compile_context.t)
(id : Ident.t) (arg : Lam.t) : Js_output.t * initialization =
match arg with
| Lfunction { params; body; attr = { return_unit; async; oneUnitArg } } ->
| Lfunction { params; body; attr = { return_unit; async; oneUnitArg; directive } } ->
(* TODO: Think about recursive value
{[
let rec v = ref (fun _ ...
Expand Down Expand Up @@ -357,7 +357,7 @@ and compile_recursive_let ~all_bindings (cxt : Lam_compile_context.t)
it will be renamed into [method]
when it is detected by a primitive
*)
~return_unit ~async ~oneUnitArg ~immutable_mask:ret.immutable_mask
~return_unit ~async ~oneUnitArg ?directive ~immutable_mask:ret.immutable_mask
(Ext_list.map params (fun x ->
Map_ident.find_default ret.new_params x x))
[
Expand All @@ -368,7 +368,7 @@ and compile_recursive_let ~all_bindings (cxt : Lam_compile_context.t)
]
else
(* TODO: save computation of length several times *)
E.ocaml_fun params (Js_output.output_as_block output) ~return_unit ~async ~oneUnitArg
E.ocaml_fun params (Js_output.output_as_block output) ~return_unit ~async ~oneUnitArg ?directive
in
( Js_output.output_of_expression
(Declare (Alias, id))
Expand Down Expand Up @@ -1670,10 +1670,10 @@ and compile_prim (prim_info : Lam.prim_info)
and compile_lambda (lambda_cxt : Lam_compile_context.t) (cur_lam : Lam.t) :
Js_output.t =
match cur_lam with
| Lfunction { params; body; attr = { return_unit; async; oneUnitArg } } ->
| Lfunction { params; body; attr = { return_unit; async; oneUnitArg; directive } } ->
Js_output.output_of_expression lambda_cxt.continuation
~no_effects:no_effects_const
(E.ocaml_fun params ~return_unit ~async ~oneUnitArg
(E.ocaml_fun params ~return_unit ~async ~oneUnitArg ?directive
(* Invariant: jmp_table can not across function boundary,
here we share env
*)
Expand Down
2 changes: 1 addition & 1 deletion jscomp/core/lam_pass_count.ml
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ let collect_occurs lam : occ_tbl =
count bv body
(* Note there is a difference here when do beta reduction for *)
| Lapply { ap_func = Lfunction ({ params; body } as lfunction); ap_args = args; _ }
when Ext_list.same_length params args && Lam_analysis.lfunction_can_be_beta_reduced lfunction ->
when Ext_list.same_length params args && Lam_analysis.lfunction_can_be_inlined lfunction ->
count bv (Lam_beta_reduce.no_names_beta_reduce params body args)
(* | Lapply{fn = Lfunction{function_kind = Tupled; params; body}; *)
(* args = [Lprim {primitive = Pmakeblock _; args; _}]; _} *)
Expand Down
2 changes: 1 addition & 1 deletion jscomp/core/lam_pass_lets_dce.ml
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ let lets_helper (count_var : Ident.t -> Lam_pass_count.used_info) lam : Lam.t =
| Lsequence(l1, l2) -> Lam.seq (simplif l1) (simplif l2)

| Lapply{ap_func = Lfunction ({params; body} as lfunction); ap_args = args; _}
when Ext_list.same_length params args && Lam_analysis.lfunction_can_be_beta_reduced lfunction ->
when Ext_list.same_length params args && Lam_analysis.lfunction_can_be_inlined lfunction ->
simplif (Lam_beta_reduce.no_names_beta_reduce params body args)
(* | Lapply{ fn = Lfunction{function_kind = Tupled; params; body}; *)
(* args = [Lprim {primitive = Pmakeblock _; args; _}]; _} *)
Expand Down
8 changes: 4 additions & 4 deletions jscomp/core/lam_pass_remove_alias.ml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t =
rec_flag );
})
when Ext_list.same_length ap_args params
&& Lam_analysis.lfunction_can_be_beta_reduced m
&& Lam_analysis.lfunction_can_be_inlined m
&& Lam_analysis.ok_to_inline_fun_when_app m ap_args -> (
let param_map =
Lam_closure.is_closed_with_map meta.export_idents params body
Expand Down Expand Up @@ -184,7 +184,7 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t =
| Some v -> v <> Parameter
| None -> true)
| _ -> true)
&& Lam_analysis.lfunction_can_be_beta_reduced lfunction ->
&& Lam_analysis.lfunction_can_be_inlined lfunction ->
simpl (Lam_beta_reduce.propagate_beta_reduce meta params body args)
| _ -> Lam.apply (simpl l1) (Ext_list.map args simpl) ap_info)
(* Function inlining interact with other optimizations...
Expand All @@ -208,7 +208,7 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t =
( Lfunction ({ params; body; attr = { is_a_functor } } as m),
rec_flag );
})
when Lam_analysis.lfunction_can_be_beta_reduced m ->
when Lam_analysis.lfunction_can_be_inlined m ->
if Ext_list.same_length ap_args params then
if
is_a_functor
Expand Down Expand Up @@ -266,7 +266,7 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t =
_;
}
when Ext_list.same_length params args
&& Lam_analysis.lfunction_can_be_beta_reduced lfunction ->
&& Lam_analysis.lfunction_can_be_inlined lfunction ->
simpl (Lam_beta_reduce.propagate_beta_reduce meta params body args)
(* | Lapply{ fn = Lfunction{function_kind = Tupled; params; body}; *)
(* args = [Lprim {primitive = Pmakeblock _; args; _}]; _} *)
Expand Down
2 changes: 2 additions & 0 deletions jscomp/ml/lambda.ml
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ type function_attribute = {
stub: bool;
return_unit : bool;
async : bool;
directive : string option;
oneUnitArg : bool;
}

Expand Down Expand Up @@ -394,6 +395,7 @@ let default_function_attribute = {
return_unit = false;
async = false;
oneUnitArg = false;
directive = None;
}

let default_stub_attribute =
Expand Down
1 change: 1 addition & 0 deletions jscomp/ml/lambda.mli
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ type function_attribute = {
stub: bool;
return_unit : bool;
async : bool;
directive : string option;
oneUnitArg : bool;
}

Expand Down
11 changes: 11 additions & 0 deletions jscomp/ml/translcore.ml
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,11 @@ let try_ids = Hashtbl.create 8

let has_async_attribute exp = exp.exp_attributes |> List.exists (fun ({txt}, _payload) -> txt = "res.async")

let extract_directive_for_fn exp =
exp.exp_attributes |> List.find_map (
fun ({txt}, payload) -> if txt = "directive" then Ast_payload.is_single_string payload else None)


let rec transl_exp e =
List.iter (Translattribute.check_attribute e) e.exp_attributes;
transl_exp0 e
Expand All @@ -732,6 +737,11 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda =
transl_let rec_flag pat_expr_list (transl_exp body)
| Texp_function { arg_label = _; param; cases; partial } ->
let async = has_async_attribute e in
let directive = (
match extract_directive_for_fn e with
| None -> None
| Some (directive, _) -> Some directive
) in
let params, body, return_unit =
let pl = push_defaults e.exp_loc [] cases partial in
transl_function e.exp_loc partial param pl
Expand All @@ -742,6 +752,7 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda =
inline = Translattribute.get_inline_attribute e.exp_attributes;
async;
return_unit;
directive;
}
in
let loc = e.exp_loc in
Expand Down
1 change: 1 addition & 0 deletions jscomp/ml/translmod.ml
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ let rec compile_functor mexp coercion root_path loc =
return_unit = false;
async = false;
oneUnitArg = false;
directive = None;
};
loc;
body;
Expand Down
4 changes: 3 additions & 1 deletion jscomp/test/build.ninja

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions jscomp/test/function_directives.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions jscomp/test/function_directives.res
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let testFnWithDirective = @directive("'use memo'") (name: string) => "Hello " ++ name
14 changes: 14 additions & 0 deletions jscomp/test/function_directives_no_inline.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions jscomp/test/function_directives_no_inline.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
let testFnWithDirective = @directive("'use server'") (name: string) => "Hello " ++ name

let x = testFnWithDirective("test")
Loading