Skip to content

Commit b1d5a43

Browse files
authored
@Directives in functions (#6756)
* base implementation for directives in functions * prevent inlining of functions with directives * changelog
1 parent 1e822bb commit b1d5a43

21 files changed

+93
-30
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
1313
# 12.0.0-alpha.1 (Unreleased)
1414

15+
#### :rocket: New Feature
16+
17+
- 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
18+
1519
#### :boom: Breaking Change
1620

1721
- `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

jscomp/core/j.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ and expression_desc =
135135
env : Js_fun_env.t;
136136
return_unit : bool;
137137
async : bool;
138+
directive : string option;
138139
}
139140
| Str of { delim : delim; txt : string }
140141
(* A string is UTF-8 encoded, and may contain

jscomp/core/js_dump.ml

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ let rec try_optimize_curry cxt f len function_id =
282282
Curry_gen.pp_optimize_curry f len;
283283
P.paren_group f 1 (fun _ -> expression ~level:1 cxt f function_id)
284284

285-
and pp_function ~return_unit ~async ~is_method cxt (f : P.t) ~fn_state
285+
and pp_function ~return_unit ~async ~is_method ?directive cxt (f : P.t) ~fn_state
286286
(l : Ident.t list) (b : J.block) (env : Js_fun_env.t) : cxt =
287287
match b with
288288
| [
@@ -363,13 +363,13 @@ and pp_function ~return_unit ~async ~is_method cxt (f : P.t) ~fn_state
363363
if Js_fun_env.get_unused env 0 then cxt
364364
else pp_var_assign_this cxt f this
365365
in
366-
function_body ~return_unit cxt f b))
366+
function_body ?directive ~return_unit cxt f b))
367367
else
368368
let cxt =
369369
P.paren_group f 1 (fun _ -> formal_parameter_list inner_cxt f l)
370370
in
371371
P.space f;
372-
P.brace_vgroup f 1 (fun _ -> function_body ~return_unit cxt f b)
372+
P.brace_vgroup f 1 (fun _ -> function_body ?directive ~return_unit cxt f b)
373373
in
374374
let enclose () =
375375
let handle () =
@@ -483,9 +483,9 @@ and expression_desc cxt ~(level : int) f x : cxt =
483483
let cxt = expression ~level:0 cxt f e1 in
484484
comma_sp f;
485485
expression ~level:0 cxt f e2)
486-
| Fun { is_method; params; body; env; return_unit; async } ->
486+
| Fun { is_method; params; body; env; return_unit; async; directive } ->
487487
(* TODO: dump for comments *)
488-
pp_function ~is_method cxt f ~fn_state:default_fn_exp_state params body
488+
pp_function ?directive ~is_method cxt f ~fn_state:default_fn_exp_state params body
489489
env ~return_unit ~async
490490
(* TODO:
491491
when [e] is [Js_raw_code] with arity
@@ -515,10 +515,11 @@ and expression_desc cxt ~(level : int) f x : cxt =
515515
env;
516516
return_unit;
517517
async;
518+
directive;
518519
};
519520
};
520521
] ->
521-
pp_function ~is_method ~return_unit ~async cxt f
522+
pp_function ?directive ~is_method ~return_unit ~async cxt f
522523
~fn_state:(No_name { single_arg = true })
523524
params body env
524525
| _ ->
@@ -920,8 +921,8 @@ and variable_declaration top cxt f (variable : J.variable_declaration) : cxt =
920921
statement_desc top cxt f (J.Exp e)
921922
| _ -> (
922923
match e.expression_desc with
923-
| Fun { is_method; params; body; env; return_unit; async } ->
924-
pp_function ~is_method cxt f ~return_unit ~async
924+
| Fun { is_method; params; body; env; return_unit; async; directive } ->
925+
pp_function ?directive ~is_method cxt f ~return_unit ~async
925926
~fn_state:(if top then Name_top name else Name_non_top name)
926927
params body env
927928
| _ ->
@@ -1124,9 +1125,9 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt =
11241125
cxt
11251126
| Return e -> (
11261127
match e.expression_desc with
1127-
| Fun { is_method; params; body; env; return_unit; async } ->
1128+
| Fun { is_method; params; body; env; return_unit; async; directive } ->
11281129
let cxt =
1129-
pp_function ~return_unit ~is_method ~async cxt f ~fn_state:Is_return
1130+
pp_function ?directive ~return_unit ~is_method ~async cxt f ~fn_state:Is_return
11301131
params body env
11311132
in
11321133
semi f;
@@ -1216,8 +1217,15 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt =
12161217
P.string f L.finally;
12171218
P.space f;
12181219
brace_block cxt f b))
1220+
12191221

1220-
and function_body (cxt : cxt) f ~return_unit (b : J.block) : unit =
1222+
and function_body ?directive (cxt : cxt) f ~return_unit (b : J.block) : unit =
1223+
(match directive with
1224+
| None -> ()
1225+
| Some directive ->
1226+
P.newline f;
1227+
P.string f directive; P.string f ";";
1228+
P.newline f);
12211229
match b with
12221230
| [] -> ()
12231231
| [ s ] -> (

jscomp/core/js_exp_make.ml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ let unit : t = { expression_desc = Undefined {isUnit = true}; comment = None }
207207
[Js_fun_env.empty] is a mutable state ..
208208
*)
209209

210-
let ocaml_fun ?comment ?immutable_mask ~return_unit ~async ~oneUnitArg params body : t =
210+
let ocaml_fun ?comment ?immutable_mask ~return_unit ~async ~oneUnitArg ?directive params body : t =
211211
let params = if oneUnitArg then [] else params in
212212
let len = List.length params in
213213
{
@@ -220,6 +220,7 @@ let ocaml_fun ?comment ?immutable_mask ~return_unit ~async ~oneUnitArg params bo
220220
env = Js_fun_env.make ?immutable_mask len;
221221
return_unit;
222222
async;
223+
directive;
223224
};
224225
comment;
225226
}
@@ -236,6 +237,7 @@ let method_ ?comment ?immutable_mask ~return_unit params body : t =
236237
env = Js_fun_env.make ?immutable_mask len;
237238
return_unit;
238239
async = false;
240+
directive = None;
239241
};
240242
comment;
241243
}
@@ -1301,6 +1303,7 @@ let of_block ?comment ?e block : t =
13011303
env = Js_fun_env.make 0;
13021304
return_unit;
13031305
async = false;
1306+
directive = None;
13041307
};
13051308
}
13061309
[]

jscomp/core/js_exp_make.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ val ocaml_fun :
9191
return_unit:bool ->
9292
async:bool ->
9393
oneUnitArg:bool ->
94+
?directive:string ->
9495
J.ident list ->
9596
J.block ->
9697
t

jscomp/core/js_pass_tailcall_inline.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ let subst (export_set : Set_ident.t) stats =
165165
Some
166166
{
167167
expression_desc =
168-
Fun {is_method=false; params; body; env; async=false};
168+
Fun {is_method=false; params; body; env; async=false; directive=None};
169169
comment = _;
170170
};
171171
(*TODO: don't inline method tail call yet,
@@ -200,7 +200,7 @@ let subst (export_set : Set_ident.t) stats =
200200
Call
201201
( {
202202
expression_desc =
203-
Fun {is_method=false; params; body; env; async=false};
203+
Fun {is_method=false; params; body; env; async=false; directive=None};
204204
},
205205
args,
206206
_info );

jscomp/core/lam_analysis.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,8 @@ let destruct_pattern (body : Lam.t) params args =
249249
| _ -> false
250250

251251
(* Async functions cannot be beta reduced *)
252-
let lfunction_can_be_beta_reduced (lfunction : Lam.lfunction) =
253-
not lfunction.attr.async
252+
let lfunction_can_be_inlined (lfunction : Lam.lfunction) =
253+
not lfunction.attr.async && lfunction.attr.directive = None
254254

255255
(** Hints to inlining *)
256256
let ok_to_inline_fun_when_app (m : Lam.lfunction) (args : Lam.t list) =

jscomp/core/lam_analysis.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ val no_side_effects : Lam.t -> bool
2929

3030
val size : Lam.t -> int
3131

32-
val lfunction_can_be_beta_reduced : Lam.lfunction -> bool
32+
val lfunction_can_be_inlined : Lam.lfunction -> bool
3333

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

jscomp/core/lam_compile.ml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ and compile_external_field_apply ?(dynamic_import = false) (appinfo : Lam.apply)
272272
let ap_args = appinfo.ap_args in
273273
match ident_info.persistent_closed_lambda with
274274
| Some (Lfunction ({ params; body; _ } as lfunction))
275-
when Ext_list.same_length params ap_args && Lam_analysis.lfunction_can_be_beta_reduced lfunction ->
275+
when Ext_list.same_length params ap_args && Lam_analysis.lfunction_can_be_inlined lfunction ->
276276
(* TODO: serialize it when exporting to save compile time *)
277277
let _, param_map =
278278
Lam_closure.is_closed_with_map Set_ident.empty params body
@@ -319,7 +319,7 @@ and compile_external_field_apply ?(dynamic_import = false) (appinfo : Lam.apply)
319319
and compile_recursive_let ~all_bindings (cxt : Lam_compile_context.t)
320320
(id : Ident.t) (arg : Lam.t) : Js_output.t * initialization =
321321
match arg with
322-
| Lfunction { params; body; attr = { return_unit; async; oneUnitArg } } ->
322+
| Lfunction { params; body; attr = { return_unit; async; oneUnitArg; directive } } ->
323323
(* TODO: Think about recursive value
324324
{[
325325
let rec v = ref (fun _ ...
@@ -357,7 +357,7 @@ and compile_recursive_let ~all_bindings (cxt : Lam_compile_context.t)
357357
it will be renamed into [method]
358358
when it is detected by a primitive
359359
*)
360-
~return_unit ~async ~oneUnitArg ~immutable_mask:ret.immutable_mask
360+
~return_unit ~async ~oneUnitArg ?directive ~immutable_mask:ret.immutable_mask
361361
(Ext_list.map params (fun x ->
362362
Map_ident.find_default ret.new_params x x))
363363
[
@@ -368,7 +368,7 @@ and compile_recursive_let ~all_bindings (cxt : Lam_compile_context.t)
368368
]
369369
else
370370
(* TODO: save computation of length several times *)
371-
E.ocaml_fun params (Js_output.output_as_block output) ~return_unit ~async ~oneUnitArg
371+
E.ocaml_fun params (Js_output.output_as_block output) ~return_unit ~async ~oneUnitArg ?directive
372372
in
373373
( Js_output.output_of_expression
374374
(Declare (Alias, id))
@@ -1670,10 +1670,10 @@ and compile_prim (prim_info : Lam.prim_info)
16701670
and compile_lambda (lambda_cxt : Lam_compile_context.t) (cur_lam : Lam.t) :
16711671
Js_output.t =
16721672
match cur_lam with
1673-
| Lfunction { params; body; attr = { return_unit; async; oneUnitArg } } ->
1673+
| Lfunction { params; body; attr = { return_unit; async; oneUnitArg; directive } } ->
16741674
Js_output.output_of_expression lambda_cxt.continuation
16751675
~no_effects:no_effects_const
1676-
(E.ocaml_fun params ~return_unit ~async ~oneUnitArg
1676+
(E.ocaml_fun params ~return_unit ~async ~oneUnitArg ?directive
16771677
(* Invariant: jmp_table can not across function boundary,
16781678
here we share env
16791679
*)

jscomp/core/lam_pass_count.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ let collect_occurs lam : occ_tbl =
144144
count bv body
145145
(* Note there is a difference here when do beta reduction for *)
146146
| Lapply { ap_func = Lfunction ({ params; body } as lfunction); ap_args = args; _ }
147-
when Ext_list.same_length params args && Lam_analysis.lfunction_can_be_beta_reduced lfunction ->
147+
when Ext_list.same_length params args && Lam_analysis.lfunction_can_be_inlined lfunction ->
148148
count bv (Lam_beta_reduce.no_names_beta_reduce params body args)
149149
(* | Lapply{fn = Lfunction{function_kind = Tupled; params; body}; *)
150150
(* args = [Lprim {primitive = Pmakeblock _; args; _}]; _} *)

jscomp/core/lam_pass_lets_dce.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ let lets_helper (count_var : Ident.t -> Lam_pass_count.used_info) lam : Lam.t =
147147
| Lsequence(l1, l2) -> Lam.seq (simplif l1) (simplif l2)
148148

149149
| Lapply{ap_func = Lfunction ({params; body} as lfunction); ap_args = args; _}
150-
when Ext_list.same_length params args && Lam_analysis.lfunction_can_be_beta_reduced lfunction ->
150+
when Ext_list.same_length params args && Lam_analysis.lfunction_can_be_inlined lfunction ->
151151
simplif (Lam_beta_reduce.no_names_beta_reduce params body args)
152152
(* | Lapply{ fn = Lfunction{function_kind = Tupled; params; body}; *)
153153
(* args = [Lprim {primitive = Pmakeblock _; args; _}]; _} *)

jscomp/core/lam_pass_remove_alias.ml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t =
8282
rec_flag );
8383
})
8484
when Ext_list.same_length ap_args params
85-
&& Lam_analysis.lfunction_can_be_beta_reduced m
85+
&& Lam_analysis.lfunction_can_be_inlined m
8686
&& Lam_analysis.ok_to_inline_fun_when_app m ap_args -> (
8787
let param_map =
8888
Lam_closure.is_closed_with_map meta.export_idents params body
@@ -184,7 +184,7 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t =
184184
| Some v -> v <> Parameter
185185
| None -> true)
186186
| _ -> true)
187-
&& Lam_analysis.lfunction_can_be_beta_reduced lfunction ->
187+
&& Lam_analysis.lfunction_can_be_inlined lfunction ->
188188
simpl (Lam_beta_reduce.propagate_beta_reduce meta params body args)
189189
| _ -> Lam.apply (simpl l1) (Ext_list.map args simpl) ap_info)
190190
(* Function inlining interact with other optimizations...
@@ -208,7 +208,7 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t =
208208
( Lfunction ({ params; body; attr = { is_a_functor } } as m),
209209
rec_flag );
210210
})
211-
when Lam_analysis.lfunction_can_be_beta_reduced m ->
211+
when Lam_analysis.lfunction_can_be_inlined m ->
212212
if Ext_list.same_length ap_args params then
213213
if
214214
is_a_functor
@@ -266,7 +266,7 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t =
266266
_;
267267
}
268268
when Ext_list.same_length params args
269-
&& Lam_analysis.lfunction_can_be_beta_reduced lfunction ->
269+
&& Lam_analysis.lfunction_can_be_inlined lfunction ->
270270
simpl (Lam_beta_reduce.propagate_beta_reduce meta params body args)
271271
(* | Lapply{ fn = Lfunction{function_kind = Tupled; params; body}; *)
272272
(* args = [Lprim {primitive = Pmakeblock _; args; _}]; _} *)

jscomp/ml/lambda.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ type function_attribute = {
323323
stub: bool;
324324
return_unit : bool;
325325
async : bool;
326+
directive : string option;
326327
oneUnitArg : bool;
327328
}
328329

@@ -394,6 +395,7 @@ let default_function_attribute = {
394395
return_unit = false;
395396
async = false;
396397
oneUnitArg = false;
398+
directive = None;
397399
}
398400

399401
let default_stub_attribute =

jscomp/ml/lambda.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ type function_attribute = {
293293
stub: bool;
294294
return_unit : bool;
295295
async : bool;
296+
directive : string option;
296297
oneUnitArg : bool;
297298
}
298299

jscomp/ml/translcore.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,11 @@ let try_ids = Hashtbl.create 8
717717

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

720+
let extract_directive_for_fn exp =
721+
exp.exp_attributes |> List.find_map (
722+
fun ({txt}, payload) -> if txt = "directive" then Ast_payload.is_single_string payload else None)
723+
724+
720725
let rec transl_exp e =
721726
List.iter (Translattribute.check_attribute e) e.exp_attributes;
722727
transl_exp0 e
@@ -732,6 +737,11 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda =
732737
transl_let rec_flag pat_expr_list (transl_exp body)
733738
| Texp_function { arg_label = _; param; cases; partial } ->
734739
let async = has_async_attribute e in
740+
let directive = (
741+
match extract_directive_for_fn e with
742+
| None -> None
743+
| Some (directive, _) -> Some directive
744+
) in
735745
let params, body, return_unit =
736746
let pl = push_defaults e.exp_loc [] cases partial in
737747
transl_function e.exp_loc partial param pl
@@ -742,6 +752,7 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda =
742752
inline = Translattribute.get_inline_attribute e.exp_attributes;
743753
async;
744754
return_unit;
755+
directive;
745756
}
746757
in
747758
let loc = e.exp_loc in

jscomp/ml/translmod.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ let rec compile_functor mexp coercion root_path loc =
278278
return_unit = false;
279279
async = false;
280280
oneUnitArg = false;
281+
directive = None;
281282
};
282283
loc;
283284
body;

jscomp/test/build.ninja

Lines changed: 3 additions & 1 deletion
Large diffs are not rendered by default.

jscomp/test/function_directives.js

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jscomp/test/function_directives.res

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let testFnWithDirective = @directive("'use memo'") (name: string) => "Hello " ++ name

jscomp/test/function_directives_no_inline.js

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
let testFnWithDirective = @directive("'use server'") (name: string) => "Hello " ++ name
2+
3+
let x = testFnWithDirective("test")

0 commit comments

Comments
 (0)