@@ -155,6 +155,22 @@ module ErrorMessages = struct
155155 let multiple_inline_record_definitions_at_same_path =
156156 " Only one inline record definition is allowed per record field. This \
157157 defines more than one inline record."
158+
159+ let keyword_field_in_expr keyword_txt =
160+ " Cannot use keyword `" ^ keyword_txt
161+ ^ " ` as a record field name. Suggestion: rename it (e.g. `" ^ keyword_txt
162+ ^ " _`)"
163+
164+ let keyword_field_in_pattern keyword_txt =
165+ " Cannot use keyword `" ^ keyword_txt
166+ ^ " ` here. Keywords are not allowed as record field names."
167+
168+ let keyword_field_in_type keyword_txt =
169+ " Cannot use keyword `" ^ keyword_txt
170+ ^ " ` as a record field name. Suggestion: rename it (e.g. `" ^ keyword_txt
171+ ^ " _`)\n If you need the field to be \" " ^ keyword_txt
172+ ^ " \" at runtime, annotate the field: `@as(\" " ^ keyword_txt ^ " \" ) "
173+ ^ keyword_txt ^ " _ : ...`"
158174end
159175
160176module InExternal = struct
@@ -403,6 +419,30 @@ let build_longident words =
403419 | [] -> assert false
404420 | hd :: tl -> List. fold_left (fun p s -> Longident. Ldot (p, s)) (Lident hd) tl
405421
422+ let emit_keyword_field_error (p : Parser.t ) ~mk_message =
423+ let keyword_txt = Token. to_string p.token in
424+ let keyword_start = p.Parser. start_pos in
425+ let keyword_end = p.Parser. end_pos in
426+ Parser. err ~start_pos: keyword_start ~end_pos: keyword_end p
427+ (Diagnostics. message (mk_message keyword_txt))
428+
429+ (* Recovers a keyword used as field name if it's probable that it's a full
430+ field name (not punning etc), by checking if there's a colon after it. *)
431+ let recover_keyword_field_name_if_probably_field p ~mk_message :
432+ (string * Location. t ) option =
433+ if
434+ Token. is_keyword p.Parser. token
435+ && Parser. lookahead p (fun st ->
436+ Parser. next st;
437+ st.Parser. token = Colon )
438+ then (
439+ emit_keyword_field_error p ~mk_message ;
440+ let loc = mk_loc p.Parser. start_pos p.Parser. end_pos in
441+ let recovered_field_name = Token. to_string p.token ^ " _" in
442+ Parser. next p;
443+ Some (recovered_field_name, loc))
444+ else None
445+
406446let make_infix_operator (p : Parser.t ) token start_pos end_pos =
407447 let stringified_token =
408448 if token = Token. Equal then (
@@ -1382,7 +1422,25 @@ and parse_record_pattern_row p =
13821422 | Underscore ->
13831423 Parser. next p;
13841424 Some (false , PatUnderscore )
1385- | _ -> None
1425+ | _ ->
1426+ if Token. is_keyword p.token then (
1427+ match
1428+ recover_keyword_field_name_if_probably_field p
1429+ ~mk_message: ErrorMessages. keyword_field_in_pattern
1430+ with
1431+ | Some (recovered_field_name , loc ) ->
1432+ Parser. expect Colon p;
1433+ let optional = parse_optional_label p in
1434+ let pat = parse_pattern p in
1435+ let field =
1436+ Location. mkloc (Longident. Lident recovered_field_name) loc
1437+ in
1438+ Some (false , PatField {lid = field; x = pat; opt = optional})
1439+ | None ->
1440+ emit_keyword_field_error p
1441+ ~mk_message: ErrorMessages. keyword_field_in_pattern;
1442+ None )
1443+ else None
13861444
13871445and parse_record_pattern ~attrs p =
13881446 let start_pos = p.start_pos in
@@ -2928,6 +2986,26 @@ and parse_braced_or_record_expr p =
29282986 let start_pos = p.Parser. start_pos in
29292987 Parser. expect Lbrace p;
29302988 match p.Parser. token with
2989+ | token when Token. is_keyword token -> (
2990+ match
2991+ recover_keyword_field_name_if_probably_field p
2992+ ~mk_message: ErrorMessages. keyword_field_in_expr
2993+ with
2994+ | Some (recovered_field_name , loc ) ->
2995+ Parser. expect Colon p;
2996+ let optional = parse_optional_label p in
2997+ let field_expr = parse_expr p in
2998+ let field = Location. mkloc (Longident. Lident recovered_field_name) loc in
2999+ let first_row = {Parsetree. lid = field; x = field_expr; opt = optional} in
3000+ let expr = parse_record_expr ~start_pos [first_row] p in
3001+ Parser. expect Rbrace p;
3002+ expr
3003+ | None ->
3004+ let expr = parse_expr_block p in
3005+ Parser. expect Rbrace p;
3006+ let loc = mk_loc start_pos p.prev_end_pos in
3007+ let braces = make_braces_attr loc in
3008+ {expr with pexp_attributes = braces :: expr .pexp_attributes})
29313009 | Rbrace ->
29323010 Parser. next p;
29333011 let loc = mk_loc start_pos p.prev_end_pos in
@@ -3244,7 +3322,25 @@ and parse_record_expr_row p :
32443322 in
32453323 Some {lid = field; x = value; opt = true }
32463324 | _ -> None )
3247- | _ -> None
3325+ | _ ->
3326+ if Token. is_keyword p.token then (
3327+ match
3328+ recover_keyword_field_name_if_probably_field p
3329+ ~mk_message: ErrorMessages. keyword_field_in_expr
3330+ with
3331+ | Some (recovered_field_name , loc ) ->
3332+ Parser. expect Colon p;
3333+ let optional = parse_optional_label p in
3334+ let field_expr = parse_expr p in
3335+ let field =
3336+ Location. mkloc (Longident. Lident recovered_field_name) loc
3337+ in
3338+ Some {lid = field; x = field_expr; opt = optional}
3339+ | None ->
3340+ emit_keyword_field_error p
3341+ ~mk_message: ErrorMessages. keyword_field_in_expr;
3342+ None )
3343+ else None
32483344
32493345and parse_dict_expr_row p =
32503346 match p.Parser. token with
@@ -4742,17 +4838,36 @@ and parse_field_declaration_region ?current_type_name_path ?inline_types_context
47424838 let loc = mk_loc start_pos typ.ptyp_loc.loc_end in
47434839 Some (Ast_helper.Type. field ~attrs ~loc ~mut ~optional name typ)
47444840 | _ ->
4745- if attrs <> [] then
4746- Parser. err ~start_pos p
4747- (Diagnostics. message
4748- " Attributes and doc comments can only be used at the beginning of a \
4749- field declaration" );
4750- if mut = Mutable then
4751- Parser. err ~start_pos p
4752- (Diagnostics. message
4753- " The `mutable` qualifier can only be used at the beginning of a \
4754- field declaration" );
4755- None
4841+ if Token. is_keyword p.token then (
4842+ match
4843+ recover_keyword_field_name_if_probably_field p
4844+ ~mk_message: ErrorMessages. keyword_field_in_type
4845+ with
4846+ | Some (recovered_field_name , name_loc ) ->
4847+ let optional = parse_optional_label p in
4848+ Parser. expect Colon p;
4849+ let typ =
4850+ parse_poly_type_expr ?current_type_name_path ?inline_types_context p
4851+ in
4852+ let loc = mk_loc start_pos typ.ptyp_loc.loc_end in
4853+ let name = Location. mkloc recovered_field_name name_loc in
4854+ Some (Ast_helper.Type. field ~attrs ~loc ~mut ~optional name typ)
4855+ | None ->
4856+ emit_keyword_field_error p
4857+ ~mk_message: ErrorMessages. keyword_field_in_type;
4858+ None )
4859+ else (
4860+ if attrs <> [] then
4861+ Parser. err ~start_pos p
4862+ (Diagnostics. message
4863+ " Attributes and doc comments can only be used at the beginning of \
4864+ a field declaration" );
4865+ if mut = Mutable then
4866+ Parser. err ~start_pos p
4867+ (Diagnostics. message
4868+ " The `mutable` qualifier can only be used at the beginning of a \
4869+ field declaration" );
4870+ None )
47564871
47574872(* record-decl ::=
47584873 * | { field-decl }
0 commit comments