Skip to content

Commit 767e553

Browse files
authored
Consolidate large record mutation output into a single spread object literal (#8473)
* Consolidate large record update output into a single spread object literal So it emits `{...x, a: 1}` instead of `let newrecord = {...x}; newrecord.a = 1;`. * codex review * changelog
1 parent 28c3d4a commit 767e553

8 files changed

Lines changed: 144 additions & 57 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
#### :nail_care: Polish
4747

48+
- Consolidate record mutation output into a single spread object literal. https://github.com/rescript-lang/rescript/pull/8473
4849
- Improve default argument type mismatch errors. https://github.com/rescript-lang/rescript/pull/8389
4950
- Resolve workspace dependencies in editor analysis. https://github.com/rescript-lang/rescript/pull/8392
5051
- Build system: Add OpenTelemetry tracing support for cli commands. https://github.com/rescript-lang/rescript/pull/8370

compiler/core/js_analyzer.ml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,11 @@ let rec no_side_effect_expression_desc (x : J.expression_desc) =
113113
*)
114114
Ext_list.for_all xs no_side_effect
115115
| Optional_block (x, _) -> no_side_effect x
116-
| Object (_, kvs) -> Ext_list.for_all_snd kvs no_side_effect
116+
| Object (dup, kvs) ->
117+
(match dup with
118+
| Some e -> no_side_effect e
119+
| None -> true)
120+
&& Ext_list.for_all_snd kvs no_side_effect
117121
| String_append (a, b) | Seq (a, b) -> no_side_effect a && no_side_effect b
118122
| Length (e, _) | Caml_block_tag (e, _) | Typeof e -> no_side_effect e
119123
| Bin (op, a, b) -> op <> Eq && no_side_effect a && no_side_effect b

compiler/core/js_dump.ml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,29 @@ and expression_desc cxt ~(level : int) f x : cxt =
671671
| [tag; ({expression_desc = J.Var _} as spread_props)] ->
672672
(* All the props are spread *)
673673
print_jsx cxt ~level ~spread_props f fn_name tag []
674+
| [tag; {expression_desc = J.Object (Some spread, props)}] ->
675+
(* Spread props with overrides emitted as a single object literal:
676+
{...base, x: 1}
677+
*)
678+
let fields =
679+
List.filter_map
680+
(fun (n, x) ->
681+
match n with
682+
| Js_op.Lit name -> Some (name, x)
683+
| Symbol_name -> None)
684+
props
685+
in
686+
print_jsx cxt ~level ~spread_props:spread f fn_name tag fields
687+
| [tag; {expression_desc = J.Object (Some spread, props)}; key] ->
688+
let fields =
689+
List.filter_map
690+
(fun (n, x) ->
691+
match n with
692+
| Js_op.Lit name -> Some (name, x)
693+
| Symbol_name -> None)
694+
props
695+
in
696+
print_jsx cxt ~level ~spread_props:spread ~key f fn_name tag fields
674697
| _ ->
675698
(* This should not happen, we fallback to the general case *)
676699
expression_desc cxt ~level f

compiler/core/lam_compile.ml

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,59 @@ let compile output_prefix =
18341834
in
18351835
Js_output.output_of_block_and_expression lambda_cxt.continuation args_code
18361836
exp
1837+
and collect_dup_overrides (copy_id : Ident.t) (lam : Lam.t)
1838+
(acc : (Lam_compat.set_field_dbg_info * Lam.t) list) :
1839+
(Lam_compat.set_field_dbg_info * Lam.t) list option =
1840+
match lam with
1841+
| Lsequence
1842+
( Lprim
1843+
{primitive = Psetfield (_, fld_info); args = [Lvar id'; value]; _},
1844+
rest )
1845+
when Ident.same id' copy_id ->
1846+
collect_dup_overrides copy_id rest ((fld_info, value) :: acc)
1847+
| Lvar id' when Ident.same id' copy_id -> Some acc
1848+
| _ -> None
1849+
and try_compile_record_spread (lambda_cxt : Lam_compile_context.t)
1850+
(id : Ident.t) (arg : Lam.t) (body : Lam.t) : Js_output.t option =
1851+
match arg with
1852+
| Lprim {primitive = Pduprecord; args = [init]; _} -> (
1853+
match collect_dup_overrides id body [] with
1854+
| None -> None
1855+
| Some overrides ->
1856+
let need_value_cxt =
1857+
{lambda_cxt with continuation = NeedValue Not_tail}
1858+
in
1859+
let init_output = compile_lambda need_value_cxt init in
1860+
let init_val =
1861+
match init_output.value with
1862+
| Some v -> v
1863+
| None -> assert false
1864+
in
1865+
let blocks, props =
1866+
List.fold_left
1867+
(fun (blocks, props)
1868+
((fld_info : Lam_compat.set_field_dbg_info), value_lam) ->
1869+
let val_output = compile_lambda need_value_cxt value_lam in
1870+
let val_val =
1871+
match val_output.value with
1872+
| Some v -> v
1873+
| None -> assert false
1874+
in
1875+
let name =
1876+
match fld_info with
1877+
| Fld_record_set name
1878+
| Fld_record_inline_set name
1879+
| Fld_record_extension_set name ->
1880+
name
1881+
in
1882+
(blocks @ val_output.block, (Js_op.Lit name, val_val) :: props))
1883+
(init_output.block, []) (List.rev overrides)
1884+
in
1885+
Some
1886+
(Js_output.output_of_block_and_expression lambda_cxt.continuation
1887+
blocks
1888+
(E.obj ~dup:init_val props)))
1889+
| _ -> None
18371890
and compile_lambda (lambda_cxt : Lam_compile_context.t) (cur_lam : Lam.t) :
18381891
Js_output.t =
18391892
match cur_lam with
@@ -1859,14 +1912,17 @@ let compile output_prefix =
18591912
}
18601913
body)))
18611914
| Lapply appinfo -> compile_apply appinfo lambda_cxt
1862-
| Llet (let_kind, id, arg, body) ->
1863-
(* Order matters.. see comment below in [Lletrec] *)
1864-
let args_code =
1865-
compile_lambda
1866-
{lambda_cxt with continuation = Declare (let_kind, id)}
1867-
arg
1868-
in
1869-
Js_output.append_output args_code (compile_lambda lambda_cxt body)
1915+
| Llet (let_kind, id, arg, body) -> (
1916+
match try_compile_record_spread lambda_cxt id arg body with
1917+
| Some output -> output
1918+
| None ->
1919+
(* Order matters.. see comment below in [Lletrec] *)
1920+
let args_code =
1921+
compile_lambda
1922+
{lambda_cxt with continuation = Declare (let_kind, id)}
1923+
arg
1924+
in
1925+
Js_output.append_output args_code (compile_lambda lambda_cxt body))
18701926
| Lletrec (id_args, body) ->
18711927
(* There is a bug in our current design,
18721928
it requires compile args first (register that some objects are jsidentifiers)

tests/tests/src/jsx_preserve_test.mjs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,15 @@ let baseProps = {
5454
title: "foo"
5555
};
5656

57-
let newrecord = {...baseProps};
58-
5957
let _unary_element_with_spread_props = <input
60-
{...newrecord}
58+
{...baseProps}
6159
type={"text"}
6260
/>;
6361

64-
let newrecord$1 = {...baseProps};
65-
6662
let _container_with_spread_props = <div
67-
{...newrecord$1}
68-
title={"barry"}
63+
{...baseProps}
6964
className={"barry"}
65+
title={"barry"}
7066
>
7167
{"Hello, world!"}
7268
<input
@@ -83,21 +79,17 @@ let baseChildren = [
8379
</span>
8480
];
8581

86-
let newrecord$2 = {...baseProps};
87-
8882
let _unary_element_with_spread_props_keyed = <input
8983
key={"barry-key"}
90-
{...newrecord$2}
84+
{...baseProps}
9185
type={"text"}
9286
/>;
9387

94-
let newrecord$3 = {...baseProps};
95-
9688
let _container_with_spread_props_keyed = <div
9789
key={"barry-key"}
98-
{...newrecord$3}
99-
title={"barry"}
90+
{...baseProps}
10091
className={"barry"}
92+
title={"barry"}
10193
>
10294
{"Hello, world!"}
10395
<input

tests/tests/src/large_record_duplication_test.mjs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,10 @@ let A0 = /* @__PURE__ */Primitive_exceptions.create("Large_record_duplication_te
3535

3636
Mocha.describe("Large_record_duplication_test", () => {
3737
Mocha.test("large record spread operation", () => {
38-
let f0 = x => {
39-
let newrecord = {...x};
40-
newrecord.x0 = 1;
41-
return newrecord;
42-
};
38+
let f0 = x => ({
39+
...x,
40+
x0: 1
41+
});
4342
let result = f0(v0);
4443
Test_utils.eq("File \"large_record_duplication_test.res\", line 143, characters 7-14", result.x0, 1);
4544
Test_utils.eq("File \"large_record_duplication_test.res\", line 144, characters 7-14", result.x1, 9);
@@ -86,10 +85,12 @@ Mocha.describe("Large_record_duplication_test", () => {
8685
let f1 = x => {
8786
if (typeof x !== "object") {
8887
return "A1";
88+
} else {
89+
return {
90+
...x,
91+
x0: 1
92+
};
8993
}
90-
let newrecord = {...x};
91-
newrecord.x0 = 1;
92-
return newrecord;
9394
};
9495
Test_utils.eq("File \"large_record_duplication_test.res\", line 201, characters 7-14", get_x0(f1({
9596
TAG: "A0",
@@ -125,12 +126,14 @@ Mocha.describe("Large_record_duplication_test", () => {
125126
}
126127
};
127128
let f2 = x => {
128-
if (x.TAG !== "A0") {
129+
if (x.TAG === "A0") {
130+
return {
131+
...x,
132+
x0: 1
133+
};
134+
} else {
129135
return x;
130136
}
131-
let newrecord = {...x};
132-
newrecord.x0 = 1;
133-
return newrecord;
134137
};
135138
Test_utils.eq("File \"large_record_duplication_test.res\", line 243, characters 7-14", get_x0(f2({
136139
TAG: "A0",
@@ -161,12 +164,14 @@ Mocha.describe("Large_record_duplication_test", () => {
161164
});
162165
Mocha.test("exception extension with large record", () => {
163166
let f3 = x => {
164-
if (x.RE_EXN_ID !== A0) {
167+
if (x.RE_EXN_ID === A0) {
168+
return {
169+
...x,
170+
x0: 1
171+
};
172+
} else {
165173
return x;
166174
}
167-
let newrecord = {...x};
168-
newrecord.x0 = 1;
169-
return newrecord;
170175
};
171176
let get_x0 = x => {
172177
if (x.RE_EXN_ID === A0) {

tests/tests/src/record_regression.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ let newrecord$3 = {...v1};
3939
newrecord$3.y1 = 22;
4040

4141
function h11(v1) {
42-
let newrecord = {...v1};
43-
newrecord.y1 = 22;
44-
return newrecord;
42+
return {
43+
...v1,
44+
y1: 22
45+
};
4546
}
4647

4748
let po = {

tests/tests/src/stdlib/intl/Stdlib_Intl_DateTimeFormatTest.mjs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,33 +46,38 @@ let formatter$1 = new Intl.DateTimeFormat(undefined, options);
4646

4747
console.log(formatter$1.format(new Date(Date.now())));
4848

49-
let newrecord = {...options};
50-
51-
let formatter$2 = new Intl.DateTimeFormat(undefined, (newrecord.timeZoneName = "long", newrecord));
49+
let formatter$2 = new Intl.DateTimeFormat(undefined, {
50+
...options,
51+
timeZoneName: "long"
52+
});
5253

5354
console.log(formatter$2.format(new Date(Date.now())));
5455

55-
let newrecord$1 = {...options};
56-
57-
let formatter$3 = new Intl.DateTimeFormat(undefined, (newrecord$1.timeZoneName = "longOffset", newrecord$1));
56+
let formatter$3 = new Intl.DateTimeFormat(undefined, {
57+
...options,
58+
timeZoneName: "longOffset"
59+
});
5860

5961
console.log(formatter$3.format(new Date(Date.now())));
6062

61-
let newrecord$2 = {...options};
62-
63-
let formatter$4 = new Intl.DateTimeFormat(undefined, (newrecord$2.timeZoneName = "short", newrecord$2));
63+
let formatter$4 = new Intl.DateTimeFormat(undefined, {
64+
...options,
65+
timeZoneName: "short"
66+
});
6467

6568
console.log(formatter$4.format(new Date(Date.now())));
6669

67-
let newrecord$3 = {...options};
68-
69-
let formatter$5 = new Intl.DateTimeFormat(undefined, (newrecord$3.timeZoneName = "shortGeneric", newrecord$3));
70+
let formatter$5 = new Intl.DateTimeFormat(undefined, {
71+
...options,
72+
timeZoneName: "shortGeneric"
73+
});
7074

7175
console.log(formatter$5.format(new Date(Date.now())));
7276

73-
let newrecord$4 = {...options};
74-
75-
let formatter$6 = new Intl.DateTimeFormat(undefined, (newrecord$4.timeZoneName = "shortOffset", newrecord$4));
77+
let formatter$6 = new Intl.DateTimeFormat(undefined, {
78+
...options,
79+
timeZoneName: "shortOffset"
80+
});
7681

7782
console.log(formatter$6.format(new Date(Date.now())));
7883

0 commit comments

Comments
 (0)