Skip to content

Commit 984837b

Browse files
authored
Fix FS2014 duplicate .cctor error by renaming and delegating static constructors (#18801)
1 parent 1de7a62 commit 984837b

File tree

3 files changed

+67
-5
lines changed
  • docs/release-notes/.FSharp.Compiler.Service
  • src/Compiler/AbstractIL
  • tests/FSharp.Compiler.ComponentTests

3 files changed

+67
-5
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
### Fixed
1111

12-
* Fix F# compiler to prevent tail call emission when pinned locals are present ([PR #XXXX](https://github.com/dotnet/fsharp/pull/XXXX))
12+
* Fix duplicate .cctor issue for discriminated unions with generic statics ([Issue #18767](https://github.com/dotnet/fsharp/issues/18767))
13+
* Fix F# compiler to prevent tail call emission when pinned locals are present ([PR #18893](https://github.com/dotnet/fsharp/pull/18893))
1314
* Fix SignatureHash to include constant values in hash computation ([Issue #18758](https://github.com/dotnet/fsharp/issues/18758))
1415
* Fix parsing errors using anonymous records and units of measures ([PR #18543](https://github.com/dotnet/fsharp/pull/18543))
1516
* Fix parsing errors using anonymous records and code quotations ([PR #18603](https://github.com/dotnet/fsharp/pull/18603))

src/Compiler/AbstractIL/il.fs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4112,18 +4112,49 @@ let prependInstrsToMethod newCode md =
41124112
let cdef_cctorCode2CodeOrCreate tag imports f (cd: ILTypeDef) =
41134113
let mdefs = cd.Methods
41144114

4115-
let cctor =
4115+
let cctor, renamedCctors =
41164116
match mdefs.FindByName ".cctor" with
4117-
| [ mdef ] -> mdef
4117+
| [ mdef ] -> mdef, []
41184118
| [] ->
41194119
let body = mkMethodBody (false, [], 1, nonBranchingInstrsToCode [], tag, imports)
4120-
mkILClassCtor body
4121-
| _ -> failwith "bad method table: more than one .cctor found"
4120+
mkILClassCtor body, []
4121+
| multipleCctors ->
4122+
// Handle multiple .cctor methods by renaming them and creating a new .cctor that calls them
4123+
// This resolves the "duplicate entry '.cctor' in method table" error (FS2014)
4124+
let renamedCctors =
4125+
multipleCctors
4126+
|> List.mapi (fun i mdef ->
4127+
let newName = sprintf "cctor_renamed_%d" i
4128+
mdef.With(name = newName))
4129+
4130+
// Create call instructions for each renamed .cctor
4131+
// Use a simple self-referencing type
4132+
let currentTypeRef = mkILTyRef (ILScopeRef.Local, cd.Name)
4133+
let currentType = mkILNonGenericBoxedTy currentTypeRef
4134+
4135+
let callInstrs =
4136+
renamedCctors
4137+
|> List.map (fun mdef ->
4138+
let mspec =
4139+
mkILNonGenericStaticMethSpecInTy (currentType, mdef.Name, [], ILType.Void)
4140+
4141+
mkNormalCall mspec)
4142+
4143+
// Create new .cctor that calls all renamed methods
4144+
let newCctorInstrs = callInstrs @ [ I_ret ]
4145+
4146+
let newCctorBody =
4147+
mkMethodBody (false, [], 8, nonBranchingInstrsToCode newCctorInstrs, tag, imports)
4148+
4149+
let newCctor = mkILClassCtor newCctorBody
4150+
4151+
newCctor, renamedCctors
41224152

41234153
let methods =
41244154
ILMethodDefs(fun () ->
41254155
[|
41264156
yield f cctor
4157+
yield! renamedCctors
41274158
for md in mdefs do
41284159
if md.Name <> ".cctor" then
41294160
yield md

tests/FSharp.Compiler.ComponentTests/Misc.fs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,33 @@ IL_0005: ret"""
3030
"""
3131
IL_0000: call !!0[] [runtime]System.Array::Empty<!!0>()
3232
IL_0005: ret""" ]
33+
34+
[<Fact>]
35+
let ``Discriminated union with generic statics generates single cctor calling renamed methods``() =
36+
FSharp """
37+
module DuplicateCctorFix
38+
39+
type TestUnion<'T when 'T: comparison> =
40+
| A of 'T
41+
| B of string
42+
| C // nullary case that triggers union erasure .cctor for constant field initialization
43+
44+
// Static member that triggers incremental class .cctor generation
45+
static member val StaticProperty = "test" with get, set
46+
47+
// Another static member to ensure .cctor has meaningful initialization
48+
static member CompareStuff x y = compare x y
49+
"""
50+
|> compile
51+
|> shouldSucceed
52+
|> verifyIL [""".method private specialname rtspecialname static
53+
void .cctor() cil managed
54+
{
55+
// Code size
56+
IL_0000: call void DuplicateCctorFix/TestUnion`1::cctor_renamed_0()
57+
IL_0005: call void DuplicateCctorFix/TestUnion`1::cctor_renamed_1()
58+
IL_000a: ret
59+
} // end of method TestUnion`1::.cctor
60+
61+
.method private static void cctor_renamed_0() cil managed
62+
.method private static void cctor_renamed_1() cil managed"""]

0 commit comments

Comments
 (0)