Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.

Commit 402b9c8

Browse files
Spread anywhere feature (#692)
* let spread at anywhere when creating a list * concat accept an array; tests update * intead by fold_left to suppress reanalyze termination check * remove unused error message * printing test for spread anywhere feature * add attribute for Belt.List.concatMany * tweak * printer tests update * tests * format * remove non-error test-cases from error * pull out long branch to enhance readability * remove useless comments * simplify pattern match guard * more tests about new feature * one more edge test * fix typo in comment
1 parent 2863daf commit 402b9c8

File tree

10 files changed

+205
-66
lines changed

10 files changed

+205
-66
lines changed

src/res_core.ml

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,6 @@ module ErrorMessages = struct
8181
...b}` wouldn't make sense, as `b` would override every field of `a` \
8282
anyway."
8383

84-
let listExprSpread =
85-
"Lists can only have one `...` spread, and at the end.\n\
86-
Explanation: lists are singly-linked list, where a node contains a value \
87-
and points to the next node. `list{a, ...bc}` efficiently creates a new \
88-
item and links `bc` as its next nodes. `list{...bc, a}` would be \
89-
expensive, as it'd need to traverse `bc` and prepend each item to `a` one \
90-
by one. We therefore disallow such syntax sugar.\n\
91-
Solution: directly use `concat`."
92-
9384
let variantIdent =
9485
"A polymorphic variant (e.g. #id) must start with an alphabetical letter \
9586
or be a number (e.g. #742)"
@@ -181,6 +172,8 @@ let suppressFragileMatchWarningAttr =
181172
let makeBracesAttr loc = (Location.mkloc "ns.braces" loc, Parsetree.PStr [])
182173
let templateLiteralAttr = (Location.mknoloc "res.template", Parsetree.PStr [])
183174

175+
let spreadAttr = (Location.mknoloc "res.spread", Parsetree.PStr [])
176+
184177
type typDefOrExt =
185178
| TypeDef of {
186179
recFlag: Asttypes.rec_flag;
@@ -3705,38 +3698,60 @@ and parseTupleExpr ~first ~startPos p =
37053698
let loc = mkLoc startPos p.prevEndPos in
37063699
Ast_helper.Exp.tuple ~loc exprs
37073700

3708-
and parseSpreadExprRegion p =
3701+
and parseSpreadExprRegionWithLoc p =
3702+
let startPos = p.Parser.prevEndPos in
37093703
match p.Parser.token with
37103704
| DotDotDot ->
37113705
Parser.next p;
37123706
let expr = parseConstrainedOrCoercedExpr p in
3713-
Some (true, expr)
3707+
Some (true, expr, startPos, p.prevEndPos)
37143708
| token when Grammar.isExprStart token ->
3715-
Some (false, parseConstrainedOrCoercedExpr p)
3709+
Some (false, parseConstrainedOrCoercedExpr p, startPos, p.prevEndPos)
37163710
| _ -> None
37173711

37183712
and parseListExpr ~startPos p =
3719-
let check_all_non_spread_exp exprs =
3720-
exprs
3721-
|> List.map (fun (spread, expr) ->
3722-
if spread then
3723-
Parser.err p (Diagnostics.message ErrorMessages.listExprSpread);
3724-
expr)
3725-
|> List.rev
3713+
let split_by_spread exprs =
3714+
List.fold_left
3715+
(fun acc curr ->
3716+
match (curr, acc) with
3717+
| (true, expr, startPos, endPos), _ ->
3718+
(* find a spread expression, prepend a new sublist *)
3719+
([], Some expr, startPos, endPos) :: acc
3720+
| ( (false, expr, startPos, _endPos),
3721+
(no_spreads, spread, _accStartPos, accEndPos) :: acc ) ->
3722+
(* find a non-spread expression, and the accumulated is not empty,
3723+
* prepend to the first sublist, and update the loc of the first sublist *)
3724+
(expr :: no_spreads, spread, startPos, accEndPos) :: acc
3725+
| (false, expr, startPos, endPos), [] ->
3726+
(* find a non-spread expression, and the accumulated is empty *)
3727+
[([expr], None, startPos, endPos)])
3728+
[] exprs
3729+
in
3730+
let make_sub_expr = function
3731+
| exprs, Some spread, startPos, endPos ->
3732+
makeListExpression (mkLoc startPos endPos) exprs (Some spread)
3733+
| exprs, None, startPos, endPos ->
3734+
makeListExpression (mkLoc startPos endPos) exprs None
37263735
in
37273736
let listExprsRev =
37283737
parseCommaDelimitedReversedList p ~grammar:Grammar.ListExpr ~closing:Rbrace
3729-
~f:parseSpreadExprRegion
3738+
~f:parseSpreadExprRegionWithLoc
37303739
in
37313740
Parser.expect Rbrace p;
37323741
let loc = mkLoc startPos p.prevEndPos in
3733-
match listExprsRev with
3734-
| (true (* spread expression *), expr) :: exprs ->
3735-
let exprs = check_all_non_spread_exp exprs in
3736-
makeListExpression loc exprs (Some expr)
3742+
match split_by_spread listExprsRev with
3743+
| [] -> makeListExpression loc [] None
3744+
| [(exprs, Some spread, _, _)] -> makeListExpression loc exprs (Some spread)
3745+
| [(exprs, None, _, _)] -> makeListExpression loc exprs None
37373746
| exprs ->
3738-
let exprs = check_all_non_spread_exp exprs in
3739-
makeListExpression loc exprs None
3747+
let listExprs = List.map make_sub_expr exprs in
3748+
Ast_helper.Exp.apply ~loc
3749+
(Ast_helper.Exp.ident ~loc ~attrs:[spreadAttr]
3750+
(Location.mkloc
3751+
(Longident.Ldot
3752+
(Longident.Ldot (Longident.Lident "Belt", "List"), "concatMany"))
3753+
loc))
3754+
[(Asttypes.Nolabel, Ast_helper.Exp.array ~loc listExprs)]
37403755

37413756
(* Overparse ... and give a nice error message *)
37423757
and parseNonSpreadExp ~msg p =

src/res_parsetree_viewer.ml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,25 @@ let isTemplateLiteral expr =
618618
| Pexp_constant _ when hasTemplateLiteralAttr expr.pexp_attributes -> true
619619
| _ -> false
620620

621+
let hasSpreadAttr attrs =
622+
List.exists
623+
(fun attr ->
624+
match attr with
625+
| {Location.txt = "res.spread"}, _ -> true
626+
| _ -> false)
627+
attrs
628+
629+
let isSpreadBeltListConcat expr =
630+
match expr.pexp_desc with
631+
| Pexp_ident
632+
{
633+
txt =
634+
Longident.Ldot
635+
(Longident.Ldot (Longident.Lident "Belt", "List"), "concatMany");
636+
} ->
637+
hasSpreadAttr expr.pexp_attributes
638+
| _ -> false
639+
621640
(* Blue | Red | Green -> [Blue; Red; Green] *)
622641
let collectOrPatternChain pat =
623642
let rec loop pattern chain =

src/res_parsetree_viewer.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ val isBlockExpr : Parsetree.expression -> bool
132132
val isTemplateLiteral : Parsetree.expression -> bool
133133
val hasTemplateLiteralAttr : Parsetree.attributes -> bool
134134

135+
val isSpreadBeltListConcat : Parsetree.expression -> bool
136+
135137
val collectOrPatternChain : Parsetree.pattern -> Parsetree.pattern list
136138

137139
val processBracesAttr :

src/res_printer.ml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2980,6 +2980,9 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl =
29802980
])
29812981
| extension ->
29822982
printExtension ~customLayout ~atModuleLvl:false extension cmtTbl)
2983+
| Pexp_apply (e, [(Nolabel, {pexp_desc = Pexp_array subLists})])
2984+
when ParsetreeViewer.isSpreadBeltListConcat e ->
2985+
printBeltListConcatApply ~customLayout subLists cmtTbl
29832986
| Pexp_apply _ ->
29842987
if ParsetreeViewer.isUnaryExpression e then
29852988
printUnaryExpression ~customLayout e cmtTbl
@@ -3768,6 +3771,63 @@ and printBinaryExpression ~customLayout (expr : Parsetree.expression) cmtTbl =
37683771
])
37693772
| _ -> Doc.nil
37703773

3774+
and printBeltListConcatApply ~customLayout subLists cmtTbl =
3775+
let makeSpreadDoc commaBeforeSpread = function
3776+
| Some expr ->
3777+
Doc.concat
3778+
[
3779+
commaBeforeSpread;
3780+
Doc.dotdotdot;
3781+
(let doc = printExpressionWithComments ~customLayout expr cmtTbl in
3782+
match Parens.expr expr with
3783+
| Parens.Parenthesized -> addParens doc
3784+
| Braced braces -> printBraces doc expr braces
3785+
| Nothing -> doc);
3786+
]
3787+
| None -> Doc.nil
3788+
in
3789+
let makeSubListDoc (expressions, spread) =
3790+
let commaBeforeSpread =
3791+
match expressions with
3792+
| [] -> Doc.nil
3793+
| _ -> Doc.concat [Doc.text ","; Doc.line]
3794+
in
3795+
let spreadDoc = makeSpreadDoc commaBeforeSpread spread in
3796+
Doc.concat
3797+
[
3798+
Doc.join
3799+
~sep:(Doc.concat [Doc.text ","; Doc.line])
3800+
(List.map
3801+
(fun expr ->
3802+
let doc =
3803+
printExpressionWithComments ~customLayout expr cmtTbl
3804+
in
3805+
match Parens.expr expr with
3806+
| Parens.Parenthesized -> addParens doc
3807+
| Braced braces -> printBraces doc expr braces
3808+
| Nothing -> doc)
3809+
expressions);
3810+
spreadDoc;
3811+
]
3812+
in
3813+
Doc.group
3814+
(Doc.concat
3815+
[
3816+
Doc.text "list{";
3817+
Doc.indent
3818+
(Doc.concat
3819+
[
3820+
Doc.softLine;
3821+
Doc.join
3822+
~sep:(Doc.concat [Doc.text ","; Doc.line])
3823+
(List.map makeSubListDoc
3824+
(List.map ParsetreeViewer.collectListExpressions subLists));
3825+
]);
3826+
Doc.trailingComma;
3827+
Doc.softLine;
3828+
Doc.rbrace;
3829+
])
3830+
37713831
(* callExpr(arg1, arg2) *)
37723832
and printPexpApply ~customLayout expr cmtTbl =
37733833
match expr.pexp_desc with

tests/parsing/errors/other/expected/spread.res.txt

Lines changed: 24 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -42,79 +42,64 @@ Explanation: since records have a known, fixed shape, a spread like `{a, ...b}`
4242
4 │ let record = {...x, ...y}
4343
5 │ let {...x, ...y} = myRecord
4444
6 │
45-
7 │ let myList = list{...x, ...y}
45+
7 │ let list{...x, ...y} = myList
4646

4747
Record's `...` spread is not supported in pattern matches.
4848
Explanation: you can't collect a subset of a record's field into its own record, since a record needs an explicit declaration and that subset wouldn't have one.
4949
Solution: you need to pull out each field you want explicitly.
5050

5151

5252
Syntax error!
53-
tests/parsing/errors/other/spread.res:8:1-3
53+
tests/parsing/errors/other/spread.res:7:13-22
5454

55-
6 │
56-
7 │ let myList = list{...x, ...y}
57-
8 │ let list{...x, ...y} = myList
58-
9 │
59-
10 │ type t = {...a}
60-
61-
Lists can only have one `...` spread, and at the end.
62-
Explanation: lists are singly-linked list, where a node contains a value and points to the next node. `list{a, ...bc}` efficiently creates a new item and links `bc` as its next nodes. `list{...bc, a}` would be expensive, as it'd need to traverse `bc` and prepend each item to `a` one by one. We therefore disallow such syntax sugar.
63-
Solution: directly use `concat`.
64-
65-
66-
Syntax error!
67-
tests/parsing/errors/other/spread.res:8:13-22
68-
69-
6 │
70-
7 │ let myList = list{...x, ...y}
71-
8 │ let list{...x, ...y} = myList
72-
9 │
73-
10 │ type t = {...a}
55+
5 │ let {...x, ...y} = myRecord
56+
6 │
57+
7 │ let list{...x, ...y} = myList
58+
8 │
59+
9 │ type t = {...a}
7460

7561
List pattern matches only supports one `...` spread, at the end.
7662
Explanation: a list spread at the tail is efficient, but a spread in the middle would create new lists; out of performance concern, our pattern matching currently guarantees to never create new intermediate data.
7763

7864

7965
Syntax error!
80-
tests/parsing/errors/other/spread.res:10:11-13
66+
tests/parsing/errors/other/spread.res:9:11-13
8167

82-
8 │ let list{...x, ...y} = myList
83-
9
84-
10 │ type t = {...a}
85-
11 │ type t = Foo({...a})
86-
12 │ type t = option<foo, {...x}>
68+
7 │ let list{...x, ...y} = myList
69+
8
70+
9 │ type t = {...a}
71+
10 │ type t = Foo({...a})
72+
11 │ type t = option<foo, {...x}>
8773

8874
You're using a ... spread without extra fields. This is the same type.
8975

9076

9177
Syntax error!
92-
tests/parsing/errors/other/spread.res:11:15-17
78+
tests/parsing/errors/other/spread.res:10:15-17
9379

94-
9
95-
10 │ type t = {...a}
96-
11 │ type t = Foo({...a})
97-
12 │ type t = option<foo, {...x}>
98-
13
80+
8
81+
9 │ type t = {...a}
82+
10 │ type t = Foo({...a})
83+
11 │ type t = option<foo, {...x}>
84+
12
9985

10086
You're using a ... spread without extra fields. This is the same type.
10187

10288

10389
Syntax error!
104-
tests/parsing/errors/other/spread.res:12:23-26
90+
tests/parsing/errors/other/spread.res:11:23-26
10591

106-
10 │ type t = {...a}
107-
11 │ type t = Foo({...a})
108-
12 │ type t = option<foo, {...x}>
109-
13
92+
9 │ type t = {...a}
93+
10 │ type t = Foo({...a})
94+
11 │ type t = option<foo, {...x}>
95+
12
11096

11197
You're using a ... spread without extra fields. This is the same type.
11298

11399
let arr = [|x;y|]
114100
let [|arr;_|] = [|1;2;3|]
115101
let record = { x with y }
116102
let { x; y } = myRecord
117-
let myList = x :: y
118103
let x::y = myList
119104
type nonrec t = < a >
120105
type nonrec t =

tests/parsing/errors/other/spread.res

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ let [...arr, _] = [1, 2, 3]
44
let record = {...x, ...y}
55
let {...x, ...y} = myRecord
66

7-
let myList = list{...x, ...y}
87
let list{...x, ...y} = myList
98

109
type t = {...a}

tests/parsing/grammar/expressions/expected/list.res.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ let x = [1; 2; 3]
33
let x = [1; 2; 3]
44
let x = [(1 : int); (2 : int); (3 : int)]
55
let x = 4 :: 5 :: y
6+
let x = ((Belt.List.concatMany)[@res.spread ]) [|(1 :: x);(2 :: 3 :: x)|]
67
let x = 1 :: 2 :: (y : int list)

tests/parsing/grammar/expressions/list.res

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ let x = list{1: int, (2: int), 3: int}
1212
// spread
1313
let x = list{4, 5, ...y}
1414

15+
// spread anywhere
16+
let x = list{1, ...x, 2, 3, ...x}
17+
1518
// spread constrained expression
1619
let x = list{1, 2, ...y: list<int>}

tests/printer/expr/expected/list.res.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ let x = list{}
22
let x = list{1}
33
let x = list{1, 2}
44
let x = list{1, 2, 3}
5+
let x = xs
6+
let x = list{1, ...xs}
7+
let x = list{xs, ...ys}
8+
let x = list{...xs, ...ys}
9+
let x = list{...xs, 1, ...ys}
10+
let x = list{1, 2, ...xs, 3, ...xs}
11+
let x = Belt.List.concatMany([list{1, 2, ...x}, [list{3, ...x}]])
512

613
let x = list{
714
superLoooooooooooooooooooooooooooooongIiiiiiiiiideeeentifieeeeeeeeeeeeeeeeer,
@@ -17,3 +24,23 @@ let x = list{
1724
superLoooooooooooooooooooooooooooooongIiiiiiiiiideeeentifieeeeeeeeeeeeeeeeer,
1825
...superLoooooooooooooooooooooooooooooongListHere,
1926
}
27+
28+
let x = Belt.List.concatMany([
29+
list{
30+
superLoooooooooooooooooooooooooooooongIiiiiiiiiideeeentifieeeeeeeeeeeeeeeeer,
31+
superLoooooooooooooooooooooooooooooongIiiiiiiiiideeeentifieeeeeeeeeeeeeeeeer,
32+
...superLoooooooooooooooooooooooooooooongListHere,
33+
},
34+
list{
35+
superLoooooooooooooooooooooooooooooongIiiiiiiiiideeeentifieeeeeeeeeeeeeeeeer,
36+
...superLoooooooooooooooooooooooooooooongListHere,
37+
},
38+
])
39+
40+
let x = list{
41+
superLoooooooooooooooooooooooooooooongIiiiiiiiiideeeentifieeeeeeeeeeeeeeeeer,
42+
superLoooooooooooooooooooooooooooooongIiiiiiiiiideeeentifieeeeeeeeeeeeeeeeer,
43+
...superLoooooooooooooooooooooooooooooongListHere,
44+
superLoooooooooooooooooooooooooooooongIiiiiiiiiideeeentifieeeeeeeeeeeeeeeeer,
45+
...superLoooooooooooooooooooooooooooooongListHere,
46+
}

0 commit comments

Comments
 (0)