Skip to content

Commit 69431b4

Browse files
authored
support let unwrap syntax (let?) (#7586)
* support let unwrap syntax (`let?`) * fix printing of let?
1 parent 66d0b64 commit 69431b4

File tree

15 files changed

+125
-28
lines changed

15 files changed

+125
-28
lines changed

compiler/syntax/src/res_core.ml

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ module ErrorMessages = struct
105105
]
106106
|> Doc.to_string ~width:80
107107

108+
let experimental_let_unwrap_rec =
109+
"let? is not allowed to be recursive. Use a regular `let` or remove `rec`."
110+
111+
let experimental_let_unwrap_sig =
112+
"let? is not allowed in signatures. Use a regular `let` instead."
113+
108114
let type_param =
109115
"A type param consists of a singlequote followed by a name like `'a` or \
110116
`'A`"
@@ -2518,21 +2524,35 @@ and parse_attributes_and_binding (p : Parser.t) =
25182524
| _ -> []
25192525

25202526
(* definition ::= let [rec] let-binding { and let-binding } *)
2521-
and parse_let_bindings ~attrs ~start_pos p =
2522-
Parser.optional p Let |> ignore;
2527+
and parse_let_bindings ~unwrap ~attrs ~start_pos p =
2528+
Parser.optional p (Let {unwrap}) |> ignore;
25232529
let rec_flag =
25242530
if Parser.optional p Token.Rec then Asttypes.Recursive
25252531
else Asttypes.Nonrecursive
25262532
in
2533+
let end_pos = p.Parser.start_pos in
2534+
if rec_flag = Asttypes.Recursive && unwrap then
2535+
Parser.err ~start_pos ~end_pos p
2536+
(Diagnostics.message ErrorMessages.experimental_let_unwrap_rec);
2537+
let add_unwrap_attr ~unwrap ~start_pos ~end_pos attrs =
2538+
if unwrap then
2539+
( {Asttypes.txt = "let.unwrap"; loc = mk_loc start_pos end_pos},
2540+
Ast_payload.empty )
2541+
:: attrs
2542+
else attrs
2543+
in
2544+
let attrs = add_unwrap_attr ~unwrap ~start_pos ~end_pos attrs in
25272545
let first = parse_let_binding_body ~start_pos ~attrs p in
25282546

25292547
let rec loop p bindings =
25302548
let start_pos = p.Parser.start_pos in
2549+
let end_pos = p.Parser.end_pos in
25312550
let attrs = parse_attributes_and_binding p in
2551+
let attrs = add_unwrap_attr ~unwrap ~start_pos ~end_pos attrs in
25322552
match p.Parser.token with
25332553
| And ->
25342554
Parser.next p;
2535-
ignore (Parser.optional p Let);
2555+
ignore (Parser.optional p (Let {unwrap = false}));
25362556
(* overparse for fault tolerance *)
25372557
let let_binding = parse_let_binding_body ~start_pos ~attrs p in
25382558
loop p (let_binding :: bindings)
@@ -3275,8 +3295,10 @@ and parse_expr_block_item p =
32753295
let block_expr = parse_expr_block p in
32763296
let loc = mk_loc start_pos p.prev_end_pos in
32773297
Ast_helper.Exp.open_ ~loc od.popen_override od.popen_lid block_expr
3278-
| Let ->
3279-
let rec_flag, let_bindings = parse_let_bindings ~attrs ~start_pos p in
3298+
| Let {unwrap} ->
3299+
let rec_flag, let_bindings =
3300+
parse_let_bindings ~unwrap ~attrs ~start_pos p
3301+
in
32803302
parse_newline_or_semicolon_expr_block p;
32813303
let next =
32823304
if Grammar.is_block_expr_start p.Parser.token then parse_expr_block p
@@ -3447,7 +3469,7 @@ and parse_if_or_if_let_expression p =
34473469
Parser.expect If p;
34483470
let expr =
34493471
match p.Parser.token with
3450-
| Let ->
3472+
| Let _ ->
34513473
Parser.next p;
34523474
let if_let_expr = parse_if_let_expr start_pos p in
34533475
Parser.err ~start_pos:if_let_expr.pexp_loc.loc_start
@@ -5787,8 +5809,10 @@ and parse_structure_item_region p =
57875809
parse_newline_or_semicolon_structure p;
57885810
let loc = mk_loc start_pos p.prev_end_pos in
57895811
Some (Ast_helper.Str.open_ ~loc open_description)
5790-
| Let ->
5791-
let rec_flag, let_bindings = parse_let_bindings ~attrs ~start_pos p in
5812+
| Let {unwrap} ->
5813+
let rec_flag, let_bindings =
5814+
parse_let_bindings ~unwrap ~attrs ~start_pos p
5815+
in
57925816
parse_newline_or_semicolon_structure p;
57935817
let loc = mk_loc start_pos p.prev_end_pos in
57945818
Some (Ast_helper.Str.value ~loc rec_flag let_bindings)
@@ -6417,7 +6441,11 @@ and parse_signature_item_region p =
64176441
let start_pos = p.Parser.start_pos in
64186442
let attrs = parse_attributes p in
64196443
match p.Parser.token with
6420-
| Let ->
6444+
| Let {unwrap} ->
6445+
if unwrap then (
6446+
Parser.err ~start_pos ~end_pos:p.Parser.end_pos p
6447+
(Diagnostics.message ErrorMessages.experimental_let_unwrap_sig);
6448+
Parser.next p);
64216449
Parser.begin_region p;
64226450
let value_desc = parse_sign_let_desc ~attrs p in
64236451
parse_newline_or_semicolon_signature p;
@@ -6617,7 +6645,7 @@ and parse_module_type_declaration ~attrs ~start_pos p =
66176645

66186646
and parse_sign_let_desc ~attrs p =
66196647
let start_pos = p.Parser.start_pos in
6620-
Parser.optional p Let |> ignore;
6648+
Parser.optional p (Let {unwrap = false}) |> ignore;
66216649
let name, loc = parse_lident p in
66226650
let name = Location.mkloc name loc in
66236651
Parser.expect Colon p;

compiler/syntax/src/res_grammar.ml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ let to_string = function
124124
| DictRows -> "rows of a dict"
125125

126126
let is_signature_item_start = function
127-
| Token.At | Let | Typ | External | Exception | Open | Include | Module | AtAt
128-
| PercentPercent ->
127+
| Token.At | Let _ | Typ | External | Exception | Open | Include | Module
128+
| AtAt | PercentPercent ->
129129
true
130130
| _ -> false
131131

@@ -162,7 +162,7 @@ let is_jsx_attribute_start = function
162162
| _ -> false
163163

164164
let is_structure_item_start = function
165-
| Token.Open | Let | Typ | External | Exception | Include | Module | AtAt
165+
| Token.Open | Let _ | Typ | External | Exception | Include | Module | AtAt
166166
| PercentPercent | At ->
167167
true
168168
| t when is_expr_start t -> true
@@ -265,7 +265,7 @@ let is_jsx_child_start = is_atomic_expr_start
265265
let is_block_expr_start = function
266266
| Token.Assert | At | Await | Backtick | Bang | Codepoint _ | Exception
267267
| False | Float _ | For | Forwardslash | ForwardslashDot | Hash | If | Int _
268-
| Lbrace | Lbracket | LessThan | Let | Lident _ | List | Lparen | Minus
268+
| Lbrace | Lbracket | LessThan | Let _ | Lident _ | List | Lparen | Minus
269269
| MinusDot | Module | Open | Percent | Plus | PlusDot | String _ | Switch
270270
| True | Try | Uident _ | Underscore | While | Dict ->
271271
true

compiler/syntax/src/res_printer.ml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,11 +2078,20 @@ and print_type_parameter ~state (attrs, lbl, typ) cmt_tbl =
20782078

20792079
and print_value_binding ~state ~rec_flag (vb : Parsetree.value_binding) cmt_tbl
20802080
i =
2081+
let has_unwrap = ref false in
20812082
let attrs =
2082-
print_attributes ~state ~loc:vb.pvb_pat.ppat_loc vb.pvb_attributes cmt_tbl
2083-
in
2083+
vb.pvb_attributes
2084+
|> List.filter_map (function
2085+
| {Asttypes.txt = "let.unwrap"}, _ ->
2086+
has_unwrap := true;
2087+
None
2088+
| attr -> Some attr)
2089+
in
2090+
let attrs = print_attributes ~state ~loc:vb.pvb_pat.ppat_loc attrs cmt_tbl in
20842091
let header =
2085-
if i == 0 then Doc.concat [Doc.text "let "; rec_flag] else Doc.text "and "
2092+
if i == 0 then
2093+
Doc.concat [Doc.text (if !has_unwrap then "let? " else "let "); rec_flag]
2094+
else Doc.text "and "
20862095
in
20872096
match vb with
20882097
| {

compiler/syntax/src/res_scanner.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ let scan_identifier scanner =
205205
next scanner;
206206
(* TODO: this isn't great *)
207207
Token.lookup_keyword "dict{"
208+
| {ch = '?'}, "let" ->
209+
next scanner;
210+
(* TODO: this isn't great *)
211+
Token.lookup_keyword "let?"
208212
| _ -> Token.lookup_keyword str
209213

210214
let scan_digits scanner ~base =

compiler/syntax/src/res_token.ml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type t =
1717
| DotDotDot
1818
| Bang
1919
| Semicolon
20-
| Let
20+
| Let of {unwrap: bool}
2121
| And
2222
| Rec
2323
| Underscore
@@ -134,7 +134,8 @@ let to_string = function
134134
| Float {f} -> "Float: " ^ f
135135
| Bang -> "!"
136136
| Semicolon -> ";"
137-
| Let -> "let"
137+
| Let {unwrap = true} -> "let?"
138+
| Let {unwrap = false} -> "let"
138139
| And -> "and"
139140
| Rec -> "rec"
140141
| Underscore -> "_"
@@ -233,7 +234,8 @@ let keyword_table = function
233234
| "if" -> If
234235
| "in" -> In
235236
| "include" -> Include
236-
| "let" -> Let
237+
| "let?" -> Let {unwrap = true}
238+
| "let" -> Let {unwrap = false}
237239
| "list{" -> List
238240
| "dict{" -> Dict
239241
| "module" -> Module
@@ -253,7 +255,7 @@ let keyword_table = function
253255

254256
let is_keyword = function
255257
| Await | And | As | Assert | Constraint | Else | Exception | External | False
256-
| For | If | In | Include | Land | Let | List | Lor | Module | Mutable | Of
258+
| For | If | In | Include | Land | Let _ | List | Lor | Module | Mutable | Of
257259
| Open | Private | Rec | Switch | True | Try | Typ | When | While | Dict ->
258260
true
259261
| _ -> false
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
Syntax error!
3+
syntax_tests/data/parsing/errors/expressions/letUnwrapRec.res:1:1-9
4+
5+
1 │ let? rec Some(baz) = someOption
6+
2 │ and Some(bar) = baz
7+
8+
let? is not allowed to be recursive. Use a regular `let` or remove `rec`.
9+
10+
let rec Some baz = someOption[@@let.unwrap ]
11+
and Some bar = baz[@@let.unwrap ]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
let? rec Some(baz) = someOption
2+
and Some(bar) = baz
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
Syntax error!
3+
syntax_tests/data/parsing/errors/signature/letUnwrap.resi:1:1-4
4+
5+
1 │ let? foo: string
6+
7+
let? is not allowed in signatures. Use a regular `let` instead.
8+
9+
val foo : string
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let? foo: string
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
let Ok foo = someResult[@@let.unwrap ]
2+
let Some bar = someOption[@@let.unwrap ]
3+
let Some baz = someOption[@@let.unwrap ]
4+
and Some bar = someOtherOption[@@let.unwrap ]

0 commit comments

Comments
 (0)