diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index e2d4a51f46..0970e5418b 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -267,33 +267,35 @@ let rec exprToContextPathInner ~(inJsxContext : bool) (e : Parsetree.expression) }; args = [(_, lhs); (_, {pexp_desc = Pexp_apply {funct = d; args; partial}})]; + transformed_jsx; } -> (* Transform away pipe with apply call *) exprToContextPath ~inJsxContext { pexp_desc = - Pexp_apply {funct = d; args = (Nolabel, lhs) :: args; partial}; + Pexp_apply + {funct = d; args = (Nolabel, lhs) :: args; partial; transformed_jsx}; pexp_loc; pexp_attributes; } | Pexp_apply - { - funct = {pexp_desc = Pexp_ident {txt = Lident "->"}}; - args = - [ - (_, lhs); (_, {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes}); - ]; - partial; - } -> + ({ + funct = {pexp_desc = Pexp_ident {txt = Lident "->"}}; + args = + [ + (_, lhs); + (_, {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes}); + ]; + } as app) -> (* Transform away pipe with identifier *) exprToContextPath ~inJsxContext { pexp_desc = Pexp_apply { + app with funct = {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes}; args = [(Nolabel, lhs)]; - partial; }; pexp_loc; pexp_attributes; diff --git a/compiler/bsc/rescript_compiler_main.ml b/compiler/bsc/rescript_compiler_main.ml index a5950f198e..f9113c40b6 100644 --- a/compiler/bsc/rescript_compiler_main.ml +++ b/compiler/bsc/rescript_compiler_main.ml @@ -255,6 +255,7 @@ let buckle_script_flags : (string * Bsc_args.spec * string) array = ( "-bs-jsx-mode", string_call ignore, "*internal* Set jsx mode, this is no longer used and is a no-op." ); + ("-bs-jsx-preserve", set Js_config.jsx_preserve, "*internal* Preserve jsx"); ( "-bs-package-output", string_call Js_packages_state.update_npm_package_path, "*internal* Set npm-output-path: [opt_module]:path, for example: \ diff --git a/compiler/common/js_config.ml b/compiler/common/js_config.ml index bf062d0f21..8c92235f04 100644 --- a/compiler/common/js_config.ml +++ b/compiler/common/js_config.ml @@ -50,6 +50,7 @@ let force_cmi = ref false let force_cmj = ref false let jsx_version = ref None let jsx_module = ref React +let jsx_preserve = ref false let js_stdout = ref true let all_module_aliases = ref false let no_stdlib = ref false diff --git a/compiler/common/js_config.mli b/compiler/common/js_config.mli index ec6e1829da..253b567a34 100644 --- a/compiler/common/js_config.mli +++ b/compiler/common/js_config.mli @@ -80,6 +80,8 @@ val jsx_version : jsx_version option ref val jsx_module : jsx_module ref +val jsx_preserve : bool ref + val js_stdout : bool ref val all_module_aliases : bool ref diff --git a/compiler/core/js_call_info.ml b/compiler/core/js_call_info.ml index 547f03c7f8..c58aad901f 100644 --- a/compiler/core/js_call_info.ml +++ b/compiler/core/js_call_info.ml @@ -33,10 +33,15 @@ type call_info = {[ fun x y -> (f x y) === f ]} when [f] is an atom *) -type t = {call_info: call_info; arity: arity} +type t = {call_info: call_info; arity: arity; call_transformed_jsx: bool} -let dummy = {arity = NA; call_info = Call_na} +let dummy = {arity = NA; call_info = Call_na; call_transformed_jsx = false} -let builtin_runtime_call = {arity = Full; call_info = Call_builtin_runtime} +let builtin_runtime_call = + {arity = Full; call_info = Call_builtin_runtime; call_transformed_jsx = false} -let ml_full_call = {arity = Full; call_info = Call_ml} +let ml_full_call = + {arity = Full; call_info = Call_ml; call_transformed_jsx = false} + +let na_full_call transformed_jsx = + {arity = Full; call_info = Call_na; call_transformed_jsx = transformed_jsx} diff --git a/compiler/core/js_call_info.mli b/compiler/core/js_call_info.mli index 0381c0cd2b..ff0d3ad875 100644 --- a/compiler/core/js_call_info.mli +++ b/compiler/core/js_call_info.mli @@ -35,10 +35,12 @@ type call_info = {[ fun x y -> f x y === f ]} when [f] is an atom *) -type t = {call_info: call_info; arity: arity} +type t = {call_info: call_info; arity: arity; call_transformed_jsx: bool} val dummy : t val builtin_runtime_call : t val ml_full_call : t + +val na_full_call : bool -> t diff --git a/compiler/core/js_dump.ml b/compiler/core/js_dump.ml index bc174e7dd5..05c300d0a3 100644 --- a/compiler/core/js_dump.ml +++ b/compiler/core/js_dump.ml @@ -515,7 +515,7 @@ and expression_desc cxt ~(level : int) f x : cxt = (* TODO: dump for comments *) pp_function ?directive ~is_method ~return_unit ~async ~fn_state:default_fn_exp_state cxt f params body env - (* TODO: + (* TODO: when [e] is [Js_raw_code] with arity print it in a more precise way It seems the optimizer already did work to make sure @@ -524,6 +524,116 @@ and expression_desc cxt ~(level : int) f x : cxt = when Ext_list.length_equal el i ]} *) + (* When -bs-preserve-jsx is enabled, we marked each transformed application node throughout the compilation. + Here we print the transformed application node into a JSX syntax. + The JSX is slightly different from what a user would write, + but it is still valid JSX and is usable by tools like ESBuild. + *) + | Call + ( ({ + expression_desc = + J.Var + (J.Qualified + ( _, + Some fnName + (* We care about the function name when it is jsxs, + If this is the case, we need to unpack an array later on *) + )); + } as e), + el, + {call_transformed_jsx = true} ) + when !Js_config.jsx_preserve -> ( + (* We match a JsxRuntime.jsx call *) + match el with + | [ + tag; + { + expression_desc = + (* This is the props javascript object *) + Caml_block (el, _mutable_flag, _, Lambda.Blk_record {fields}); + }; + ] -> + (* We extract the props from the javascript object *) + let fields = + Ext_list.array_list_filter_map fields el (fun (f, opt) x -> + match x.expression_desc with + | Undefined _ when opt -> None + | _ -> Some (f, x)) + in + print_jsx cxt ~level f fnName tag fields + | [ + tag; + { + expression_desc = + Caml_block (el, _mutable_flag, _, Lambda.Blk_record {fields}); + }; + key; + ] -> + (* When a component has a key the matching runtime function call will have a third argument being the key *) + let fields = + Ext_list.array_list_filter_map fields el (fun (f, opt) x -> + match x.expression_desc with + | Undefined _ when opt -> None + | _ -> Some (f, x)) + in + print_jsx cxt ~level ~key f fnName tag fields + | [tag; ({expression_desc = J.Seq _} as props)] -> + (* In the case of prop spreading, the expression will look like: + (props.a = "Hello, world!", props) + which is equivalent to + + + We need to extract the props and the spread object. + *) + let fields, spread_props = + let rec visit acc e = + match e.J.expression_desc with + | J.Seq + ( { + J.expression_desc = + J.Bin + ( Js_op.Eq, + {J.expression_desc = J.Static_index (_, name, _)}, + value ); + }, + rest ) -> + visit ((name, value) :: acc) rest + | _ -> (List.rev acc, e) + in + visit [] props + in + print_jsx cxt ~level ~spread_props f fnName tag fields + | [tag; ({expression_desc = J.Seq _} as props); key] -> + (* In the case of props + prop spreading and key argument *) + let fields, spread_props = + let rec visit acc e = + match e.J.expression_desc with + | J.Seq + ( { + J.expression_desc = + J.Bin + ( Js_op.Eq, + {J.expression_desc = J.Static_index (_, name, _)}, + value ); + }, + rest ) -> + visit ((name, value) :: acc) rest + | _ -> (List.rev acc, e) + in + visit [] props + in + print_jsx cxt ~level ~spread_props ~key f fnName tag fields + | [tag; ({expression_desc = J.Var _} as spread_props)] -> + (* All the props are spread *) + print_jsx cxt ~level ~spread_props f fnName tag [] + | _ -> + (* This should not happen, we fallback to the general case *) + expression_desc cxt ~level f + (Call + ( e, + el, + {call_transformed_jsx = false; arity = Full; call_info = Call_ml} + ))) | Call (e, el, info) -> P.cond_paren_group f (level > 15) (fun _ -> P.group f 0 (fun _ -> @@ -956,6 +1066,103 @@ and expression_desc cxt ~(level : int) f x : cxt = P.string f "..."; expression ~level:13 cxt f e) +and print_jsx cxt ?(spread_props : J.expression option) + ?(key : J.expression option) ~(level : int) f (fnName : string) + (tag : J.expression) (fields : (string * J.expression) list) : cxt = + let print_tag cxt = + match tag.expression_desc with + (* "div" or any other primitive tag *) + | J.Str {txt} -> + P.string f txt; + cxt + (* fragment *) + | J.Var (J.Qualified ({id = {name = "JsxRuntime"}}, Some "Fragment")) -> cxt + (* A user defined component or external component *) + | _ -> expression ~level cxt f tag + in + let children_opt = + List.find_map + (fun (n, e) -> + if n = "children" then + if fnName = "jsxs" then + match e.J.expression_desc with + | J.Array (xs, _) + | J.Optional_block ({expression_desc = J.Array (xs, _)}, _) -> + Some xs + | _ -> Some [e] + else Some [e] + else None) + fields + in + let print_props cxt = + (* If a key is present, should be printed before the spread props, + This is to ensure tools like ESBuild use the automatic JSX runtime *) + let cxt = + match key with + | None -> cxt + | Some key -> + P.string f " key={"; + let cxt = expression ~level:0 cxt f key in + P.string f "} "; + cxt + in + let props = List.filter (fun (n, _) -> n <> "children") fields in + let cxt = + match spread_props with + | None -> cxt + | Some spread -> + P.string f " {..."; + let cxt = expression ~level:0 cxt f spread in + P.string f "} "; + cxt + in + if List.length props = 0 then cxt + else + (List.fold_left (fun acc (n, x) -> + P.space f; + P.string f n; + P.string f "="; + P.string f "{"; + let next = expression ~level:0 acc f x in + P.string f "}"; + next)) + cxt props + in + match children_opt with + | None -> + P.string f "<"; + let cxt = cxt |> print_tag |> print_props in + P.string f "/>"; + cxt + | Some children -> + let child_is_jsx child = + match child.J.expression_desc with + | J.Call (_, _, {call_transformed_jsx = is_jsx}) -> is_jsx + | _ -> false + in + + P.string f "<"; + let cxt = cxt |> print_tag |> print_props in + + P.string f ">"; + if List.length children > 0 then P.newline f; + + let cxt = + List.fold_left + (fun acc e -> + if not (child_is_jsx e) then P.string f "{"; + let next = expression ~level acc f e in + if not (child_is_jsx e) then P.string f "}"; + P.newline f; + next) + cxt children + in + + P.string f ""; + cxt + and property_name_and_value_list cxt f (l : J.property_map) = iter_lst cxt f l (fun cxt f (pn, e) -> diff --git a/compiler/core/lam.ml b/compiler/core/lam.ml index 77f991e181..1c20bb2e8e 100644 --- a/compiler/core/lam.ml +++ b/compiler/core/lam.ml @@ -81,7 +81,12 @@ module Types = struct *) and prim_info = {primitive: Lam_primitive.t; args: t list; loc: Location.t} - and apply = {ap_func: t; ap_args: t list; ap_info: ap_info} + and apply = { + ap_func: t; + ap_args: t list; + ap_info: ap_info; + ap_transformed_jsx: bool; + } and t = | Lvar of ident @@ -121,7 +126,12 @@ module X = struct loc: Location.t; } - and apply = Types.apply = {ap_func: t; ap_args: t list; ap_info: ap_info} + and apply = Types.apply = { + ap_func: t; + ap_args: t list; + ap_info: ap_info; + ap_transformed_jsx: bool; + } and lfunction = Types.lfunction = { arity: int; @@ -159,10 +169,10 @@ include Types let inner_map (l : t) (f : t -> X.t) : X.t = match l with | Lvar (_ : ident) | Lconst (_ : Lam_constant.t) -> ((* Obj.magic *) l : X.t) - | Lapply {ap_func; ap_args; ap_info} -> + | Lapply {ap_func; ap_args; ap_info; ap_transformed_jsx} -> let ap_func = f ap_func in let ap_args = Ext_list.map ap_args f in - Lapply {ap_func; ap_args; ap_info} + Lapply {ap_func; ap_args; ap_info; ap_transformed_jsx} | Lfunction {body; arity; params; attr} -> let body = f body in Lfunction {body; arity; params; attr} @@ -279,7 +289,7 @@ let rec is_eta_conversion_exn params inner_args outer_args : t list = | _, _, _ -> raise_notrace Not_simple_form (** FIXME: more robust inlining check later, we should inline it before we add stub code*) -let rec apply fn args (ap_info : ap_info) : t = +let rec apply ?(ap_transformed_jsx = false) fn args (ap_info : ap_info) : t = match fn with | Lfunction { @@ -300,7 +310,7 @@ let rec apply fn args (ap_info : ap_info) : t = Lprim {primitive = wrap; args = [Lprim {primitive_call with args; loc}]; loc} | exception Not_simple_form -> - Lapply {ap_func = fn; ap_args = args; ap_info}) + Lapply {ap_func = fn; ap_args = args; ap_info; ap_transformed_jsx}) | Lfunction { params; @@ -308,7 +318,8 @@ let rec apply fn args (ap_info : ap_info) : t = } -> ( match is_eta_conversion_exn params inner_args args with | args -> Lprim {primitive_call with args; loc = ap_info.ap_loc} - | exception _ -> Lapply {ap_func = fn; ap_args = args; ap_info}) + | exception _ -> + Lapply {ap_func = fn; ap_args = args; ap_info; ap_transformed_jsx}) | Lfunction { params; @@ -321,17 +332,17 @@ let rec apply fn args (ap_info : ap_info) : t = | args -> Lsequence (Lprim {primitive_call with args; loc = ap_info.ap_loc}, const) | exception _ -> - Lapply {ap_func = fn; ap_args = args; ap_info} + Lapply {ap_func = fn; ap_args = args; ap_info; ap_transformed_jsx} (* | Lfunction {params;body} when Ext_list.same_length params args -> Ext_list.fold_right2 (fun p arg acc -> Llet(Strict,p,arg,acc) ) params args body *) (* TODO: more rigirous analysis on [let_kind] *)) | Llet (kind, id, e, (Lfunction _ as fn)) -> - Llet (kind, id, e, apply fn args ap_info) + Llet (kind, id, e, apply fn args ap_info ~ap_transformed_jsx) (* | Llet (kind0, id0, e0, Llet (kind,id, e, (Lfunction _ as fn))) -> Llet(kind0,id0,e0,Llet (kind, id, e, apply fn args loc status)) *) - | _ -> Lapply {ap_func = fn; ap_args = args; ap_info} + | _ -> Lapply {ap_func = fn; ap_args = args; ap_info; ap_transformed_jsx} let rec eq_approx (l1 : t) (l2 : t) = match l1 with @@ -712,10 +723,12 @@ let result_wrap loc (result_type : External_ffi_types.return_wrapper) result = prim ~primitive:Pundefined_to_opt ~args:[result] loc | Return_unset | Return_identity -> result -let handle_bs_non_obj_ffi (arg_types : External_arg_spec.params) +let handle_bs_non_obj_ffi ?(transformed_jsx = false) + (arg_types : External_arg_spec.params) (result_type : External_ffi_types.return_wrapper) ffi args loc prim_name ~dynamic_import = result_wrap loc result_type (prim - ~primitive:(Pjs_call {prim_name; arg_types; ffi; dynamic_import}) + ~primitive: + (Pjs_call {prim_name; arg_types; ffi; dynamic_import; transformed_jsx}) ~args loc) diff --git a/compiler/core/lam.mli b/compiler/core/lam.mli index 66858ac2a4..560d247669 100644 --- a/compiler/core/lam.mli +++ b/compiler/core/lam.mli @@ -41,7 +41,12 @@ type lambda_switch = { sw_names: Ast_untagged_variants.switch_names option; } -and apply = private {ap_func: t; ap_args: t list; ap_info: ap_info} +and apply = private { + ap_func: t; + ap_args: t list; + ap_info: ap_info; + ap_transformed_jsx: bool; +} and lfunction = { arity: int; @@ -85,6 +90,7 @@ and t = private val inner_map : t -> (t -> t) -> t val handle_bs_non_obj_ffi : + ?transformed_jsx:bool -> External_arg_spec.params -> External_ffi_types.return_wrapper -> External_ffi_types.external_spec -> @@ -103,7 +109,7 @@ val global_module : ?dynamic_import:bool -> ident -> t val const : Lam_constant.t -> t -val apply : t -> t list -> ap_info -> t +val apply : ?ap_transformed_jsx:bool -> t -> t list -> ap_info -> t val function_ : attr:Lambda.function_attribute -> diff --git a/compiler/core/lam_bounded_vars.ml b/compiler/core/lam_bounded_vars.ml index 15ee9cff97..e038e56798 100644 --- a/compiler/core/lam_bounded_vars.ml +++ b/compiler/core/lam_bounded_vars.ml @@ -108,10 +108,10 @@ let rewrite (map : _ Hash_ident.t) (lam : Lam.t) : Lam.t = (* here it makes sure that global vars are not rebound *) Lam.prim ~primitive ~args:(Ext_list.map args aux) loc | Lglobal_module _ -> lam - | Lapply {ap_func; ap_args; ap_info} -> + | Lapply {ap_func; ap_args; ap_info; ap_transformed_jsx} -> let fn = aux ap_func in let args = Ext_list.map ap_args aux in - Lam.apply fn args ap_info + Lam.apply ~ap_transformed_jsx fn args ap_info | Lswitch ( l, { diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index ea865bbe6f..159b9d9012 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -31,12 +31,13 @@ let args_either_function_or_const (args : Lam.t list) = | Lfunction _ | Lconst _ -> true | _ -> false) -let call_info_of_ap_status (ap_status : Lam.apply_status) : Js_call_info.t = +let call_info_of_ap_status call_transformed_jsx (ap_status : Lam.apply_status) : + Js_call_info.t = (* XXX *) match ap_status with - | App_infer_full -> {arity = Full; call_info = Call_ml} - | App_uncurry -> {arity = Full; call_info = Call_na} - | App_na -> {arity = NA; call_info = Call_ml} + | App_infer_full -> {arity = Full; call_info = Call_ml; call_transformed_jsx} + | App_uncurry -> {arity = Full; call_info = Call_na; call_transformed_jsx} + | App_na -> {arity = NA; call_info = Call_ml; call_transformed_jsx} let rec apply_with_arity_aux (fn : J.expression) (arity : int list) (args : E.t list) (len : int) : E.t = @@ -49,7 +50,7 @@ let rec apply_with_arity_aux (fn : J.expression) (arity : int list) if len >= x then let first_part, continue = Ext_list.split_at args x in apply_with_arity_aux - (E.call ~info:{arity = Full; call_info = Call_ml} fn first_part) + (E.call ~info:Js_call_info.ml_full_call fn first_part) rest continue (len - x) else if (* GPR #1423 *) @@ -62,9 +63,7 @@ let rec apply_with_arity_aux (fn : J.expression) (arity : int list) ~async:false ~one_unit_arg:false [ S.return_stmt - (E.call - ~info:{arity = Full; call_info = Call_ml} - fn + (E.call ~info:Js_call_info.ml_full_call fn (Ext_list.append args @@ Ext_list.map params E.var)); ] else E.call ~info:Js_call_info.dummy fn args @@ -306,7 +305,9 @@ let compile output_prefix = let expression = match appinfo.ap_info.ap_status with | (App_infer_full | App_uncurry) as ap_status -> - E.call ~info:(call_info_of_ap_status ap_status) fn args + E.call + ~info:(call_info_of_ap_status appinfo.ap_transformed_jsx ap_status) + fn args | App_na -> ( match ident_info.arity with | Submodule _ | Single Arity_na -> @@ -1439,6 +1440,7 @@ let compile output_prefix = ap_func = Lapply {ap_func; ap_args; ap_info = {ap_status = App_na; ap_inlined}}; ap_info = {ap_status = App_na} as outer_ap_info; + ap_transformed_jsx; } -> (* After inlining, we can generate such code, see {!Ari_regress_test}*) let ap_info = @@ -1446,7 +1448,9 @@ let compile output_prefix = else {outer_ap_info with ap_inlined} in compile_lambda lambda_cxt - (Lam.apply ap_func (Ext_list.append ap_args appinfo.ap_args) ap_info) + (Lam.apply ap_func + (Ext_list.append ap_args appinfo.ap_args) + ap_info ~ap_transformed_jsx) (* External function call: it can not be tailcall in this case*) | { ap_func = @@ -1529,7 +1533,9 @@ let compile output_prefix = Js_output.output_of_block_and_expression lambda_cxt.continuation args_code (E.call - ~info:(call_info_of_ap_status appinfo.ap_info.ap_status) + ~info: + (call_info_of_ap_status appinfo.ap_transformed_jsx + appinfo.ap_info.ap_status) fn_code args)) and compile_prim (prim_info : Lam.prim_info) (lambda_cxt : Lam_compile_context.t) = diff --git a/compiler/core/lam_compile_external_call.ml b/compiler/core/lam_compile_external_call.ml index 6d9056e570..34cdcbd9d0 100644 --- a/compiler/core/lam_compile_external_call.ml +++ b/compiler/core/lam_compile_external_call.ml @@ -267,9 +267,9 @@ let translate_scoped_access scopes obj = | [] -> obj | x :: xs -> Ext_list.fold_left xs (E.dot obj x) E.dot -let translate_ffi (cxt : Lam_compile_context.t) arg_types - (ffi : External_ffi_types.external_spec) (args : J.expression list) - ~dynamic_import = +let translate_ffi ?(transformed_jsx = false) (cxt : Lam_compile_context.t) + arg_types (ffi : External_ffi_types.external_spec) + (args : J.expression list) ~dynamic_import = match ffi with | Js_call {external_module_name; name; splice : _; scopes; tagged_template = true} @@ -287,7 +287,8 @@ let translate_ffi (cxt : Lam_compile_context.t) arg_types | _ -> let args, eff, dynamic = assemble_args_has_splice arg_types args in let args = if dynamic then E.variadic_args args else args in - add_eff eff (E.call ~info:{arity = Full; call_info = Call_na} fn args)) + add_eff eff + (E.call ~info:(Js_call_info.na_full_call transformed_jsx) fn args)) | Js_call { external_module_name = module_name; @@ -302,20 +303,31 @@ let translate_ffi (cxt : Lam_compile_context.t) arg_types if splice then let args, eff, dynamic = assemble_args_has_splice arg_types args in let args = if dynamic then E.variadic_args args else args in - add_eff eff (E.call ~info:{arity = Full; call_info = Call_na} fn args) + add_eff eff + (E.call ~info:(Js_call_info.na_full_call transformed_jsx) fn args) else let args, eff = assemble_args_no_splice arg_types args in - add_eff eff @@ E.call ~info:{arity = Full; call_info = Call_na} fn args + add_eff eff + @@ E.call + ~info: + { + arity = Full; + call_info = Call_na; + call_transformed_jsx = transformed_jsx; + } + fn args | Js_module_as_fn {external_module_name; splice} -> let fn = external_var external_module_name ~dynamic_import in if splice then let args, eff, dynamic = assemble_args_has_splice arg_types args in let args = if dynamic then E.variadic_args args else args in - add_eff eff (E.call ~info:{arity = Full; call_info = Call_na} fn args) + add_eff eff + (E.call ~info:(Js_call_info.na_full_call transformed_jsx) fn args) else let args, eff = assemble_args_no_splice arg_types args in (* TODO: fix in rest calling convention *) - add_eff eff (E.call ~info:{arity = Full; call_info = Call_na} fn args) + add_eff eff + (E.call ~info:(Js_call_info.na_full_call transformed_jsx) fn args) | Js_new {external_module_name = module_name; name = fn; splice; scopes} -> (* handle [@@new]*) (* This has some side effect, it will @@ -362,14 +374,19 @@ let translate_ffi (cxt : Lam_compile_context.t) arg_types add_eff eff (let self = translate_scoped_access js_send_scopes self in E.call - ~info:{arity = Full; call_info = Call_na} + ~info: + { + arity = Full; + call_info = Call_na; + call_transformed_jsx = transformed_jsx; + } (E.dot self name) args) else let args, eff = assemble_args_no_splice arg_types args in add_eff eff (let self = translate_scoped_access js_send_scopes self in E.call - ~info:{arity = Full; call_info = Call_na} + ~info:(Js_call_info.na_full_call transformed_jsx) (E.dot self name) args) | _ -> assert false) | Js_module_as_var module_name -> external_var module_name ~dynamic_import @@ -384,7 +401,7 @@ let translate_ffi (cxt : Lam_compile_context.t) arg_types ~dynamic_import in if args = [] then e - else E.call ~info:{arity = Full; call_info = Call_na} e args + else E.call ~info:(Js_call_info.na_full_call transformed_jsx) e args | Js_module_as_class module_name -> let fn = external_var module_name ~dynamic_import in let args, eff = assemble_args_no_splice arg_types args in diff --git a/compiler/core/lam_compile_external_call.mli b/compiler/core/lam_compile_external_call.mli index e8c974f10a..29e05c96f9 100644 --- a/compiler/core/lam_compile_external_call.mli +++ b/compiler/core/lam_compile_external_call.mli @@ -30,6 +30,7 @@ val ocaml_to_js_eff : (** Compile ocaml external function call to JS IR. *) val translate_ffi : + ?transformed_jsx:bool -> Lam_compile_context.t -> External_arg_spec.params -> External_ffi_types.external_spec -> diff --git a/compiler/core/lam_compile_primitive.ml b/compiler/core/lam_compile_primitive.ml index aac979d926..1a52d835fd 100644 --- a/compiler/core/lam_compile_primitive.ml +++ b/compiler/core/lam_compile_primitive.ml @@ -54,17 +54,15 @@ let get_module_system () = | [module_system] -> module_system | _ -> Commonjs +let call_info = + {Js_call_info.arity = Full; call_info = Call_na; call_transformed_jsx = false} + let import_of_path path = - E.call - ~info:{arity = Full; call_info = Call_na} - (E.js_global "import") - [E.str path] + E.call ~info:call_info (E.js_global "import") [E.str path] let wrap_then import value = let arg = Ident.create "m" in - E.call - ~info:{arity = Full; call_info = Call_na} - (E.dot import "then") + E.call ~info:call_info (E.dot import "then") [ E.ocaml_fun ~return_unit:false ~async:false ~one_unit_arg:false [arg] [{statement_desc = J.Return (E.dot (E.var arg) value); comment = None}]; @@ -88,7 +86,7 @@ let translate output_prefix loc (cxt : Lam_compile_context.t) | _ -> assert false) | Pjs_apply -> ( match args with - | fn :: rest -> E.call ~info:{arity = Full; call_info = Call_na} fn rest + | fn :: rest -> E.call ~info:call_info fn rest | _ -> assert false) | Pnull_to_opt -> ( match args with @@ -594,9 +592,9 @@ let translate output_prefix loc (cxt : Lam_compile_context.t) (* Lam_compile_external_call.translate loc cxt prim args *) (* Test if the argument is a block or an immediate integer *) | Pjs_object_create _ -> assert false - | Pjs_call {arg_types; ffi; dynamic_import} -> + | Pjs_call {arg_types; ffi; dynamic_import; transformed_jsx} -> Lam_compile_external_call.translate_ffi cxt arg_types ffi args - ~dynamic_import + ~dynamic_import ~transformed_jsx (* FIXME, this can be removed later *) | Pisint -> E.is_type_number (Ext_list.singleton_exn args) | Pis_poly_var_block -> E.is_type_object (Ext_list.singleton_exn args) diff --git a/compiler/core/lam_convert.ml b/compiler/core/lam_convert.ml index 3f252011ef..a6be3cc311 100644 --- a/compiler/core/lam_convert.ml +++ b/compiler/core/lam_convert.ml @@ -387,8 +387,8 @@ let convert (exports : Set_ident.t) (lam : Lambda.lambda) : | Param_number i -> Ext_list.init i (fun _ -> External_arg_spec.dummy) in let args = Ext_list.map args convert_aux in - Lam.handle_bs_non_obj_ffi arg_types result_type ffi args loc prim_name - ~dynamic_import + Lam.handle_bs_non_obj_ffi ~transformed_jsx:a_prim.transformed_jsx + arg_types result_type ffi args loc prim_name ~dynamic_import | Ffi_inline_const i -> Lam.const i | Ffi_normal -> Location.raise_errorf ~loc @@ -414,11 +414,19 @@ let convert (exports : Set_ident.t) (lam : Lambda.lambda) : let setter = Ext_string.ends_with name Literals.setter_suffix in let _ = assert (not setter) in prim ~primitive:(Pjs_unsafe_downgrade {name; setter}) ~args loc - | Lapply {ap_func = fn; ap_args = args; ap_loc = loc; ap_inlined} -> + | Lapply + { + ap_func = fn; + ap_args = args; + ap_loc = loc; + ap_inlined; + ap_transformed_jsx; + } -> (* we need do this eargly in case [aux fn] add some wrapper *) Lam.apply (convert_aux fn) (Ext_list.map args convert_aux) {ap_loc = loc; ap_inlined; ap_status = App_uncurry} + ~ap_transformed_jsx | Lfunction {params; body; attr} -> let new_map, body = rename_optional_parameters Map_ident.empty params body @@ -571,8 +579,8 @@ let convert (exports : Set_ident.t) (lam : Lambda.lambda) : when Ext_list.for_all2_no_exn inner_args params lam_is_var && Ext_list.length_larger_than_n inner_args args 1 -> Lam.prim ~primitive ~args:(Ext_list.append_one args x) outer_loc - | Lapply {ap_func; ap_args; ap_info} -> - Lam.apply ap_func + | Lapply {ap_func; ap_args; ap_info; ap_transformed_jsx} -> + Lam.apply ~ap_transformed_jsx ap_func (Ext_list.append_one ap_args x) { ap_loc = outer_loc; diff --git a/compiler/core/lam_pass_alpha_conversion.ml b/compiler/core/lam_pass_alpha_conversion.ml index 3beadbeb0e..1d80ae16ed 100644 --- a/compiler/core/lam_pass_alpha_conversion.ml +++ b/compiler/core/lam_pass_alpha_conversion.ml @@ -23,14 +23,17 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) let alpha_conversion (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = - let rec populate_apply_info (args_arity : int list) (len : int) (fn : Lam.t) - (args : Lam.t list) ap_info : Lam.t = + let rec populate_apply_info ?(ap_transformed_jsx = false) + (args_arity : int list) (len : int) (fn : Lam.t) (args : Lam.t list) + ap_info : Lam.t = match args_arity with - | 0 :: _ | [] -> Lam.apply (simpl fn) (Ext_list.map args simpl) ap_info + | 0 :: _ | [] -> + Lam.apply (simpl fn) (Ext_list.map args simpl) ap_info ~ap_transformed_jsx | x :: _ -> if x = len then Lam.apply (simpl fn) (Ext_list.map args simpl) {ap_info with ap_status = App_infer_full} + ~ap_transformed_jsx else if x > len then let fn = simpl fn in let args = Ext_list.map args simpl in @@ -39,7 +42,7 @@ let alpha_conversion (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = fn args else let first, rest = Ext_list.split_at args x in - Lam.apply + Lam.apply ~ap_transformed_jsx (Lam.apply (simpl fn) (Ext_list.map first simpl) {ap_info with ap_status = App_infer_full}) (Ext_list.map rest simpl) ap_info @@ -48,13 +51,14 @@ let alpha_conversion (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = match lam with | Lconst _ -> lam | Lvar _ -> lam - | Lapply {ap_func; ap_args; ap_info} -> + | Lapply {ap_func; ap_args; ap_info; ap_transformed_jsx} -> (* detect functor application *) let args_arity = Lam_arity.extract_arity (Lam_arity_analysis.get_arity meta ap_func) in let len = List.length ap_args in - populate_apply_info args_arity len ap_func ap_args ap_info + populate_apply_info ~ap_transformed_jsx args_arity len ap_func ap_args + ap_info | Llet (str, v, l1, l2) -> Lam.let_ str v (simpl l1) (simpl l2) | Lletrec (bindings, body) -> let bindings = Ext_list.map_snd bindings simpl in diff --git a/compiler/core/lam_pass_deep_flatten.ml b/compiler/core/lam_pass_deep_flatten.ml index c55aeec841..0eddcb1a9d 100644 --- a/compiler/core/lam_pass_deep_flatten.ml +++ b/compiler/core/lam_pass_deep_flatten.ml @@ -224,8 +224,8 @@ let deep_flatten (lam : Lam.t) : Lam.t = (* can we switch to the tupled backend? *\) *) (* when List.length params = List.length args -> *) (* aux (beta_reduce params body args) *) - | Lapply {ap_func = l1; ap_args = ll; ap_info} -> - Lam.apply (aux l1) (Ext_list.map ll aux) ap_info + | Lapply {ap_func = l1; ap_args = ll; ap_info; ap_transformed_jsx} -> + Lam.apply (aux l1) (Ext_list.map ll aux) ap_info ~ap_transformed_jsx (* This kind of simple optimizations should be done each time and as early as possible *) | Lglobal_module _ -> lam diff --git a/compiler/core/lam_pass_eliminate_ref.ml b/compiler/core/lam_pass_eliminate_ref.ml index 4a251c1877..eb54fb2067 100644 --- a/compiler/core/lam_pass_eliminate_ref.ml +++ b/compiler/core/lam_pass_eliminate_ref.ml @@ -52,8 +52,10 @@ let rec eliminate_ref id (lam : Lam.t) = Lam.assign id (Lam.prim ~primitive:(Poffsetint delta) ~args:[Lam.var id] loc) | Lconst _ -> lam - | Lapply {ap_func = e1; ap_args = el; ap_info} -> - Lam.apply (eliminate_ref id e1) (Ext_list.map el (eliminate_ref id)) ap_info + | Lapply {ap_func = e1; ap_args = el; ap_info; ap_transformed_jsx} -> + Lam.apply ~ap_transformed_jsx (eliminate_ref id e1) + (Ext_list.map el (eliminate_ref id)) + ap_info | Llet (str, v, e1, e2) -> Lam.let_ str v (eliminate_ref id e1) (eliminate_ref id e2) | Lletrec (idel, e2) -> diff --git a/compiler/core/lam_pass_exits.ml b/compiler/core/lam_pass_exits.ml index ceba4af6e5..2eb6295699 100644 --- a/compiler/core/lam_pass_exits.ml +++ b/compiler/core/lam_pass_exits.ml @@ -199,8 +199,10 @@ let subst_helper (subst : subst_tbl) (query : int -> int) (lam : Lam.t) : Lam.t Lam.let_ Strict y l r) | None -> Lam.staticraise i ls) | Lvar _ | Lconst _ -> lam - | Lapply {ap_func; ap_args; ap_info} -> - Lam.apply (simplif ap_func) (Ext_list.map ap_args simplif) ap_info + | Lapply {ap_func; ap_args; ap_info; ap_transformed_jsx} -> + Lam.apply (simplif ap_func) + (Ext_list.map ap_args simplif) + ap_info ~ap_transformed_jsx | Lfunction {arity; params; body; attr} -> Lam.function_ ~arity ~params ~body:(simplif body) ~attr | Llet (kind, v, l1, l2) -> Lam.let_ kind v (simplif l1) (simplif l2) diff --git a/compiler/core/lam_pass_lets_dce.ml b/compiler/core/lam_pass_lets_dce.ml index ca6e32bc7c..04ab02d4bc 100644 --- a/compiler/core/lam_pass_lets_dce.ml +++ b/compiler/core/lam_pass_lets_dce.ml @@ -144,8 +144,9 @@ let lets_helper (count_var : Ident.t -> Lam_pass_count.used_info) lam : Lam.t = (* *\) *) (* when Ext_list.same_length params args -> *) (* simplif (Lam_beta_reduce.beta_reduce params body args) *) - | Lapply {ap_func = l1; ap_args = ll; ap_info} -> + | Lapply {ap_func = l1; ap_args = ll; ap_info; ap_transformed_jsx} -> Lam.apply (simplif l1) (Ext_list.map ll simplif) ap_info + ~ap_transformed_jsx | Lfunction {arity; params; body; attr} -> Lam.function_ ~arity ~params ~body:(simplif body) ~attr | Lconst _ -> lam diff --git a/compiler/core/lam_pass_remove_alias.ml b/compiler/core/lam_pass_remove_alias.ml index 065ea65edf..67472564fe 100644 --- a/compiler/core/lam_pass_remove_alias.ml +++ b/compiler/core/lam_pass_remove_alias.ml @@ -140,19 +140,23 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = | _ -> true) && 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) + | _ -> + Lam.apply (simpl l1) (Ext_list.map args simpl) ap_info + ?ap_transformed_jsx:None) (* Function inlining interact with other optimizations... - parameter attributes - scope issues - code bloat *) - | Lapply {ap_func = Lvar v as fn; ap_args; ap_info} -> ( + | Lapply {ap_func = Lvar v as fn; ap_args; ap_info; ap_transformed_jsx} -> ( (* Check info for always inlining *) (* Ext_log.dwarn __LOC__ "%s/%d" v.name v.stamp; *) let ap_args = Ext_list.map ap_args simpl in - let[@local] normal () = Lam.apply (simpl fn) ap_args ap_info in + let[@local] normal () = + Lam.apply (simpl fn) ap_args ap_info ~ap_transformed_jsx + in match Hash_ident.find_opt meta.ident_tbl v with | Some (FunctionId @@ -221,8 +225,8 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = (* *\) *) (* when Ext_list.same_length params args -> *) (* simpl (Lam_beta_reduce.propogate_beta_reduce meta params body args) *) - | Lapply {ap_func = l1; ap_args = ll; ap_info} -> - Lam.apply (simpl l1) (Ext_list.map ll simpl) ap_info + | Lapply {ap_func = l1; ap_args = ll; ap_info; ap_transformed_jsx} -> + Lam.apply (simpl l1) (Ext_list.map ll simpl) ap_info ~ap_transformed_jsx | Lfunction {arity; params; body; attr} -> Lam.function_ ~arity ~params ~body:(simpl body) ~attr | Lswitch diff --git a/compiler/core/lam_primitive.ml b/compiler/core/lam_primitive.ml index e28c652cd9..f07b5aa024 100644 --- a/compiler/core/lam_primitive.ml +++ b/compiler/core/lam_primitive.ml @@ -46,6 +46,7 @@ type t = arg_types: External_arg_spec.params; ffi: External_ffi_types.external_spec; dynamic_import: bool; + transformed_jsx: bool; } | Pjs_object_create of External_arg_spec.obj_params (* Exceptions *) @@ -250,7 +251,7 @@ let eq_primitive_approx (lhs : t) (rhs : t) = | Pmakeblock (i1, info1, flag1) -> i0 = i1 && flag0 = flag1 && eq_tag_info info0 info1 | _ -> false) - | Pjs_call {prim_name; arg_types; ffi; dynamic_import} -> ( + | Pjs_call {prim_name; arg_types; ffi; dynamic_import; _} -> ( match rhs with | Pjs_call rhs -> prim_name = rhs.prim_name && arg_types = rhs.arg_types && ffi = rhs.ffi diff --git a/compiler/core/lam_primitive.mli b/compiler/core/lam_primitive.mli index 460ef392c4..19b10cf964 100644 --- a/compiler/core/lam_primitive.mli +++ b/compiler/core/lam_primitive.mli @@ -42,6 +42,7 @@ type t = arg_types: External_arg_spec.params; ffi: External_ffi_types.external_spec; dynamic_import: bool; + transformed_jsx: bool; } | Pjs_object_create of External_arg_spec.obj_params | Praise diff --git a/compiler/core/lam_util.ml b/compiler/core/lam_util.ml index 7ea859f6be..f85cdc0f41 100644 --- a/compiler/core/lam_util.ml +++ b/compiler/core/lam_util.ml @@ -66,7 +66,7 @@ let refine_let (* let v= subst_lambda (Map_ident.singleton param arg ) l in *) (* Ext_log.err "@[substitution << @]@."; *) (* v *) - | _, _, Lapply {ap_func=fn; ap_args = [Lvar w]; ap_info} when + | _, _, Lapply {ap_func=fn; ap_args = [Lvar w]; ap_info; ap_transformed_jsx} when Ident.same w param && (not (Lam_hit.hit_variable param fn )) -> @@ -79,7 +79,7 @@ let refine_let ]} #1667 make sure body does not hit k *) - Lam.apply fn [arg] ap_info + Lam.apply fn [arg] ap_info ~ap_transformed_jsx | (Strict | StrictOpt ), ( Lvar _ | Lconst _ | Lprim {primitive = Pfield (_ , Fld_module _) ; diff --git a/compiler/frontend/ast_compatible.ml b/compiler/frontend/ast_compatible.ml index 04a1be4f4a..0610abb015 100644 --- a/compiler/frontend/ast_compatible.ml +++ b/compiler/frontend/ast_compatible.ml @@ -44,6 +44,7 @@ let apply_simple ?(loc = default_loc) ?(attrs = []) (fn : expression) funct = fn; args = Ext_list.map args (fun x -> (Asttypes.Nolabel, x)); partial = false; + transformed_jsx = false; }; } @@ -52,7 +53,13 @@ let app1 ?(loc = default_loc) ?(attrs = []) fn arg1 : expression = pexp_loc = loc; pexp_attributes = attrs; pexp_desc = - Pexp_apply {funct = fn; args = [(Nolabel, arg1)]; partial = false}; + Pexp_apply + { + funct = fn; + args = [(Nolabel, arg1)]; + partial = false; + transformed_jsx = false; + }; } let app2 ?(loc = default_loc) ?(attrs = []) fn arg1 arg2 : expression = @@ -61,7 +68,12 @@ let app2 ?(loc = default_loc) ?(attrs = []) fn arg1 arg2 : expression = pexp_attributes = attrs; pexp_desc = Pexp_apply - {funct = fn; args = [(Nolabel, arg1); (Nolabel, arg2)]; partial = false}; + { + funct = fn; + args = [(Nolabel, arg1); (Nolabel, arg2)]; + partial = false; + transformed_jsx = false; + }; } let app3 ?(loc = default_loc) ?(attrs = []) fn arg1 arg2 arg3 : expression = @@ -74,6 +86,7 @@ let app3 ?(loc = default_loc) ?(attrs = []) fn arg1 arg2 arg3 : expression = funct = fn; args = [(Nolabel, arg1); (Nolabel, arg2); (Nolabel, arg3)]; partial = false; + transformed_jsx = false; }; } @@ -121,6 +134,7 @@ let apply_labels ?(loc = default_loc) ?(attrs = []) fn Ext_list.map args (fun (l, a) -> (Asttypes.Labelled {txt = l; loc = Location.none}, a)); partial = false; + transformed_jsx = false; }; } diff --git a/compiler/frontend/ast_exp_apply.ml b/compiler/frontend/ast_exp_apply.ml index fb5b500db9..afffea4e3c 100644 --- a/compiler/frontend/ast_exp_apply.ml +++ b/compiler/frontend/ast_exp_apply.ml @@ -88,11 +88,12 @@ let app_exp_mapper (e : exp) (self : Bs_ast_mapper.mapper) : exp = {f with pexp_desc = Pexp_variant (label, Some a); pexp_loc = e.pexp_loc} | Pexp_construct (ctor, None) -> {f with pexp_desc = Pexp_construct (ctor, Some a); pexp_loc = e.pexp_loc} - | Pexp_apply {funct = fn1; args; partial} -> + | Pexp_apply {funct = fn1; args; partial; transformed_jsx} -> Bs_ast_invariant.warn_discarded_unused_attributes fn1.pexp_attributes; { pexp_desc = - Pexp_apply {funct = fn1; args = (Nolabel, a) :: args; partial}; + Pexp_apply + {funct = fn1; args = (Nolabel, a) :: args; partial; transformed_jsx}; pexp_loc = e.pexp_loc; pexp_attributes = e.pexp_attributes @ f.pexp_attributes; } @@ -108,7 +109,7 @@ let app_exp_mapper (e : exp) (self : Bs_ast_mapper.mapper) : exp = fn with pexp_desc = Pexp_construct (ctor, Some bounded_obj_arg); } - | Pexp_apply {funct = fn; args} -> + | Pexp_apply {funct = fn; args; transformed_jsx} -> Bs_ast_invariant.warn_discarded_unused_attributes fn.pexp_attributes; { @@ -118,6 +119,7 @@ let app_exp_mapper (e : exp) (self : Bs_ast_mapper.mapper) : exp = funct = fn; args = (Nolabel, bounded_obj_arg) :: args; partial = false; + transformed_jsx; }; pexp_attributes = []; pexp_loc = fn.pexp_loc; diff --git a/compiler/frontend/ast_uncurry_gen.ml b/compiler/frontend/ast_uncurry_gen.ml index 70e4e2d550..9e0a43fe37 100644 --- a/compiler/frontend/ast_uncurry_gen.ml +++ b/compiler/frontend/ast_uncurry_gen.ml @@ -75,4 +75,5 @@ let to_method_callback loc (self : Bs_ast_mapper.mapper) label [Typ.any ~loc ()]) ); ]; partial = false; + transformed_jsx = false; } diff --git a/compiler/frontend/bs_ast_mapper.ml b/compiler/frontend/bs_ast_mapper.ml index fff7690b20..a6dfc764e3 100644 --- a/compiler/frontend/bs_ast_mapper.ml +++ b/compiler/frontend/bs_ast_mapper.ml @@ -330,8 +330,8 @@ module E = struct fun_ ~loc ~attrs ~arity ~async lab (map_opt (sub.expr sub) def) (sub.pat sub p) (sub.expr sub e) - | Pexp_apply {funct = e; args = l; partial} -> - apply ~loc ~attrs ~partial (sub.expr sub e) + | Pexp_apply {funct = e; args = l; partial; transformed_jsx} -> + apply ~loc ~attrs ~partial ~transformed_jsx (sub.expr sub e) (List.map (map_snd (sub.expr sub)) l) | Pexp_match (e, pel) -> match_ ~loc ~attrs (sub.expr sub e) (sub.cases sub pel) diff --git a/compiler/frontend/external_ffi_types.ml b/compiler/frontend/external_ffi_types.ml index 35419958fd..9016341603 100644 --- a/compiler/frontend/external_ffi_types.ml +++ b/compiler/frontend/external_ffi_types.ml @@ -246,6 +246,7 @@ let () = prim_native_name; prim_alloc = _; prim_from_constructor = _; + transformed_jsx = _; } : Primitive.description) (p2 : Primitive.description) diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml index 347b5b5e0d..05c715e6f5 100644 --- a/compiler/ml/ast_helper.ml +++ b/compiler/ml/ast_helper.ml @@ -154,8 +154,9 @@ module Exp = struct let fun_ ?loc ?attrs ?(async = false) ~arity a b c d = mk ?loc ?attrs (Pexp_fun {arg_label = a; default = b; lhs = c; rhs = d; arity; async}) - let apply ?loc ?attrs ?(partial = false) funct args = - mk ?loc ?attrs (Pexp_apply {funct; args; partial}) + let apply ?loc ?attrs ?(partial = false) ?(transformed_jsx = false) funct args + = + mk ?loc ?attrs (Pexp_apply {funct; args; partial; transformed_jsx}) let match_ ?loc ?attrs a b = mk ?loc ?attrs (Pexp_match (a, b)) let try_ ?loc ?attrs a b = mk ?loc ?attrs (Pexp_try (a, b)) let tuple ?loc ?attrs a = mk ?loc ?attrs (Pexp_tuple a) diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli index d8cfef1c5e..467050bb5b 100644 --- a/compiler/ml/ast_helper.mli +++ b/compiler/ml/ast_helper.mli @@ -149,6 +149,7 @@ module Exp : sig ?loc:loc -> ?attrs:attrs -> ?partial:bool -> + ?transformed_jsx:bool -> expression -> (arg_label * expression) list -> expression diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml index ba678c1a85..dbaea2b466 100644 --- a/compiler/ml/ast_mapper.ml +++ b/compiler/ml/ast_mapper.ml @@ -293,8 +293,8 @@ module E = struct fun_ ~loc ~attrs ~arity ~async lab (map_opt (sub.expr sub) def) (sub.pat sub p) (sub.expr sub e) - | Pexp_apply {funct = e; args = l; partial} -> - apply ~loc ~attrs ~partial (sub.expr sub e) + | Pexp_apply {funct = e; args = l; partial; transformed_jsx} -> + apply ~loc ~attrs ~partial ~transformed_jsx (sub.expr sub e) (List.map (map_snd (sub.expr sub)) l) | Pexp_match (e, pel) -> match_ ~loc ~attrs (sub.expr sub e) (sub.cases sub pel) diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index 26aa8a8c74..dfb8c33d1b 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -383,6 +383,7 @@ and lambda_apply = { ap_args: lambda list; ap_loc: Location.t; ap_inlined: inline_attribute; + ap_transformed_jsx: bool; } and lambda_switch = { diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index 9e1c9b9d7c..5927479bcc 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -352,6 +352,7 @@ and lambda_apply = { ap_args: lambda list; ap_loc: Location.t; ap_inlined: inline_attribute; (* specified with the [@inlined] attribute *) + ap_transformed_jsx: bool; } and lambda_switch = { diff --git a/compiler/ml/matching.ml b/compiler/ml/matching.ml index fd90f38535..d450ab305d 100644 --- a/compiler/ml/matching.ml +++ b/compiler/ml/matching.ml @@ -1449,6 +1449,7 @@ let inline_lazy_force arg loc = ap_inlined = Default_inline; ap_args = [arg]; ap_loc = loc; + ap_transformed_jsx = false; } let make_lazy_matching def = function | [] -> fatal_error "Matching.make_lazy_matching" diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml index 1fefea4a2d..7374827192 100644 --- a/compiler/ml/parsetree.ml +++ b/compiler/ml/parsetree.ml @@ -244,6 +244,7 @@ and expression_desc = funct: expression; args: (arg_label * expression) list; partial: bool; + transformed_jsx: bool; } (* E0 ~l1:E1 ... ~ln:En li can be empty (non labeled argument) or start with '?' diff --git a/compiler/ml/primitive.ml b/compiler/ml/primitive.ml index 602cb22089..3ddc60a488 100644 --- a/compiler/ml/primitive.ml +++ b/compiler/ml/primitive.ml @@ -25,8 +25,11 @@ type description = { prim_native_name: string; (* Name of C function for the nat. code gen. *) prim_from_constructor: bool; (* Is it from a type constructor instead of a concrete function type? *) + transformed_jsx: bool; } +let set_transformed_jsx d ~transformed_jsx = {d with transformed_jsx} + let coerce : (description -> description -> bool) ref = ref (fun (p1 : description) (p2 : description) -> p1 = p2) @@ -43,6 +46,7 @@ let parse_declaration valdecl ~arity ~from_constructor = prim_alloc = true; prim_native_name = native_name; prim_from_constructor = from_constructor; + transformed_jsx = false; } open Outcometree diff --git a/compiler/ml/primitive.mli b/compiler/ml/primitive.mli index 9166ae1a5c..8f5c58100d 100644 --- a/compiler/ml/primitive.mli +++ b/compiler/ml/primitive.mli @@ -22,8 +22,11 @@ type description = private { prim_native_name: string; (* Name of C function for the nat. code gen. *) prim_from_constructor: bool; (* Is it from a type constructor instead of a concrete function type? *) + transformed_jsx: bool; } +val set_transformed_jsx : description -> transformed_jsx:bool -> description + (* Invariant [List.length d.prim_native_repr_args = d.prim_arity] *) val parse_declaration : diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml index ded0cfd35b..66a53135c1 100644 --- a/compiler/ml/printast.ml +++ b/compiler/ml/printast.ml @@ -251,11 +251,12 @@ and expression i ppf x = option i expression ppf eo; pattern i ppf p; expression i ppf e - | Pexp_apply {funct = e; args = l; partial} -> + | Pexp_apply {funct = e; args = l; partial; transformed_jsx} -> line i ppf "Pexp_apply\n"; if partial then line i ppf "partial\n"; expression i ppf e; - list i label_x_expression ppf l + list i label_x_expression ppf l; + line i ppf "transformed_jsx: %b\n" transformed_jsx | Pexp_match (e, l) -> line i ppf "Pexp_match\n"; expression i ppf e; diff --git a/compiler/ml/tast_mapper.ml b/compiler/ml/tast_mapper.ml index 8064d65990..c2e33e7b4f 100644 --- a/compiler/ml/tast_mapper.ml +++ b/compiler/ml/tast_mapper.ml @@ -199,12 +199,13 @@ let expr sub x = | Texp_function {arg_label; arity; param; case; partial; async} -> Texp_function {arg_label; arity; param; case = sub.case sub case; partial; async} - | Texp_apply {funct = exp; args = list; partial} -> + | Texp_apply {funct = exp; args = list; partial; transformed_jsx} -> Texp_apply { funct = sub.expr sub exp; args = List.map (tuple2 id (opt (sub.expr sub))) list; partial; + transformed_jsx; } | Texp_match (exp, cases, exn_cases, p) -> Texp_match diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index 4cdeb34aa5..6340b8ca9d 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -710,6 +710,7 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = exp_type = prim_type; } as funct; args = oargs; + transformed_jsx; } when List.length oargs >= p.prim_arity && List.for_all (fun (_, arg) -> arg <> None) oargs -> ( @@ -720,7 +721,7 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = let inlined, _ = Translattribute.get_and_remove_inlined_attribute funct in - transl_apply ~inlined f args' e.exp_loc + transl_apply ~inlined ~transformed_jsx f args' e.exp_loc in let args = List.map @@ -749,8 +750,12 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = | Ploc _, _ -> assert false | _, _ -> ( match (prim, argl) with + | Pccall d, _ -> + wrap + (Lprim + (Pccall (set_transformed_jsx d ~transformed_jsx), argl, e.exp_loc)) | _ -> wrap (Lprim (prim, argl, e.exp_loc)))) - | Texp_apply {funct; args = oargs; partial} -> + | Texp_apply {funct; args = oargs; partial; transformed_jsx} -> let inlined, funct = Translattribute.get_and_remove_inlined_attribute funct in @@ -766,8 +771,8 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = | None -> None else None in - transl_apply ~inlined ~uncurried_partial_application (transl_exp funct) - oargs e.exp_loc + transl_apply ~inlined ~uncurried_partial_application ~transformed_jsx + (transl_exp funct) oargs e.exp_loc | Texp_match (arg, pat_expr_list, exn_pat_expr_list, partial) -> transl_match e arg pat_expr_list exn_pat_expr_list partial | Texp_try (body, pat_expr_list) -> @@ -948,9 +953,17 @@ and transl_case_try {c_lhs; c_guard; c_rhs} = and transl_cases_try cases = List.map transl_case_try cases and transl_apply ?(inlined = Default_inline) - ?(uncurried_partial_application = None) lam sargs loc = + ?(uncurried_partial_application = None) ?(transformed_jsx = false) lam sargs + loc = let lapply ap_func ap_args = - Lapply {ap_loc = loc; ap_func; ap_args; ap_inlined = inlined} + Lapply + { + ap_loc = loc; + ap_func; + ap_args; + ap_inlined = inlined; + ap_transformed_jsx = transformed_jsx; + } in let rec build_apply lam args = function | (None, optional) :: l -> @@ -1008,7 +1021,14 @@ and transl_apply ?(inlined = Default_inline) let extra_args = Ext_list.map extra_ids (fun id -> Lvar id) in let ap_args = args @ extra_args in let l0 = - Lapply {ap_func = lam; ap_args; ap_inlined = inlined; ap_loc = loc} + Lapply + { + ap_func = lam; + ap_args; + ap_inlined = inlined; + ap_loc = loc; + ap_transformed_jsx = transformed_jsx; + } in Lfunction { diff --git a/compiler/ml/translmod.ml b/compiler/ml/translmod.ml index f815a536c0..87471ac26b 100644 --- a/compiler/ml/translmod.ml +++ b/compiler/ml/translmod.ml @@ -100,6 +100,7 @@ and apply_coercion_result loc strict funct param arg cc_res = ap_func = Lvar id; ap_args = [arg]; ap_inlined = Default_inline; + ap_transformed_jsx = false; }); }) @@ -276,6 +277,7 @@ and transl_module cc rootpath mexp = ap_func = transl_module Tcoerce_none None funct; ap_args = [transl_module ccarg None arg]; ap_inlined = inlined_attribute; + ap_transformed_jsx = false; }) | Tmod_constraint (arg, _, _, ccarg) -> transl_module (compose_coercions cc ccarg) rootpath arg diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index d1e593607f..a7ac76a341 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -2401,7 +2401,7 @@ and type_expect_ ?type_clash_context ?in_function ?(recarg = Rejected) env sexp type_function ?in_function ~arity ~async loc sexp.pexp_attributes env ty_expected l [Ast_helper.Exp.case spat sbody] - | Pexp_apply {funct = sfunct; args = sargs; partial} -> + | Pexp_apply {funct = sfunct; args = sargs; partial; transformed_jsx} -> assert (sargs <> []); begin_def (); (* one more level for non-returning functions *) @@ -2423,7 +2423,7 @@ and type_expect_ ?type_clash_context ?in_function ?(recarg = Rejected) env sexp let mk_apply funct args = rue { - exp_desc = Texp_apply {funct; args; partial}; + exp_desc = Texp_apply {funct; args; partial; transformed_jsx}; exp_loc = loc; exp_extra = []; exp_type = ty_res; diff --git a/compiler/ml/typedtree.ml b/compiler/ml/typedtree.ml index 626950caec..f9769ee13b 100644 --- a/compiler/ml/typedtree.ml +++ b/compiler/ml/typedtree.ml @@ -87,6 +87,7 @@ and expression_desc = funct: expression; args: (Noloc.arg_label * expression option) list; partial: bool; + transformed_jsx: bool; } | Texp_match of expression * case list * case list * partial | Texp_try of expression * case list diff --git a/compiler/ml/typedtree.mli b/compiler/ml/typedtree.mli index 96da873af0..f3368c9539 100644 --- a/compiler/ml/typedtree.mli +++ b/compiler/ml/typedtree.mli @@ -150,6 +150,7 @@ and expression_desc = funct: expression; args: (Noloc.arg_label * expression option) list; partial: bool; + transformed_jsx: bool; } (** E0 ~l1:E1 ... ~ln:En diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index febc245f21..89bb301f2d 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -1208,7 +1208,7 @@ let append_children_prop (config : Jsx_common.jsx_config) mapper Exp.apply (Exp.ident {txt = Ldot (element_binding, "someElement"); loc = Location.none}) - [(Nolabel, child)] + [(Nolabel, mapper.expr mapper child)] in let is_optional = match component_description with @@ -1277,7 +1277,7 @@ let mk_react_jsx (config : Jsx_common.jsx_config) mapper loc attrs [key_prop; (nolabel, unit_expr ~loc:Location.none)] ) in let args = [(nolabel, elementTag); (nolabel, props_record)] @ key_and_unit in - Exp.apply ~loc ~attrs jsx_expr args + Exp.apply ~loc ~attrs ~transformed_jsx:true jsx_expr args (* In most situations, the component name is the make function from a module. However, if the name contains a lowercase letter, it means it probably an external component. diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index c9c36496c6..e610d4589d 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -2188,12 +2188,18 @@ and parse_binary_expr ?(context = OrdinaryExpr) ?a p prec = let loc = mk_loc a.Parsetree.pexp_loc.loc_start b.pexp_loc.loc_end in let expr = match (token, b.pexp_desc) with - | BarGreater, Pexp_apply {funct = fun_expr; args; partial} -> + | ( BarGreater, + Pexp_apply {funct = fun_expr; args; partial; transformed_jsx} ) -> { b with pexp_desc = Pexp_apply - {funct = fun_expr; args = args @ [(Nolabel, a)]; partial}; + { + funct = fun_expr; + args = args @ [(Nolabel, a)]; + partial; + transformed_jsx; + }; } | BarGreater, _ -> Ast_helper.Exp.apply ~loc b [(Nolabel, a)] | _ -> diff --git a/compiler/syntax/src/res_parsetree_viewer.ml b/compiler/syntax/src/res_parsetree_viewer.ml index b2d6444e59..c23ed3b0f2 100644 --- a/compiler/syntax/src/res_parsetree_viewer.ml +++ b/compiler/syntax/src/res_parsetree_viewer.ml @@ -142,7 +142,13 @@ let rewrite_underscore_apply expr = { e with pexp_desc = - Pexp_apply {funct = call_expr; args = new_args; partial = false}; + Pexp_apply + { + funct = call_expr; + args = new_args; + partial = false; + transformed_jsx = false; + }; } | _ -> expr diff --git a/scripts/test.js b/scripts/test.js index c2edd0c4ae..b7fddb16ac 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -83,10 +83,22 @@ if (mochaTest) { stdio: "inherit", }); - await mocha(["-t", "10000", "tests/tests/**/*_test.mjs"], { - cwd: projectDir, - stdio: "inherit", - }); + await mocha( + [ + "-t", + "10000", + "tests/tests/**/*_test.mjs", + // Ignore the preserve_jsx_test.mjs file. + // I can't run because Mocha doesn't support jsx. + // We also want to keep the output as is. + "--ignore", + "tests/tests/src/preserve_jsx_test.mjs", + ], + { + cwd: projectDir, + stdio: "inherit", + }, + ); await node("tests/tests/src/core/Core_TestSuite.mjs", [], { cwd: projectDir, @@ -149,8 +161,8 @@ if (runtimeDocstrings) { await execClean([], { cwd: docstringTestDir, - stdio: "inherit" - }) + stdio: "inherit", + }); await execBuild([], { cwd: docstringTestDir, @@ -177,12 +189,9 @@ if (runtimeDocstrings) { }); console.log("Run mocha test"); - await mocha( - [path.join(docstringTestDir, "generated_mocha_test.res.js")], - { - cwd: projectDir, - stdio: "inherit", - }, - ); + await mocha([path.join(docstringTestDir, "generated_mocha_test.res.js")], { + cwd: projectDir, + stdio: "inherit", + }); } } diff --git a/tests/tests/src/preserve_jsx_test.mjs b/tests/tests/src/preserve_jsx_test.mjs new file mode 100644 index 0000000000..0c25c00707 --- /dev/null +++ b/tests/tests/src/preserve_jsx_test.mjs @@ -0,0 +1,137 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Primitive_option from "rescript/lib/es6/Primitive_option.js"; +import * as JsxRuntime from "react/jsx-runtime"; + +let React = {}; + +let ReactDOM = {}; + +function Preserve_jsx_test$Icon(props) { + return ; +} + +let Icon = { + make: Preserve_jsx_test$Icon +}; + +let _single_element_child =
+

+{"Hello, world!"} +

+
; + +let _multiple_element_children =
+

+{"Hello, world!"} +

+ +
; + +let _single_element_fragment = <> +{Primitive_option.some()} +; + +let _multiple_element_fragment = <> + + +; + +let _unary_element_with_props = ; + +let _container_element_with_props_and_children =
+{"Hello, world!"} +
; + +let baseProps = { + className: "foo", + title: "foo" +}; + +let newrecord = {...baseProps}; + +let _unary_element_with_spread_props = ; + +let newrecord$1 = {...baseProps}; + +let _container_with_spread_props =
+{"Hello, world!"} + +
; + +let baseChildren = [ + + {"Hello, world!"} + , + + {"Hello, world!"} + +]; + +let _container_with_spread_children =
+{baseChildren} +
; + +let newrecord$2 = {...baseProps}; + +let _container_with_spread_props_and_children =
+{baseChildren} +
; + +let newrecord$3 = {...baseProps}; + +let _unary_element_with_spread_props_keyed = ; + +let newrecord$4 = {...baseProps}; + +let _container_with_spread_props_keyed =
+{"Hello, world!"} + +
; + +let _unary_element_with_only_spread_props = ; + +function QueryClientProvider(props) { return props.children } +; + +let A = {}; + +function Preserve_jsx_test$B(props) { + return

+ {"Hello, world!"} +

; +} + +let B = { + make: Preserve_jsx_test$B +}; + +let _external_component_with_children = + + +; + +export { + React, + ReactDOM, + Icon, + _single_element_child, + _multiple_element_children, + _single_element_fragment, + _multiple_element_fragment, + _unary_element_with_props, + _container_element_with_props_and_children, + baseProps, + _unary_element_with_spread_props, + _container_with_spread_props, + baseChildren, + _container_with_spread_children, + _container_with_spread_props_and_children, + _unary_element_with_spread_props_keyed, + _container_with_spread_props_keyed, + _unary_element_with_only_spread_props, + A, + B, + _external_component_with_children, +} +/* _single_element_child Not a pure module */ diff --git a/tests/tests/src/preserve_jsx_test.res b/tests/tests/src/preserve_jsx_test.res new file mode 100644 index 0000000000..81042f4b25 --- /dev/null +++ b/tests/tests/src/preserve_jsx_test.res @@ -0,0 +1,157 @@ +@@config({ + flags: ["-bs-jsx", "4", "-bs-jsx-preserve"], +}) + +module React = { + type element = Jsx.element + + @val external null: element = "null" + + external float: float => element = "%identity" + external int: int => element = "%identity" + external string: string => element = "%identity" + + external array: array => element = "%identity" + + type componentLike<'props, 'return> = Jsx.componentLike<'props, 'return> + + type component<'props> = Jsx.component<'props> + + external component: componentLike<'props, element> => component<'props> = "%identity" + + @module("react") + external createElement: (component<'props>, 'props) => element = "createElement" + + @module("react") + external cloneElement: (element, 'props) => element = "cloneElement" + + @module("react") + external isValidElement: 'a => bool = "isValidElement" + + @variadic @module("react") + external createElementVariadic: (component<'props>, 'props, array) => element = + "createElement" + + @module("react/jsx-runtime") + external jsx: (component<'props>, 'props) => element = "jsx" + + @module("react/jsx-runtime") + external jsxKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsx" + + @module("react/jsx-runtime") + external jsxs: (component<'props>, 'props) => element = "jsxs" + + @module("react/jsx-runtime") + external jsxsKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsxs" + + type fragmentProps = {children?: element} + + @module("react/jsx-runtime") external jsxFragment: component = "Fragment" +} + +module ReactDOM = { + external someElement: React.element => option = "%identity" + + @module("react/jsx-runtime") + external jsx: (string, JsxDOM.domProps) => Jsx.element = "jsx" + + @module("react/jsx-runtime") + external jsxKeyed: (string, JsxDOM.domProps, ~key: string=?, @ignore unit) => Jsx.element = "jsx" + + @module("react/jsx-runtime") + external jsxs: (string, JsxDOM.domProps) => Jsx.element = "jsxs" + + @module("react/jsx-runtime") + external jsxsKeyed: (string, JsxDOM.domProps, ~key: string=?, @ignore unit) => Jsx.element = + "jsxs" +} + +module Icon = { + @react.component + let make = () => { + + } +} + +let _single_element_child = +
+

{React.string("Hello, world!")}

+
+ +let _multiple_element_children = +
+

{React.string("Hello, world!")}

+ +
+ +let _single_element_fragment = + <> + + + +let _multiple_element_fragment = + <> + + + + +let _unary_element_with_props = + +let _container_element_with_props_and_children = +
{React.string("Hello, world!")}
+ +let baseProps: JsxDOM.domProps = { + title: "foo", + className: "foo", +} + +let _unary_element_with_spread_props = + +let _container_with_spread_props = +
+ {React.string("Hello, world!")} + +
+ +let baseChildren = React.array([ + {React.string("Hello, world!")} , + {React.string("Hello, world!")} , +]) + +let _container_with_spread_children =
...baseChildren
+ +let _container_with_spread_props_and_children = +
...baseChildren
+ +let _unary_element_with_spread_props_keyed = + +let _container_with_spread_props_keyed = +
+ {React.string("Hello, world!")} + +
+ +let _unary_element_with_only_spread_props = + +// Simulate an external component +%%raw(` + function QueryClientProvider(props) { return props.children } + `) + +module A = { + @react.component + external make: (~children: React.element) => React.element = "QueryClientProvider" +} + +module B = { + @react.component + let make = () => { +

{React.string("Hello, world!")}

+ } +} + +let _external_component_with_children = + + + +