Skip to content

Commit 9f65e63

Browse files
authored
Make inline record fields that overlap with a variant's tag a compile error (#7875)
* Make it a compile error to have inline record fields whose fields conflict with the variant tag * Handle @as attribute * Format code * Add super error fixtures * Check variants without a @tag decorator * Use effective field name instead of checking actual field name and @as separately * Format OCaml * Add CHANGELOG entry * Reuse process_tag_type instead of duplicating logic in process_as_name * Clarify tag/as conflict error message by using field name from type instead of only showing runtime name * Quote constructor name
1 parent fbb8a4e commit 9f65e63

8 files changed

+77
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#### :bug: Bug fix
2222

2323
- Fix result examples. https://github.com/rescript-lang/rescript/pull/7914
24+
- Make inline record fields that overlap with a variant's tag a compile error. https://github.com/rescript-lang/rescript/pull/7875
2425

2526
#### :memo: Documentation
2627

compiler/ml/ast_untagged_variants.ml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type error =
5757
| Duplicated_bs_as
5858
| InvalidVariantTagAnnotation
5959
| InvalidUntaggedVariantDefinition of untagged_error
60+
| TagFieldNameConflict of string * string * string
6061
exception Error of Location.t * error
6162

6263
let report_error ppf =
@@ -90,6 +91,12 @@ let report_error ppf =
9091
| DuplicateLiteral s -> "Duplicate literal " ^ s ^ "."
9192
| ConstructorMoreThanOneArg name ->
9293
"Constructor " ^ name ^ " has more than one argument.")
94+
| TagFieldNameConflict (constructor_name, field_name, runtime_value) ->
95+
fprintf ppf
96+
"Constructor \"%s\": the @tag name \"%s\" conflicts with the runtime \
97+
value of inline record field \"%s\". Use a different @tag name or \
98+
rename the field."
99+
constructor_name runtime_value field_name
93100

94101
(* Type of the runtime representation of an untagged block (case with payoad) *)
95102
type block_type =
@@ -462,12 +469,44 @@ let names_from_type_variant ?(is_untagged_def = false) ~env
462469
let blocks = Ext_array.reverse_of_list blocks in
463470
Some {consts; blocks}
464471

472+
let check_tag_field_conflicts (cstrs : Types.constructor_declaration list) =
473+
List.iter
474+
(fun (cstr : Types.constructor_declaration) ->
475+
let constructor_name = Ident.name cstr.cd_id in
476+
let effective_tag_name =
477+
match process_tag_name cstr.cd_attributes with
478+
| Some explicit_tag -> explicit_tag
479+
| None -> constructor_name
480+
in
481+
match cstr.cd_args with
482+
| Cstr_record fields ->
483+
List.iter
484+
(fun (field : Types.label_declaration) ->
485+
let field_name = Ident.name field.ld_id in
486+
let effective_field_name =
487+
match process_tag_type field.ld_attributes with
488+
| Some (String as_name) -> as_name
489+
(* @as payload types other than string have no effect on record fields *)
490+
| Some _ | None -> field_name
491+
in
492+
(* Check if effective field name conflicts with tag *)
493+
if effective_field_name = effective_tag_name then
494+
raise
495+
(Error
496+
( cstr.cd_loc,
497+
TagFieldNameConflict
498+
(constructor_name, field_name, effective_field_name) )))
499+
fields
500+
| _ -> ())
501+
cstrs
502+
465503
type well_formedness_check = {
466504
is_untagged_def: bool;
467505
cstrs: Types.constructor_declaration list;
468506
}
469507

470508
let check_well_formed ~env {is_untagged_def; cstrs} =
509+
check_tag_field_conflicts cstrs;
471510
ignore (names_from_type_variant ~env ~is_untagged_def cstrs)
472511

473512
let has_undefined_literal attrs = process_tag_type attrs = Some Undefined
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/duplicate_as_tag_inline_record.res:1:35-37
4+
5+
1 │ type animal = Cat({@as("catName") @as("catName2") name: string})
6+
2 │
7+
8+
duplicate @as
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/variant_tag_overlaps_with_field.res:2:15-33
4+
5+
1 │ @tag("name")
6+
2 │ type animal = Cat({name: string})
7+
3 │
8+
4 │ let cat = Cat({name: "my cat"})
9+
10+
Constructor "Cat": the @tag name "name" conflicts with the runtime value of inline record field "name". Use a different @tag name or rename the field.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/variant_tag_overlaps_with_field_as.res:2:15-48
4+
5+
1 │ @tag("name")
6+
2 │ type animal = Cat({@as("name") catName: string})
7+
3 │
8+
4 │ let cat = Cat({catName: "my cat"})
9+
10+
Constructor "Cat": the @tag name "name" conflicts with the runtime value of inline record field "catName". Use a different @tag name or rename the field.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
type animal = Cat({@as("catName") @as("catName2") name: string})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@tag("name")
2+
type animal = Cat({name: string})
3+
4+
let cat = Cat({name: "my cat"})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@tag("name")
2+
type animal = Cat({@as("name") catName: string})
3+
4+
let cat = Cat({catName: "my cat"})

0 commit comments

Comments
 (0)