diff --git a/CHANGELOG.md b/CHANGELOG.md index 070c866edc8..69131928ff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ #### :nail_care: Polish +- Consolidate record mutation output into a single spread object literal. https://github.com/rescript-lang/rescript/pull/8473 - Improve default argument type mismatch errors. https://github.com/rescript-lang/rescript/pull/8389 - Resolve workspace dependencies in editor analysis. https://github.com/rescript-lang/rescript/pull/8392 - Build system: Add OpenTelemetry tracing support for cli commands. https://github.com/rescript-lang/rescript/pull/8370 diff --git a/compiler/core/js_analyzer.ml b/compiler/core/js_analyzer.ml index 25852412667..4675e29a708 100644 --- a/compiler/core/js_analyzer.ml +++ b/compiler/core/js_analyzer.ml @@ -102,7 +102,11 @@ let rec no_side_effect_expression_desc (x : J.expression_desc) = *) Ext_list.for_all xs no_side_effect | Optional_block (x, _) -> no_side_effect x - | Object (_, kvs) -> Ext_list.for_all_snd kvs no_side_effect + | Object (dup, kvs) -> + (match dup with + | Some e -> no_side_effect e + | None -> true) + && Ext_list.for_all_snd kvs no_side_effect | String_append (a, b) | Seq (a, b) -> no_side_effect a && no_side_effect b | Length (e, _) | Caml_block_tag (e, _) | Typeof e -> no_side_effect e | Bin (op, a, b) -> op <> Eq && no_side_effect a && no_side_effect b diff --git a/compiler/core/js_dump.ml b/compiler/core/js_dump.ml index 6f6da8b605c..998b5e9b340 100644 --- a/compiler/core/js_dump.ml +++ b/compiler/core/js_dump.ml @@ -618,6 +618,29 @@ and expression_desc cxt ~(level : int) f x : cxt = | [tag; ({expression_desc = J.Var _} as spread_props)] -> (* All the props are spread *) print_jsx cxt ~level ~spread_props f fn_name tag [] + | [tag; {expression_desc = J.Object (Some spread, props)}] -> + (* Spread props with overrides emitted as a single object literal: + {...base, x: 1} + *) + let fields = + List.filter_map + (fun (n, x) -> + match n with + | Js_op.Lit name -> Some (name, x) + | Symbol_name -> None) + props + in + print_jsx cxt ~level ~spread_props:spread f fn_name tag fields + | [tag; {expression_desc = J.Object (Some spread, props)}; key] -> + let fields = + List.filter_map + (fun (n, x) -> + match n with + | Js_op.Lit name -> Some (name, x) + | Symbol_name -> None) + props + in + print_jsx cxt ~level ~spread_props:spread ~key f fn_name tag fields | _ -> (* This should not happen, we fallback to the general case *) expression_desc cxt ~level f diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 97f6bec84e5..dd7497c2416 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -1834,6 +1834,59 @@ let compile output_prefix = in Js_output.output_of_block_and_expression lambda_cxt.continuation args_code exp + and collect_dup_overrides (copy_id : Ident.t) (lam : Lam.t) + (acc : (Lam_compat.set_field_dbg_info * Lam.t) list) : + (Lam_compat.set_field_dbg_info * Lam.t) list option = + match lam with + | Lsequence + ( Lprim + {primitive = Psetfield (_, fld_info); args = [Lvar id'; value]; _}, + rest ) + when Ident.same id' copy_id -> + collect_dup_overrides copy_id rest ((fld_info, value) :: acc) + | Lvar id' when Ident.same id' copy_id -> Some acc + | _ -> None + and try_compile_record_spread (lambda_cxt : Lam_compile_context.t) + (id : Ident.t) (arg : Lam.t) (body : Lam.t) : Js_output.t option = + match arg with + | Lprim {primitive = Pduprecord; args = [init]; _} -> ( + match collect_dup_overrides id body [] with + | None -> None + | Some overrides -> + let need_value_cxt = + {lambda_cxt with continuation = NeedValue Not_tail} + in + let init_output = compile_lambda need_value_cxt init in + let init_val = + match init_output.value with + | Some v -> v + | None -> assert false + in + let blocks, props = + List.fold_left + (fun (blocks, props) + ((fld_info : Lam_compat.set_field_dbg_info), value_lam) -> + let val_output = compile_lambda need_value_cxt value_lam in + let val_val = + match val_output.value with + | Some v -> v + | None -> assert false + in + let name = + match fld_info with + | Fld_record_set name + | Fld_record_inline_set name + | Fld_record_extension_set name -> + name + in + (blocks @ val_output.block, (Js_op.Lit name, val_val) :: props)) + (init_output.block, []) (List.rev overrides) + in + Some + (Js_output.output_of_block_and_expression lambda_cxt.continuation + blocks + (E.obj ~dup:init_val props))) + | _ -> None and compile_lambda (lambda_cxt : Lam_compile_context.t) (cur_lam : Lam.t) : Js_output.t = match cur_lam with @@ -1859,14 +1912,17 @@ let compile output_prefix = } body))) | Lapply appinfo -> compile_apply appinfo lambda_cxt - | Llet (let_kind, id, arg, body) -> - (* Order matters.. see comment below in [Lletrec] *) - let args_code = - compile_lambda - {lambda_cxt with continuation = Declare (let_kind, id)} - arg - in - Js_output.append_output args_code (compile_lambda lambda_cxt body) + | Llet (let_kind, id, arg, body) -> ( + match try_compile_record_spread lambda_cxt id arg body with + | Some output -> output + | None -> + (* Order matters.. see comment below in [Lletrec] *) + let args_code = + compile_lambda + {lambda_cxt with continuation = Declare (let_kind, id)} + arg + in + Js_output.append_output args_code (compile_lambda lambda_cxt body)) | Lletrec (id_args, body) -> (* There is a bug in our current design, it requires compile args first (register that some objects are jsidentifiers) diff --git a/tests/tests/src/jsx_preserve_test.mjs b/tests/tests/src/jsx_preserve_test.mjs index bcc16a2d3ce..a839e400133 100644 --- a/tests/tests/src/jsx_preserve_test.mjs +++ b/tests/tests/src/jsx_preserve_test.mjs @@ -54,19 +54,15 @@ let baseProps = { title: "foo" }; -let newrecord = {...baseProps}; - let _unary_element_with_spread_props = ; -let newrecord$1 = {...baseProps}; - let _container_with_spread_props =