From a50c8115123d8a69fb44dd1fa90f6638b6fa4c2b Mon Sep 17 00:00:00 2001 From: AndiJingzhiTse Date: Sat, 2 May 2026 12:53:20 -0400 Subject: [PATCH 1/5] Add polyfills for C# 15 union types (UnionAttribute and IUnion) Adds source-only polyfills for the UnionAttribute and IUnion interface from System.Runtime.CompilerServices, enabling C# 15 union type features on downlevel targets before .NET 11 ships these types in the BCL. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 3 +++ .../System.Runtime.CompilerServices.IUnion.cs | 20 ++++++++++++++++++ ...Runtime.CompilerServices.UnionAttribute.cs | 21 +++++++++++++++++++ tests/PolySharp.Tests/LanguageFeatures.cs | 19 +++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.IUnion.cs create mode 100644 src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs diff --git a/README.md b/README.md index 2fb6527..453b80a 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,9 @@ Here's an example of some of the new features that **PolySharp** can enable down - `[OverloadResolutionPriority]` (needed for [overload resolution priority](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#overload-resolution-priority)) - `[ParamsCollection]` (needed for [params collection](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#params-collections)) - `[ConstantExpected]` (see [proposal](https://github.com/dotnet/runtime/issues/33771)) +- Union types (needed for [C# 15 union types](https://devblogs.microsoft.com/dotnet/csharp-15-union-types/)) + - `[Union]` + - `IUnion` - `[CompilerLoweringPreserve]` (see [proposal](https://github.com/dotnet/runtime/issues/103430)) - `[ExtensionMarker]` (needed for [extensions](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#extension-members)) - `[IsClosedType]` (needed for [closed hierarchies](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-15.0/closed-hierarchies)) diff --git a/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.IUnion.cs b/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.IUnion.cs new file mode 100644 index 0000000..d8371b7 --- /dev/null +++ b/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.IUnion.cs @@ -0,0 +1,20 @@ +// +#pragma warning disable +#nullable enable annotations + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ + /// + /// Defines the contract for union types, providing access to the underlying value. + /// + internal interface IUnion + { + /// + /// Gets the underlying value of the union. + /// + object? Value { get; } + } +} diff --git a/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs b/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs new file mode 100644 index 0000000..609f09c --- /dev/null +++ b/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs @@ -0,0 +1,21 @@ +// +#pragma warning disable +#nullable enable annotations + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ + /// + /// Indicates that a type is a union type, allowing the C# compiler to recognize it for union type features. + /// + [global::System.AttributeUsage( + global::System.AttributeTargets.Class | + global::System.AttributeTargets.Struct, + AllowMultiple = false)] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal sealed class UnionAttribute : global::System.Attribute + { + } +} diff --git a/tests/PolySharp.Tests/LanguageFeatures.cs b/tests/PolySharp.Tests/LanguageFeatures.cs index 9899c95..b81e9a0 100644 --- a/tests/PolySharp.Tests/LanguageFeatures.cs +++ b/tests/PolySharp.Tests/LanguageFeatures.cs @@ -305,6 +305,25 @@ public static void AnotherCpuIntrinsic([ConstantExpected(Min = 0, Max = 8)] int } } +// UnionAttribute, IUnion +[Union] +internal sealed class TestUnion : IUnion +{ + private readonly object? value; + + public TestUnion(int value) + { + this.value = value; + } + + public TestUnion(string value) + { + this.value = value; + } + + public object? Value => this.value; +} + [CompilerLoweringPreserve] internal sealed class TestFlowingAttribute : Attribute; From 97a397d46f64d3c4bcf667a537182176a40e9c44 Mon Sep 17 00:00:00 2001 From: Andi Tse <103150017+AndiJingzhiTse@users.noreply.github.com> Date: Sat, 12 Sep 2026 17:25:22 -0400 Subject: [PATCH 2/5] Update src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs Co-authored-by: Theodore Tsirpanis --- .../System.Runtime.CompilerServices.UnionAttribute.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs b/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs index 609f09c..1cf370e 100644 --- a/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs +++ b/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs @@ -15,6 +15,7 @@ namespace System.Runtime.CompilerServices global::System.AttributeTargets.Struct, AllowMultiple = false)] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::Microsoft.CodeAnalysis.Embedded] internal sealed class UnionAttribute : global::System.Attribute { } From 25c4da4081577215a69b5a657ebdac0c3c12ceac Mon Sep 17 00:00:00 2001 From: Andi Tse <103150017+AndiJingzhiTse@users.noreply.github.com> Date: Mon, 14 Sep 2026 18:41:44 -0400 Subject: [PATCH 3/5] Update README.md Co-authored-by: Theodore Tsirpanis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 453b80a..bf62a5b 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Here's an example of some of the new features that **PolySharp** can enable down - `[OverloadResolutionPriority]` (needed for [overload resolution priority](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#overload-resolution-priority)) - `[ParamsCollection]` (needed for [params collection](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#params-collections)) - `[ConstantExpected]` (see [proposal](https://github.com/dotnet/runtime/issues/33771)) -- Union types (needed for [C# 15 union types](https://devblogs.microsoft.com/dotnet/csharp-15-union-types/)) +- Union types (needed for [C# 15 union types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union)) - `[Union]` - `IUnion` - `[CompilerLoweringPreserve]` (see [proposal](https://github.com/dotnet/runtime/issues/103430)) From 8fd413e8155a7fc9fb76b049bb67946aba15fd87 Mon Sep 17 00:00:00 2001 From: Andi Tse <103150017+AndiJingzhiTse@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:44:35 -0400 Subject: [PATCH 4/5] Fix union type polyfills --- .../System.Runtime.CompilerServices.IUnion.cs | 1 + .../System.Runtime.CompilerServices.UnionAttribute.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) rename src/PolySharp.SourceGenerators/EmbeddedResources/{ => LanguageSupport}/System.Runtime.CompilerServices.IUnion.cs (92%) rename src/PolySharp.SourceGenerators/EmbeddedResources/{ => LanguageSupport}/System.Runtime.CompilerServices.UnionAttribute.cs (92%) diff --git a/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.IUnion.cs b/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.IUnion.cs similarity index 92% rename from src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.IUnion.cs rename to src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.IUnion.cs index d8371b7..c5bfdbf 100644 --- a/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.IUnion.cs +++ b/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.IUnion.cs @@ -10,6 +10,7 @@ namespace System.Runtime.CompilerServices /// /// Defines the contract for union types, providing access to the underlying value. /// + [global::Microsoft.CodeAnalysis.Embedded] internal interface IUnion { /// diff --git a/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs b/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.UnionAttribute.cs similarity index 92% rename from src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs rename to src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.UnionAttribute.cs index 1cf370e..6908784 100644 --- a/src/PolySharp.SourceGenerators/EmbeddedResources/System.Runtime.CompilerServices.UnionAttribute.cs +++ b/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.UnionAttribute.cs @@ -13,7 +13,8 @@ namespace System.Runtime.CompilerServices [global::System.AttributeUsage( global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct, - AllowMultiple = false)] + AllowMultiple = false, + Inherited = false)] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Microsoft.CodeAnalysis.Embedded] internal sealed class UnionAttribute : global::System.Attribute From 8191892cbd59b84b8744b315acf176082973afc0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 25 Oct 2026 18:58:47 -0700 Subject: [PATCH 5/5] Improve union polyfill documentation and test coverage Synchronize README entries, align API documentation, and cover generated and manual unions with non-boxing accessors. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- README.md | 6 +- src/PolySharp.Package/README.md | 3 + .../System.Runtime.CompilerServices.IUnion.cs | 14 ++- ...Runtime.CompilerServices.UnionAttribute.cs | 8 +- tests/PolySharp.Tests/LanguageFeatures.cs | 90 +++++++++++++++++-- 5 files changed, 107 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index bf62a5b..9e55a55 100644 --- a/README.md +++ b/README.md @@ -55,13 +55,13 @@ Here's an example of some of the new features that **PolySharp** can enable down - `[OverloadResolutionPriority]` (needed for [overload resolution priority](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#overload-resolution-priority)) - `[ParamsCollection]` (needed for [params collection](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#params-collections)) - `[ConstantExpected]` (see [proposal](https://github.com/dotnet/runtime/issues/33771)) -- Union types (needed for [C# 15 union types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union)) - - `[Union]` - - `IUnion` - `[CompilerLoweringPreserve]` (see [proposal](https://github.com/dotnet/runtime/issues/103430)) - `[ExtensionMarker]` (needed for [extensions](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#extension-members)) - `[IsClosedType]` (needed for [closed hierarchies](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-15.0/closed-hierarchies)) - `[RequiresUnsafe]` (needed for [unsafe evolution](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/unsafe-evolution)) +- Union types (needed for [union types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union)) + - `[Union]` + - `IUnion` To leverage them, make sure to bump your C# language version. You can do this by setting the `` MSBuild property in your project. For instance, by adding `15.0` (or your desired C# version) to the first `` of your .csproj file. For more info on this, [see here](https://sergiopedri.medium.com/enabling-and-using-c-9-features-on-older-and-unsupported-runtimes-ce384d8debb), but remember that you don't need to manually copy polyfills anymore: simply adding a reference to **PolySharp** will do this for you automatically. diff --git a/src/PolySharp.Package/README.md b/src/PolySharp.Package/README.md index 66bb46e..67d3a2b 100644 --- a/src/PolySharp.Package/README.md +++ b/src/PolySharp.Package/README.md @@ -57,6 +57,9 @@ Here's an example of some of the new features that **PolySharp** can enable down - `[ExtensionMarker]` (needed for [extensions](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#extension-members)) - `[IsClosedType]` (needed for [closed hierarchies](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-15.0/closed-hierarchies)) - `[RequiresUnsafe]` (needed for [unsafe evolution](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/unsafe-evolution)) +- Union types (needed for [union types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union)) + - `[Union]` + - `IUnion` To leverage them, make sure to bump your C# language version. You can do this by setting the `` MSBuild property in your project. For instance, by adding `15.0` (or your desired C# version) to the first `` of your .csproj file. For more info on this, [see here](https://sergiopedri.medium.com/enabling-and-using-c-9-features-on-older-and-unsupported-runtimes-ce384d8debb), but remember that you don't need to manually copy polyfills anymore: simply adding a reference to **PolySharp** will do this for you automatically. diff --git a/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.IUnion.cs b/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.IUnion.cs index c5bfdbf..85a2825 100644 --- a/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.IUnion.cs +++ b/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.IUnion.cs @@ -8,14 +8,24 @@ namespace System.Runtime.CompilerServices { /// - /// Defines the contract for union types, providing access to the underlying value. + /// Provides a common interface for accessing the contents of a union type at runtime. /// + /// + /// The C# compiler automatically implements this interface on types generated by union declarations. + /// User-defined types annotated with may also implement this interface + /// to provide runtime access to the union's value. Implementing this interface is not required + /// for union behaviors provided by the compiler. + /// + /// [global::Microsoft.CodeAnalysis.Embedded] internal interface IUnion { /// - /// Gets the underlying value of the union. + /// Gets the value contained in the union, or if the union has no value. /// + /// + /// The current value of the union as one of its case types, or . + /// object? Value { get; } } } diff --git a/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.UnionAttribute.cs b/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.UnionAttribute.cs index 6908784..5e69fed 100644 --- a/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.UnionAttribute.cs +++ b/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.UnionAttribute.cs @@ -8,8 +8,14 @@ namespace System.Runtime.CompilerServices { /// - /// Indicates that a type is a union type, allowing the C# compiler to recognize it for union type features. + /// Indicates that a class or struct is a union type, enabling compiler support for union behaviors. /// + /// + /// Any class or struct annotated with this attribute is recognized by the C# compiler as a union type. + /// Union types may support behaviors such as implicit conversions from case types, pattern matching + /// that unwraps the union's contents, and switch exhaustiveness checking. + /// + /// [global::System.AttributeUsage( global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct, diff --git a/tests/PolySharp.Tests/LanguageFeatures.cs b/tests/PolySharp.Tests/LanguageFeatures.cs index b81e9a0..2222fe3 100644 --- a/tests/PolySharp.Tests/LanguageFeatures.cs +++ b/tests/PolySharp.Tests/LanguageFeatures.cs @@ -306,22 +306,96 @@ public static void AnotherCpuIntrinsic([ConstantExpected(Min = 0, Max = 8)] int } // UnionAttribute, IUnion -[Union] -internal sealed class TestUnion : IUnion +internal static class UnionTests { - private readonly object? value; + public static int Test() + { + IntOrString number = 42; + IntOrString text = "PolySharp"; + IUnion union = number; + + _ = union.Value; + _ = GetValue(number); + _ = GetValue(default); + + return GetValue(text); + } + + public static int GetValue(IntOrString value) + { + return value switch + { + int number => number, + string text => text.Length, + null => 0 + }; + } - public TestUnion(int value) + public static int TestManualUnion() { - this.value = value; + IntOrBool number = 42; + IntOrBool boolean = true; + + _ = GetManualValue(number); + _ = GetManualValue(default); + + return GetManualValue(boolean); } - public TestUnion(string value) + public static int GetManualValue(IntOrBool value) { - this.value = value; + return value switch + { + int number => number, + bool boolean => boolean ? 1 : 0, + null => -1 + }; } - public object? Value => this.value; + public readonly union IntOrString(int, string); + + [Union] + public readonly struct IntOrBool : IUnion + { + private readonly int number; + private readonly bool boolean; + private readonly byte tag; + + public IntOrBool(int value) + { + this.number = value; + this.tag = 1; + } + + public IntOrBool(bool value) + { + this.boolean = value; + this.tag = 2; + } + + public object? Value => this.tag switch + { + 1 => this.number, + 2 => this.boolean, + _ => null + }; + + public bool HasValue => this.tag != 0; + + public bool TryGetValue(out int value) + { + value = this.number; + + return this.tag == 1; + } + + public bool TryGetValue(out bool value) + { + value = this.boolean; + + return this.tag == 2; + } + } } [CompilerLoweringPreserve]