diff --git a/README.md b/README.md index 2fb6527..9e55a55 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,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.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 new file mode 100644 index 0000000..85a2825 --- /dev/null +++ b/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.IUnion.cs @@ -0,0 +1,31 @@ +// +#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 +{ + /// + /// 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 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 new file mode 100644 index 0000000..5e69fed --- /dev/null +++ b/src/PolySharp.SourceGenerators/EmbeddedResources/LanguageSupport/System.Runtime.CompilerServices.UnionAttribute.cs @@ -0,0 +1,29 @@ +// +#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 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, + AllowMultiple = false, + Inherited = false)] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::Microsoft.CodeAnalysis.Embedded] + internal sealed class UnionAttribute : global::System.Attribute + { + } +} diff --git a/tests/PolySharp.Tests/LanguageFeatures.cs b/tests/PolySharp.Tests/LanguageFeatures.cs index 9899c95..2222fe3 100644 --- a/tests/PolySharp.Tests/LanguageFeatures.cs +++ b/tests/PolySharp.Tests/LanguageFeatures.cs @@ -305,6 +305,99 @@ public static void AnotherCpuIntrinsic([ConstantExpected(Min = 0, Max = 8)] int } } +// UnionAttribute, IUnion +internal static class UnionTests +{ + 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 static int TestManualUnion() + { + IntOrBool number = 42; + IntOrBool boolean = true; + + _ = GetManualValue(number); + _ = GetManualValue(default); + + return GetManualValue(boolean); + } + + public static int GetManualValue(IntOrBool value) + { + return value switch + { + int number => number, + bool boolean => boolean ? 1 : 0, + null => -1 + }; + } + + 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] internal sealed class TestFlowingAttribute : Attribute;