Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
72be215
Implement support for System.Runtime.InteropServices.ExtendedLayoutAt…
jkoritzinsky Feb 22, 2025
140c7f8
Implement NoPia support for importing ExtendedLayoutAttribute on types
jkoritzinsky Feb 23, 2025
00c5e58
Update test
jkoritzinsky Feb 25, 2025
23b2105
Merge branch 'main' of github.com:dotnet/roslyn into extended-layout
jkoritzinsky May 24, 2025
8da6a1b
Fix test failures and add validation for mixing ExtendedLayout with o…
jkoritzinsky May 30, 2025
838698c
Add ExtendedLayoutAttribute support to VB
jkoritzinsky May 30, 2025
3857e79
Add a speclet for this feature.
jkoritzinsky May 30, 2025
4468b16
Per offline feedback, move ExtendedLayoutAttribute and ExtendedLayout…
jkoritzinsky Jun 3, 2025
0e2370a
Adjust VB tests for SpecialType changes
jkoritzinsky Jun 3, 2025
ffc2a59
Update ErrorFacts
jkoritzinsky Jun 3, 2025
98922c4
Fix various test failures
jkoritzinsky Jun 3, 2025
9797dbe
Account for missing types in InternalSpecialType
jkoritzinsky Jun 4, 2025
a610c4b
Merge branch 'main' of github.com:dotnet/roslyn into extended-layout
jkoritzinsky Jun 12, 2025
57f5c77
Don't run verify on the extendedlayout emit test. it will fail
jkoritzinsky Jun 20, 2025
fd4d41f
Set debug format to match test default.
jkoritzinsky Jun 20, 2025
91e5314
Merge branch 'main' of github.com:dotnet/roslyn into extended-layout
jkoritzinsky Jun 20, 2025
e873123
Merge branch 'main' of github.com:dotnet/roslyn into extended-layout
jkoritzinsky Sep 4, 2025
23be26d
Update tests
jkoritzinsky Sep 5, 2025
97c9d07
Merge branch 'main' of https://github.com/dotnet/roslyn into extended…
jkoritzinsky Oct 20, 2025
7317277
Merge branch 'main' of github.com:dotnet/roslyn into extended-layout
jkoritzinsky Dec 1, 2025
bfafeef
Add missing commas
jkoritzinsky Dec 5, 2025
a918ed4
Merge branch 'main' of github.com:dotnet/roslyn into extended-layout
jkoritzinsky Dec 5, 2025
e97e4d9
PR feedback
jkoritzinsky Dec 11, 2025
f7343d6
Add inverted order tests, remove MinimalCoreLibBuilder
jkoritzinsky Dec 11, 2025
6f08876
Fix debug info format setting for Unix runs
jkoritzinsky Dec 11, 2025
9900fe3
Revert moving AllowByRefLike bit to also use an extension
jkoritzinsky Dec 15, 2025
7451867
Use helpers for creating compilations everywhere.
jkoritzinsky Dec 15, 2025
ccdbb3d
Merge branch 'extended-layout' of github.com:jkoritzinsky/roslyn; bra…
jkoritzinsky Dec 17, 2025
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
16 changes: 16 additions & 0 deletions docs/features/ExtendedLayoutAttribute.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@jcouv jcouv Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkoritzinsky Add the following line beneath "extension indexers" in the feature status page.
| [ExtendedLayoutAttribute](https://github.com/dotnet/runtime/issues/100896) | main | [Merged into 18.3](https://github.com/dotnet/roslyn/pull/78741) | [jkoritzinsky](https://github.com/jkoritzinsky) | [333fred](https://github.com/333fred), [jcouv](https://github.com/jcouv) | | |

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
System.Runtime.InteropServices.ExtendedLayoutAttribute Compiler Support
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR: will the F# compiler need to be updated as well? Consider filing an issue

=======================================================================

The .NET runtime team is introducing a new attribute, `System.Runtime.InteropServices.ExtendedLayoutAttribute`, which will allow the runtime team to provide additional layout options for types, primarily for interop scenarios.
To provide the user experience requested by the runtime team, the C# and VB compilers will have the following support for this attribute:

- If a type has the `ExtendedLayoutAttribute` applied, the compiler will emit the `TypeAttributes.ExtendedLayout` value in the type's `TypeAttributes` flags.
- If a type has the `ExtendedLayoutAttribute` applied, the compiler will not allow the `StructLayoutAttribute` to be applied to the type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR: The linked API proposal wasn't updated to clarify that ExtendedLayout attribute implies StructLayout(Extended). The code samples still show explicit [StructLayout(Extended)].

- (C# compiler only) If a type has the `ExtendedLayoutAttribute` applied, the compiler will not allow the `InlineArrayAttribute` to be applied to the type.
- Within the Roslyn compiler, the `ITypeSymbol` for a type with the `ExtendedLayoutAttribute` applied will have the following behavior:
- The `Layout` property will return a `TypeLayout` instance with the `LayoutKind` set to `Extended` (`1`), `Size` set to `0` and `Pack` set to `0`.
- A type that is embedded in an assembly (using the `NoPia` technology) will have the `ExtendedLayoutAttribute` preserved on the embedded type.

The Roslyn compiler will not have knowledge of the specific options available on the `ExtendedLayoutAttribute`, as these options will be defined by the runtime team and may expand over time. The compiler will not attempt to detect invalid field types for specific layout options. The runtime team may add analyzers in the future to handle these scenarios.

The compiler will only recognize the presence of the attribute and its implications for the type symbol's `LayoutKind` and the emitted attribute.
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -8099,6 +8099,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_PPShebangInProjectBasedProgram" xml:space="preserve">
<value>'#!' directives can be only used in scripts or file-based programs</value>
</data>
<data name="ERR_StructLayoutAndExtendedLayout" xml:space="preserve">
<value>Use of 'StructLayoutAttribute' and 'ExtendedLayoutAttribute' on the same type is not allowed.</value>
</data>`
<data name="ERR_RuntimeDoesNotSupportExtendedLayoutTypes" xml:space="preserve">
<value>The target runtime does not support extended layout types.</value>
</data>
<data name="ERR_MisplacedExtension" xml:space="preserve">
<value>An extension member syntax is disallowed in nested position within an extension member syntax</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal sealed class EmbeddedTypesManager :
private readonly ConcurrentDictionary<Symbol, bool> _reportedSymbolsMap = new ConcurrentDictionary<Symbol, bool>(ReferenceEqualityComparer.Instance);
private NamedTypeSymbol _lazySystemStringType = ErrorTypeSymbol.UnknownResultType;
private readonly MethodSymbol[] _lazyWellKnownTypeMethods;
private readonly MethodSymbol[] _lazySpecialTypeMethods;

public EmbeddedTypesManager(PEModuleBuilder moduleBeingBuilt) :
base(moduleBeingBuilt)
Expand All @@ -49,6 +50,13 @@ public EmbeddedTypesManager(PEModuleBuilder moduleBeingBuilt) :
{
_lazyWellKnownTypeMethods[i] = ErrorMethodSymbol.UnknownMethod;
}

_lazySpecialTypeMethods = new MethodSymbol[(int)SpecialMember.Count];

for (int i = 0; i < _lazySpecialTypeMethods.Length; i++)
{
_lazySpecialTypeMethods[i] = ErrorMethodSymbol.UnknownMethod;
}
}

public NamedTypeSymbol GetSystemStringType(SyntaxNode syntaxNodeOpt, DiagnosticBag diagnostics)
Expand Down Expand Up @@ -115,6 +123,38 @@ private MethodSymbol LazyGetWellKnownTypeMethod(ref MethodSymbol lazyMethod, Wel
return lazyMethod;
}

public MethodSymbol GetSpecialMethod(SpecialMember method, SyntaxNode syntaxNodeOpt, DiagnosticBag diagnostics)
{
return LazyGetSpecialTypeMethod(ref _lazySpecialTypeMethods[(int)method],
method,
syntaxNodeOpt,
diagnostics);
}

private MethodSymbol LazyGetSpecialTypeMethod(ref MethodSymbol lazyMethod, SpecialMember member, SyntaxNode syntaxNodeOpt, DiagnosticBag diagnostics)
{
if ((object)lazyMethod == (object)ErrorMethodSymbol.UnknownMethod)
{
BindingDiagnosticBag bindingDiagnosticBag = BindingDiagnosticBag.GetInstance();
var symbol = (MethodSymbol)Binder.GetSpecialTypeMember(ModuleBeingBuilt.Compilation,
member,
bindingDiagnosticBag,
syntaxNodeOpt);

if (bindingDiagnosticBag.HasAnyErrors())
{
symbol = null;
}

if (Interlocked.CompareExchange(ref lazyMethod, symbol, ErrorMethodSymbol.UnknownMethod) == ErrorMethodSymbol.UnknownMethod)
{
diagnostics.AddRange(bindingDiagnosticBag.DiagnosticBag);
}
}

return lazyMethod;
}

internal override int GetTargetAttributeSignatureIndex(CSharpAttributeData attrData, AttributeDescription description)
{
return attrData.GetTargetAttributeSignatureIndex(description);
Expand Down Expand Up @@ -152,6 +192,17 @@ internal override CSharpAttributeData CreateSynthesizedAttribute(WellKnownMember
}
}

internal override CSharpAttributeData CreateSynthesizedAttribute(SpecialMember constructor, ImmutableArray<TypedConstant> constructorArguments, ImmutableArray<KeyValuePair<string, TypedConstant>> namedArguments, SyntaxNode syntaxNodeOpt, DiagnosticBag diagnostics)
{
var ctor = GetSpecialMethod(constructor, syntaxNodeOpt, diagnostics);
if ((object)ctor == null)
{
return null;
}

return SynthesizedAttributeData.Create(ModuleBeingBuilt.Compilation, ctor, constructorArguments, namedArguments);
}

internal override bool TryGetAttributeArguments(CSharpAttributeData attrData, out ImmutableArray<TypedConstant> constructorArguments, out ImmutableArray<KeyValuePair<string, TypedConstant>> namedArguments, SyntaxNode syntaxNodeOpt, DiagnosticBag diagnostics)
{
bool result = !attrData.HasErrors;
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2447,6 +2447,9 @@ internal enum ErrorCode
ERR_EncUpdateRequiresEmittingExplicitInterfaceImplementationNotSupportedByTheRuntime = 9346,
ERR_ExtensionParameterInStaticContext = 9347,

ERR_StructLayoutAndExtendedLayout = 9348,
ERR_RuntimeDoesNotSupportExtendedLayoutTypes = 9349,

// Note: you will need to do the following after adding errors:
// 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs)
// 2) Add message to CSharpResources.resx
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2554,6 +2554,8 @@ or ErrorCode.ERR_UnexpectedArgumentListInBaseTypeWithoutParameterList
or ErrorCode.ERR_EqualityOperatorInPatternNotSupported
or ErrorCode.ERR_InequalityOperatorInPatternNotSupported
or ErrorCode.ERR_DesignatorBeforePropertyPattern
or ErrorCode.ERR_StructLayoutAndExtendedLayout
or ErrorCode.ERR_RuntimeDoesNotSupportExtendedLayoutTypes
=> false,
};
#pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
Expand Down
12 changes: 12 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,18 @@ internal bool RuntimeSupportsInlineArrayTypes
}
}

/// <summary>
/// Figure out if the target runtime supports extended layout types.
/// </summary>
internal bool RuntimeSupportsExtendedLayout
{
// Keep in sync with VB's AssemblySymbol.RuntimeSupportsExtendedLayout
get
{
return GetSpecialTypeMember(SpecialMember.System_Runtime_InteropServices_ExtendedLayoutAttribute__ctor) is object;
}
}

/// <summary>
/// Figure out if the target runtime supports inline array types.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2724,7 +2724,7 @@ internal ImmutableArray<Diagnostic> GetUnusedFieldWarnings(CancellationToken can
handledUnreadFields.Add(field);
}

if (containingType.HasStructLayoutAttribute || containingType.HasInlineArrayAttribute(out _))
if (containingType.HasStructLayoutAttribute || containingType.HasExtendedLayoutAttribute || containingType.HasInlineArrayAttribute(out _))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's another usage of HasStructLayoutAttribute below at line 2768. Should it also be updated?

{
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3035,7 +3035,7 @@ private static bool InfiniteFlatteningGraph(SourceMemberContainerTypeSymbol top,

private void CheckSequentialOnPartialType(BindingDiagnosticBag diagnostics)
{
if (!IsPartial || this.Layout.Kind != LayoutKind.Sequential)
if (!IsPartial || (this.Layout.Kind != LayoutKind.Sequential && this.Layout.Kind != LayoutKind.Extended))
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -1227,6 +1228,10 @@ protected sealed override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownA
{
arguments.GetOrCreateData<TypeWellKnownAttributeData>().HasCompilerLoweringPreserveAttribute = true;
}
else if (attribute.IsTargetAttribute(AttributeDescription.ExtendedLayoutAttribute))
{
arguments.GetOrCreateData<TypeWellKnownAttributeData>().HasExtendedLayoutAttribute = true;
}
else
{
var compilation = this.DeclaringCompilation;
Expand Down Expand Up @@ -1509,7 +1514,13 @@ internal sealed override TypeLayout Layout
get
{
var data = GetDecodedWellKnownAttributeData();
if (data != null && data.HasStructLayoutAttribute)

if (data is { HasExtendedLayoutAttribute: true })
{
return new TypeLayout(LayoutKind.Extended, 0, alignment: 0);
}

if (data is { HasStructLayoutAttribute: true })
{
return data.Layout;
}
Expand All @@ -1521,6 +1532,7 @@ internal sealed override TypeLayout Layout
//
// Dev11 compiler sets the value to 1 for structs with no instance fields and no size specified.
// It does not change the size value if it was explicitly specified to be 0, nor does it report an error.

return new TypeLayout(LayoutKind.Sequential, this.HasInstanceFields() ? 0 : 1, alignment: 0);
}

Expand All @@ -1546,6 +1558,15 @@ internal override CharSet MarshallingCharSet
}
}

internal bool HasExtendedLayoutAttribute
{
get
{
var data = GetDecodedWellKnownAttributeData();
return data is { HasExtendedLayoutAttribute: true };
}
}

internal sealed override bool HasDeclarativeSecurity
{
get
Expand Down Expand Up @@ -1885,9 +1906,15 @@ protected override void AfterMembersCompletedChecks(BindingDiagnosticBag diagnos
Binder.GetWellKnownTypeMember(DeclaringCompilation, WellKnownMember.System_Reflection_DefaultMemberAttribute__ctor, diagnostics, indexerSymbol.TryGetFirstLocation() ?? GetFirstLocation());
}

if (HasStructLayoutAttribute && HasExtendedLayoutAttribute)
{
// Use of 'StructLayoutAttribute' and 'ExtendedLayoutAttribute' on the same type is not allowed.
diagnostics.Add(ErrorCode.ERR_StructLayoutAndExtendedLayout, GetFirstLocation());
}

if (TypeKind == TypeKind.Struct && !IsRecordStruct && HasInlineArrayAttribute(out _))
{
if (Layout.Kind == LayoutKind.Explicit)
if (Layout.Kind is not (LayoutKind.Sequential or LayoutKind.Auto))
{
diagnostics.Add(ErrorCode.ERR_InvalidInlineArrayLayout, GetFirstLocation());
}
Expand Down Expand Up @@ -1971,6 +1998,14 @@ protected override void AfterMembersCompletedChecks(BindingDiagnosticBag diagnos
}
}

if (TypeKind == TypeKind.Struct && HasExtendedLayoutAttribute)
{
if (!ContainingAssembly.RuntimeSupportsExtendedLayout)
{
diagnostics.Add(ErrorCode.ERR_RuntimeDoesNotSupportExtendedLayoutTypes, GetFirstLocation());
}
}

if (this.IsMicrosoftCodeAnalysisEmbeddedAttribute())
{
// This is a user-defined implementation of the special attribute Microsoft.CodeAnalysis.EmbeddedAttribute. It needs to follow specific rules:
Expand Down
10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading