Skip to content

Commit 9670f60

Browse files
Disallow recursive structs with lifted type parameters (#19031)
1 parent 12efe3b commit 9670f60

File tree

5 files changed

+111
-4
lines changed

5 files changed

+111
-4
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
* Fix name is bound multiple times is not reported in 'as' pattern ([PR #18984](https://github.com/dotnet/fsharp/pull/18984))
77
* Fix: warn FS0049 on upper union case label. ([PR #19003](https://github.com/dotnet/fsharp/pull/19003))
88
* Type relations cache: handle potentially "infinite" types ([PR #19010](https://github.com/dotnet/fsharp/pull/19010))
9+
* Disallow recursive structs with lifted type parameters ([Issue #18993](https://github.com/dotnet/fsharp/issues/18993), [PR #19031](https://github.com/dotnet/fsharp/pull/19031))
910

1011
### Added
1112

1213
### Changed
1314

1415
* Parallel compilation stabilised and enabled by default ([PR #18998](https://github.com/dotnet/fsharp/pull/18998))
1516

16-
### Breaking Changes
17+
### Breaking Changes

src/Compiler/Checking/CheckDeclarations.fs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3961,8 +3961,12 @@ module EstablishTypeDefinitionCores =
39613961

39623962
// collect edges from the fields of a given struct type.
39633963
and accStructFields includeStaticFields ty (structTycon: Tycon) tinst (doneTypes, acc) =
3964-
if List.exists (typeEquiv g ty) doneTypes then
3964+
if
39653965
// This type (type instance) has been seen before, so no need to collect the same edges again (and avoid loops!)
3966+
List.exists (typeEquiv g ty) doneTypes
3967+
// This tycon is the outer tycon with a lifted generic argument, e.g., `'a list`.
3968+
|| not (isNil doneTypes) && tryTcrefOfAppTy g ty |> ValueOption.bind _.TryDeref |> ValueOption.exists ((===) tycon)
3969+
then
39663970
doneTypes, acc
39673971
else
39683972
// Only collect once from each type instance.

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,4 +591,28 @@ module RecordTypes =
591591
(Error 668, Line 5, Col 9, Line 5, Col 10, "The field 'A' appears multiple times in this record expression or pattern")
592592
(Error 668, Line 5, Col 24, Line 5, Col 25, "The field 'A' appears multiple times in this record expression or pattern")
593593
(Error 668, Line 5, Col 31, Line 5, Col 32, "The field 'B' appears multiple times in this record expression or pattern")
594-
]
594+
]
595+
596+
[<Fact>]
597+
let ``Cyclic reference check works for recursive reference with a lifted generic argument`` () =
598+
Fsx
599+
"""
600+
namespace Foo
601+
[<Struct>]
602+
type NestedRecord<'a> = { A : int; B : NestedRecord<'a list> }
603+
"""
604+
|> typecheck
605+
|> shouldFail
606+
|> withSingleDiagnostic (Error 954, Line 4, Col 18, Line 4, Col 30, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation")
607+
608+
[<Fact>]
609+
let ``Cyclic reference check works for recursive reference with a lifted generic argument: signature`` () =
610+
Fsi
611+
"""
612+
namespace Foo
613+
[<Struct>]
614+
type NestedRecord<'a> = { A : int; B : NestedRecord<'a list> }
615+
"""
616+
|> typecheck
617+
|> shouldFail
618+
|> withSingleDiagnostic (Error 954, Line 4, Col 18, Line 4, Col 30, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation")

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,58 @@ module StructTypes =
5454
(Error 946, Line 6, Col 12, Line 6, Col 20, "Cannot inherit from interface type. Use interface ... with instead.")
5555
]
5656

57+
[<Fact>]
58+
let ``Cyclic reference check works for recursive reference with a lifted generic argument`` () =
59+
Fsx
60+
"""
61+
namespace Foo
62+
[<Struct>]
63+
type NestedRegularStruct<'a> = val A : NestedRegularStruct<'a list>
64+
"""
65+
|> typecheck
66+
|> shouldFail
67+
|> withSingleDiagnostic (Error 954, Line 4, Col 18, Line 4, Col 37, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation")
68+
69+
[<Fact>]
70+
let ``Cyclic reference check works for recursive reference with a lifted generic argument: signature`` () =
71+
Fsi
72+
"""
73+
namespace Foo
74+
[<Struct>]
75+
type NestedRegularStruct<'a> = val A : NestedRegularStruct<'a list>
76+
"""
77+
|> typecheck
78+
|> shouldFail
79+
|> withSingleDiagnostic (Error 954, Line 4, Col 18, Line 4, Col 37, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation")
80+
81+
[<Fact>]
82+
let ``Cyclic reference check works for mutually-recursive reference with a lifted generic argument`` () =
83+
Fsx
84+
"""
85+
namespace Foo
86+
[<Struct>]
87+
type MyStruct<'T> =
88+
val field : YourStruct<MyStruct<MyStruct<'T>>>
89+
90+
and [<Struct>] YourStruct<'T> =
91+
val field : 'T
92+
"""
93+
|> typecheck
94+
|> shouldFail
95+
|> withSingleDiagnostic (Error 954, Line 4, Col 18, Line 4, Col 26, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation")
96+
97+
[<Fact>]
98+
let ``Cyclic reference check works for mutually-recursive reference with a lifted generic argument: signature`` () =
99+
Fsi
100+
"""
101+
namespace Foo
102+
[<Struct>]
103+
type MyStruct<'T> =
104+
val field : YourStruct<MyStruct<MyStruct<'T>>>
105+
106+
and [<Struct>] YourStruct<'T> =
107+
val field : 'T
108+
"""
109+
|> typecheck
110+
|> shouldFail
111+
|> withSingleDiagnostic (Error 954, Line 4, Col 18, Line 4, Col 26, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation")

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Conformance.Types
1+
namespace Conformance.Types
22

33
open Xunit
44
open FSharp.Test.Compiler
@@ -675,6 +675,29 @@ type StructUnion = A of X:int | B of Y:StructUnion
675675
(Error 954, Line 4, Col 6, Line 4, Col 17, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation")
676676
]
677677

678+
[<Fact>]
679+
let ``Cyclic reference check works for recursive reference with a lifted generic argument`` () =
680+
Fsx
681+
"""
682+
namespace Foo
683+
[<Struct>]
684+
type NestedUnion<'a> = Cons of 'a * NestedUnion<'a list> | Nil
685+
"""
686+
|> typecheck
687+
|> shouldFail
688+
|> withSingleDiagnostic (Error 954, Line 4, Col 18, Line 4, Col 29, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation")
689+
690+
[<Fact>]
691+
let ``Cyclic reference check works for recursive reference with a lifted generic argument: signature`` () =
692+
Fsi
693+
"""
694+
namespace Foo
695+
[<Struct>]
696+
type NestedUnion<'a> = Cons of 'a * NestedUnion<'a list> | Nil
697+
"""
698+
|> typecheck
699+
|> shouldFail
700+
|> withSingleDiagnostic (Error 954, Line 4, Col 18, Line 4, Col 29, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation")
678701

679702
let createMassiveStructDuProgram countOfCases =
680703
let codeSb =

0 commit comments

Comments
 (0)