Skip to content

Commit d37f026

Browse files
committed
error recovery
1 parent 8b3f0d0 commit d37f026

File tree

3 files changed

+131
-91
lines changed

3 files changed

+131
-91
lines changed

compiler/syntax/src/res_core.ml

Lines changed: 128 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,27 @@ let build_longident words =
398398
| [] -> assert false
399399
| hd :: tl -> List.fold_left (fun p s -> Longident.Ldot (p, s)) (Lident hd) tl
400400

401+
(* Recovers a keyword used as field name if it's probable that it's a full
402+
field name (not punning etc), by checking if there's a colon after it. *)
403+
let recover_keyword_field_name_if_probably_field p ~mk_message :
404+
(string * Location.t) option =
405+
if
406+
Token.is_keyword p.Parser.token
407+
&& Parser.lookahead p (fun st ->
408+
Parser.next st;
409+
st.Parser.token = Colon)
410+
then (
411+
let keyword_txt = Token.to_string p.token in
412+
let keyword_start = p.Parser.start_pos in
413+
let keyword_end = p.Parser.end_pos in
414+
Parser.err ~start_pos:keyword_start ~end_pos:keyword_end p
415+
(Diagnostics.message (mk_message keyword_txt));
416+
let loc = mk_loc keyword_start keyword_end in
417+
let recovered_field_name = keyword_txt ^ "_" in
418+
Parser.next p;
419+
Some (recovered_field_name, loc))
420+
else None
421+
401422
let make_infix_operator (p : Parser.t) token start_pos end_pos =
402423
let stringified_token =
403424
if token = Token.Equal then (
@@ -1379,16 +1400,31 @@ and parse_record_pattern_row p =
13791400
Some (false, PatUnderscore)
13801401
| _ ->
13811402
if Token.is_keyword p.token then (
1382-
let keyword_txt = Token.to_string p.token in
1383-
let keyword_start = p.Parser.start_pos in
1384-
let keyword_end = p.Parser.end_pos in
1385-
let message =
1386-
"Cannot use keyword `" ^ keyword_txt
1387-
^ "` here. Keywords are not allowed as record field names."
1388-
in
1389-
Parser.err ~start_pos:keyword_start ~end_pos:keyword_end p
1390-
(Diagnostics.message message);
1391-
None)
1403+
match
1404+
recover_keyword_field_name_if_probably_field p
1405+
~mk_message:(fun keyword_txt ->
1406+
"Cannot use keyword `" ^ keyword_txt
1407+
^ "` here. Keywords are not allowed as record field names.")
1408+
with
1409+
| Some (recovered_field_name, loc) ->
1410+
Parser.expect Colon p;
1411+
let optional = parse_optional_label p in
1412+
let pat = parse_pattern p in
1413+
let field =
1414+
Location.mkloc (Longident.Lident recovered_field_name) loc
1415+
in
1416+
Some (false, PatField {lid = field; x = pat; opt = optional})
1417+
| None ->
1418+
let keyword_txt = Token.to_string p.token in
1419+
let keyword_start = p.Parser.start_pos in
1420+
let keyword_end = p.Parser.end_pos in
1421+
let message =
1422+
"Cannot use keyword `" ^ keyword_txt
1423+
^ "` here. Keywords are not allowed as record field names."
1424+
in
1425+
Parser.err ~start_pos:keyword_start ~end_pos:keyword_end p
1426+
(Diagnostics.message message);
1427+
None)
13921428
else None
13931429

13941430
and parse_record_pattern ~attrs p =
@@ -2935,25 +2971,29 @@ and parse_braced_or_record_expr p =
29352971
let start_pos = p.Parser.start_pos in
29362972
Parser.expect Lbrace p;
29372973
match p.Parser.token with
2938-
| token when Token.is_keyword token ->
2939-
let colon_follows =
2940-
Parser.lookahead p (fun st ->
2941-
Parser.next st;
2942-
st.Parser.token = Colon)
2943-
in
2944-
(* If a colon follows then this is likely to be a record field. *)
2945-
(if colon_follows then
2946-
let keyword_txt = Token.to_string token in
2947-
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
2948-
(Diagnostics.message
2949-
("Cannot use keyword `" ^ keyword_txt
2950-
^ "` as a record field name. Suggestion: rename it (e.g. `"
2951-
^ keyword_txt ^ "_`)")));
2952-
let expr = parse_expr_block p in
2953-
Parser.expect Rbrace p;
2954-
let loc = mk_loc start_pos p.prev_end_pos in
2955-
let braces = make_braces_attr loc in
2956-
{expr with pexp_attributes = braces :: expr.pexp_attributes}
2974+
| token when Token.is_keyword token -> (
2975+
match
2976+
recover_keyword_field_name_if_probably_field p
2977+
~mk_message:(fun keyword_txt ->
2978+
"Cannot use keyword `" ^ keyword_txt
2979+
^ "` as a record field name. Suggestion: rename it (e.g. `"
2980+
^ keyword_txt ^ "_`)")
2981+
with
2982+
| Some (recovered_field_name, loc) ->
2983+
Parser.expect Colon p;
2984+
let optional = parse_optional_label p in
2985+
let field_expr = parse_expr p in
2986+
let field = Location.mkloc (Longident.Lident recovered_field_name) loc in
2987+
let first_row = {Parsetree.lid = field; x = field_expr; opt = optional} in
2988+
let expr = parse_record_expr ~start_pos [first_row] p in
2989+
Parser.expect Rbrace p;
2990+
expr
2991+
| None ->
2992+
let expr = parse_expr_block p in
2993+
Parser.expect Rbrace p;
2994+
let loc = mk_loc start_pos p.prev_end_pos in
2995+
let braces = make_braces_attr loc in
2996+
{expr with pexp_attributes = braces :: expr.pexp_attributes})
29572997
| Rbrace ->
29582998
Parser.next p;
29592999
let loc = mk_loc start_pos p.prev_end_pos in
@@ -3272,17 +3312,33 @@ and parse_record_expr_row p :
32723312
| _ -> None)
32733313
| _ ->
32743314
if Token.is_keyword p.token then (
3275-
let keyword_txt = Token.to_string p.token in
3276-
let keyword_start = p.Parser.start_pos in
3277-
let keyword_end = p.Parser.end_pos in
3278-
let message =
3279-
"Cannot use keyword `" ^ keyword_txt
3280-
^ "` as a record field name. Suggestion: rename it (e.g. `"
3281-
^ keyword_txt ^ "_`)"
3282-
in
3283-
Parser.err ~start_pos:keyword_start ~end_pos:keyword_end p
3284-
(Diagnostics.message message);
3285-
None)
3315+
match
3316+
recover_keyword_field_name_if_probably_field p
3317+
~mk_message:(fun keyword_txt ->
3318+
"Cannot use keyword `" ^ keyword_txt
3319+
^ "` as a record field name. Suggestion: rename it (e.g. `"
3320+
^ keyword_txt ^ "_`)")
3321+
with
3322+
| Some (recovered_field_name, loc) ->
3323+
Parser.expect Colon p;
3324+
let optional = parse_optional_label p in
3325+
let field_expr = parse_expr p in
3326+
let field =
3327+
Location.mkloc (Longident.Lident recovered_field_name) loc
3328+
in
3329+
Some {lid = field; x = field_expr; opt = optional}
3330+
| None ->
3331+
let keyword_txt = Token.to_string p.token in
3332+
let keyword_start = p.Parser.start_pos in
3333+
let keyword_end = p.Parser.end_pos in
3334+
let message =
3335+
"Cannot use keyword `" ^ keyword_txt
3336+
^ "` as a record field name. Suggestion: rename it (e.g. `"
3337+
^ keyword_txt ^ "_`)"
3338+
in
3339+
Parser.err ~start_pos:keyword_start ~end_pos:keyword_end p
3340+
(Diagnostics.message message);
3341+
None)
32863342
else None
32873343

32883344
and parse_dict_expr_row p =
@@ -4781,19 +4837,38 @@ and parse_field_declaration_region ?current_type_name_path ?inline_types_context
47814837
Some (Ast_helper.Type.field ~attrs ~loc ~mut ~optional name typ)
47824838
| _ ->
47834839
if Token.is_keyword p.token then (
4784-
let keyword_txt = Token.to_string p.token in
4785-
let keyword_start = p.Parser.start_pos in
4786-
let keyword_end = p.Parser.end_pos in
4787-
let message =
4788-
"Cannot use keyword `" ^ keyword_txt
4789-
^ "` as a record field name. Suggestion: rename it (e.g. `"
4790-
^ keyword_txt ^ "_`)\n" ^ " If you need the field to be \""
4791-
^ keyword_txt ^ "\" at runtime, annotate the field: `@as(\""
4792-
^ keyword_txt ^ "\") " ^ keyword_txt ^ "_ : ...`"
4793-
in
4794-
Parser.err ~start_pos:keyword_start ~end_pos:keyword_end p
4795-
(Diagnostics.message message);
4796-
None)
4840+
match
4841+
recover_keyword_field_name_if_probably_field p
4842+
~mk_message:(fun keyword_txt ->
4843+
"Cannot use keyword `" ^ keyword_txt
4844+
^ "` as a record field name. Suggestion: rename it (e.g. `"
4845+
^ keyword_txt ^ "_`)\n" ^ " If you need the field to be \""
4846+
^ keyword_txt ^ "\" at runtime, annotate the field: `@as(\""
4847+
^ keyword_txt ^ "\") " ^ keyword_txt ^ "_ : ...`")
4848+
with
4849+
| Some (recovered_field_name, name_loc) ->
4850+
let optional = parse_optional_label p in
4851+
Parser.expect Colon p;
4852+
let typ =
4853+
parse_poly_type_expr ?current_type_name_path ?inline_types_context p
4854+
in
4855+
let loc = mk_loc start_pos typ.ptyp_loc.loc_end in
4856+
let name = Location.mkloc recovered_field_name name_loc in
4857+
Some (Ast_helper.Type.field ~attrs ~loc ~mut ~optional name typ)
4858+
| None ->
4859+
let keyword_txt = Token.to_string p.token in
4860+
let keyword_start = p.Parser.start_pos in
4861+
let keyword_end = p.Parser.end_pos in
4862+
let message =
4863+
"Cannot use keyword `" ^ keyword_txt
4864+
^ "` as a record field name. Suggestion: rename it (e.g. `"
4865+
^ keyword_txt ^ "_`)\n" ^ " If you need the field to be \""
4866+
^ keyword_txt ^ "\" at runtime, annotate the field: `@as(\""
4867+
^ keyword_txt ^ "\") " ^ keyword_txt ^ "_ : ...`"
4868+
in
4869+
Parser.err ~start_pos:keyword_start ~end_pos:keyword_end p
4870+
(Diagnostics.message message);
4871+
None)
47974872
else (
47984873
if attrs <> [] then
47994874
Parser.err ~start_pos p

tests/syntax_tests/data/parsing/errors/structure/expected/recordFieldKeywordInExpr.res.txt

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,4 @@
77

88
Cannot use keyword `type` as a record field name. Suggestion: rename it (e.g. `type_`)
99

10-
11-
Syntax error!
12-
syntax_tests/data/parsing/errors/structure/recordFieldKeywordInExpr.res:1:15-16
13-
14-
1 │ let r = {type: 1}
15-
2 │
16-
17-
consecutive statements on a line must be separated by ';' or a newline
18-
19-
let r = ((([%rescript.exprhole ] : [%rescript.typehole ]))[@res.braces ])
20-
;;1
10+
let r = { type = 1 }

tests/syntax_tests/data/parsing/errors/structure/expected/recordFieldKeywordInType.res.txt

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,6 @@
1111
Cannot use keyword `type` as a record field name. Suggestion: rename it (e.g. `type_`)
1212
If you need the field to be "type" at runtime, annotate the field: `@as("type") type_ : ...`
1313

14-
15-
Syntax error!
16-
syntax_tests/data/parsing/errors/structure/recordFieldKeywordInType.res:3:7
17-
18-
1 │ type r = {
19-
2 │ id: string,
20-
3 │ type: int,
21-
4 │ }
22-
5 │
23-
24-
I'm not sure what to parse here when looking at ":".
25-
26-
27-
Syntax error!
28-
syntax_tests/data/parsing/errors/structure/recordFieldKeywordInType.res:3:12
29-
30-
1 │ type r = {
31-
2 │ id: string,
32-
3 │ type: int,
33-
4 │ }
34-
5 │
35-
36-
I'm not sure what to parse here when looking at ",".
37-
3814
type nonrec r = {
39-
id: string }
40-
type nonrec _
41-
;;int
15+
id: string ;
16+
type: int }

0 commit comments

Comments
 (0)