Skip to content

Commit 817d8c1

Browse files
committed
specialized errors for when colon and equals are mixed up in records, function arguments, and more
1 parent 83aa0bc commit 817d8c1

9 files changed

Lines changed: 199 additions & 7 deletions

File tree

compiler/syntax/src/res_core.ml

Lines changed: 141 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,24 @@ module ErrorMessages = struct
9797
...b}` wouldn't make sense, as `b` would override every field of `a` \
9898
anyway."
9999

100+
let record_field_missing_colon =
101+
"Records use `:` when assigning fields. Example: `{field: value}`"
102+
103+
let record_pattern_field_missing_colon =
104+
"Record patterns use `:` when matching fields. Example: `{field: value}`"
105+
106+
let record_type_field_missing_colon =
107+
"Record fields in type declarations use `:`. Example: `{field: string}`"
108+
109+
let dict_field_missing_colon =
110+
"Dict entries use `:` to separate keys from values. Example: `{\"k\": v}`"
111+
112+
let labelled_argument_missing_equal =
113+
"Use `=` to pass a labelled argument. Example: `~label=value`"
114+
115+
let optional_labelled_argument_missing_equal =
116+
"Optional labelled arguments use `=?`. Example: `~label=?value`"
117+
100118
let variant_ident =
101119
"A polymorphic variant (e.g. #id) must start with an alphabetical letter \
102120
or be a number (e.g. #742)"
@@ -1412,6 +1430,13 @@ and parse_record_pattern_row_field ~attrs p =
14121430
let optional = parse_optional_label p in
14131431
let pat = parse_pattern p in
14141432
(pat, optional)
1433+
| Equal ->
1434+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
1435+
(Diagnostics.message ErrorMessages.record_pattern_field_missing_colon);
1436+
Parser.next p;
1437+
let optional = parse_optional_label p in
1438+
let pat = parse_pattern p in
1439+
(pat, optional)
14151440
| _ ->
14161441
( Ast_helper.Pat.var ~loc:label.loc ~attrs
14171442
(Location.mkloc (Longident.last label.txt) label.loc),
@@ -3060,6 +3085,19 @@ and parse_braced_or_record_expr p =
30603085
in
30613086
Parser.expect Rbrace p;
30623087
expr
3088+
| Equal ->
3089+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
3090+
(Diagnostics.message ErrorMessages.record_field_missing_colon);
3091+
Parser.next p;
3092+
let field_expr = parse_expr p in
3093+
Parser.optional p Comma |> ignore;
3094+
let expr =
3095+
parse_record_expr_with_string_keys ~start_pos
3096+
{Parsetree.lid = field; x = field_expr; opt = false}
3097+
p
3098+
in
3099+
Parser.expect Rbrace p;
3100+
expr
30633101
| _ -> (
30643102
let tag = if p.mode = ParseForTypeChecker then Some "js" else None in
30653103
let constant =
@@ -3153,6 +3191,28 @@ and parse_braced_or_record_expr p =
31533191
in
31543192
Parser.expect Rbrace p;
31553193
expr)
3194+
| Equal -> (
3195+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
3196+
(Diagnostics.message ErrorMessages.record_field_missing_colon);
3197+
Parser.next p;
3198+
let optional = parse_optional_label p in
3199+
let field_expr = parse_expr p in
3200+
match p.Parser.token with
3201+
| Rbrace ->
3202+
Parser.next p;
3203+
let loc = mk_loc start_pos p.prev_end_pos in
3204+
Ast_helper.Exp.record ~loc
3205+
[{lid = path_ident; x = field_expr; opt = optional}]
3206+
None
3207+
| _ ->
3208+
Parser.expect Comma p;
3209+
let expr =
3210+
parse_record_expr ~start_pos
3211+
[{lid = path_ident; x = field_expr; opt = optional}]
3212+
p
3213+
in
3214+
Parser.expect Rbrace p;
3215+
expr)
31563216
(* error case *)
31573217
| Lident _ ->
31583218
if p.prev_end_pos.pos_lnum < p.start_pos.pos_lnum then (
@@ -3295,6 +3355,12 @@ and parse_record_expr_row_with_string_key p :
32953355
Parser.next p;
32963356
let field_expr = parse_expr p in
32973357
Some {lid = field; x = field_expr; opt = false}
3358+
| Equal ->
3359+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
3360+
(Diagnostics.message ErrorMessages.record_field_missing_colon);
3361+
Parser.next p;
3362+
let field_expr = parse_expr p in
3363+
Some {lid = field; x = field_expr; opt = false}
32983364
| _ ->
32993365
Some
33003366
{
@@ -3324,6 +3390,13 @@ and parse_record_expr_row p :
33243390
let optional = parse_optional_label p in
33253391
let field_expr = parse_expr p in
33263392
Some {lid = field; x = field_expr; opt = optional}
3393+
| Equal ->
3394+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
3395+
(Diagnostics.message ErrorMessages.record_field_missing_colon);
3396+
Parser.next p;
3397+
let optional = parse_optional_label p in
3398+
let field_expr = parse_expr p in
3399+
Some {lid = field; x = field_expr; opt = optional}
33273400
| _ ->
33283401
let value = Ast_helper.Exp.ident ~loc:field.loc ~attrs field in
33293402
let value =
@@ -3377,6 +3450,12 @@ and parse_dict_expr_row p =
33773450
Parser.next p;
33783451
let fieldExpr = parse_expr p in
33793452
Some (field, fieldExpr)
3453+
| Equal ->
3454+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
3455+
(Diagnostics.message ErrorMessages.dict_field_missing_colon);
3456+
Parser.next p;
3457+
let fieldExpr = parse_expr p in
3458+
Some (field, fieldExpr)
33803459
| _ -> Some (field, Ast_helper.Exp.ident ~loc:field.loc field))
33813460
| _ -> None
33823461

@@ -3881,12 +3960,42 @@ and parse_argument2 p : argument option =
38813960
in
38823961
Some {label; expr}
38833962
| Colon ->
3963+
let colon_start = p.start_pos in
38843964
Parser.next p;
3885-
let typ = parse_typ_expr p in
3886-
let loc = mk_loc start_pos p.prev_end_pos in
3887-
let expr = Ast_helper.Exp.constraint_ ~loc ident_expr typ in
3888-
Some
3889-
{label = Asttypes.Labelled {txt = ident; loc = named_arg_loc}; expr}
3965+
let colon_end = p.prev_end_pos in
3966+
if Grammar.is_typ_expr_start p.Parser.token then
3967+
let typ = parse_typ_expr p in
3968+
let loc = mk_loc start_pos p.prev_end_pos in
3969+
let expr = Ast_helper.Exp.constraint_ ~loc ident_expr typ in
3970+
Some
3971+
{label = Asttypes.Labelled {txt = ident; loc = named_arg_loc}; expr}
3972+
else
3973+
let label, expr =
3974+
match p.Parser.token with
3975+
| Question ->
3976+
Parser.err ~start_pos:colon_start ~end_pos:colon_end p
3977+
(Diagnostics.message
3978+
ErrorMessages.optional_labelled_argument_missing_equal);
3979+
Parser.next p;
3980+
let expr = parse_constrained_or_coerced_expr p in
3981+
(Asttypes.Optional {txt = ident; loc = named_arg_loc}, expr)
3982+
| _ ->
3983+
Parser.err ~start_pos:colon_start ~end_pos:colon_end p
3984+
(Diagnostics.message
3985+
ErrorMessages.labelled_argument_missing_equal);
3986+
let expr =
3987+
match p.Parser.token with
3988+
| Underscore
3989+
when not (is_es6_arrow_expression ~in_ternary:false p) ->
3990+
let loc = mk_loc p.start_pos p.end_pos in
3991+
Parser.next p;
3992+
Ast_helper.Exp.ident ~loc
3993+
(Location.mkloc (Longident.Lident "_") loc)
3994+
| _ -> parse_constrained_or_coerced_expr p
3995+
in
3996+
(Asttypes.Labelled {txt = ident; loc = named_arg_loc}, expr)
3997+
in
3998+
Some {label; expr}
38903999
| _ ->
38914000
Some
38924001
{
@@ -4783,7 +4892,13 @@ and parse_string_field_declaration p =
47834892
let name_end_pos = p.end_pos in
47844893
Parser.next p;
47854894
let field_name = Location.mkloc name (mk_loc name_start_pos name_end_pos) in
4786-
Parser.expect ~grammar:Grammar.TypeExpression Colon p;
4895+
(match p.Parser.token with
4896+
| Colon -> Parser.next p
4897+
| Equal ->
4898+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
4899+
(Diagnostics.message ErrorMessages.record_type_field_missing_colon);
4900+
Parser.next p
4901+
| _ -> Parser.expect ~grammar:Grammar.TypeExpression Colon p);
47874902
let typ = parse_poly_type_expr p in
47884903
Some (Parsetree.Otag (field_name, attrs, typ))
47894904
| DotDotDot ->
@@ -4796,7 +4911,13 @@ and parse_string_field_declaration p =
47964911
(Diagnostics.message (ErrorMessages.object_quoted_field_name name));
47974912
Parser.next p;
47984913
let field_name = Location.mkloc name name_loc in
4799-
Parser.expect ~grammar:Grammar.TypeExpression Colon p;
4914+
(match p.Parser.token with
4915+
| Colon -> Parser.next p
4916+
| Equal ->
4917+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
4918+
(Diagnostics.message ErrorMessages.record_type_field_missing_colon);
4919+
Parser.next p
4920+
| _ -> Parser.expect ~grammar:Grammar.TypeExpression Colon p);
48004921
let typ = parse_poly_type_expr p in
48014922
Some (Parsetree.Otag (field_name, attrs, typ))
48024923
| _token -> None
@@ -4825,6 +4946,14 @@ and parse_field_declaration ?current_type_name_path ?inline_types_context p =
48254946
extend_current_type_name_path current_type_name_path name.txt
48264947
in
48274948
parse_poly_type_expr ?current_type_name_path ?inline_types_context p
4949+
| Equal ->
4950+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
4951+
(Diagnostics.message ErrorMessages.record_type_field_missing_colon);
4952+
Parser.next p;
4953+
let current_type_name_path =
4954+
extend_current_type_name_path current_type_name_path name.txt
4955+
in
4956+
parse_poly_type_expr ?current_type_name_path ?inline_types_context p
48284957
| _ ->
48294958
Ast_helper.Typ.constr ~loc:name.loc {name with txt = Lident name.txt} []
48304959
in
@@ -4866,6 +4995,11 @@ and parse_field_declaration_region ?current_type_name_path ?inline_types_context
48664995
| Colon ->
48674996
Parser.next p;
48684997
parse_poly_type_expr ?current_type_name_path ?inline_types_context p
4998+
| Equal ->
4999+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
5000+
(Diagnostics.message ErrorMessages.record_type_field_missing_colon);
5001+
Parser.next p;
5002+
parse_poly_type_expr ?current_type_name_path ?inline_types_context p
48695003
| _ ->
48705004
Ast_helper.Typ.constr ~loc:name.loc ~attrs
48715005
{name with txt = Lident name.txt}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Syntax error!
3+
syntax_tests/data/parsing/errors/expressions/labelledArgumentMissingEqual.res:1:16
4+
5+
1 │ let _ = fn(~foo:1)
6+
2 │ let _ = fn(~bar:?value)
7+
3 │
8+
9+
Use `=` to pass a labelled argument. Example: `~label=value`
10+
11+
12+
Syntax error!
13+
syntax_tests/data/parsing/errors/expressions/labelledArgumentMissingEqual.res:2:16
14+
15+
1 │ let _ = fn(~foo:1)
16+
2 │ let _ = fn(~bar:?value)
17+
3 │
18+
19+
Optional labelled arguments use `=?`. Example: `~label=?value`
20+
21+
let _ = fn ~foo:1
22+
let _ = fn ?bar:value
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
let _ = fn(~foo:1)
2+
let _ = fn(~bar:?value)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
Syntax error!
3+
syntax_tests/data/parsing/errors/pattern/recordFieldWrongAssignment.res:1:9
4+
5+
1 │ let {foo=bar} = record
6+
2 │
7+
8+
Record patterns use `:` when matching fields. Example: `{field: value}`
9+
10+
let { foo = bar } = record
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let {foo=bar} = record
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
Syntax error!
3+
syntax_tests/data/parsing/errors/structure/recordFieldWrongAssignment.res:1:13
4+
5+
1 │ let r = {foo=1}
6+
2 │
7+
8+
Records use `:` when assigning fields. Example: `{field: value}`
9+
10+
let r = { foo = 1 }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let r = {foo=1}
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/typeDef/recordFieldWrongAssignment.res:1:14
4+
5+
1 │ type t = {foo=string}
6+
2 │
7+
8+
Record fields in type declarations use `:`. Example: `{field: string}`
9+
10+
type nonrec t = {
11+
foo: string }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
type t = {foo=string}

0 commit comments

Comments
 (0)