Skip to content

Commit ab60e12

Browse files
authored
feat(compiler): Record spread syntax (#1565)
Closes #778
1 parent 5146b14 commit ab60e12

File tree

26 files changed

+547
-112
lines changed

26 files changed

+547
-112
lines changed

compiler/src/codegen/mashtree.re

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ type allocation_type =
314314
| MTuple(list(immediate))
315315
| MBox(immediate)
316316
| MArray(list(immediate))
317-
| MRecord(immediate, list((string, immediate)))
317+
| MRecord(immediate, list((option(string), immediate)))
318318
| MADT(immediate, immediate, list(immediate)) /* Type Tag, Variant Tag, Elements */
319319
| MBytes(bytes)
320320
| MString(string)

compiler/src/codegen/transl_anf.re

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,11 @@ let rec compile_comp = (~id=?, env, c) => {
822822
MRecord(
823823
compile_imm(env, ttag),
824824
List.map(
825-
(({txt: name}, arg)) => (name, compile_imm(env, arg)),
825+
((name, arg)) =>
826+
(
827+
Option.map(({txt: name}) => name, name),
828+
compile_imm(env, arg),
829+
),
826830
args,
827831
),
828832
),

compiler/src/formatting/debug.re

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ let debug_expression = (expr: Parsetree.expression) => {
5656
print_loc("PExpArrayGet", expr.pexp_loc)
5757
| PExpArraySet(expression1, expression2, expression3) =>
5858
print_loc("PExpArraySet", expr.pexp_loc)
59-
| PExpRecord(record) => print_loc("PExpRecord", expr.pexp_loc)
59+
| PExpRecord(base, record) => print_loc("PExpRecord", expr.pexp_loc)
6060
| PExpRecordGet(expression, {txt, _}) =>
6161
print_loc("PExpRecordGet", expr.pexp_loc)
6262
| PExpRecordSet(expression, {txt, _}, expression2) =>

compiler/src/formatting/format.re

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ type sugared_list_item =
128128
| Regular(Parsetree.expression)
129129
| Spread(Parsetree.expression);
130130

131+
type record_item =
132+
| Field((Location.loc(Identifier.t), Parsetree.expression))
133+
| RecordSpread(Parsetree.expression);
134+
131135
type sugared_pattern_item =
132136
| RegularPattern(Parsetree.pattern)
133137
| SpreadPattern(Parsetree.pattern);
@@ -1627,53 +1631,81 @@ and print_ident = (ident: Identifier.t) => {
16271631

16281632
and print_record =
16291633
(
1634+
~base: option(Parsetree.expression),
16301635
~fields: list((Location.loc(Identifier.t), Parsetree.expression)),
16311636
~original_source: array(string),
16321637
~comments: list(Parsetree.comment),
16331638
recloc: Location.t,
16341639
) => {
1635-
let get_loc = (field: (Location.loc(Identifier.t), Parsetree.expression)) => {
1636-
let (_, expr) = field;
1637-
expr.pexp_loc;
1640+
let get_loc = item => {
1641+
switch (item) {
1642+
| Field((_, expr)) => expr.pexp_loc
1643+
| RecordSpread(base) => base.pexp_loc
1644+
};
16381645
};
16391646

1640-
let print_item =
1641-
(~comments, field: (Location.loc(Identifier.t), Parsetree.expression)) => {
1642-
let (locidentifier, expr) = field;
1643-
let ident = locidentifier.txt;
1644-
let printed_ident = print_ident(ident);
1645-
let printed_expr =
1646-
print_expression(
1647-
~expression_parent=GenericExpression,
1648-
~original_source,
1649-
~comments,
1650-
expr,
1651-
);
1652-
let punned_expr = check_for_pun(expr);
1647+
let print_item = (~comments, item) => {
1648+
switch (item) {
1649+
| Field(field) =>
1650+
let (locidentifier, expr) = field;
1651+
let ident = locidentifier.txt;
1652+
let printed_ident = print_ident(ident);
1653+
let printed_expr =
1654+
print_expression(
1655+
~expression_parent=GenericExpression,
1656+
~original_source,
1657+
~comments,
1658+
expr,
1659+
);
1660+
let punned_expr = check_for_pun(expr);
16531661

1654-
let pun =
1655-
switch (printed_ident, punned_expr: Doc.t) {
1656-
| (Text(i), Text(e)) => i == e
1657-
| _ => false
1658-
};
1662+
let pun =
1663+
switch (printed_ident, punned_expr: Doc.t) {
1664+
| (Text(i), Text(e)) => i == e
1665+
| _ => false
1666+
};
16591667

1660-
if (!pun) {
1661-
Doc.group(
1662-
Doc.concat([printed_ident, Doc.text(":"), Doc.space, printed_expr]),
1663-
);
1664-
} else {
1665-
Doc.group(printed_ident);
1668+
if (!pun) {
1669+
Doc.group(
1670+
Doc.concat([
1671+
printed_ident,
1672+
Doc.text(":"),
1673+
Doc.space,
1674+
printed_expr,
1675+
]),
1676+
);
1677+
} else {
1678+
Doc.group(printed_ident);
1679+
};
1680+
| RecordSpread(base) =>
1681+
Doc.concat([
1682+
Doc.text("..."),
1683+
print_expression(
1684+
~expression_parent=GenericExpression,
1685+
~original_source,
1686+
~comments,
1687+
base,
1688+
),
1689+
])
16661690
};
16671691
};
16681692

1693+
let items =
1694+
Option.to_list(Option.map(x => RecordSpread(x), base))
1695+
@ List.map(x => Field(x), fields);
1696+
16691697
let after_brace_comments =
1670-
switch (fields) {
1671-
| [field, ..._] =>
1672-
let (ident, expr) = field;
1698+
switch (items) {
1699+
| [item, ..._] =>
1700+
let loc =
1701+
switch (item) {
1702+
| Field((ident, _)) => ident.loc
1703+
| RecordSpread(exp) => exp.pexp_loc
1704+
};
16731705

16741706
Comment_utils.get_after_brace_comments(
16751707
~loc=recloc,
1676-
~first=ident.loc,
1708+
~first=loc,
16771709
comments,
16781710
);
16791711

@@ -1689,7 +1721,7 @@ and print_record =
16891721
~print_item,
16901722
~comments=cleaned_comments,
16911723
~iterated_item=IteratedRecord,
1692-
fields,
1724+
items,
16931725
);
16941726
let printed_fields = Doc.join(~sep=Doc.line, items);
16951727

@@ -1707,7 +1739,7 @@ and print_record =
17071739
printed_fields_after_brace,
17081740
Doc.ifBreaks(
17091741
Doc.nil,
1710-
switch (fields) {
1742+
switch (items) {
17111743
| [_one] =>
17121744
// TODO: not needed once we annotate with ::
17131745
Doc.comma // append a comma as single argument record look like block {data:val}
@@ -2787,8 +2819,14 @@ and print_expression_inner =
27872819
]),
27882820
)
27892821

2790-
| PExpRecord(record) =>
2791-
print_record(~fields=record, ~original_source, ~comments, expr.pexp_loc)
2822+
| PExpRecord(base, record) =>
2823+
print_record(
2824+
~base,
2825+
~fields=record,
2826+
~original_source,
2827+
~comments,
2828+
expr.pexp_loc,
2829+
)
27922830
| PExpRecordGet(expression, {txt, _}) =>
27932831
Doc.concat([
27942832
print_expression(

compiler/src/middle_end/anf_helper.rei

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ module Comp: {
168168
~attributes: attributes=?,
169169
~env: env=?,
170170
imm_expression,
171-
list((str, imm_expression))
171+
list((option(str), imm_expression))
172172
) =>
173173
comp_expression;
174174
let adt:

compiler/src/middle_end/anftree.re

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ and comp_expression_desc =
328328
| CArray(list(imm_expression))
329329
| CArrayGet(imm_expression, imm_expression)
330330
| CArraySet(imm_expression, imm_expression, imm_expression)
331-
| CRecord(imm_expression, list((loc(string), imm_expression)))
331+
| CRecord(imm_expression, list((option(loc(string)), imm_expression)))
332332
| CAdt(imm_expression, imm_expression, list(imm_expression))
333333
| CGetTupleItem(int32, imm_expression)
334334
| CSetTupleItem(int32, imm_expression, imm_expression)

compiler/src/middle_end/anftree.rei

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ and comp_expression_desc =
308308
| CArray(list(imm_expression))
309309
| CArrayGet(imm_expression, imm_expression)
310310
| CArraySet(imm_expression, imm_expression, imm_expression)
311-
| CRecord(imm_expression, list((loc(string), imm_expression)))
311+
| CRecord(imm_expression, list((option(loc(string)), imm_expression)))
312312
| CAdt(imm_expression, imm_expression, list(imm_expression))
313313
| CGetTupleItem(int32, imm_expression)
314314
| CSetTupleItem(int32, imm_expression, imm_expression)

compiler/src/middle_end/linearize.re

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -757,35 +757,56 @@ let rec transl_imm =
757757
),
758758
],
759759
);
760-
| TExpRecord(args) =>
760+
| TExpRecord(base, args) =>
761+
let base_imm = Option.map(transl_imm, base);
761762
let tmp = gensym("record");
762-
let definitions =
763-
Array.to_list @@ Array.map(((desc, def)) => def, args);
764-
let definitions =
765-
List.map(
766-
fun
767-
| Kept(_) => assert(false)
768-
| Overridden(name, def) => (name, def),
769-
definitions,
770-
);
771763
let (new_args, new_setup) =
772764
List.split(
773765
List.map(
774-
(({txt: name, loc}, expr)) => {
775-
let (var, setup) = transl_imm(expr);
776-
(
777-
(Location.mkloc(Identifier.string_of_ident(name), loc), var),
778-
setup,
779-
);
780-
},
781-
definitions,
766+
arg =>
767+
switch (arg) {
768+
| (ld, Kept) =>
769+
let (base_var, _) = Option.get(base_imm);
770+
let fieldtmp = gensym("field");
771+
(
772+
(None, Imm.id(~loc, ~env, fieldtmp)),
773+
[
774+
BLet(
775+
fieldtmp,
776+
Comp.record_get(
777+
~loc,
778+
~env,
779+
~allocation_type,
780+
Int32.of_int(ld.lbl_pos),
781+
base_var,
782+
),
783+
Nonglobal,
784+
),
785+
],
786+
);
787+
| (_, Overridden({txt: name, loc}, expr)) =>
788+
let (var, setup) = transl_imm(expr);
789+
(
790+
(
791+
Some(
792+
Location.mkloc(Identifier.string_of_ident(name), loc),
793+
),
794+
var,
795+
),
796+
setup,
797+
);
798+
},
799+
Array.to_list(args),
782800
),
783801
);
784802
let (typath, _, _) = Typepat.extract_concrete_record(env, typ);
785803
let ty_id = get_type_id(typath);
786804
(
787805
Imm.id(~loc, ~env, tmp),
788-
List.concat(new_setup)
806+
List.concat(
807+
Option.to_list(Option.map(((_, setup)) => setup, base_imm))
808+
@ new_setup,
809+
)
789810
@ [
790811
BLet(
791812
tmp,

compiler/src/parsing/ast_helper.re

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ type listitem('a) =
2424
| ListItem('a)
2525
| ListSpread('a, Location.t);
2626

27+
type recorditem =
28+
| RecordItem(loc(Identifier.t), expression)
29+
| RecordSpread(expression, Location.t);
30+
2731
type id = loc(Identifier.t);
2832
type str = loc(string);
2933
type loc = Location.t;
@@ -192,8 +196,47 @@ module Exp = {
192196
mk(~loc?, ~attributes?, PExpConstant(a));
193197
let tuple = (~loc=?, ~attributes=?, a) =>
194198
mk(~loc?, ~attributes?, PExpTuple(a));
195-
let record = (~loc=?, ~attributes=?, a) =>
196-
mk(~loc?, ~attributes?, PExpRecord(a));
199+
let record = (~loc=?, ~attributes=?, a, b) =>
200+
mk(~loc?, ~attributes?, PExpRecord(a, b));
201+
let record_fields = (~loc=?, ~attributes=?, a) =>
202+
switch (a) {
203+
| [] => failwith("Impossible: empty record field list")
204+
| [base, ...rest] =>
205+
let (spread_base, record_items) =
206+
switch (base) {
207+
| RecordItem(id, expr) => (None, [(id, expr)])
208+
| RecordSpread(expr, _) => (Some(expr), [])
209+
};
210+
let record_items =
211+
List.fold_left(
212+
(acc, expr) => {
213+
switch (expr) {
214+
| RecordItem(id, expr) => [(id, expr), ...acc]
215+
| RecordSpread(_, loc) =>
216+
switch (spread_base) {
217+
| None =>
218+
raise(
219+
SyntaxError(
220+
loc,
221+
"A record spread can only appear at the beginning of a record expression.",
222+
),
223+
)
224+
| Some(_) =>
225+
raise(
226+
SyntaxError(
227+
loc,
228+
"A record expression may only contain one record spread.",
229+
),
230+
)
231+
}
232+
}
233+
},
234+
record_items,
235+
rest,
236+
);
237+
let record_items = List.rev(record_items);
238+
record(~loc?, ~attributes?, spread_base, record_items);
239+
};
197240
let record_get = (~loc=?, ~attributes=?, a, b) =>
198241
mk(~loc?, ~attributes?, PExpRecordGet(a, b));
199242
let record_set = (~loc=?, ~attributes=?, a, b, c) =>

compiler/src/parsing/ast_helper.rei

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ type listitem('a) =
2424
| ListItem('a)
2525
| ListSpread('a, Location.t);
2626

27+
type recorditem =
28+
| RecordItem(loc(Identifier.t), expression)
29+
| RecordSpread(expression, Location.t);
30+
2731
type id = loc(Identifier.t);
2832
type str = loc(string);
2933
type loc = Location.t;
@@ -113,8 +117,15 @@ module Exp: {
113117
let tuple:
114118
(~loc: loc=?, ~attributes: attributes=?, list(expression)) => expression;
115119
let record:
116-
(~loc: loc=?, ~attributes: attributes=?, list((id, expression))) =>
120+
(
121+
~loc: loc=?,
122+
~attributes: attributes=?,
123+
option(expression),
124+
list((id, expression))
125+
) =>
117126
expression;
127+
let record_fields:
128+
(~loc: loc=?, ~attributes: attributes=?, list(recorditem)) => expression;
118129
let record_get:
119130
(~loc: loc=?, ~attributes: attributes=?, expression, id) => expression;
120131
let record_set:

0 commit comments

Comments
 (0)