Skip to content

Commit ec44763

Browse files
tsnobipzth
authored andcommitted
support let unwrap syntax (let?) (#7586)
* support let unwrap syntax (`let?`) * fix printing of let?
1 parent 555522e commit ec44763

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
@@ -110,6 +110,12 @@ module ErrorMessages = struct
110110
]
111111
|> Doc.to_string ~width:80
112112

113+
let experimental_let_unwrap_rec =
114+
"let? is not allowed to be recursive. Use a regular `let` or remove `rec`."
115+
116+
let experimental_let_unwrap_sig =
117+
"let? is not allowed in signatures. Use a regular `let` instead."
118+
113119
let type_param =
114120
"A type param consists of a singlequote followed by a name like `'a` or \
115121
`'A`"
@@ -2689,21 +2695,35 @@ and parse_attributes_and_binding (p : Parser.t) =
26892695
| _ -> []
26902696

26912697
(* definition ::= let [rec] let-binding { and let-binding } *)
2692-
and parse_let_bindings ~attrs ~start_pos p =
2693-
Parser.optional p Let |> ignore;
2698+
and parse_let_bindings ~unwrap ~attrs ~start_pos p =
2699+
Parser.optional p (Let {unwrap}) |> ignore;
26942700
let rec_flag =
26952701
if Parser.optional p Token.Rec then Asttypes.Recursive
26962702
else Asttypes.Nonrecursive
26972703
in
2704+
let end_pos = p.Parser.start_pos in
2705+
if rec_flag = Asttypes.Recursive && unwrap then
2706+
Parser.err ~start_pos ~end_pos p
2707+
(Diagnostics.message ErrorMessages.experimental_let_unwrap_rec);
2708+
let add_unwrap_attr ~unwrap ~start_pos ~end_pos attrs =
2709+
if unwrap then
2710+
( {Asttypes.txt = "let.unwrap"; loc = mk_loc start_pos end_pos},
2711+
Ast_payload.empty )
2712+
:: attrs
2713+
else attrs
2714+
in
2715+
let attrs = add_unwrap_attr ~unwrap ~start_pos ~end_pos attrs in
26982716
let first = parse_let_binding_body ~start_pos ~attrs p in
26992717

27002718
let rec loop p bindings =
27012719
let start_pos = p.Parser.start_pos in
2720+
let end_pos = p.Parser.end_pos in
27022721
let attrs = parse_attributes_and_binding p in
2722+
let attrs = add_unwrap_attr ~unwrap ~start_pos ~end_pos attrs in
27032723
match p.Parser.token with
27042724
| And ->
27052725
Parser.next p;
2706-
ignore (Parser.optional p Let);
2726+
ignore (Parser.optional p (Let {unwrap = false}));
27072727
(* overparse for fault tolerance *)
27082728
let let_binding = parse_let_binding_body ~start_pos ~attrs p in
27092729
loop p (let_binding :: bindings)
@@ -3437,8 +3457,10 @@ and parse_expr_block_item p =
34373457
let block_expr = parse_expr_block p in
34383458
let loc = mk_loc start_pos p.prev_end_pos in
34393459
Ast_helper.Exp.open_ ~loc od.popen_override od.popen_lid block_expr
3440-
| Let ->
3441-
let rec_flag, let_bindings = parse_let_bindings ~attrs ~start_pos p in
3460+
| Let {unwrap} ->
3461+
let rec_flag, let_bindings =
3462+
parse_let_bindings ~unwrap ~attrs ~start_pos p
3463+
in
34423464
parse_newline_or_semicolon_expr_block p;
34433465
let next =
34443466
if Grammar.is_block_expr_start p.Parser.token then parse_expr_block p
@@ -3609,7 +3631,7 @@ and parse_if_or_if_let_expression p =
36093631
Parser.expect If p;
36103632
let expr =
36113633
match p.Parser.token with
3612-
| Let ->
3634+
| Let _ ->
36133635
Parser.next p;
36143636
let if_let_expr = parse_if_let_expr start_pos p in
36153637
Parser.err ~start_pos:if_let_expr.pexp_loc.loc_start
@@ -6008,8 +6030,10 @@ and parse_structure_item_region p =
60086030
parse_newline_or_semicolon_structure p;
60096031
let loc = mk_loc start_pos p.prev_end_pos in
60106032
Some (Ast_helper.Str.open_ ~loc open_description)
6011-
| Let ->
6012-
let rec_flag, let_bindings = parse_let_bindings ~attrs ~start_pos p in
6033+
| Let {unwrap} ->
6034+
let rec_flag, let_bindings =
6035+
parse_let_bindings ~unwrap ~attrs ~start_pos p
6036+
in
60136037
parse_newline_or_semicolon_structure p;
60146038
let loc = mk_loc start_pos p.prev_end_pos in
60156039
Some (Ast_helper.Str.value ~loc rec_flag let_bindings)
@@ -6638,7 +6662,11 @@ and parse_signature_item_region p =
66386662
let start_pos = p.Parser.start_pos in
66396663
let attrs = parse_attributes p in
66406664
match p.Parser.token with
6641-
| Let ->
6665+
| Let {unwrap} ->
6666+
if unwrap then (
6667+
Parser.err ~start_pos ~end_pos:p.Parser.end_pos p
6668+
(Diagnostics.message ErrorMessages.experimental_let_unwrap_sig);
6669+
Parser.next p);
66426670
Parser.begin_region p;
66436671
let value_desc = parse_sign_let_desc ~attrs p in
66446672
parse_newline_or_semicolon_signature p;
@@ -6838,7 +6866,7 @@ and parse_module_type_declaration ~attrs ~start_pos p =
68386866

68396867
and parse_sign_let_desc ~attrs p =
68406868
let start_pos = p.Parser.start_pos in
6841-
Parser.optional p Let |> ignore;
6869+
Parser.optional p (Let {unwrap = false}) |> ignore;
68426870
let name, loc = parse_lident p in
68436871
let name = Location.mkloc name loc in
68446872
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)