Correct StructLayout size emission for data-less struct unions#19759
Correct StructLayout size emission for data-less struct unions#19759Copilot wants to merge 4 commits into
Conversation
Agent-Logs-Url: https://github.com/dotnet/fsharp/sessions/9455dd40-47bc-4c6a-8bbf-751de6577dc7 Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
T-Gro
left a comment
There was a problem hiding this comment.
Review: Correct StructLayout size emission for data-less struct unions
The fix is correct for the motivating scenario — multi-case all-nullary struct unions (e.g., [<Struct>] type ABC = A | B | C) were incorrectly emitted with .size 1 even though the compiler generates a _tag field that makes them non-empty. Removing the explicit size is the right call.
Issues to address
1. Dead else branch (bug)
The condition at line 12036:
sharp if tycon.IsUnionTycon then ILTypeDefLayout.Sequential { Size = None; Pack = None } else ILTypeDefLayout.Sequential { Size = Some 1; Pack = Some 0us }
This code lives inside the TFSharpUnion match arm (line 12000–12003), so tycon.IsUnionTycon is always true here. The else branch is unreachable dead code.
The intent should be simplified to:
sharp let layout = if isStructTy g thisTy then ILTypeDefLayout.Sequential { Size = None; Pack = None } else ILTypeDefLayout.Auto
2. Inaccurate comment
// Struct unions always carry a hidden tag field, so never emit size 1 here
This is not true for single-case struct unions (e.g., [<Struct>] type Foo = | Bar). Those use SingleCaseStruct layout which has NoTagField — no _tag field is emitted. The fix still works correctly for that case (CLR guarantees ≥1 byte for value types without explicit size), but the comment misleads future readers.
Suggested:
sharp // Multi-case struct unions carry a hidden tag field; single-case struct unions // are handled by the CLR's minimum-1-byte guarantee. No explicit size needed.
Minor
- The test rename is good. Asserting that
.pack 0/.size 1are absent properly validates the fix. - Consider adding a single-case struct union (
[<Struct>] type X = | Y) to the test suite to document behavior in that edge case, since the original Size=1 annotation is being removed for all struct unions, not just multi-case ones.
❗ Release notes required
|
…ngle-case test - Simplify IlxGen layout logic by removing the always-true tycon.IsUnionTycon check inside the TFSharpUnion match arm (dead else branch) - Update comment to accurately describe behavior for both multi-case and single-case struct unions - Add regression test for single-case struct DU ([<Struct>] type X = | Y) - Add release notes entry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…outattribute-size
Struct unions with no declared payload fields were emitted with
StructLayout(Size = 1), even though the compiler-generated tag field makes the actual runtime size larger. This updates union codegen so data-less struct unions no longer advertise an invalid explicit size.Codegen
IlxGen.fsto stop treating struct unions as empty structs.Size = 1, allowing the generated tag field to determine the effective size.Regression coverage
Example
Before, this could be emitted with
StructLayout(Size = 1). With this change, the compiler no longer emits that incorrect explicit size for the struct union.