Skip to content

Commit f53e36d

Browse files
authored
Specialized errors for when colon and equals are mixed up (#7900)
* specialized errors for when colon and equals are mixed up in records, function arguments, and more * changelog
1 parent d3539ef commit f53e36d

File tree

10 files changed

+200
-7
lines changed

10 files changed

+200
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
- Add (dev-)dependencies to build schema. https://github.com/rescript-lang/rescript/pull/7892
3434
- Dedicated error for dict literal spreads. https://github.com/rescript-lang/rescript/pull/7901
35+
- Dedicated error message for when mixing up `:` and `=` in various positions. https://github.com/rescript-lang/rescript/pull/7900
3536

3637
#### :house: Internal
3738

compiler/syntax/src/res_core.ml

Lines changed: 141 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,24 @@ module ErrorMessages = struct
9999

100100
let dict_expr_spread = "Dict literals do not support spread (`...`) yet."
101101

102+
let record_field_missing_colon =
103+
"Records use `:` when assigning fields. Example: `{field: value}`"
104+
105+
let record_pattern_field_missing_colon =
106+
"Record patterns use `:` when matching fields. Example: `{field: value}`"
107+
108+
let record_type_field_missing_colon =
109+
"Record fields in type declarations use `:`. Example: `{field: string}`"
110+
111+
let dict_field_missing_colon =
112+
"Dict entries use `:` to separate keys from values. Example: `{\"k\": v}`"
113+
114+
let labelled_argument_missing_equal =
115+
"Use `=` to pass a labelled argument. Example: `~label=value`"
116+
117+
let optional_labelled_argument_missing_equal =
118+
"Optional labelled arguments use `=?`. Example: `~label=?value`"
119+
102120
let variant_ident =
103121
"A polymorphic variant (e.g. #id) must start with an alphabetical letter \
104122
or be a number (e.g. #742)"
@@ -1414,6 +1432,13 @@ and parse_record_pattern_row_field ~attrs p =
14141432
let optional = parse_optional_label p in
14151433
let pat = parse_pattern p in
14161434
(pat, optional)
1435+
| Equal ->
1436+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
1437+
(Diagnostics.message ErrorMessages.record_pattern_field_missing_colon);
1438+
Parser.next p;
1439+
let optional = parse_optional_label p in
1440+
let pat = parse_pattern p in
1441+
(pat, optional)
14171442
| _ ->
14181443
( Ast_helper.Pat.var ~loc:label.loc ~attrs
14191444
(Location.mkloc (Longident.last label.txt) label.loc),
@@ -3062,6 +3087,19 @@ and parse_braced_or_record_expr p =
30623087
in
30633088
Parser.expect Rbrace p;
30643089
expr
3090+
| Equal ->
3091+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
3092+
(Diagnostics.message ErrorMessages.record_field_missing_colon);
3093+
Parser.next p;
3094+
let field_expr = parse_expr p in
3095+
Parser.optional p Comma |> ignore;
3096+
let expr =
3097+
parse_record_expr_with_string_keys ~start_pos
3098+
{Parsetree.lid = field; x = field_expr; opt = false}
3099+
p
3100+
in
3101+
Parser.expect Rbrace p;
3102+
expr
30653103
| _ -> (
30663104
let tag = if p.mode = ParseForTypeChecker then Some "js" else None in
30673105
let constant =
@@ -3155,6 +3193,28 @@ and parse_braced_or_record_expr p =
31553193
in
31563194
Parser.expect Rbrace p;
31573195
expr)
3196+
| Equal -> (
3197+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
3198+
(Diagnostics.message ErrorMessages.record_field_missing_colon);
3199+
Parser.next p;
3200+
let optional = parse_optional_label p in
3201+
let field_expr = parse_expr p in
3202+
match p.Parser.token with
3203+
| Rbrace ->
3204+
Parser.next p;
3205+
let loc = mk_loc start_pos p.prev_end_pos in
3206+
Ast_helper.Exp.record ~loc
3207+
[{lid = path_ident; x = field_expr; opt = optional}]
3208+
None
3209+
| _ ->
3210+
Parser.expect Comma p;
3211+
let expr =
3212+
parse_record_expr ~start_pos
3213+
[{lid = path_ident; x = field_expr; opt = optional}]
3214+
p
3215+
in
3216+
Parser.expect Rbrace p;
3217+
expr)
31583218
(* error case *)
31593219
| Lident _ ->
31603220
if p.prev_end_pos.pos_lnum < p.start_pos.pos_lnum then (
@@ -3297,6 +3357,12 @@ and parse_record_expr_row_with_string_key p :
32973357
Parser.next p;
32983358
let field_expr = parse_expr p in
32993359
Some {lid = field; x = field_expr; opt = false}
3360+
| Equal ->
3361+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
3362+
(Diagnostics.message ErrorMessages.record_field_missing_colon);
3363+
Parser.next p;
3364+
let field_expr = parse_expr p in
3365+
Some {lid = field; x = field_expr; opt = false}
33003366
| _ ->
33013367
Some
33023368
{
@@ -3326,6 +3392,13 @@ and parse_record_expr_row p :
33263392
let optional = parse_optional_label p in
33273393
let field_expr = parse_expr p in
33283394
Some {lid = field; x = field_expr; opt = optional}
3395+
| Equal ->
3396+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
3397+
(Diagnostics.message ErrorMessages.record_field_missing_colon);
3398+
Parser.next p;
3399+
let optional = parse_optional_label p in
3400+
let field_expr = parse_expr p in
3401+
Some {lid = field; x = field_expr; opt = optional}
33293402
| _ ->
33303403
let value = Ast_helper.Exp.ident ~loc:field.loc ~attrs field in
33313404
let value =
@@ -3385,6 +3458,12 @@ and parse_dict_expr_row p =
33853458
Parser.next p;
33863459
let fieldExpr = parse_expr p in
33873460
Some (field, fieldExpr)
3461+
| Equal ->
3462+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
3463+
(Diagnostics.message ErrorMessages.dict_field_missing_colon);
3464+
Parser.next p;
3465+
let fieldExpr = parse_expr p in
3466+
Some (field, fieldExpr)
33883467
| _ -> Some (field, Ast_helper.Exp.ident ~loc:field.loc field))
33893468
| _ -> None
33903469

@@ -3889,12 +3968,42 @@ and parse_argument2 p : argument option =
38893968
in
38903969
Some {label; expr}
38913970
| Colon ->
3971+
let colon_start = p.start_pos in
38923972
Parser.next p;
3893-
let typ = parse_typ_expr p in
3894-
let loc = mk_loc start_pos p.prev_end_pos in
3895-
let expr = Ast_helper.Exp.constraint_ ~loc ident_expr typ in
3896-
Some
3897-
{label = Asttypes.Labelled {txt = ident; loc = named_arg_loc}; expr}
3973+
let colon_end = p.prev_end_pos in
3974+
if Grammar.is_typ_expr_start p.Parser.token then
3975+
let typ = parse_typ_expr p in
3976+
let loc = mk_loc start_pos p.prev_end_pos in
3977+
let expr = Ast_helper.Exp.constraint_ ~loc ident_expr typ in
3978+
Some
3979+
{label = Asttypes.Labelled {txt = ident; loc = named_arg_loc}; expr}
3980+
else
3981+
let label, expr =
3982+
match p.Parser.token with
3983+
| Question ->
3984+
Parser.err ~start_pos:colon_start ~end_pos:colon_end p
3985+
(Diagnostics.message
3986+
ErrorMessages.optional_labelled_argument_missing_equal);
3987+
Parser.next p;
3988+
let expr = parse_constrained_or_coerced_expr p in
3989+
(Asttypes.Optional {txt = ident; loc = named_arg_loc}, expr)
3990+
| _ ->
3991+
Parser.err ~start_pos:colon_start ~end_pos:colon_end p
3992+
(Diagnostics.message
3993+
ErrorMessages.labelled_argument_missing_equal);
3994+
let expr =
3995+
match p.Parser.token with
3996+
| Underscore
3997+
when not (is_es6_arrow_expression ~in_ternary:false p) ->
3998+
let loc = mk_loc p.start_pos p.end_pos in
3999+
Parser.next p;
4000+
Ast_helper.Exp.ident ~loc
4001+
(Location.mkloc (Longident.Lident "_") loc)
4002+
| _ -> parse_constrained_or_coerced_expr p
4003+
in
4004+
(Asttypes.Labelled {txt = ident; loc = named_arg_loc}, expr)
4005+
in
4006+
Some {label; expr}
38984007
| _ ->
38994008
Some
39004009
{
@@ -4791,7 +4900,13 @@ and parse_string_field_declaration p =
47914900
let name_end_pos = p.end_pos in
47924901
Parser.next p;
47934902
let field_name = Location.mkloc name (mk_loc name_start_pos name_end_pos) in
4794-
Parser.expect ~grammar:Grammar.TypeExpression Colon p;
4903+
(match p.Parser.token with
4904+
| Colon -> Parser.next p
4905+
| Equal ->
4906+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
4907+
(Diagnostics.message ErrorMessages.record_type_field_missing_colon);
4908+
Parser.next p
4909+
| _ -> Parser.expect ~grammar:Grammar.TypeExpression Colon p);
47954910
let typ = parse_poly_type_expr p in
47964911
Some (Parsetree.Otag (field_name, attrs, typ))
47974912
| DotDotDot ->
@@ -4804,7 +4919,13 @@ and parse_string_field_declaration p =
48044919
(Diagnostics.message (ErrorMessages.object_quoted_field_name name));
48054920
Parser.next p;
48064921
let field_name = Location.mkloc name name_loc in
4807-
Parser.expect ~grammar:Grammar.TypeExpression Colon p;
4922+
(match p.Parser.token with
4923+
| Colon -> Parser.next p
4924+
| Equal ->
4925+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
4926+
(Diagnostics.message ErrorMessages.record_type_field_missing_colon);
4927+
Parser.next p
4928+
| _ -> Parser.expect ~grammar:Grammar.TypeExpression Colon p);
48084929
let typ = parse_poly_type_expr p in
48094930
Some (Parsetree.Otag (field_name, attrs, typ))
48104931
| _token -> None
@@ -4833,6 +4954,14 @@ and parse_field_declaration ?current_type_name_path ?inline_types_context p =
48334954
extend_current_type_name_path current_type_name_path name.txt
48344955
in
48354956
parse_poly_type_expr ?current_type_name_path ?inline_types_context p
4957+
| Equal ->
4958+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
4959+
(Diagnostics.message ErrorMessages.record_type_field_missing_colon);
4960+
Parser.next p;
4961+
let current_type_name_path =
4962+
extend_current_type_name_path current_type_name_path name.txt
4963+
in
4964+
parse_poly_type_expr ?current_type_name_path ?inline_types_context p
48364965
| _ ->
48374966
Ast_helper.Typ.constr ~loc:name.loc {name with txt = Lident name.txt} []
48384967
in
@@ -4874,6 +5003,11 @@ and parse_field_declaration_region ?current_type_name_path ?inline_types_context
48745003
| Colon ->
48755004
Parser.next p;
48765005
parse_poly_type_expr ?current_type_name_path ?inline_types_context p
5006+
| Equal ->
5007+
Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
5008+
(Diagnostics.message ErrorMessages.record_type_field_missing_colon);
5009+
Parser.next p;
5010+
parse_poly_type_expr ?current_type_name_path ?inline_types_context p
48775011
| _ ->
48785012
Ast_helper.Typ.constr ~loc:name.loc ~attrs
48795013
{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)