Skip to content

Commit 740322b

Browse files
tsnobipzth
authored andcommitted
support let unwrap syntax (let?) (#7586)
* support let unwrap syntax (`let?`) * fix printing of let?
1 parent 7c60058 commit 740322b

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`"
@@ -2626,21 +2632,35 @@ and parse_attributes_and_binding (p : Parser.t) =
26262632
| _ -> []
26272633

26282634
(* definition ::= let [rec] let-binding { and let-binding } *)
2629-
and parse_let_bindings ~attrs ~start_pos p =
2630-
Parser.optional p Let |> ignore;
2635+
and parse_let_bindings ~unwrap ~attrs ~start_pos p =
2636+
Parser.optional p (Let {unwrap}) |> ignore;
26312637
let rec_flag =
26322638
if Parser.optional p Token.Rec then Asttypes.Recursive
26332639
else Asttypes.Nonrecursive
26342640
in
2641+
let end_pos = p.Parser.start_pos in
2642+
if rec_flag = Asttypes.Recursive && unwrap then
2643+
Parser.err ~start_pos ~end_pos p
2644+
(Diagnostics.message ErrorMessages.experimental_let_unwrap_rec);
2645+
let add_unwrap_attr ~unwrap ~start_pos ~end_pos attrs =
2646+
if unwrap then
2647+
( {Asttypes.txt = "let.unwrap"; loc = mk_loc start_pos end_pos},
2648+
Ast_payload.empty )
2649+
:: attrs
2650+
else attrs
2651+
in
2652+
let attrs = add_unwrap_attr ~unwrap ~start_pos ~end_pos attrs in
26352653
let first = parse_let_binding_body ~start_pos ~attrs p in
26362654

26372655
let rec loop p bindings =
26382656
let start_pos = p.Parser.start_pos in
2657+
let end_pos = p.Parser.end_pos in
26392658
let attrs = parse_attributes_and_binding p in
2659+
let attrs = add_unwrap_attr ~unwrap ~start_pos ~end_pos attrs in
26402660
match p.Parser.token with
26412661
| And ->
26422662
Parser.next p;
2643-
ignore (Parser.optional p Let);
2663+
ignore (Parser.optional p (Let {unwrap = false}));
26442664
(* overparse for fault tolerance *)
26452665
let let_binding = parse_let_binding_body ~start_pos ~attrs p in
26462666
loop p (let_binding :: bindings)
@@ -3336,8 +3356,10 @@ and parse_expr_block_item p =
33363356
let block_expr = parse_expr_block p in
33373357
let loc = mk_loc start_pos p.prev_end_pos in
33383358
Ast_helper.Exp.open_ ~loc od.popen_override od.popen_lid block_expr
3339-
| Let ->
3340-
let rec_flag, let_bindings = parse_let_bindings ~attrs ~start_pos p in
3359+
| Let {unwrap} ->
3360+
let rec_flag, let_bindings =
3361+
parse_let_bindings ~unwrap ~attrs ~start_pos p
3362+
in
33413363
parse_newline_or_semicolon_expr_block p;
33423364
let next =
33433365
if Grammar.is_block_expr_start p.Parser.token then parse_expr_block p
@@ -3508,7 +3530,7 @@ and parse_if_or_if_let_expression p =
35083530
Parser.expect If p;
35093531
let expr =
35103532
match p.Parser.token with
3511-
| Let ->
3533+
| Let _ ->
35123534
Parser.next p;
35133535
let if_let_expr = parse_if_let_expr start_pos p in
35143536
Parser.err ~start_pos:if_let_expr.pexp_loc.loc_start
@@ -5854,8 +5876,10 @@ and parse_structure_item_region p =
58545876
parse_newline_or_semicolon_structure p;
58555877
let loc = mk_loc start_pos p.prev_end_pos in
58565878
Some (Ast_helper.Str.open_ ~loc open_description)
5857-
| Let ->
5858-
let rec_flag, let_bindings = parse_let_bindings ~attrs ~start_pos p in
5879+
| Let {unwrap} ->
5880+
let rec_flag, let_bindings =
5881+
parse_let_bindings ~unwrap ~attrs ~start_pos p
5882+
in
58595883
parse_newline_or_semicolon_structure p;
58605884
let loc = mk_loc start_pos p.prev_end_pos in
58615885
Some (Ast_helper.Str.value ~loc rec_flag let_bindings)
@@ -6484,7 +6508,11 @@ and parse_signature_item_region p =
64846508
let start_pos = p.Parser.start_pos in
64856509
let attrs = parse_attributes p in
64866510
match p.Parser.token with
6487-
| Let ->
6511+
| Let {unwrap} ->
6512+
if unwrap then (
6513+
Parser.err ~start_pos ~end_pos:p.Parser.end_pos p
6514+
(Diagnostics.message ErrorMessages.experimental_let_unwrap_sig);
6515+
Parser.next p);
64886516
Parser.begin_region p;
64896517
let value_desc = parse_sign_let_desc ~attrs p in
64906518
parse_newline_or_semicolon_signature p;
@@ -6684,7 +6712,7 @@ and parse_module_type_declaration ~attrs ~start_pos p =
66846712

66856713
and parse_sign_let_desc ~attrs p =
66866714
let start_pos = p.Parser.start_pos in
6687-
Parser.optional p Let |> ignore;
6715+
Parser.optional p (Let {unwrap = false}) |> ignore;
66886716
let name, loc = parse_lident p in
66896717
let name = Location.mkloc name loc in
66906718
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
@@ -2093,11 +2093,20 @@ and print_type_parameter ~state {attrs; lbl; typ} cmt_tbl =
20932093

20942094
and print_value_binding ~state ~rec_flag (vb : Parsetree.value_binding) cmt_tbl
20952095
i =
2096+
let has_unwrap = ref false in
20962097
let attrs =
2097-
print_attributes ~state ~loc:vb.pvb_pat.ppat_loc vb.pvb_attributes cmt_tbl
2098-
in
2098+
vb.pvb_attributes
2099+
|> List.filter_map (function
2100+
| {Asttypes.txt = "let.unwrap"}, _ ->
2101+
has_unwrap := true;
2102+
None
2103+
| attr -> Some attr)
2104+
in
2105+
let attrs = print_attributes ~state ~loc:vb.pvb_pat.ppat_loc attrs cmt_tbl in
20992106
let header =
2100-
if i == 0 then Doc.concat [Doc.text "let "; rec_flag] else Doc.text "and "
2107+
if i == 0 then
2108+
Doc.concat [Doc.text (if !has_unwrap then "let? " else "let "); rec_flag]
2109+
else Doc.text "and "
21012110
in
21022111
match vb with
21032112
| {

compiler/syntax/src/res_scanner.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ let scan_identifier scanner =
209209
next scanner;
210210
(* TODO: this isn't great *)
211211
Token.lookup_keyword "dict{"
212+
| {ch = '?'}, "let" ->
213+
next scanner;
214+
(* TODO: this isn't great *)
215+
Token.lookup_keyword "let?"
212216
| _ -> Token.lookup_keyword str
213217

214218
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
@@ -133,7 +133,8 @@ let to_string = function
133133
| Float {f} -> "Float: " ^ f
134134
| Bang -> "!"
135135
| Semicolon -> ";"
136-
| Let -> "let"
136+
| Let {unwrap = true} -> "let?"
137+
| Let {unwrap = false} -> "let"
137138
| And -> "and"
138139
| Rec -> "rec"
139140
| Underscore -> "_"
@@ -231,7 +232,8 @@ let keyword_table = function
231232
| "if" -> If
232233
| "in" -> In
233234
| "include" -> Include
234-
| "let" -> Let
235+
| "let?" -> Let {unwrap = true}
236+
| "let" -> Let {unwrap = false}
235237
| "list{" -> List
236238
| "dict{" -> Dict
237239
| "module" -> Module
@@ -251,7 +253,7 @@ let keyword_table = function
251253

252254
let is_keyword = function
253255
| Await | And | As | Assert | Constraint | Else | Exception | External | False
254-
| For | If | In | Include | Land | Let | List | Lor | Module | Mutable | Of
256+
| For | If | In | Include | Land | Let _ | List | Lor | Module | Mutable | Of
255257
| Open | Private | Rec | Switch | True | Try | Typ | When | While | Dict ->
256258
true
257259
| _ -> 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)