Skip to content

Commit 14f4369

Browse files
authored
Better error reporting for unions duplicated fields (#17521)
* Better error reporting for unions duplicated fields * update tests * release notes
1 parent f61dc16 commit 14f4369

File tree

7 files changed

+87
-12
lines changed

7 files changed

+87
-12
lines changed

docs/release-notes/.FSharp.Compiler.Service/9.0.100.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@
2727
* Enforce `AttributeTargets` on unions. ([PR #17389](https://github.com/dotnet/fsharp/pull/17389))
2828
* Ensure that isinteractive multi-emit backing fields are not public. ([Issue #17439](https://github.com/dotnet/fsharp/issues/17438)), ([PR #17439](https://github.com/dotnet/fsharp/pull/17439))
2929
* Enable FSharp 9.0 Language Version ([Issue #17497](https://github.com/dotnet/fsharp/issues/17438)), [PR](https://github.com/dotnet/fsharp/pull/17500)))
30+
* Better error reporting for unions with duplicated fields. ([PR #17521](https://github.com/dotnet/fsharp/pull/17521))
3031
### Breaking Changes

src/Compiler/Checking/CheckDeclarations.fs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -500,18 +500,26 @@ module TcRecdUnionAndEnumDeclarations =
500500
if not (String.isLeadingIdentifierCharacterUpperCase name) && name <> opNameCons && name <> opNameNil then
501501
errorR(NotUpperCaseConstructor(id.idRange))
502502

503-
let ValidateFieldNames (synFields: SynField list, tastFields: RecdField list) =
503+
let private CheckUnionDuplicateFields (elems: Ident list) =
504+
elems |> List.iteri (fun i (uc1: Ident) ->
505+
elems |> List.iteri (fun j (uc2: Ident) ->
506+
if j > i && uc1.idText = uc2.idText then
507+
errorR(Error(FSComp.SR.tcFieldNameIsUsedModeThanOnce(uc1.idText), uc1.idRange))))
508+
509+
let ValidateFieldNames (synFields: SynField list, tastFields: RecdField list) =
510+
let fields = synFields |> List.choose (function SynField(idOpt = Some ident) -> Some ident | _ -> None)
511+
if fields.Length > 1 then
512+
CheckUnionDuplicateFields fields
513+
504514
let seen = Dictionary()
505515
(synFields, tastFields) ||> List.iter2 (fun sf f ->
506516
match seen.TryGetValue f.LogicalName with
507517
| true, synField ->
508518
match sf, synField with
509-
| SynField(idOpt = Some id), SynField(idOpt = Some _) ->
510-
error(Error(FSComp.SR.tcFieldNameIsUsedModeThanOnce(id.idText), id.idRange))
511519
| SynField(idOpt = Some id), SynField(idOpt = None)
512520
| SynField(idOpt = None), SynField(idOpt = Some id) ->
513-
error(Error(FSComp.SR.tcFieldNameConflictsWithGeneratedNameForAnonymousField(id.idText), id.idRange))
514-
| _ -> assert false
521+
errorR(Error(FSComp.SR.tcFieldNameConflictsWithGeneratedNameForAnonymousField(id.idText), id.idRange))
522+
| _ -> ()
515523
| _ ->
516524
seen.Add(f.LogicalName, sf))
517525

tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/ExceptionDefinitions/E_ExnFieldConflictingName.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@
55

66
exception AAA of Data1 : int * string
77

8-
exception BBB of A : int * A : string
8+
exception BBB of A : int * A : string
9+
10+
exception CCC of A : int * A : string * A : int

tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/ExceptionDefinitions/ExceptionDefinitions.fs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,10 @@ module ExceptionDefinition =
226226
|> compile
227227
|> shouldFail
228228
|> withDiagnostics [
229-
(Error 3176, Line 6, Col 18, Line 6, Col 23, "Named field 'Data1' conflicts with autogenerated name for anonymous field.")
230-
(Error 3176, Line 8, Col 28, Line 8, Col 29, "Named field 'A' is used more than once.")
229+
(Error 3176, Line 6, Col 18, Line 6, Col 23, "Named field 'Data1' conflicts with autogenerated name for anonymous field.");
230+
(Error 3176, Line 8, Col 18, Line 8, Col 19, "Named field 'A' is used more than once.");
231+
(Error 3176, Line 10, Col 18, Line 10, Col 19, "Named field 'A' is used more than once.");
232+
(Error 3176, Line 10, Col 28, Line 10, Col 29, "Named field 'A' is used more than once.")
231233
]
232234

233235
// SOURCE=E_FieldNameUsedMulti.fs SCFLAGS="--test:ErrorRanges" # E_FieldNameUsedMulti.fs

tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/E_UnionFieldConflictingName.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ type MyDU =
77
| Case1 of Item2 : int * string
88

99
type MyDU2 =
10-
| Case1 of A : int * A : string
10+
| Case1 of A : int * A : string * A : int

tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/UnionStructTypes.fs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,9 @@ type StructUnion =
402402
|> typecheck
403403
|> shouldFail
404404
|> withDiagnostics [
405-
(Error 3176, Line 5, Col 27, Line 5, Col 31, "Named field 'item' is used more than once.")
405+
(Error 3176, Line 5, Col 12, Line 5, Col 16, "Named field 'item' is used more than once.");
406+
(Error 3585, Line 5, Col 12, Line 5, Col 16, "If a multicase union type is a struct, then all fields with the same name must be of the same type. This rule applies also to the generated 'Item' name in case of unnamed fields.");
407+
(Error 3585, Line 5, Col 27, Line 5, Col 31, "If a multicase union type is a struct, then all fields with the same name must be of the same type. This rule applies also to the generated 'Item' name in case of unnamed fields.")
406408
]
407409

408410
[<Fact>]
@@ -417,7 +419,10 @@ type StructUnion =
417419
|> typecheck
418420
|> shouldFail
419421
|> withDiagnostics [
420-
(Error 3176, Line 5, Col 27, Line 5, Col 31, "Named field 'Item' is used more than once.")
422+
(Error 3176, Line 5, Col 12, Line 5, Col 16, "Named field 'Item' is used more than once.");
423+
(Error 3585, Line 5, Col 12, Line 5, Col 16, "If a multicase union type is a struct, then all fields with the same name must be of the same type. This rule applies also to the generated 'Item' name in case of unnamed fields.");
424+
(Error 3585, Line 5, Col 27, Line 5, Col 31, "If a multicase union type is a struct, then all fields with the same name must be of the same type. This rule applies also to the generated 'Item' name in case of unnamed fields.");
425+
(Error 3585, Line 6, Col 12, Line 6, Col 18, "If a multicase union type is a struct, then all fields with the same name must be of the same type. This rule applies also to the generated 'Item' name in case of unnamed fields.")
421426
]
422427

423428
[<Fact>]

tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/UnionTypes.fs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,8 @@ module UnionTypes =
272272
|> verifyCompile
273273
|> shouldFail
274274
|> withDiagnostics [
275-
(Error 3176, Line 7, Col 16, Line 7, Col 21, "Named field 'Item2' conflicts with autogenerated name for anonymous field.")
275+
(Error 3176, Line 7, Col 16, Line 7, Col 21, "Named field 'Item2' conflicts with autogenerated name for anonymous field.");
276+
(Error 3176, Line 10, Col 16, Line 10, Col 17, "Named field 'A' is used more than once.");
276277
(Error 3176, Line 10, Col 26, Line 10, Col 27, "Named field 'A' is used more than once.")
277278
]
278279

@@ -750,3 +751,59 @@ type MyId =
750751
(Error 23, Line 7, Col 17, Line 7, Col 20, "The member 'IdA' can not be defined because the name 'IdA' clashes with the union case 'IdA' in this type or module")
751752
(Error 23, Line 17, Col 17, Line 17, Col 20, "The member 'IdC' can not be defined because the name 'IdC' clashes with the union case 'IdC' in this type or module")
752753
]
754+
755+
756+
[<Fact>]
757+
let ``Union field appears multiple times in union declaration`` () =
758+
Fsx """
759+
type X =
760+
| A of a: int * a: int
761+
"""
762+
|> typecheck
763+
|> shouldFail
764+
|> withDiagnostics [
765+
(Error 3176, Line 3, Col 12, Line 3, Col 13, "Named field 'a' is used more than once.")
766+
]
767+
768+
[<Fact>]
769+
let ``Union field appears multiple times in union declaration 2`` () =
770+
Fsx """
771+
type X =
772+
| A of a: int * a: int
773+
| B of a: int * a: int
774+
"""
775+
|> typecheck
776+
|> shouldFail
777+
|> withDiagnostics [
778+
(Error 3176, Line 3, Col 12, Line 3, Col 13, "Named field 'a' is used more than once.")
779+
(Error 3176, Line 4, Col 12, Line 4, Col 13, "Named field 'a' is used more than once.")
780+
]
781+
782+
[<Fact>]
783+
let ``Union field appears multiple times in union declaration 3`` () =
784+
Fsx """
785+
type X =
786+
| A of a: int * a: int * a: int
787+
"""
788+
|> typecheck
789+
|> shouldFail
790+
|> withDiagnostics [
791+
(Error 3176, Line 3, Col 12, Line 3, Col 13, "Named field 'a' is used more than once.")
792+
(Error 3176, Line 3, Col 21, Line 3, Col 22, "Named field 'a' is used more than once.")
793+
]
794+
795+
[<Fact>]
796+
let ``Union field appears multiple times in union declaration 4`` () =
797+
Fsx """
798+
type X =
799+
| A of a: int * a: int
800+
let x = A (1, 2)
801+
match x with
802+
| A(a = 1) -> ()
803+
| _ -> ()
804+
"""
805+
|> typecheck
806+
|> shouldFail
807+
|> withDiagnostics [
808+
(Error 3176, Line 3, Col 12, Line 3, Col 13, "Named field 'a' is used more than once.")
809+
]

0 commit comments

Comments
 (0)