diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md index 70920a02872..af2934d1a8a 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md @@ -4,6 +4,7 @@ ### Fixed +* Fix duplicate .cctor issue for discriminated unions with generic statics ([Issue #18767](https://github.com/dotnet/fsharp/issues/18767)) * Fix SignatureHash to include constant values in hash computation ([Issue #18758](https://github.com/dotnet/fsharp/issues/18758)) * Fix parsing errors using anonymous records and units of measures ([PR #18543](https://github.com/dotnet/fsharp/pull/18543)) * Fix parsing errors using anonymous records and code quotations ([PR #18603](https://github.com/dotnet/fsharp/pull/18603)) diff --git a/src/Compiler/AbstractIL/il.fs b/src/Compiler/AbstractIL/il.fs index d0770b414ed..a9431b6e6b4 100644 --- a/src/Compiler/AbstractIL/il.fs +++ b/src/Compiler/AbstractIL/il.fs @@ -4112,18 +4112,47 @@ let prependInstrsToMethod newCode md = let cdef_cctorCode2CodeOrCreate tag imports f (cd: ILTypeDef) = let mdefs = cd.Methods - let cctor = + let cctor, renamedCctors = match mdefs.FindByName ".cctor" with - | [ mdef ] -> mdef + | [ mdef ] -> mdef, [] | [] -> let body = mkMethodBody (false, [], 1, nonBranchingInstrsToCode [], tag, imports) - mkILClassCtor body - | _ -> failwith "bad method table: more than one .cctor found" + mkILClassCtor body, [] + | multipleCctors -> + // Handle multiple .cctor methods by renaming them and creating a new .cctor that calls them + // This resolves the "duplicate entry '.cctor' in method table" error (FS2014) + let renamedCctors = + multipleCctors + |> List.mapi (fun i mdef -> + let newName = + match i with + | 0 -> "cctor_IncrClass" + | 1 -> "cctor_UnionErasure" + | _ -> sprintf "cctor_%d" i + mdef.With(name = newName)) + + // Create call instructions for each renamed .cctor + // Use a simple self-referencing type + let currentTypeRef = mkILTyRef (ILScopeRef.Local, cd.Name) + let currentType = mkILNonGenericBoxedTy currentTypeRef + let callInstrs = + renamedCctors + |> List.map (fun mdef -> + let mspec = mkILNonGenericStaticMethSpecInTy (currentType, mdef.Name, [], ILType.Void) + mkNormalCall mspec) + + // Create new .cctor that calls all renamed methods + let newCctorInstrs = callInstrs @ [I_ret] + let newCctorBody = mkMethodBody (false, [], 8, nonBranchingInstrsToCode newCctorInstrs, tag, imports) + let newCctor = mkILClassCtor newCctorBody + + newCctor, renamedCctors let methods = ILMethodDefs(fun () -> [| yield f cctor + yield! renamedCctors for md in mdefs do if md.Name <> ".cctor" then yield md diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/DuplicateCctorFix.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/DuplicateCctorFix.fs new file mode 100644 index 00000000000..f7e98ba8735 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/DuplicateCctorFix.fs @@ -0,0 +1,15 @@ +// #NoMono #NoMT #CodeGen #EmittedIL +// Regression test for duplicate .cctor issue: https://github.com/dotnet/fsharp/issues/18767 +// This test verifies that discriminated unions with generic statics and nullary cases +// generate only one .cctor method instead of failing with "duplicate entry '.cctor' in method table" + +type TestUnion<'T when 'T: comparison> = + | A of 'T + | B of string + | C // nullary case that triggers union erasure .cctor for constant field initialization + + // Static member that triggers incremental class .cctor generation + static member val StaticProperty = "test" with get, set + + // Another static member to ensure .cctor has meaningful initialization + static member CompareStuff x y = compare x y \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Misc.fs b/tests/FSharp.Compiler.ComponentTests/Misc.fs index f212441ede7..8680c073151 100644 --- a/tests/FSharp.Compiler.ComponentTests/Misc.fs +++ b/tests/FSharp.Compiler.ComponentTests/Misc.fs @@ -30,3 +30,33 @@ IL_0005: ret""" """ IL_0000: call !!0[] [runtime]System.Array::Empty() IL_0005: ret""" ] + + [] + let ``Discriminated union with generic statics generates single cctor calling renamed methods``() = + FSharp """ +module DuplicateCctorFix + +type TestUnion<'T when 'T: comparison> = + | A of 'T + | B of string + | C // nullary case that triggers union erasure .cctor for constant field initialization + + // Static member that triggers incremental class .cctor generation + static member val StaticProperty = "test" with get, set + + // Another static member to ensure .cctor has meaningful initialization + static member CompareStuff x y = compare x y + """ + |> compile + |> shouldSucceed + |> verifyIL [""".method private specialname rtspecialname static + void .cctor() cil managed + { + // Code size + IL_0000: call void DuplicateCctorFix/TestUnion`1::cctor_IncrClass() + IL_0005: call void DuplicateCctorFix/TestUnion`1::cctor_UnionErasure() + IL_000a: ret + } // end of method TestUnion`1::.cctor + + .method private static void cctor_IncrClass() cil managed + .method private static void cctor_UnionErasure() cil managed"""]