Skip to content

Commit 7a7012b

Browse files
committed
fix disallowing mixing record and object spreads with fields of the other kind
1 parent 1a4af07 commit 7a7012b

File tree

5 files changed

+80
-17
lines changed

5 files changed

+80
-17
lines changed

compiler/ml/typedecl.ml

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type error =
2828
| Repeated_parameter
2929
| Duplicate_constructor of string
3030
| Duplicate_label of string * string option
31+
| Object_spread_with_record_field of string
3132
| Recursive_abbrev of string
3233
| Cycle_in_def of string * type_expr
3334
| Definition_mismatch of type_expr * Includecore.type_mismatch list
@@ -623,24 +624,43 @@ let transl_declaration ~type_record_as_object ~untagged_wfc env sdecl id =
623624
else if optional then Record_regular
624625
else Record_regular ),
625626
sdecl )
626-
| None ->
627-
(* Could not find record type decl for ...t: assume t is an object type and this is syntax ambiguity *)
628-
type_record_as_object := true;
629-
let fields =
630-
Ext_list.map lbls_ (fun ld ->
631-
match ld.pld_name.txt with
632-
| "..." -> Parsetree.Oinherit ld.pld_type
633-
| _ -> Otag (ld.pld_name, ld.pld_attributes, ld.pld_type))
627+
| None -> (
628+
(* Could not find record type decl for ...t. This happens when the spread
629+
target is not a record type (e.g. an object type). If additional
630+
fields are present in the record, this mixes a record field with an
631+
object-type spread and should be rejected. If only the spread exists,
632+
reinterpret as an object type for backwards compatibility. *)
633+
(* TODO: We really really need to make this "spread that needs to be resolved"
634+
concept 1st class in the AST or similar. This is quite hacky and fragile as
635+
is.*)
636+
let non_spread_field =
637+
List.find_map
638+
(fun ld -> if ld.pld_name.txt <> "..." then Some ld else None)
639+
lbls_
634640
in
635-
let sdecl =
636-
{
637-
sdecl with
638-
ptype_kind = Ptype_abstract;
639-
ptype_manifest =
640-
Some (Ast_helper.Typ.object_ ~loc:sdecl.ptype_loc fields Closed);
641-
}
642-
in
643-
(Ttype_abstract, Type_abstract, sdecl))
641+
match non_spread_field with
642+
| Some ld ->
643+
(* Error on the first record field mixed with an object spread. *)
644+
raise
645+
(Error (ld.pld_loc, Object_spread_with_record_field ld.pld_name.txt))
646+
| None ->
647+
(* Only a spread present: treat as object type (syntax ambiguity). *)
648+
type_record_as_object := true;
649+
let fields =
650+
Ext_list.map lbls_ (fun ld ->
651+
match ld.pld_name.txt with
652+
| "..." -> Parsetree.Oinherit ld.pld_type
653+
| _ -> Otag (ld.pld_name, ld.pld_attributes, ld.pld_type))
654+
in
655+
let sdecl =
656+
{
657+
sdecl with
658+
ptype_kind = Ptype_abstract;
659+
ptype_manifest =
660+
Some (Ast_helper.Typ.object_ ~loc:sdecl.ptype_loc fields Closed);
661+
}
662+
in
663+
(Ttype_abstract, Type_abstract, sdecl)))
644664
| Ptype_open -> (Ttype_open, Type_open, sdecl)
645665
in
646666
let tman, man =
@@ -2076,6 +2096,12 @@ let report_error ppf = function
20762096
"The field @{<info>%s@} is defined several times in this record. Fields \
20772097
can only be added once to a record."
20782098
s
2099+
| Object_spread_with_record_field field_name ->
2100+
fprintf ppf
2101+
"@[You cannot mix a record field with an object type spread.@\n\
2102+
Remove the record field or change it to an object field (e.g. \"%s\": \
2103+
...).@]"
2104+
field_name
20792105
| Invalid_attribute msg -> fprintf ppf "%s" msg
20802106
| Duplicate_label (s, Some record_name) ->
20812107
fprintf ppf
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/mix_object_record_spread.res:5:3-15
4+
5+
3 │ type props = {
6+
4 │ ...baseProps,
7+
5 │ label: string,
8+
6 │ }
9+
7 │
10+
11+
You cannot mix a record field with an object type spread.
12+
Remove the record field or change it to an object field (e.g. "label": ...).
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/mix_record_object_spread.res:4:6-14
4+
5+
2 │
6+
3 │ type props = {
7+
4 │ ...baseProps,
8+
5 │ "label": string,
9+
6 │ }
10+
11+
The type baseProps is not an object type
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
type baseProps = {"name": string}
2+
3+
type props = {
4+
...baseProps,
5+
label: string,
6+
}
7+
8+
let label: props = {
9+
"name": "hello",
10+
"label": "label",
11+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
type baseProps = {name: string}
2+
3+
type props = {...baseProps, "label": string}

0 commit comments

Comments
 (0)