Skip to content

Commit a87e656

Browse files
committed
Fix missing checks for duplicate literals in variants with payloads.
Fixes #7438 In variant definitions without payloads, an error is reported if two cases have the same literal `@as(literal)`. The check so far was missing for cases with payload, where the literal goes into the tag. However it's perfectly possible to use the same tag in a case without payload, and one with payloads, as the two representations don't overlap.
1 parent a745e6f commit a87e656

File tree

8 files changed

+103
-21
lines changed

8 files changed

+103
-21
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
- Fix leading comments removed when braces inside JSX contains `let` assignment. https://github.com/rescript-lang/rescript/pull/7424
3333
- Fix JSON escaping in code editor analysis: JSON was not always escaped properly, which prevented code actions from being available in certain situations https://github.com/rescript-lang/rescript/pull/7435
3434
- Fix regression in pattern matching for optional fields containing variants. https://github.com/rescript-lang/rescript/pull/7440
35+
- Fix missing checks for duplicate literals in variants with payloads. https://github.com/rescript-lang/rescript/pull/7441
3536

3637
#### :house: Internal
3738

compiler/ml/ast_untagged_variants.ml

+36-21
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,10 @@ let is_nullary_variant (x : Types.constructor_arguments) =
258258
let check_invariant ~is_untagged_def ~(consts : (Location.t * tag) list)
259259
~(blocks : (Location.t * block) list) =
260260
let module StringSet = Set.Make (String) in
261-
let string_literals = ref StringSet.empty in
262-
let nonstring_literals = ref StringSet.empty in
261+
let string_literals_consts = ref StringSet.empty in
262+
let string_literals_blocks = ref StringSet.empty in
263+
let nonstring_literals_consts = ref StringSet.empty in
264+
let nonstring_literals_blocks = ref StringSet.empty in
263265
let instance_types = Hashtbl.create 1 in
264266
let function_types = ref 0 in
265267
let object_types = ref 0 in
@@ -268,15 +270,21 @@ let check_invariant ~is_untagged_def ~(consts : (Location.t * tag) list)
268270
let bigint_types = ref 0 in
269271
let boolean_types = ref 0 in
270272
let unknown_types = ref 0 in
271-
let add_string_literal ~loc s =
272-
if StringSet.mem s !string_literals then
273+
let add_string_literal ~is_const ~loc s =
274+
let set =
275+
if is_const then string_literals_consts else string_literals_blocks
276+
in
277+
if StringSet.mem s !set then
273278
raise (Error (loc, InvalidUntaggedVariantDefinition (DuplicateLiteral s)));
274-
string_literals := StringSet.add s !string_literals
279+
set := StringSet.add s !set
275280
in
276-
let add_nonstring_literal ~loc s =
277-
if StringSet.mem s !nonstring_literals then
281+
let add_nonstring_literal ~is_const ~loc s =
282+
let set =
283+
if is_const then nonstring_literals_consts else nonstring_literals_blocks
284+
in
285+
if StringSet.mem s !set then
278286
raise (Error (loc, InvalidUntaggedVariantDefinition (DuplicateLiteral s)));
279-
nonstring_literals := StringSet.add s !nonstring_literals
287+
set := StringSet.add s !set
280288
in
281289
let invariant loc name =
282290
if !unknown_types <> 0 && List.length blocks <> 1 then
@@ -302,23 +310,27 @@ let check_invariant ~is_untagged_def ~(consts : (Location.t * tag) list)
302310
raise (Error (loc, InvalidUntaggedVariantDefinition AtMostOneBoolean));
303311
if
304312
!boolean_types > 0
305-
&& (StringSet.mem "true" !nonstring_literals
306-
|| StringSet.mem "false" !nonstring_literals)
313+
&& (StringSet.mem "true" !nonstring_literals_consts
314+
|| StringSet.mem "false" !nonstring_literals_consts)
307315
then raise (Error (loc, InvalidUntaggedVariantDefinition AtMostOneBoolean));
308316
()
309317
in
318+
let check_literal ~is_const ~loc (literal : tag) =
319+
match literal.tag_type with
320+
| Some (String s) -> add_string_literal ~is_const ~loc s
321+
| Some (Int i) -> add_nonstring_literal ~is_const ~loc (string_of_int i)
322+
| Some (Float f) -> add_nonstring_literal ~is_const ~loc f
323+
| Some (BigInt i) -> add_nonstring_literal ~is_const ~loc i
324+
| Some Null -> add_nonstring_literal ~is_const ~loc "null"
325+
| Some Undefined -> add_nonstring_literal ~is_const ~loc "undefined"
326+
| Some (Bool b) ->
327+
add_nonstring_literal ~is_const ~loc (if b then "true" else "false")
328+
| Some (Untagged _) -> ()
329+
| None -> add_string_literal ~is_const ~loc literal.name
330+
in
331+
310332
Ext_list.rev_iter consts (fun (loc, literal) ->
311-
match literal.tag_type with
312-
| Some (String s) -> add_string_literal ~loc s
313-
| Some (Int i) -> add_nonstring_literal ~loc (string_of_int i)
314-
| Some (Float f) -> add_nonstring_literal ~loc f
315-
| Some (BigInt i) -> add_nonstring_literal ~loc i
316-
| Some Null -> add_nonstring_literal ~loc "null"
317-
| Some Undefined -> add_nonstring_literal ~loc "undefined"
318-
| Some (Bool b) ->
319-
add_nonstring_literal ~loc (if b then "true" else "false")
320-
| Some (Untagged _) -> ()
321-
| None -> add_string_literal ~loc literal.name);
333+
check_literal ~is_const:true ~loc literal);
322334
if is_untagged_def then
323335
Ext_list.rev_iter blocks (fun (loc, block) ->
324336
match block.block_type with
@@ -338,6 +350,9 @@ let check_invariant ~is_untagged_def ~(consts : (Location.t * tag) list)
338350
| StringType -> incr string_types);
339351
invariant loc block.tag.name
340352
| None -> ())
353+
else
354+
Ext_list.rev_iter blocks (fun (loc, block) ->
355+
check_literal ~is_const:false ~loc block.tag)
341356

342357
let get_cstr_loc_tag (cstr : Types.constructor_declaration) =
343358
( cstr.cd_loc,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/multiple_tag_1.res:3:3-19
4+
5+
1 │ type ambiguous1 =
6+
2 │ | @as("x") A(int)
7+
3 │ | @as("x") B(int)
8+
4 │
9+
10+
This untagged variant definition is invalid: Duplicate literal x.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/multiple_tag_2.res:3:3-17
4+
5+
1 │ type ambiguous2 =
6+
2 │ | @as(3) A(int)
7+
3 │ | @as(3) B(int)
8+
4 │
9+
10+
This untagged variant definition is invalid: Duplicate literal 3.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
type ambiguous1 =
2+
| @as("x") A(int)
3+
| @as("x") B(int)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
type ambiguous2 =
2+
| @as(3) A(int)
3+
| @as(3) B(int)

tests/tests/src/multiple_tags.mjs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Generated by ReScript, PLEASE EDIT WITH CARE
2+
3+
4+
let a1 = {
5+
TAG: 3,
6+
_0: 10
7+
};
8+
9+
let b1 = {
10+
TAG: "3",
11+
_0: 10
12+
};
13+
14+
let a2 = "x";
15+
16+
let b2 = {
17+
TAG: "x",
18+
_0: 10
19+
};
20+
21+
export {
22+
a1,
23+
b1,
24+
a2,
25+
b2,
26+
}
27+
/* No side effect */

tests/tests/src/multiple_tags.res

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
type unambiguous1 =
2+
| @as(3) A(int)
3+
| @as("3") B(int)
4+
5+
let a1 = A(10)
6+
let b1 = B(10)
7+
8+
type un_ambiguous2 =
9+
| @as("x") A
10+
| @as("x") B(int)
11+
12+
let a2 = A
13+
let b2 = B(10)

0 commit comments

Comments
 (0)