From 5fe141c32062105c02be15d4115d2a27e582ae7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 11:22:37 +0000 Subject: [PATCH 1/3] Initial plan From 7384c60590364db40239d9750a1d4aa4b8164459 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 11:38:47 +0000 Subject: [PATCH 2/3] Fix struct union StructLayout size emission 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> --- src/Compiler/CodeGen/IlxGen.fs | 10 ++-------- .../CustomAttributes/Basic/Basic.fs | 6 ++---- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 939e3da324..0f180c41c9 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -12031,15 +12031,9 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option } let layout = - // Structs with no instance fields get size 1, pack 0 + // Struct unions always carry a hidden tag field, so never emit size 1 here if isStructTy g thisTy then - if - (tycon.AllFieldsArray.Length = 0 - || tycon.AllFieldsArray |> Array.exists (fun f -> not f.IsStatic)) - && (alternatives - |> Array.collect (fun a -> a.FieldDefs) - |> Array.exists (fun fd -> not fd.ILField.IsStatic)) - then + if tycon.IsUnionTycon then ILTypeDefLayout.Sequential { Size = None; Pack = None } else ILTypeDefLayout.Sequential { Size = Some 1; Pack = Some 0us } diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs index 9314881045..16448e181d 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs @@ -397,7 +397,7 @@ module CustomAttributes_Basic = ] [] - let ``StructLayoutAttribute has size=1 for struct DUs with no instance fields`` () = + let ``StructLayoutAttribute doesn't have size=1 for struct DUs with no instance fields`` () = Fsx """ [] type Option<'T> = None | Some """ @@ -413,8 +413,6 @@ module CustomAttributes_Basic = [runtime]System.IComparable, [runtime]System.Collections.IStructuralComparable { - .pack 0 - .size 1 .custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [runtime]System.Diagnostics.DebuggerDisplayAttribute::.ctor(string) = ( 01 00 15 7B 5F 5F 44 65 62 75 67 44 69 73 70 6C 61 79 28 29 2C 6E 71 7D 00 00 ) @@ -426,4 +424,4 @@ module CustomAttributes_Basic = .field public static literal int32 Some = int32(0x00000001) } """ - ] \ No newline at end of file + ] From c4c39418db093ac0c64d461a6e2694070fc6ec97 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 25 May 2026 14:24:51 +0200 Subject: [PATCH 3/3] Address review feedback: remove dead else branch, fix comment, add single-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 ([] type X = | Y) - Add release notes entry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.FSharp.Compiler.Service/11.0.100.md | 1 + src/Compiler/CodeGen/IlxGen.fs | 8 +++--- .../CustomAttributes/Basic/Basic.fs | 26 ++++++++++++++++++- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 4a6110cf51..854820a11b 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,5 +1,6 @@ ### Fixed +* Fix incorrect `StructLayout(Size = 1)` emission for data-less struct unions where the compiler-generated tag field makes the actual runtime size larger. ([PR #19759](https://github.com/dotnet/fsharp/pull/19759)) * Fix attributes on return type of unparenthesized tuple methods being silently dropped from IL. ([Issue #462](https://github.com/dotnet/fsharp/issues/462), [PR #19714](https://github.com/dotnet/fsharp/pull/19714)) * Fix internal error FS0073 "Undefined or unsolved type variable" in IlxGen when nested inline SRTP functions with multiple overloads leave unsolved typars in the non-witness codegen path. ([Issue #19709](https://github.com/dotnet/fsharp/issues/19709), [PR #19710](https://github.com/dotnet/fsharp/pull/19710)) * Fix NRE when calling virtual Object methods on value types through inline SRTP functions. ([Issue #8098](https://github.com/dotnet/fsharp/issues/8098), [PR #19511](https://github.com/dotnet/fsharp/pull/19511)) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 0f180c41c9..d26740555f 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -12031,12 +12031,10 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option } let layout = - // Struct unions always carry a hidden tag field, so never emit size 1 here + // 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. if isStructTy g thisTy then - if tycon.IsUnionTycon then - ILTypeDefLayout.Sequential { Size = None; Pack = None } - else - ILTypeDefLayout.Sequential { Size = Some 1; Pack = Some 0us } + ILTypeDefLayout.Sequential { Size = None; Pack = None } else ILTypeDefLayout.Auto diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs index 16448e181d..348fc4ba99 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs @@ -397,7 +397,7 @@ module CustomAttributes_Basic = ] [] - let ``StructLayoutAttribute doesn't have size=1 for struct DUs with no instance fields`` () = + let ``StructLayoutAttribute doesn't have size=1 for multi-case struct DUs with no instance fields`` () = Fsx """ [] type Option<'T> = None | Some """ @@ -425,3 +425,27 @@ module CustomAttributes_Basic = } """ ] + + [] + let ``StructLayoutAttribute doesn't have size=1 for single-case struct DU`` () = + Fsx """ + [] type X = | Y + """ + |> compile + |> shouldSucceed + |> verifyIL [ + """ + .class sequential autochar serializable sealed nested public beforefieldinit X + extends [runtime]System.ValueType + implements class [runtime]System.IEquatable`1, + [runtime]System.Collections.IStructuralEquatable, + class [runtime]System.IComparable`1, + [runtime]System.IComparable, + [runtime]System.Collections.IStructuralComparable + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerDisplayAttribute::.ctor(string) = ( 01 00 15 7B 5F 5F 44 65 62 75 67 44 69 73 70 6C + 61 79 28 29 2C 6E 71 7D 00 00 ) + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 01 00 00 00 00 00 ) + """ + ]