Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c438a8f
Initial plan
Copilot Jan 6, 2026
7a99315
Add ExtendedLayoutAttribute support - IL layer, infrastructure, and v…
Copilot Jan 6, 2026
a587076
Fix indentation in IlxGen.fs for ExtendedLayout support
Copilot Jan 6, 2026
49ae0a0
Add comprehensive tests for ExtendedLayoutAttribute support
Copilot Jan 6, 2026
05990ff
Fix test files with mock ExtendedLayoutAttribute for testing
Copilot Jan 6, 2026
9d5b9fb
Address code review feedback - add comments for magic numbers and fix…
Copilot Jan 6, 2026
b75b10d
Clarify source of extendedLayoutKind value with runtime issue reference
Copilot Jan 6, 2026
14e1b22
Fix CI: rebase onto main, adapt to WellKnownEntityAttributes, fix tests
T-Gro Apr 14, 2026
cce9acc
Fix CI: renumber error codes 3885-3888, move release notes to 11.0.100
T-Gro Apr 14, 2026
23d6094
Review fixes: correct ECMA-335 comment, clarify hasLayout and dead code
T-Gro Apr 14, 2026
23e2533
Add missing noExtendedLayoutAttributeCheck for unions, records, enums…
T-Gro Apr 16, 2026
8097e76
Remove unused error 3886, improve release notes
T-Gro Apr 20, 2026
b30c638
Remove duplicate release notes entry
T-Gro Apr 21, 2026
cee99f5
Review fixes: add missing noExtendedLayoutAttributeCheck for type abb…
T-Gro Apr 21, 2026
e68688c
Merge origin/main into copilot/add-extended-layout-attribute-support
T-Gro May 12, 2026
b79df32
Merge remote-tracking branch 'origin/main' into copilot/add-extended-…
T-Gro Aug 18, 2026
fcaaa81
Remove accidentally committed session artifacts (.copilot-prompt.txt,…
T-Gro Aug 18, 2026
2f0f10c
Add end-to-end ExtendedLayoutAttribute tests against net11 BCL
T-Gro Aug 18, 2026
122c437
Allow ExtendedLayoutAttribute on [<Struct>] records
T-Gro Aug 18, 2026
0eceb1c
Give discriminated unions a dedicated ExtendedLayoutAttribute diagnostic
T-Gro Aug 18, 2026
39c5d8a
Keep the ExtendedLayout test module non-empty on non-.NET-Core TFMs
T-Gro Aug 18, 2026
e5e1394
Re-run CI (flaky WindowsNoRealsig_testCoreclr timeout)
T-Gro Aug 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@
* Lines starting with `#:` are now ignored ([Language suggestion 1440](https://github.com/fsharp/fslang-suggestions/issues/1440), [RFC FS-1337](https://github.com/fsharp/fslang-design/pull/830), [PR #20212](https://github.com/dotnet/fsharp/pull/20212))

### Breaking Changes
* Add `ExtendedLayoutAttribute` support for future .NET runtime interop. `ILTypeDefLayout` has a new `Extended` case. ([Issue #19190](https://github.com/dotnet/fsharp/issues/19190), [PR #19194](https://github.com/dotnet/fsharp/pull/19194))

* `FSharp.Compiler.Syntax.SynInterpolatedStringPart.FillExpr` now carries a `SynInterpolationFormatting` value (separating .NET alignment/format from printf specifiers) instead of an `Ident option`. ([PR #19971](https://github.com/dotnet/fsharp/pull/19971))
* Optimizer: don't inline named functions in debug builds ([PR #19548](https://github.com/dotnet/fsharp/pull/19548)
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/AbstractIL/il.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2572,6 +2572,7 @@ type ILTypeDefLayout =
| Auto
| Sequential of ILTypeDefLayoutInfo
| Explicit of ILTypeDefLayoutInfo (* REVIEW: add field info here *)
| Extended

and ILTypeDefLayoutInfo =
{
Expand Down Expand Up @@ -2685,6 +2686,10 @@ let convertLayout layout =
| ILTypeDefLayout.Auto -> TypeAttributes.AutoLayout
| ILTypeDefLayout.Sequential _ -> TypeAttributes.SequentialLayout
| ILTypeDefLayout.Explicit _ -> TypeAttributes.ExplicitLayout
| ILTypeDefLayout.Extended ->
// Extended layout is represented by TypeAttributes value 0x18 (both Sequential and Explicit bits set)
// See: https://github.com/dotnet/runtime/issues/102727
enum<TypeAttributes> (0x18)

let convertEncoding encoding =
match encoding with
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/AbstractIL/il.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,7 @@ type ILTypeDefLayout =
| Auto
| Sequential of ILTypeDefLayoutInfo
| Explicit of ILTypeDefLayoutInfo
| Extended

type internal ILTypeDefLayoutInfo =
{ Size: int32 option
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/AbstractIL/ilprint.fs
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,7 @@ let splitTypeLayout =
| ILTypeDefLayout.Auto -> "auto", (fun _os () -> ())
| ILTypeDefLayout.Sequential info -> "sequential", (fun os () -> output_type_layout_info os info)
| ILTypeDefLayout.Explicit info -> "explicit", (fun os () -> output_type_layout_info os info)
| ILTypeDefLayout.Extended -> "extended", (fun _os () -> ())

let goutput_fdefs tref env os (fdefs: ILFieldDefs) =
for f in fdefs.AsList() do
Expand Down
4 changes: 4 additions & 0 deletions src/Compiler/AbstractIL/ilread.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2064,6 +2064,8 @@ and typeLayoutOfFlags (ctxt: ILMetadataReader) mdv flags tidx =
ILTypeDefLayout.Sequential(seekReadClassLayout ctxt mdv tidx)
elif f = 0x00000010 then
ILTypeDefLayout.Explicit(seekReadClassLayout ctxt mdv tidx)
elif f = 0x00000018 then
ILTypeDefLayout.Extended
else
ILTypeDefLayout.Auto

Expand Down Expand Up @@ -2136,6 +2138,8 @@ and typeDefReader ctxtH : ILTypeDefStored =
let super = seekReadSuperType ctxt numTypars AsObject extendsIdx
let layout = typeLayoutOfFlags ctxt mdv flags idx

// Only Explicit layout has per-field offsets in the FieldLayout metadata table.
// Sequential and Extended layouts don't use FieldLayout rows.
let hasLayout =
match layout with
| ILTypeDefLayout.Explicit _ -> true
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/AbstractIL/ilreflect.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2138,6 +2138,7 @@ let typeAttributesOfTypeLayout cenv emEnv x =
| ILTypeDefLayout.Auto -> None
| ILTypeDefLayout.Explicit p -> (attr 0x02 p)
| ILTypeDefLayout.Sequential p -> (attr 0x00 p)
| ILTypeDefLayout.Extended -> None // No StructLayoutAttribute needed; user's ExtendedLayoutAttribute is preserved

//----------------------------------------------------------------------------
// buildTypeDefPass1 cenv
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/AbstractIL/ilwrite.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2916,6 +2916,7 @@ let rec GenTypeDefPass3 enc cenv (tdef: ILTypeDef) =
// ClassLayout entry if needed
match tdef.Layout with
| ILTypeDefLayout.Auto -> ()
| ILTypeDefLayout.Extended -> () // No ClassLayout row for Extended; bits are in TypeAttributes
| ILTypeDefLayout.Sequential layout | ILTypeDefLayout.Explicit layout ->
if Option.isSome layout.Pack || Option.isSome layout.Size then
AddUnsharedRow cenv TableNames.ClassLayout
Expand Down
46 changes: 45 additions & 1 deletion src/Compiler/Checking/CheckDeclarations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3547,7 +3547,12 @@ module EstablishTypeDefinitionCores =
match attrs with
| EntityAttribInt g WellKnownEntityAttributes.StructLayoutAttribute v -> Some v
| _ -> None
let hasExtendedLayoutAttr =
match g.attrib_ExtendedLayoutAttribute_opt with
| Some attrib -> HasFSharpAttribute g attrib attrs
| None -> false
let hasAllowNullLiteralAttr = hasFlag entityFlags WellKnownEntityAttributes.AllowNullLiteralAttribute_True
let hasStructAttr = hasFlag entityFlags WellKnownEntityAttributes.StructAttribute

if hasAbstractAttr then
tycon.TypeContents.tcaug_abstract <- true
Expand All @@ -3567,7 +3572,13 @@ module EstablishTypeDefinitionCores =

let structLayoutAttributeCheck allowed =
let explicitKind = int32 System.Runtime.InteropServices.LayoutKind.Explicit
// LayoutKind.Extended has enum value 1 in .NET 11+ (previously unused slot)
// It cannot be specified via StructLayoutAttribute - users must use ExtendedLayoutAttribute instead
// See: https://github.com/dotnet/runtime/issues/102727
let extendedLayoutKind = 1
match structLayoutAttr with
| Some kind when kind = extendedLayoutKind ->
errorR (Error(FSComp.SR.tcInvalidStructLayoutExtendedKind(), m))
| Some kind ->
if allowed then
if kind = explicitKind then
Expand All @@ -3577,9 +3588,32 @@ module EstablishTypeDefinitionCores =
else
errorR (Error(FSComp.SR.tcGenericTypesCannotHaveStructLayout(), m))
| None -> ()

let extendedLayoutAttributeCheck () =
if hasExtendedLayoutAttr then
// Check not combined with StructLayoutAttribute
if structLayoutAttr.IsSome then
errorR (Error(FSComp.SR.tcStructLayoutAndExtendedLayout(), m))

let noExtendedLayoutAttributeCheck () =
if hasExtendedLayoutAttr then
errorR (Error(FSComp.SR.tcOnlyStructsCanHaveExtendedLayout(), m))

// Records become value types when marked [<Struct>]. Extended layout is valid on those
// (subject to the StructLayout conflict check); reference-typed records still reject it.
let recordExtendedLayoutAttributeCheck () =
if hasStructAttr then extendedLayoutAttributeCheck ()
else noExtendedLayoutAttributeCheck ()

// A discriminated union (including a [<Struct>] one) carries a case tag plus per-case fields,
// which is incompatible with the CStruct/CUnion field layout, so extended layout is never valid.
let unionExtendedLayoutAttributeCheck () =
if hasExtendedLayoutAttr then
errorR (Error(FSComp.SR.tcExtendedLayoutCannotBeUsedOnUnions(), m))

let hiddenReprChecks hasRepr =
structLayoutAttributeCheck false
noExtendedLayoutAttributeCheck()
if hasSealedAttr = Some false || (hasRepr && hasSealedAttr <> Some true && not (id.idText = "Unit" && g.compilingFSharpCore) ) then
errorR(Error(FSComp.SR.tcRepresentationOfTypeHiddenBySignature(), m))
if hasAbstractAttr then
Expand Down Expand Up @@ -3673,6 +3707,7 @@ module EstablishTypeDefinitionCores =
| TyconCoreAbbrevThatIsReallyAUnion (hasMeasureAttr, envinner, id) (unionCaseName, _) ->

structLayoutAttributeCheck false
unionExtendedLayoutAttributeCheck()
noAllowNullLiteralAttributeCheck()

let hasRQAAttribute = EntityHasWellKnownAttribute cenv.g WellKnownEntityAttributes.RequireQualifiedAccessAttribute tycon
Expand All @@ -3689,7 +3724,8 @@ module EstablishTypeDefinitionCores =
errorR (Error(FSComp.SR.tcAbbreviatedTypesCannotBeSealed(), m))
noAbstractClassAttributeCheck()
noAllowNullLiteralAttributeCheck()
if hasMeasureableAttr then
noExtendedLayoutAttributeCheck()
if hasMeasureableAttr then
let kind = if hasMeasureAttr then TyparKind.Measure else TyparKind.Type
let theTypeAbbrev, _ = TcTypeOrMeasureAndRecover (Some kind) cenv NoNewTypars CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.No envinner tpenv rhsType

Expand All @@ -3706,6 +3742,7 @@ module EstablishTypeDefinitionCores =
noAbstractClassAttributeCheck()
noAllowNullLiteralAttributeCheck()
structLayoutAttributeCheck false
unionExtendedLayoutAttributeCheck()

let hasRQAAttribute = EntityHasWellKnownAttribute cenv.g WellKnownEntityAttributes.RequireQualifiedAccessAttribute tycon
let unionCases = TcRecdUnionAndEnumDeclarations.TcUnionCaseDecls cenv envinner innerParent thisTy thisTyInst hasRQAAttribute tpenv addFixup unionCases
Expand All @@ -3722,6 +3759,7 @@ module EstablishTypeDefinitionCores =
noAbstractClassAttributeCheck()
noAllowNullLiteralAttributeCheck()
structLayoutAttributeCheck true // these are allowed for records
recordExtendedLayoutAttributeCheck()

let check pass =
let firstPass = pass = FirstPass
Expand Down Expand Up @@ -3879,6 +3917,7 @@ module EstablishTypeDefinitionCores =
noSealedAttributeCheck FSComp.SR.tcTypesAreAlwaysSealedAssemblyCode
noAllowNullLiteralAttributeCheck()
structLayoutAttributeCheck false
noExtendedLayoutAttributeCheck()
noAbstractClassAttributeCheck()
(TAsmRepr s, None, NoSafeInitInfo), ignore

Expand Down Expand Up @@ -3949,17 +3988,20 @@ module EstablishTypeDefinitionCores =
if not (isNil slotsigs) then
errorR (Error(FSComp.SR.tcStructTypesCannotContainAbstractMembers(), m))
structLayoutAttributeCheck true
extendedLayoutAttributeCheck()

TFSharpStruct
| SynTypeDefnKind.Interface ->
if hasSealedAttr = Some true then errorR (Error(FSComp.SR.tcInterfaceTypesCannotBeSealed(), m))
structLayoutAttributeCheck false
noExtendedLayoutAttributeCheck()
noAbstractClassAttributeCheck()
allowNullLiteralAttributeCheck()
noFieldsCheck userFields
TFSharpInterface
| SynTypeDefnKind.Class ->
structLayoutAttributeCheck(not isIncrClass)
noExtendedLayoutAttributeCheck()
allowNullLiteralAttributeCheck()
for slot in abstractSlots do
if not slot.IsInstanceMember then
Expand All @@ -3968,6 +4010,7 @@ module EstablishTypeDefinitionCores =
| SynTypeDefnKind.Delegate (ty, arity) ->
noSealedAttributeCheck FSComp.SR.tcTypesAreAlwaysSealedDelegate
structLayoutAttributeCheck false
noExtendedLayoutAttributeCheck()
noAllowNullLiteralAttributeCheck()
noAbstractClassAttributeCheck()
noFieldsCheck userFields
Expand Down Expand Up @@ -4039,6 +4082,7 @@ module EstablishTypeDefinitionCores =
let fieldTy, fields' = TcRecdUnionAndEnumDeclarations.TcEnumDecls cenv envinner tpenv innerParent thisTy decls
let kind = TFSharpEnum
structLayoutAttributeCheck false
noExtendedLayoutAttributeCheck()
noSealedAttributeCheck FSComp.SR.tcTypesAreAlwaysSealedEnum
noAllowNullLiteralAttributeCheck()
let vid = ident("value__", m)
Expand Down
87 changes: 47 additions & 40 deletions src/Compiler/CodeGen/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12351,46 +12351,52 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option
ILTypeDefLayout.Sequential { Size = Some 1; Pack = Some 0us }, ILDefaultPInvokeEncoding.Ansi
| _ -> ILTypeDefLayout.Auto, ILDefaultPInvokeEncoding.Ansi

match tycon.Attribs with
| EntityAttrib g WellKnownEntityAttributes.StructLayoutAttribute (Attrib(_,
_,
[ AttribInt32Arg layoutKind ],
namedArgs,
_,
_,
_)) ->
let decoder = AttributeDecoder namedArgs
let ilPack = decoder.FindInt32 "Pack" 0x0
let ilSize = decoder.FindInt32 "Size" 0x0

let tdEncoding =
match (decoder.FindInt32 "CharSet" 0x0) with
(* enumeration values for System.Runtime.InteropServices.CharSet taken from mscorlib.il *)
| 0x03 -> ILDefaultPInvokeEncoding.Unicode
| 0x04 -> ILDefaultPInvokeEncoding.Auto
| _ -> ILDefaultPInvokeEncoding.Ansi

let layoutInfo =
if ilPack = 0x0 && ilSize = 0x0 then
{ Size = None; Pack = None }
else
{
Size = Some ilSize
Pack = Some(uint16 ilPack)
}

let tdLayout =
match layoutKind with
(* enumeration values for System.Runtime.InteropServices.LayoutKind taken from mscorlib.il *)
| 0x0 -> ILTypeDefLayout.Sequential layoutInfo
| 0x2 -> ILTypeDefLayout.Explicit layoutInfo
| _ -> ILTypeDefLayout.Auto

tdLayout, tdEncoding
| EntityAttrib g WellKnownEntityAttributes.StructLayoutAttribute (Attrib(_, _, _, _, _, _, m)) ->
errorR (Error(FSComp.SR.ilStructLayoutAttributeCouldNotBeDecoded (), m))
ILTypeDefLayout.Auto, ILDefaultPInvokeEncoding.Ansi
| _ -> defaultLayout ()
// Check for ExtendedLayoutAttribute first
match g.attrib_ExtendedLayoutAttribute_opt with
| Some attrib when HasFSharpAttribute g attrib tycon.Attribs ->
ILTypeDefLayout.Extended, ILDefaultPInvokeEncoding.Ansi
| _ ->

match tycon.Attribs with
| EntityAttrib g WellKnownEntityAttributes.StructLayoutAttribute (Attrib(_,
_,
[ AttribInt32Arg layoutKind ],
namedArgs,
_,
_,
_)) ->
let decoder = AttributeDecoder namedArgs
let ilPack = decoder.FindInt32 "Pack" 0x0
let ilSize = decoder.FindInt32 "Size" 0x0

let tdEncoding =
match (decoder.FindInt32 "CharSet" 0x0) with
(* enumeration values for System.Runtime.InteropServices.CharSet taken from mscorlib.il *)
| 0x03 -> ILDefaultPInvokeEncoding.Unicode
| 0x04 -> ILDefaultPInvokeEncoding.Auto
| _ -> ILDefaultPInvokeEncoding.Ansi

let layoutInfo =
if ilPack = 0x0 && ilSize = 0x0 then
{ Size = None; Pack = None }
else
{
Size = Some ilSize
Pack = Some(uint16 ilPack)
}

let tdLayout =
match layoutKind with
(* enumeration values for System.Runtime.InteropServices.LayoutKind taken from mscorlib.il *)
| 0x0 -> ILTypeDefLayout.Sequential layoutInfo
| 0x2 -> ILTypeDefLayout.Explicit layoutInfo
| _ -> ILTypeDefLayout.Auto

tdLayout, tdEncoding
| EntityAttrib g WellKnownEntityAttributes.StructLayoutAttribute (Attrib(_, _, _, _, _, _, m)) ->
errorR (Error(FSComp.SR.ilStructLayoutAttributeCouldNotBeDecoded (), m))
ILTypeDefLayout.Auto, ILDefaultPInvokeEncoding.Ansi
| _ -> defaultLayout ()

// if the type's layout is Explicit, ensure that each field has a valid offset
let validateExplicit (fdef: ILFieldDef) =
Expand All @@ -12417,6 +12423,7 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option
match tdLayout with
| ILTypeDefLayout.Explicit _ -> List.iter validateExplicit ilFieldDefs
| ILTypeDefLayout.Sequential _ -> List.iter validateSequential ilFieldDefs
| ILTypeDefLayout.Extended -> List.iter validateSequential ilFieldDefs // Extended layout manages field layout via the attribute; FieldOffset is not allowed
| _ -> ()

let tdef =
Expand Down
Loading
Loading