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 fc7b0811cd..9779841c2c 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md @@ -9,7 +9,8 @@ ### Fixed -* Fix F# compiler to prevent tail call emission when pinned locals are present ([PR #XXXX](https://github.com/dotnet/fsharp/pull/XXXX)) +* Fix duplicate .cctor issue for discriminated unions with generic statics ([Issue #18767](https://github.com/dotnet/fsharp/issues/18767)) +* Fix F# compiler to prevent tail call emission when pinned locals are present ([PR #18893](https://github.com/dotnet/fsharp/pull/18893)) * 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 d0770b414e..a35d7fadb9 100644 --- a/src/Compiler/AbstractIL/il.fs +++ b/src/Compiler/AbstractIL/il.fs @@ -4112,18 +4112,49 @@ 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 = sprintf "cctor_renamed_%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/Misc.fs b/tests/FSharp.Compiler.ComponentTests/Misc.fs index f212441ede..61c58679ff 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_renamed_0() + IL_0005: call void DuplicateCctorFix/TestUnion`1::cctor_renamed_1() + IL_000a: ret + } // end of method TestUnion`1::.cctor + + .method private static void cctor_renamed_0() cil managed + .method private static void cctor_renamed_1() cil managed"""]