Skip to content

Merge duplicate .cctors for generic unions with nullary cases and static bindings (#19445) - #20428

Open
edgarfgp wants to merge 4 commits into
dotnet:mainfrom
edgarfgp:fix/19445-duplicate-cctor
Open

Merge duplicate .cctors for generic unions with nullary cases and static bindings (#19445)#20428
edgarfgp wants to merge 4 commits into
dotnet:mainfrom
edgarfgp:fix/19445-duplicate-cctor

Conversation

@edgarfgp

@edgarfgp edgarfgp commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Problem

A generic union with a nullary case crashes the compiler when it also has static member val or static let bindings.

Fixes #19445.

Before

type U<'T> =
    | A
    static member val X = 3
// error FS0192: internal error: Error in pass2 for type U`1, error: duplicate entry '.cctor' in method table

type V<'T> =
    | B
    static let mutable counter = 1
    static member Next() =
        counter <- counter + 1
        counter
// same error

After

type U<'T> =
    | A
    static member val X = 3
// compiles; U<int>.X = 3, matching on U<int>.A works
// V<int>.Next() = 2, then 3; V<string>.Next() = 2 (statics are per instantiation)

Cause

Two code paths each contribute a .cctor to the union type: union erasure emits one initializing the nullary-case singleton field (_unique_A), and the static bindings of a generic type are compiled into a .cctor member for a non-generic type they run from the file initializer instead, which is why type U = A with static member val already worked. TypeDefBuilder.AddMethodDef appended the second one and ilwrite rejected the duplicate.

The two are now merged into the single .cctor IL permits: the straight-line singleton initializer (no locals, no handlers, no control flow) is prepended into the other initializer, so the singleton is initialized before user static bindings run the same order the non-generic path produces, and required because a binding may read the singleton:

type U<'T> =
    | A
    static member val X : U<'T> = A   // X is the initialized A singleton of the same instantiation

The merged initializer passes ILVerify, and no previously-compiling code changes shape the merge only happens where compilation used to fail.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`src/Compiler` docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

@github-actions github-actions Bot added the ⚠️ Affects-Compiler-Output Tooling check: PR touches IL emission or codegen label Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ Affects-Compiler-Output Tooling check: PR touches IL emission or codegen

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

duplicate entry '.cctor' in method table

1 participant