Skip to content

Preserve JSX #7387

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 31 commits into from
May 5, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ffc7666
Add additional node to Pexp_apply
nojaf Apr 10, 2025
92e38cb
Try and pass jsx_element from typed_tree to js_call
nojaf Apr 10, 2025
dd56e61
Follow Lprim
nojaf Apr 11, 2025
a0c170f
Transform initial simple element
nojaf Apr 23, 2025
a98e3b8
Initial fragment support
nojaf Apr 24, 2025
1801da1
WIP extract, good stuff
nojaf Apr 24, 2025
ab32462
Print props, catch with key functions
nojaf Apr 25, 2025
6b075c0
Don't pass untyped ast, simple flag is sufficient.
nojaf Apr 25, 2025
d0bfedc
Support fragments
nojaf Apr 25, 2025
3d2930e
Unwrap children
nojaf May 3, 2025
547dfca
Poor man feature flag
nojaf May 3, 2025
09950f1
Remove duplicated in
nojaf May 3, 2025
0ab6987
Older camls
nojaf May 3, 2025
8e4811b
Revert "Poor man feature flag"
nojaf May 3, 2025
aa94f6f
Add new -bs-jsx-preserve flag
nojaf May 3, 2025
e30cab3
WIP, deal with prop spreading
nojaf May 4, 2025
773124f
Deal with prop spreading
nojaf May 4, 2025
46d3d50
Clean up Lprim and move the information to the call primitive.
cristianoc May 4, 2025
1a20f38
Add test for spreading children
nojaf May 4, 2025
8929f37
Refactor duplicate code
nojaf May 4, 2025
cecf67d
Support keyed and prop spreading
nojaf May 4, 2025
3fcf1df
Rename test file
nojaf May 4, 2025
8b7eb45
Keep key prop before spreading props. Also ensure test can run.
nojaf May 4, 2025
90b28f0
Add only spread props case
nojaf May 4, 2025
faa5618
Give record a name
nojaf May 5, 2025
400b550
Use config flag in Js_dump instead
nojaf May 5, 2025
3e1867e
Remove helper code
nojaf May 5, 2025
32bffd8
Extra call info
nojaf May 5, 2025
f104529
Detect direct Array as well
nojaf May 5, 2025
848b3ab
Don't run with mocha
nojaf May 5, 2025
aceb0e2
Feedback code review
nojaf May 5, 2025
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
6 changes: 5 additions & 1 deletion analysis/src/CompletionFrontEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,14 @@ 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;
}
Expand All @@ -284,6 +286,7 @@ let rec exprToContextPathInner ~(inJsxContext : bool) (e : Parsetree.expression)
(_, lhs); (_, {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes});
];
partial;
transformed_jsx;
} ->
(* Transform away pipe with identifier *)
exprToContextPath ~inJsxContext
Expand All @@ -294,6 +297,7 @@ let rec exprToContextPathInner ~(inJsxContext : bool) (e : Parsetree.expression)
funct = {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes};
args = [(Nolabel, lhs)];
partial;
transformed_jsx;
};
pexp_loc;
pexp_attributes;
Expand Down
3 changes: 3 additions & 0 deletions compiler/bsc/rescript_compiler_main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ 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",
unit_call (fun _ -> Js_config.jsx_preserve := true),
"*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: \
Expand Down
1 change: 1 addition & 0 deletions compiler/common/js_config.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions compiler/common/js_config.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions compiler/core/js_call_info.ml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,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}

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}
2 changes: 1 addition & 1 deletion compiler/core/js_call_info.mli
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ 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

Expand Down
113 changes: 113 additions & 0 deletions compiler/core/js_dump.ml
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,48 @@ and expression_desc cxt ~(level : int) f x : cxt =
when Ext_list.length_equal el i
]}
*)
| Call
( ({expression_desc = J.Var (J.Qualified (_, Some fnName))} as e),
el,
{call_transformed_jsx = true} ) -> (
match el with
| [
tag;
{
expression_desc =
Caml_block (el, _mutable_flag, _, Lambda.Blk_record {fields});
};
] ->
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;
] ->
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
let fields = ("key", key) :: fields in
print_jsx cxt ~level f fnName tag fields
| _ ->
expression_desc cxt ~level f
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this code branch ever executed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If it is, it means we missed something. Could we add some sort of assert in this case?

(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 _ ->
Expand Down Expand Up @@ -956,6 +998,77 @@ and expression_desc cxt ~(level : int) f x : cxt =
P.string f "...";
expression ~level:13 cxt f e)

and print_jsx cxt ~(level : int) f (fnName : string) (tag : J.expression)
(fields : (string * J.expression) list) : cxt =
let print_tag () =
match tag.expression_desc with
| J.Str {txt} -> P.string f txt
(* fragment *)
| J.Var (J.Qualified ({id = {name = "JsxRuntime"}}, Some "Fragment")) -> ()
| _ ->
let _ = expression ~level cxt f tag in
()
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.Optional_block ({expression_desc = J.Array (xs, _)}, _) ->
Some xs
| _ -> Some [e]
else Some [e]
else None)
fields
in
let print_props () =
let props = List.filter (fun (n, _) -> n <> "children") fields in
if List.length props > 0 then
(List.iter (fun (n, x) ->
P.space f;
P.string f n;
P.string f "=";
P.string f "{";
let _ = expression ~level:0 cxt f x in
P.string f "}"))
props
in
(match children_opt with
| None ->
P.string f "<";
print_tag ();
print_props ();
P.string f "/>"
| 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 "<";
print_tag ();
print_props ();
P.string f ">";

let _ =
children
|> 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 "}";
next)
cxt
in

P.string f "</";
print_tag ();
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) ->
Expand Down
48 changes: 48 additions & 0 deletions compiler/core/jsx_help.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
let lambda_tag_info_to_string (e : Lambda.tag_info) =
match e with
| Lambda.Blk_constructor _ -> "Blk_constructor"
| Lambda.Blk_record_inlined _ -> "Blk_record_inlined"
| Lambda.Blk_tuple -> "Blk_tuple"
| Lambda.Blk_poly_var _ -> "Blk_poly_var"
| Lambda.Blk_record _ -> "Blk_record"
| Lambda.Blk_module _ -> "Blk_module"
| Lambda.Blk_module_export _ -> "Blk_module_export"
| Lambda.Blk_extension -> "Blk_extension"
| Lambda.Blk_some -> "Blk_some"
| Lambda.Blk_some_not_nested -> "Blk_some_not_nested"
| Lambda.Blk_record_ext _ -> "Blk_record_ext"
| Lambda.Blk_lazy_general -> "Blk_lazy_general"

let j_exp_to_string (e : J.expression) =
match e.J.expression_desc with
| J.Object _ -> "Object"
| J.Str _ -> "String"
| J.Var (J.Qualified (_, Some o)) -> "Var_" ^ o
| J.Var _ -> "Var"
| J.Call _ -> "Call"
| J.Fun _ -> "Fun"
| J.Array _ -> "Array"
| J.Bin _ -> "Bin"
| J.Cond _ -> "Cond"
| J.New _ -> "New"
| J.Seq _ -> "Seq"
| J.Number _ -> "Number"
| J.Bool _ -> "Bool"
| J.Null -> "Null"
| J.Undefined _ -> "Undefined"
| J.Is_null_or_undefined _ -> "Is_null_or_undefined"
| J.Js_not _ -> "Js_not"
| J.Typeof _ -> "Typeof"
| J.String_index _ -> "String_index"
| J.Array_index _ -> "Array_index"
| J.Static_index _ -> "Static_index"
| J.Length _ -> "Length"
| J.Caml_block (_, _, _, tag) ->
Format.sprintf "Caml_block (%s)" (lambda_tag_info_to_string tag)
| J.Caml_block_tag _ -> "Caml_block_tag"
| J.Tagged_template _ -> "Tagged_template"
| J.Optional_block _ -> "Optional_block"
| J.Spread _ -> "Spread"
| J.Await _ -> "Await"
| J.Raw_js_code _ -> "Raw_js_code"
| _ -> "Other"
37 changes: 25 additions & 12 deletions compiler/core/lam.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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
{
Expand All @@ -300,15 +310,16 @@ 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;
body = Lprim ({primitive = _; args = inner_args} as primitive_call);
} -> (
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;
Expand All @@ -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
Expand Down Expand Up @@ -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)
10 changes: 8 additions & 2 deletions compiler/core/lam.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 ->
Expand All @@ -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 ->
Expand Down
Loading