From 8851018ff3304851fe29c1d7d99b534d19620f49 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Wed, 15 Apr 2026 23:03:53 +0100 Subject: [PATCH 01/11] Add `All` constant for [Flags] enums Generate a `public const` field containing the bitwise OR of all defined enum values for enums marked with [Flags]. This enables compile-time access to the combined value of all flags, useful for defaults, initialization, and validation scenarios. Closes #240 (partially) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../SourceGenerationHelper.cs | 30 ++++++++++++++++++- .../FlagsEnumExtensionsTests.cs | 26 +++++++++++++++- ...ExtensionsForFlagsEnum_Params.verified.txt | 5 ++++ ...nGenerateForExternalFlagsEnum.verified.txt | 5 ++++ ...SameNameInDifferentNamespaces.verified.txt | 10 +++++++ ...rceptEnumInDifferentNamespace.verified.txt | 5 ++++ ...nterceptEnumInGlobalNamespace.verified.txt | 5 ++++ ...ptorTests.CanInterceptHasFlag.verified.txt | 5 ++++ ...tEnumMarkedAsNotInterceptable.verified.txt | 5 ++++ ...tly_csharp14IsSupported=False.verified.txt | 5 ++++ ...ctly_csharp14IsSupported=True.verified.txt | 5 ++++ 11 files changed, 104 insertions(+), 2 deletions(-) diff --git a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs index 866e0d4..86e45fa 100644 --- a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs +++ b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs @@ -94,7 +94,35 @@ namespace /// This is a non-distinct count of defined names. /// public const int Length = - """).Append(enumToGenerate.Names.Count).Append(";").Append( + """).Append(enumToGenerate.Names.Count).Append(";"); + + if (enumToGenerate.HasFlags) + { + sb.Append( + """ + + + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const + """).Append(' ').Append(fullyQualifiedName).Append(" All = "); + + var first = true; + foreach (var member in enumToGenerate.Names) + { + if (!first) + { + sb.Append(" | "); + } + first = false; + sb.Append(fullyQualifiedName).Append('.').AppendIdentifier(member.Key); + } + + sb.Append(';'); + } + + sb.Append( """ diff --git a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs index c980c41..8d03e88 100644 --- a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs +++ b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs @@ -139,7 +139,31 @@ public void HasFlags(FlagsEnum value, FlagsEnum flag) isDefined.Should().Be(value.HasFlag(flag)); } - + + [Theory] + [InlineData(FlagsEnum.First)] + [InlineData(FlagsEnum.Second)] + [InlineData(FlagsEnum.Third)] + [InlineData(FlagsEnum.Fourth)] + [InlineData(FlagsEnum.ThirdAndFourth)] + public void AllContainsFlag(FlagsEnum flag) + { + FlagsEnumExtensions.All.HasFlag(flag).Should().BeTrue(); + } + + [Fact] + public void AllHasExpectedValue() + { + ((int)FlagsEnumExtensions.All).Should().Be(0b1111); // 15 + } + + [Fact] + public void AllIsConst() + { + const FlagsEnum all = FlagsEnumExtensions.All; + all.Should().Be(FlagsEnumExtensions.All); + } + private PackageEnumParseOptions Map(EnumParseOptions options) => new(comparisonType: options.ComparisonType, allowMatchingMetadataAttribute: options.AllowMatchingMetadataAttribute, diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt index a5a863e..f0100bd 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt @@ -25,6 +25,11 @@ namespace MyTestNameSpace /// public const int Length = 3; + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::MyTestNameSpace.MyEnum All = global::MyTestNameSpace.MyEnum.First | global::MyTestNameSpace.MyEnum.Second | global::MyTestNameSpace.MyEnum.Third; + /// /// Returns the string representation of the value. /// Directly equivalent to calling ToString() on . diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt index 7dc6b87..8c73fcc 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt @@ -25,6 +25,11 @@ namespace System.IO /// public const int Length = 6; + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::System.IO.FileShare All = global::System.IO.FileShare.None | global::System.IO.FileShare.Read | global::System.IO.FileShare.Write | global::System.IO.FileShare.ReadWrite | global::System.IO.FileShare.Delete | global::System.IO.FileShare.Inheritable; + /// /// Returns the string representation of the value. /// Directly equivalent to calling ToString() on . diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt index 3043fbb..3789634 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt @@ -26,6 +26,11 @@ namespace Foo /// public const int Length = 3; + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::Foo.MyEnum All = global::Foo.MyEnum.First | global::Foo.MyEnum.Second | global::Foo.MyEnum.Third; + /// /// Returns the string representation of the value. /// Directly equivalent to calling ToString() on . @@ -866,6 +871,11 @@ namespace Bar /// public const int Length = 3; + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::Bar.MyEnum All = global::Bar.MyEnum.First | global::Bar.MyEnum.Second | global::Bar.MyEnum.Third; + /// /// Returns the string representation of the value. /// Directly equivalent to calling ToString() on . diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt index a4155f9..c1aebfe 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt @@ -26,6 +26,11 @@ namespace Bar /// public const int Length = 3; + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::Foo.MyEnum All = global::Foo.MyEnum.First | global::Foo.MyEnum.Second | global::Foo.MyEnum.Third; + /// /// Returns the string representation of the value. /// Directly equivalent to calling ToString() on . diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt index 2208182..9ff0345 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt @@ -24,6 +24,11 @@ /// public const int Length = 3; + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::MyEnum All = global::MyEnum.First | global::MyEnum.Second | global::MyEnum.Third; + /// /// Returns the string representation of the value. /// Directly equivalent to calling ToString() on . diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt index 29cc515..17e115a 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt @@ -26,6 +26,11 @@ namespace MyTestNameSpace /// public const int Length = 3; + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::MyTestNameSpace.MyEnum All = global::MyTestNameSpace.MyEnum.First | global::MyTestNameSpace.MyEnum.Second | global::MyTestNameSpace.MyEnum.Third; + /// /// Returns the string representation of the value. /// Directly equivalent to calling ToString() on . diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt index 01973f1..dfb8f8d 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt @@ -854,6 +854,11 @@ namespace MyTestNameSpace /// public const int Length = 3; + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::MyTestNameSpace.AnotherEnum All = global::MyTestNameSpace.AnotherEnum.First | global::MyTestNameSpace.AnotherEnum.Second | global::MyTestNameSpace.AnotherEnum.Third; + /// /// Returns the string representation of the value. /// Directly equivalent to calling ToString() on . diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt index 39a725f..99845c0 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt @@ -25,6 +25,11 @@ namespace Something.Blah /// public const int Length = 2; + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::Something.Blah.ShortName All = global::Something.Blah.ShortName.First | global::Something.Blah.ShortName.Second; + /// /// Returns the string representation of the value. /// Directly equivalent to calling ToString() on . diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt index 215c19c..2378717 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt @@ -25,6 +25,11 @@ namespace Something.Blah /// public const int Length = 2; + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::Something.Blah.ShortName All = global::Something.Blah.ShortName.First | global::Something.Blah.ShortName.Second; + /// /// Returns the string representation of the value. /// Directly equivalent to calling ToString() on . From 2c306eb27ddb291fe2d735713e3d887f229421f5 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Wed, 15 Apr 2026 23:15:18 +0100 Subject: [PATCH 02/11] Add `HasAnyFlags` method for [Flags] enums Generate a `HasAnyFlags(this T value, T otherFlags)` method for enums marked with [Flags]. Returns true if any of the flags in otherFlags are set in value. When otherFlags is zero, returns true (consistent with Enum.HasFlag behavior). Partially addresses #247 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../SourceGenerationHelper.cs | 15 +++++++++ .../FlagsEnumExtensionsTests.cs | 31 +++++++++++++++++++ ...ExtensionsForFlagsEnum_Params.verified.txt | 11 +++++++ ...nGenerateForExternalFlagsEnum.verified.txt | 11 +++++++ ...SameNameInDifferentNamespaces.verified.txt | 22 +++++++++++++ ...rceptEnumInDifferentNamespace.verified.txt | 11 +++++++ ...nterceptEnumInGlobalNamespace.verified.txt | 11 +++++++ ...ptorTests.CanInterceptHasFlag.verified.txt | 11 +++++++ ...tEnumMarkedAsNotInterceptable.verified.txt | 11 +++++++ ...tly_csharp14IsSupported=False.verified.txt | 13 +++++++- ...ctly_csharp14IsSupported=True.verified.txt | 13 +++++++- 11 files changed, 158 insertions(+), 2 deletions(-) diff --git a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs index 86e45fa..61fc839 100644 --- a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs +++ b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs @@ -578,6 +578,21 @@ public static bool HasFlagFast(this """ flag) => flag == 0 ? true : (value & flag) == flag; + + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this + """).Append(' ').Append(fullyQualifiedName).Append(" value, ").Append(fullyQualifiedName) + .Append( + """ + otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; """); } diff --git a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs index 8d03e88..8a71c50 100644 --- a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs +++ b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs @@ -164,6 +164,37 @@ public void AllIsConst() all.Should().Be(FlagsEnumExtensions.All); } + public static TheoryData HasAnyFlagsData => new() + { + // Single flag present + { FlagsEnum.First | FlagsEnum.Second, FlagsEnum.First, true }, + // No queried flags present + { FlagsEnum.First | FlagsEnum.Second, FlagsEnum.Third, false }, + // Partial overlap — only some flags match + { FlagsEnum.First, FlagsEnum.First | FlagsEnum.Third, true }, + // None has no flags + { FlagsEnum.None, FlagsEnum.First, false }, + // Querying with zero (None) — consistent with HasFlag(0) behavior + { FlagsEnum.First, FlagsEnum.None, true }, + // Both zero + { FlagsEnum.None, FlagsEnum.None, true }, + // Composite member overlaps + { FlagsEnum.Third, FlagsEnum.ThirdAndFourth, true }, + // Composite member no overlap + { FlagsEnum.First, FlagsEnum.ThirdAndFourth, false }, + // Undefined value with overlap (65 = 64|1, First = 1) + { (FlagsEnum)65, FlagsEnum.First, true }, + // Undefined value no overlap + { (FlagsEnum)64, FlagsEnum.First, false }, + }; + + [Theory] + [MemberData(nameof(HasAnyFlagsData))] + public void HasAnyFlags(FlagsEnum value, FlagsEnum otherFlags, bool expected) + { + value.HasAnyFlags(otherFlags).Should().Be(expected); + } + private PackageEnumParseOptions Map(EnumParseOptions options) => new(comparisonType: options.ComparisonType, allowMatchingMetadataAttribute: options.AllowMatchingMetadataAttribute, diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt index f0100bd..a51fe13 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt @@ -108,6 +108,17 @@ namespace MyTestNameSpace public static bool HasFlagFast(this global::MyTestNameSpace.MyEnum value, global::MyTestNameSpace.MyEnum flag) => flag == 0 ? true : (value & flag) == flag; + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this global::MyTestNameSpace.MyEnum value, global::MyTestNameSpace.MyEnum otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt index 8c73fcc..136f1f5 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt @@ -117,6 +117,17 @@ namespace System.IO public static bool HasFlagFast(this global::System.IO.FileShare value, global::System.IO.FileShare flag) => flag == 0 ? true : (value & flag) == flag; + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this global::System.IO.FileShare value, global::System.IO.FileShare otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt index 3789634..a10e0c0 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt @@ -109,6 +109,17 @@ namespace Foo public static bool HasFlagFast(this global::Foo.MyEnum value, global::Foo.MyEnum flag) => flag == 0 ? true : (value & flag) == flag; + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this global::Foo.MyEnum value, global::Foo.MyEnum otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. @@ -954,6 +965,17 @@ namespace Bar public static bool HasFlagFast(this global::Bar.MyEnum value, global::Bar.MyEnum flag) => flag == 0 ? true : (value & flag) == flag; + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this global::Bar.MyEnum value, global::Bar.MyEnum otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt index c1aebfe..0532a7a 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt @@ -109,6 +109,17 @@ namespace Bar public static bool HasFlagFast(this global::Foo.MyEnum value, global::Foo.MyEnum flag) => flag == 0 ? true : (value & flag) == flag; + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this global::Foo.MyEnum value, global::Foo.MyEnum otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt index 9ff0345..da45324 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt @@ -107,6 +107,17 @@ public static bool HasFlagFast(this global::MyEnum value, global::MyEnum flag) => flag == 0 ? true : (value & flag) == flag; + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this global::MyEnum value, global::MyEnum otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt index 17e115a..5cca9cd 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt @@ -109,6 +109,17 @@ namespace MyTestNameSpace public static bool HasFlagFast(this global::MyTestNameSpace.MyEnum value, global::MyTestNameSpace.MyEnum flag) => flag == 0 ? true : (value & flag) == flag; + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this global::MyTestNameSpace.MyEnum value, global::MyTestNameSpace.MyEnum otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt index dfb8f8d..0487319 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt @@ -937,6 +937,17 @@ namespace MyTestNameSpace public static bool HasFlagFast(this global::MyTestNameSpace.AnotherEnum value, global::MyTestNameSpace.AnotherEnum flag) => flag == 0 ? true : (value & flag) == flag; + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this global::MyTestNameSpace.AnotherEnum value, global::MyTestNameSpace.AnotherEnum otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt index 99845c0..ccc380d 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by the NetEscapades.EnumGenerators source generator // @@ -105,6 +105,17 @@ namespace Something.Blah public static bool HasFlagFast(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName flag) => flag == 0 ? true : (value & flag) == flag; + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt index 2378717..7533ca0 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by the NetEscapades.EnumGenerators source generator // @@ -105,6 +105,17 @@ namespace Something.Blah public static bool HasFlagFast(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName flag) => flag == 0 ? true : (value & flag) == flag; + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in the flags are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. From cf19b0bade9dc1de82aca51e81031d84e73f3d0c Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Wed, 15 Apr 2026 23:23:18 +0100 Subject: [PATCH 03/11] Add `GetFlags` method for [Flags] enums Generate a `GetFlags(this T value, Span buffer)` method for enums marked with [Flags]. Decomposes the value into individual set bits, writing each as a separate enum value into the caller-provided Span buffer and returning the count. Uses zero-allocation bit manipulation. Only emitted when Span is available (netcoreapp2.1+, netstandard2.1+, or with System.Memory package). Partially addresses #247 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../SourceGenerationHelper.cs | 36 ++++++++++++++++ .../FlagsEnumExtensionsTests.cs | 31 ++++++++++++++ ...ExtensionsForFlagsEnum_Params.verified.txt | 21 ++++++++++ ...nGenerateForExternalFlagsEnum.verified.txt | 21 ++++++++++ ...SameNameInDifferentNamespaces.verified.txt | 42 +++++++++++++++++++ ...rceptEnumInDifferentNamespace.verified.txt | 21 ++++++++++ ...nterceptEnumInGlobalNamespace.verified.txt | 21 ++++++++++ ...ptorTests.CanInterceptHasFlag.verified.txt | 21 ++++++++++ ...tEnumMarkedAsNotInterceptable.verified.txt | 21 ++++++++++ ...tly_csharp14IsSupported=False.verified.txt | 21 ++++++++++ ...ctly_csharp14IsSupported=True.verified.txt | 21 ++++++++++ 11 files changed, 277 insertions(+) diff --git a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs index 61fc839..6fbf043 100644 --- a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs +++ b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs @@ -593,6 +593,42 @@ public static bool HasAnyFlags(this """ otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this + """).Append(' ').Append(fullyQualifiedName).Append(" value, global::System.Span<").Append(fullyQualifiedName).Append( + """ + > buffer) + { + var count = 0; + var v = ( + """).Append(enumToGenerate.UnderlyingType).Append( + """ + )value; + while (v != 0) + { + var flag = ( + """).Append(enumToGenerate.UnderlyingType).Append( + """ + )(v & -v); + buffer[count++] = ( + """).Append(fullyQualifiedName).Append( + """ + )flag; + v &= ( + """).Append(enumToGenerate.UnderlyingType).Append( + """ + )(v - 1); + } + return count; + } + #endif """); } diff --git a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs index 8a71c50..eedb059 100644 --- a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs +++ b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs @@ -195,6 +195,37 @@ public void HasAnyFlags(FlagsEnum value, FlagsEnum otherFlags, bool expected) value.HasAnyFlags(otherFlags).Should().Be(expected); } +#if READONLYSPAN + public static TheoryData GetFlagsData => new() + { + // Single flag decomposes to itself + { FlagsEnum.First, 1, new[] { FlagsEnum.First } }, + // Multiple flags decompose into individual bits, lowest first + { FlagsEnum.First | FlagsEnum.Third, 2, new[] { FlagsEnum.First, FlagsEnum.Third } }, + // Composite member decomposes to individual bits + { FlagsEnum.ThirdAndFourth, 2, new[] { FlagsEnum.Third, FlagsEnum.Fourth } }, + // None returns empty + { FlagsEnum.None, 0, Array.Empty() }, + // Undefined bits still returned + { (FlagsEnum)65, 2, new[] { (FlagsEnum)1, (FlagsEnum)64 } }, + // All defined flags decomposes to 4 individual bits + { FlagsEnumExtensions.All, 4, new[] { FlagsEnum.First, FlagsEnum.Second, FlagsEnum.Third, FlagsEnum.Fourth } }, + // Multiple bits set in underlying int + { (FlagsEnum)0xFF, 8, new[] { (FlagsEnum)1, (FlagsEnum)2, (FlagsEnum)4, (FlagsEnum)8, + (FlagsEnum)16, (FlagsEnum)32, (FlagsEnum)64, (FlagsEnum)128 } }, + }; + + [Theory] + [MemberData(nameof(GetFlagsData))] + public void GetFlags(FlagsEnum value, int expectedCount, FlagsEnum[] expectedFlags) + { + Span buffer = stackalloc FlagsEnum[32]; + var count = value.GetFlags(buffer); + count.Should().Be(expectedCount); + buffer.Slice(0, count).ToArray().Should().Equal(expectedFlags); + } +#endif + private PackageEnumParseOptions Map(EnumParseOptions options) => new(comparisonType: options.ComparisonType, allowMatchingMetadataAttribute: options.AllowMatchingMetadataAttribute, diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt index a51fe13..ff07fce 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt @@ -119,6 +119,27 @@ namespace MyTestNameSpace public static bool HasAnyFlags(this global::MyTestNameSpace.MyEnum value, global::MyTestNameSpace.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this global::MyTestNameSpace.MyEnum value, global::System.Span buffer) + { + var count = 0; + var v = (int)value; + while (v != 0) + { + var flag = (int)(v & -v); + buffer[count++] = (global::MyTestNameSpace.MyEnum)flag; + v &= (int)(v - 1); + } + return count; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt index 136f1f5..239cbc7 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt @@ -128,6 +128,27 @@ namespace System.IO public static bool HasAnyFlags(this global::System.IO.FileShare value, global::System.IO.FileShare otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this global::System.IO.FileShare value, global::System.Span buffer) + { + var count = 0; + var v = (int)value; + while (v != 0) + { + var flag = (int)(v & -v); + buffer[count++] = (global::System.IO.FileShare)flag; + v &= (int)(v - 1); + } + return count; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt index a10e0c0..4a2d351 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt @@ -120,6 +120,27 @@ namespace Foo public static bool HasAnyFlags(this global::Foo.MyEnum value, global::Foo.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this global::Foo.MyEnum value, global::System.Span buffer) + { + var count = 0; + var v = (int)value; + while (v != 0) + { + var flag = (int)(v & -v); + buffer[count++] = (global::Foo.MyEnum)flag; + v &= (int)(v - 1); + } + return count; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. @@ -976,6 +997,27 @@ namespace Bar public static bool HasAnyFlags(this global::Bar.MyEnum value, global::Bar.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this global::Bar.MyEnum value, global::System.Span buffer) + { + var count = 0; + var v = (int)value; + while (v != 0) + { + var flag = (int)(v & -v); + buffer[count++] = (global::Bar.MyEnum)flag; + v &= (int)(v - 1); + } + return count; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt index 0532a7a..a208927 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt @@ -120,6 +120,27 @@ namespace Bar public static bool HasAnyFlags(this global::Foo.MyEnum value, global::Foo.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this global::Foo.MyEnum value, global::System.Span buffer) + { + var count = 0; + var v = (int)value; + while (v != 0) + { + var flag = (int)(v & -v); + buffer[count++] = (global::Foo.MyEnum)flag; + v &= (int)(v - 1); + } + return count; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt index da45324..8edda85 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt @@ -118,6 +118,27 @@ public static bool HasAnyFlags(this global::MyEnum value, global::MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this global::MyEnum value, global::System.Span buffer) + { + var count = 0; + var v = (int)value; + while (v != 0) + { + var flag = (int)(v & -v); + buffer[count++] = (global::MyEnum)flag; + v &= (int)(v - 1); + } + return count; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt index 5cca9cd..a4007ff 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt @@ -120,6 +120,27 @@ namespace MyTestNameSpace public static bool HasAnyFlags(this global::MyTestNameSpace.MyEnum value, global::MyTestNameSpace.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this global::MyTestNameSpace.MyEnum value, global::System.Span buffer) + { + var count = 0; + var v = (int)value; + while (v != 0) + { + var flag = (int)(v & -v); + buffer[count++] = (global::MyTestNameSpace.MyEnum)flag; + v &= (int)(v - 1); + } + return count; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt index 0487319..2638856 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt @@ -948,6 +948,27 @@ namespace MyTestNameSpace public static bool HasAnyFlags(this global::MyTestNameSpace.AnotherEnum value, global::MyTestNameSpace.AnotherEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this global::MyTestNameSpace.AnotherEnum value, global::System.Span buffer) + { + var count = 0; + var v = (int)value; + while (v != 0) + { + var flag = (int)(v & -v); + buffer[count++] = (global::MyTestNameSpace.AnotherEnum)flag; + v &= (int)(v - 1); + } + return count; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt index ccc380d..9bb3146 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt @@ -116,6 +116,27 @@ namespace Something.Blah public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this global::Something.Blah.ShortName value, global::System.Span buffer) + { + var count = 0; + var v = (int)value; + while (v != 0) + { + var flag = (int)(v & -v); + buffer[count++] = (global::Something.Blah.ShortName)flag; + v &= (int)(v - 1); + } + return count; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt index 7533ca0..c5999ae 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt @@ -116,6 +116,27 @@ namespace Something.Blah public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// + /// The value to decompose into individual flags + /// The buffer to write the individual flags into. Must be large enough to hold all set bits. + /// The number of individual flags written to the buffer. + public static int GetFlags(this global::Something.Blah.ShortName value, global::System.Span buffer) + { + var count = 0; + var v = (int)value; + while (v != 0) + { + var flag = (int)(v & -v); + buffer[count++] = (global::Something.Blah.ShortName)flag; + v &= (int)(v - 1); + } + return count; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. From f8341855cc7f1245cf7746a51477daa0f5671ad0 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Sun, 19 Apr 2026 21:04:03 +0100 Subject: [PATCH 04/11] fixup! Add `All` constant for [Flags] enums --- .../FlagsEnumExtensionsTests.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs index eedb059..c1b3864 100644 --- a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs +++ b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs @@ -140,13 +140,8 @@ public void HasFlags(FlagsEnum value, FlagsEnum flag) isDefined.Should().Be(value.HasFlag(flag)); } - [Theory] - [InlineData(FlagsEnum.First)] - [InlineData(FlagsEnum.Second)] - [InlineData(FlagsEnum.Third)] - [InlineData(FlagsEnum.Fourth)] - [InlineData(FlagsEnum.ThirdAndFourth)] - public void AllContainsFlag(FlagsEnum flag) + [Fact] + public void AllContainsFlag() { FlagsEnumExtensions.All.HasFlag(flag).Should().BeTrue(); } From 054d804ffaadb71af73b3f2f0f1e54ec85b9db95 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Sun, 19 Apr 2026 21:04:03 +0100 Subject: [PATCH 05/11] fixup! Add `All` constant for [Flags] enums --- .../FlagsEnumExtensionsTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs index c1b3864..644a182 100644 --- a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs +++ b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs @@ -143,7 +143,10 @@ public void HasFlags(FlagsEnum value, FlagsEnum flag) [Fact] public void AllContainsFlag() { - FlagsEnumExtensions.All.HasFlag(flag).Should().BeTrue(); + foreach (var flag in FlagsEnumExtensions.GetValues()) + { + FlagsEnumExtensions.All.HasFlag(flag).Should().BeTrue(); + } } [Fact] From a08afef4c3bb9c9ebd5bcb6be0cdaad639e30c61 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Sun, 19 Apr 2026 21:04:03 +0100 Subject: [PATCH 06/11] fixup! Add `HasAnyFlags` method for [Flags] enums --- .../FlagsEnumExtensionsTests.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs index 644a182..f9346fa 100644 --- a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs +++ b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs @@ -155,13 +155,6 @@ public void AllHasExpectedValue() ((int)FlagsEnumExtensions.All).Should().Be(0b1111); // 15 } - [Fact] - public void AllIsConst() - { - const FlagsEnum all = FlagsEnumExtensions.All; - all.Should().Be(FlagsEnumExtensions.All); - } - public static TheoryData HasAnyFlagsData => new() { // Single flag present From 7262bbca938513555480daf69755bd745819db85 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Sun, 19 Apr 2026 21:11:29 +0100 Subject: [PATCH 07/11] fixup! Add `HasAnyFlags` method for [Flags] enums --- .../SourceGenerationHelper.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs index 6fbf043..f73c623 100644 --- a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs +++ b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs @@ -584,9 +584,10 @@ public static bool HasFlagFast(this /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this """).Append(' ').Append(fullyQualifiedName).Append(" value, ").Append(fullyQualifiedName) .Append( From 6ebda701b171ca9f8e24aa27ae2494a697811ae0 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Sun, 19 Apr 2026 21:22:08 +0100 Subject: [PATCH 08/11] fixup! Add `HasAnyFlags` method for [Flags] enums --- ...eEnumExtensionsForFlagsEnum_Params.verified.txt | 7 ++++--- ...ts.CanGenerateForExternalFlagsEnum.verified.txt | 7 ++++--- ...sWithSameNameInDifferentNamespaces.verified.txt | 14 ++++++++------ ...nInterceptEnumInDifferentNamespace.verified.txt | 7 ++++--- ....CanInterceptEnumInGlobalNamespace.verified.txt | 7 ++++--- ...terceptorTests.CanInterceptHasFlag.verified.txt | 7 ++++--- ...erceptEnumMarkedAsNotInterceptable.verified.txt | 7 ++++--- ...orrectly_csharp14IsSupported=False.verified.txt | 7 ++++--- ...Correctly_csharp14IsSupported=True.verified.txt | 7 ++++--- 9 files changed, 40 insertions(+), 30 deletions(-) diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt index ff07fce..61f04d0 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt @@ -113,9 +113,10 @@ namespace MyTestNameSpace /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this global::MyTestNameSpace.MyEnum value, global::MyTestNameSpace.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt index 239cbc7..50f9105 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt @@ -122,9 +122,10 @@ namespace System.IO /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this global::System.IO.FileShare value, global::System.IO.FileShare otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt index 4a2d351..b6a8824 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt @@ -114,9 +114,10 @@ namespace Foo /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this global::Foo.MyEnum value, global::Foo.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; @@ -991,9 +992,10 @@ namespace Bar /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this global::Bar.MyEnum value, global::Bar.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt index a208927..0fd245f 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt @@ -114,9 +114,10 @@ namespace Bar /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this global::Foo.MyEnum value, global::Foo.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt index 8edda85..44f2402 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt @@ -112,9 +112,10 @@ /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this global::MyEnum value, global::MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt index a4007ff..a405531 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt @@ -114,9 +114,10 @@ namespace MyTestNameSpace /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this global::MyTestNameSpace.MyEnum value, global::MyTestNameSpace.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt index 2638856..916dd1d 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt @@ -942,9 +942,10 @@ namespace MyTestNameSpace /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this global::MyTestNameSpace.AnotherEnum value, global::MyTestNameSpace.AnotherEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt index 9bb3146..840a31e 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt @@ -110,9 +110,10 @@ namespace Something.Blah /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt index c5999ae..52565d4 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt @@ -110,9 +110,10 @@ namespace Something.Blah /// /// The value of the instance to investigate /// The flags to check for - /// if any of the fields set in the flags are also set in the current instance; otherwise . - /// If the underlying value of is zero, the method returns true. - /// This is consistent with the behaviour of + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; From 4c3c8323b2892e29361b96525deae2ddeac0d81c Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Sun, 19 Apr 2026 21:39:07 +0100 Subject: [PATCH 09/11] fixup! Add `GetFlags` method for [Flags] enums --- .../SourceGenerationHelper.cs | 4 ++++ ...GenerateEnumExtensionsForFlagsEnum_Params.verified.txt | 4 ++++ ...atorTests.CanGenerateForExternalFlagsEnum.verified.txt | 4 ++++ ...dleEnumsWithSameNameInDifferentNamespaces.verified.txt | 8 ++++++++ ...ests.CanInterceptEnumInDifferentNamespace.verified.txt | 4 ++++ ...orTests.CanInterceptEnumInGlobalNamespace.verified.txt | 4 ++++ .../InterceptorTests.CanInterceptHasFlag.verified.txt | 4 ++++ ...sNotInterceptEnumMarkedAsNotInterceptable.verified.txt | 4 ++++ ...gsEnumCorrectly_csharp14IsSupported=False.verified.txt | 4 ++++ ...agsEnumCorrectly_csharp14IsSupported=True.verified.txt | 4 ++++ 10 files changed, 44 insertions(+) diff --git a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs index f73c623..4d5bfb5 100644 --- a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs +++ b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs @@ -614,6 +614,8 @@ public static int GetFlags(this )value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = ( """).Append(enumToGenerate.UnderlyingType).Append( """ @@ -622,6 +624,8 @@ public static int GetFlags(this """).Append(fullyQualifiedName).Append( """ )flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= ( """).Append(enumToGenerate.UnderlyingType).Append( """ diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt index 61f04d0..27eb6bd 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt @@ -133,8 +133,12 @@ namespace MyTestNameSpace var v = (int)value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = (int)(v & -v); buffer[count++] = (global::MyTestNameSpace.MyEnum)flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= (int)(v - 1); } return count; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt index 50f9105..cb34aa1 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt @@ -142,8 +142,12 @@ namespace System.IO var v = (int)value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = (int)(v & -v); buffer[count++] = (global::System.IO.FileShare)flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= (int)(v - 1); } return count; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt index b6a8824..1e9e3ff 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt @@ -134,8 +134,12 @@ namespace Foo var v = (int)value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = (int)(v & -v); buffer[count++] = (global::Foo.MyEnum)flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= (int)(v - 1); } return count; @@ -1012,8 +1016,12 @@ namespace Bar var v = (int)value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = (int)(v & -v); buffer[count++] = (global::Bar.MyEnum)flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= (int)(v - 1); } return count; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt index 0fd245f..0167278 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt @@ -134,8 +134,12 @@ namespace Bar var v = (int)value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = (int)(v & -v); buffer[count++] = (global::Foo.MyEnum)flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= (int)(v - 1); } return count; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt index 44f2402..7118b5e 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt @@ -132,8 +132,12 @@ var v = (int)value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = (int)(v & -v); buffer[count++] = (global::MyEnum)flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= (int)(v - 1); } return count; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt index a405531..03ac049 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt @@ -134,8 +134,12 @@ namespace MyTestNameSpace var v = (int)value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = (int)(v & -v); buffer[count++] = (global::MyTestNameSpace.MyEnum)flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= (int)(v - 1); } return count; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt index 916dd1d..f2b2b6b 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt @@ -962,8 +962,12 @@ namespace MyTestNameSpace var v = (int)value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = (int)(v & -v); buffer[count++] = (global::MyTestNameSpace.AnotherEnum)flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= (int)(v - 1); } return count; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt index 840a31e..b7b9ca1 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt @@ -130,8 +130,12 @@ namespace Something.Blah var v = (int)value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = (int)(v & -v); buffer[count++] = (global::Something.Blah.ShortName)flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= (int)(v - 1); } return count; diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt index 52565d4..a6c51b3 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt @@ -130,8 +130,12 @@ namespace Something.Blah var v = (int)value; while (v != 0) { + // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit + // (e.g. 0b0110 & -0b0110 = 0b0010). var flag = (int)(v & -v); buffer[count++] = (global::Something.Blah.ShortName)flag; + // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit + // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. v &= (int)(v - 1); } return count; From bf6ec678a7917d822ba8047adcb5419daf076946 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Sun, 10 May 2026 21:51:43 +0100 Subject: [PATCH 10/11] fixup! Add `GetFlags` method for [Flags] enums --- .../SourceGenerationHelper.cs | 136 ++- .../BitFlagsEnumExtensionsTests.cs | 76 ++ .../Enums.cs | 11 + .../FlagsEnumExtensionsTests.cs | 31 - ...ExtensionsForFlagsEnum_Params.verified.txt | 40 +- ...nGenerateForExternalFlagsEnum.verified.txt | 25 - ...SameNameInDifferentNamespaces.verified.txt | 80 +- ...rceptEnumInDifferentNamespace.verified.txt | 40 +- ...nterceptEnumInGlobalNamespace.verified.txt | 40 +- ...ptorTests.CanInterceptHasFlag.verified.txt | 40 +- ...tEnumMarkedAsNotInterceptable.verified.txt | 38 +- ...num_csharp14IsSupported=False.verified.txt | 866 +++++++++++++++++ ...Enum_csharp14IsSupported=True.verified.txt | 871 ++++++++++++++++++ ...tly_csharp14IsSupported=False.verified.txt | 36 +- ...ctly_csharp14IsSupported=True.verified.txt | 36 +- .../SourceGenerationHelperSnapshotTests.cs | 36 + 16 files changed, 2164 insertions(+), 238 deletions(-) create mode 100644 tests/NetEscapades.EnumGenerators.IntegrationTests/BitFlagsEnumExtensionsTests.cs create mode 100644 tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=False.verified.txt create mode 100644 tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=True.verified.txt diff --git a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs index 4d5bfb5..46dbecc 100644 --- a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs +++ b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs @@ -594,47 +594,93 @@ public static bool HasAnyFlags(this """ otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + """); - #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY - /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. - /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this - """).Append(' ').Append(fullyQualifiedName).Append(" value, global::System.Span<").Append(fullyQualifiedName).Append( - """ - > buffer) - { - var count = 0; - var v = ( - """).Append(enumToGenerate.UnderlyingType).Append( - """ - )value; - while (v != 0) + // Collect single-bit flag members. If any member is a non-zero composite (not a power of two), + // skip emitting TryGetFlags/DistinctFlagCount entirely — the strict rule keeps semantics deterministic. + List<(string Name, ulong Value)>? singleBitFlags = null; + var hasComposite = false; + var seenBitValues = new HashSet(); + foreach (var member in enumToGenerate.Names) + { + var bits = ToUInt64BitPattern(member.Value.ConstantValue); + if (bits == 0) + { + // Zero-valued members (e.g. None) are allowed but not emitted. + continue; + } + if ((bits & (bits - 1)) != 0) + { + hasComposite = true; + break; + } + if (seenBitValues.Add(bits)) + { + singleBitFlags ??= new List<(string, ulong)>(); + singleBitFlags.Add((member.Key, bits)); + } + } + + if (!hasComposite && singleBitFlags is { Count: > 0 }) + { + singleBitFlags.Sort((a, b) => a.Value.CompareTo(b.Value)); + + sb.Append( + """ + + + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = + """).Append(' ').Append(singleBitFlags.Count).Append(';'); + + sb.Append( + """ + + + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Writes each defined single-bit flag set in into . + /// Undefined bits and the zero/None value are excluded from the output. + /// + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this + """).Append(' ').Append(fullyQualifiedName).Append(" value, global::System.Span<").Append(fullyQualifiedName).Append( + """ + > buffer, out int count) { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = ( - """).Append(enumToGenerate.UnderlyingType).Append( - """ - )(v & -v); - buffer[count++] = ( - """).Append(fullyQualifiedName).Append( - """ - )flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= ( - """).Append(enumToGenerate.UnderlyingType).Append( - """ - )(v - 1); + if (buffer.Length < DistinctFlagCount) + { + count = 0; + return false; + } + count = 0; + """); + + foreach (var flag in singleBitFlags) + { + sb.Append("\n if ((value & ") + .Append(fullyQualifiedName).Append('.').AppendIdentifier(flag.Name) + .Append(") == ") + .Append(fullyQualifiedName).Append('.').AppendIdentifier(flag.Name) + .Append(")\n buffer[count++] = ") + .Append(fullyQualifiedName).Append('.').AppendIdentifier(flag.Name) + .Append(';'); + } + + sb.Append( + """ + + return true; } - return count; - } - #endif - """); + #endif + """); + } } sb.Append( @@ -2464,6 +2510,20 @@ private static StringBuilder AppendIdentifier(this StringBuilder sb, string iden return sb.Append(identifier); } + private static ulong ToUInt64BitPattern(object value) + => value switch + { + sbyte v => (byte)v, + byte v => v, + short v => (ushort)v, + ushort v => v, + int v => (uint)v, + uint v => v, + long v => unchecked((ulong)v), + ulong v => v, + _ => 0UL, + }; + private enum AlternativeMethodChoice { None, diff --git a/tests/NetEscapades.EnumGenerators.IntegrationTests/BitFlagsEnumExtensionsTests.cs b/tests/NetEscapades.EnumGenerators.IntegrationTests/BitFlagsEnumExtensionsTests.cs new file mode 100644 index 0000000..d5acec0 --- /dev/null +++ b/tests/NetEscapades.EnumGenerators.IntegrationTests/BitFlagsEnumExtensionsTests.cs @@ -0,0 +1,76 @@ +using FluentAssertions; +using System; +using Xunit; + +#if INTEGRATION_TESTS +namespace NetEscapades.EnumGenerators.IntegrationTests; +#elif PRIVATEASSETS_INTEGRATION_TESTS +namespace NetEscapades.EnumGenerators.PrivateAssets.IntegrationTests; +#elif NETSTANDARD_INTEGRATION_TESTS +namespace NetEscapades.EnumGenerators.NetStandard.IntegrationTests; +#elif NETSTANDARD_SYSTEMMEMORY_INTEGRATION_TESTS +namespace NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests; +#elif INTERCEPTOR_TESTS +namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests; +#elif NUGET_INTEGRATION_TESTS +namespace NetEscapades.EnumGenerators.Nuget.IntegrationTests; +#elif NUGET_INTERCEPTOR_TESTS +namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests; +#elif NUGET_SYSTEMMEMORY_INTEGRATION_TESTS +namespace NetEscapades.EnumGenerators.Nuget.SystemMemory.IntegrationTests; +#elif NUGET_SYSTEMMEMORY_PRIVATEASSETS_INTEGRATION_TESTS +namespace NetEscapades.EnumGenerators.Nuget.SystemMemory.PrivateAssets.IntegrationTests; +#else +#error Unknown integration tests +#endif + +public class BitFlagsEnumExtensionsTests +{ + [Fact] + public void DistinctFlagCountExcludesNone() + { + BitFlagsEnumExtensions.DistinctFlagCount.Should().Be(4); + } + +#if READONLYSPAN + public static TheoryData TryGetFlagsData => new() + { + // Single flag → itself + { BitFlagsEnum.First, 1, new[] { BitFlagsEnum.First } }, + // Multiple flags — bit-value order + { BitFlagsEnum.First | BitFlagsEnum.Third, 2, new[] { BitFlagsEnum.First, BitFlagsEnum.Third } }, + // None → empty output + { BitFlagsEnum.None, 0, Array.Empty() }, + // Undefined bits silently dropped (16 is not defined) + { (BitFlagsEnum)17, 1, new[] { BitFlagsEnum.First } }, + // Only undefined bits → empty output + { (BitFlagsEnum)16, 0, Array.Empty() }, + // All defined flags set → 4 single-bit members + { BitFlagsEnumExtensions.All, 4, + new[] { BitFlagsEnum.First, BitFlagsEnum.Second, BitFlagsEnum.Third, BitFlagsEnum.Fourth } }, + // All bits set across underlying type → still just the 4 defined flags + { (BitFlagsEnum)0xFF, 4, + new[] { BitFlagsEnum.First, BitFlagsEnum.Second, BitFlagsEnum.Third, BitFlagsEnum.Fourth } }, + }; + + [Theory] + [MemberData(nameof(TryGetFlagsData))] + public void TryGetFlags(BitFlagsEnum value, int expectedCount, BitFlagsEnum[] expectedFlags) + { + Span buffer = stackalloc BitFlagsEnum[BitFlagsEnumExtensions.DistinctFlagCount]; + var result = value.TryGetFlags(buffer, out var count); + result.Should().BeTrue(); + count.Should().Be(expectedCount); + buffer.Slice(0, count).ToArray().Should().Equal(expectedFlags); + } + + [Fact] + public void TryGetFlags_BufferSmallerThanDistinctFlagCount_ReturnsFalse() + { + Span buffer = stackalloc BitFlagsEnum[BitFlagsEnumExtensions.DistinctFlagCount - 1]; + var result = BitFlagsEnum.First.TryGetFlags(buffer, out var count); + result.Should().BeFalse(); + count.Should().Be(0); + } +#endif +} diff --git a/tests/NetEscapades.EnumGenerators.IntegrationTests/Enums.cs b/tests/NetEscapades.EnumGenerators.IntegrationTests/Enums.cs index 2be7a12..0059824 100644 --- a/tests/NetEscapades.EnumGenerators.IntegrationTests/Enums.cs +++ b/tests/NetEscapades.EnumGenerators.IntegrationTests/Enums.cs @@ -167,6 +167,17 @@ public enum FlagsEnum ThirdAndFourth = Third | Fourth, } + [EnumExtensions] + [Flags] + public enum BitFlagsEnum + { + None = 0, + First = 1 << 0, + Second = 1 << 1, + Third = 1 << 2, + Fourth = 1 << 3, + } + [EnumExtensions(MetadataSource = MetadataSource.DescriptionAttribute)] public enum StringTesting { diff --git a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs index f9346fa..a77b731 100644 --- a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs +++ b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs @@ -186,37 +186,6 @@ public void HasAnyFlags(FlagsEnum value, FlagsEnum otherFlags, bool expected) value.HasAnyFlags(otherFlags).Should().Be(expected); } -#if READONLYSPAN - public static TheoryData GetFlagsData => new() - { - // Single flag decomposes to itself - { FlagsEnum.First, 1, new[] { FlagsEnum.First } }, - // Multiple flags decompose into individual bits, lowest first - { FlagsEnum.First | FlagsEnum.Third, 2, new[] { FlagsEnum.First, FlagsEnum.Third } }, - // Composite member decomposes to individual bits - { FlagsEnum.ThirdAndFourth, 2, new[] { FlagsEnum.Third, FlagsEnum.Fourth } }, - // None returns empty - { FlagsEnum.None, 0, Array.Empty() }, - // Undefined bits still returned - { (FlagsEnum)65, 2, new[] { (FlagsEnum)1, (FlagsEnum)64 } }, - // All defined flags decomposes to 4 individual bits - { FlagsEnumExtensions.All, 4, new[] { FlagsEnum.First, FlagsEnum.Second, FlagsEnum.Third, FlagsEnum.Fourth } }, - // Multiple bits set in underlying int - { (FlagsEnum)0xFF, 8, new[] { (FlagsEnum)1, (FlagsEnum)2, (FlagsEnum)4, (FlagsEnum)8, - (FlagsEnum)16, (FlagsEnum)32, (FlagsEnum)64, (FlagsEnum)128 } }, - }; - - [Theory] - [MemberData(nameof(GetFlagsData))] - public void GetFlags(FlagsEnum value, int expectedCount, FlagsEnum[] expectedFlags) - { - Span buffer = stackalloc FlagsEnum[32]; - var count = value.GetFlags(buffer); - count.Should().Be(expectedCount); - buffer.Slice(0, count).ToArray().Should().Equal(expectedFlags); - } -#endif - private PackageEnumParseOptions Map(EnumParseOptions options) => new(comparisonType: options.ComparisonType, allowMatchingMetadataAttribute: options.AllowMatchingMetadataAttribute, diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt index 27eb6bd..c894226 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt @@ -120,28 +120,36 @@ namespace MyTestNameSpace public static bool HasAnyFlags(this global::MyTestNameSpace.MyEnum value, global::MyTestNameSpace.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 3; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// Writes each defined single-bit flag set in into . + /// Undefined bits and the zero/None value are excluded from the output. /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this global::MyTestNameSpace.MyEnum value, global::System.Span buffer) + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::MyTestNameSpace.MyEnum value, global::System.Span buffer, out int count) { - var count = 0; - var v = (int)value; - while (v != 0) + if (buffer.Length < DistinctFlagCount) { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = (int)(v & -v); - buffer[count++] = (global::MyTestNameSpace.MyEnum)flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= (int)(v - 1); + count = 0; + return false; } - return count; + count = 0; + if ((value & global::MyTestNameSpace.MyEnum.First) == global::MyTestNameSpace.MyEnum.First) + buffer[count++] = global::MyTestNameSpace.MyEnum.First; + if ((value & global::MyTestNameSpace.MyEnum.Second) == global::MyTestNameSpace.MyEnum.Second) + buffer[count++] = global::MyTestNameSpace.MyEnum.Second; + if ((value & global::MyTestNameSpace.MyEnum.Third) == global::MyTestNameSpace.MyEnum.Third) + buffer[count++] = global::MyTestNameSpace.MyEnum.Third; + return true; } #endif diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt index cb34aa1..f65cef0 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt @@ -129,31 +129,6 @@ namespace System.IO public static bool HasAnyFlags(this global::System.IO.FileShare value, global::System.IO.FileShare otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; -#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY - /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. - /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this global::System.IO.FileShare value, global::System.Span buffer) - { - var count = 0; - var v = (int)value; - while (v != 0) - { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = (int)(v & -v); - buffer[count++] = (global::System.IO.FileShare)flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= (int)(v - 1); - } - return count; - } -#endif - /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt index 1e9e3ff..d7cbe9b 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt @@ -121,28 +121,36 @@ namespace Foo public static bool HasAnyFlags(this global::Foo.MyEnum value, global::Foo.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 3; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// Writes each defined single-bit flag set in into . + /// Undefined bits and the zero/None value are excluded from the output. /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this global::Foo.MyEnum value, global::System.Span buffer) + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::Foo.MyEnum value, global::System.Span buffer, out int count) { - var count = 0; - var v = (int)value; - while (v != 0) + if (buffer.Length < DistinctFlagCount) { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = (int)(v & -v); - buffer[count++] = (global::Foo.MyEnum)flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= (int)(v - 1); + count = 0; + return false; } - return count; + count = 0; + if ((value & global::Foo.MyEnum.First) == global::Foo.MyEnum.First) + buffer[count++] = global::Foo.MyEnum.First; + if ((value & global::Foo.MyEnum.Second) == global::Foo.MyEnum.Second) + buffer[count++] = global::Foo.MyEnum.Second; + if ((value & global::Foo.MyEnum.Third) == global::Foo.MyEnum.Third) + buffer[count++] = global::Foo.MyEnum.Third; + return true; } #endif @@ -1003,28 +1011,36 @@ namespace Bar public static bool HasAnyFlags(this global::Bar.MyEnum value, global::Bar.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 3; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// Writes each defined single-bit flag set in into . + /// Undefined bits and the zero/None value are excluded from the output. /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this global::Bar.MyEnum value, global::System.Span buffer) + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::Bar.MyEnum value, global::System.Span buffer, out int count) { - var count = 0; - var v = (int)value; - while (v != 0) + if (buffer.Length < DistinctFlagCount) { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = (int)(v & -v); - buffer[count++] = (global::Bar.MyEnum)flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= (int)(v - 1); + count = 0; + return false; } - return count; + count = 0; + if ((value & global::Bar.MyEnum.First) == global::Bar.MyEnum.First) + buffer[count++] = global::Bar.MyEnum.First; + if ((value & global::Bar.MyEnum.Second) == global::Bar.MyEnum.Second) + buffer[count++] = global::Bar.MyEnum.Second; + if ((value & global::Bar.MyEnum.Third) == global::Bar.MyEnum.Third) + buffer[count++] = global::Bar.MyEnum.Third; + return true; } #endif diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt index 0167278..08f7b84 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt @@ -121,28 +121,36 @@ namespace Bar public static bool HasAnyFlags(this global::Foo.MyEnum value, global::Foo.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 3; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// Writes each defined single-bit flag set in into . + /// Undefined bits and the zero/None value are excluded from the output. /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this global::Foo.MyEnum value, global::System.Span buffer) + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::Foo.MyEnum value, global::System.Span buffer, out int count) { - var count = 0; - var v = (int)value; - while (v != 0) + if (buffer.Length < DistinctFlagCount) { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = (int)(v & -v); - buffer[count++] = (global::Foo.MyEnum)flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= (int)(v - 1); + count = 0; + return false; } - return count; + count = 0; + if ((value & global::Foo.MyEnum.First) == global::Foo.MyEnum.First) + buffer[count++] = global::Foo.MyEnum.First; + if ((value & global::Foo.MyEnum.Second) == global::Foo.MyEnum.Second) + buffer[count++] = global::Foo.MyEnum.Second; + if ((value & global::Foo.MyEnum.Third) == global::Foo.MyEnum.Third) + buffer[count++] = global::Foo.MyEnum.Third; + return true; } #endif diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt index 7118b5e..2e89a72 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt @@ -119,28 +119,36 @@ public static bool HasAnyFlags(this global::MyEnum value, global::MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 3; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// Writes each defined single-bit flag set in into . + /// Undefined bits and the zero/None value are excluded from the output. /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this global::MyEnum value, global::System.Span buffer) + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::MyEnum value, global::System.Span buffer, out int count) { - var count = 0; - var v = (int)value; - while (v != 0) + if (buffer.Length < DistinctFlagCount) { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = (int)(v & -v); - buffer[count++] = (global::MyEnum)flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= (int)(v - 1); + count = 0; + return false; } - return count; + count = 0; + if ((value & global::MyEnum.First) == global::MyEnum.First) + buffer[count++] = global::MyEnum.First; + if ((value & global::MyEnum.Second) == global::MyEnum.Second) + buffer[count++] = global::MyEnum.Second; + if ((value & global::MyEnum.Third) == global::MyEnum.Third) + buffer[count++] = global::MyEnum.Third; + return true; } #endif diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt index 03ac049..fbe4069 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt @@ -121,28 +121,36 @@ namespace MyTestNameSpace public static bool HasAnyFlags(this global::MyTestNameSpace.MyEnum value, global::MyTestNameSpace.MyEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 3; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// Writes each defined single-bit flag set in into . + /// Undefined bits and the zero/None value are excluded from the output. /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this global::MyTestNameSpace.MyEnum value, global::System.Span buffer) + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::MyTestNameSpace.MyEnum value, global::System.Span buffer, out int count) { - var count = 0; - var v = (int)value; - while (v != 0) + if (buffer.Length < DistinctFlagCount) { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = (int)(v & -v); - buffer[count++] = (global::MyTestNameSpace.MyEnum)flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= (int)(v - 1); + count = 0; + return false; } - return count; + count = 0; + if ((value & global::MyTestNameSpace.MyEnum.First) == global::MyTestNameSpace.MyEnum.First) + buffer[count++] = global::MyTestNameSpace.MyEnum.First; + if ((value & global::MyTestNameSpace.MyEnum.Second) == global::MyTestNameSpace.MyEnum.Second) + buffer[count++] = global::MyTestNameSpace.MyEnum.Second; + if ((value & global::MyTestNameSpace.MyEnum.Third) == global::MyTestNameSpace.MyEnum.Third) + buffer[count++] = global::MyTestNameSpace.MyEnum.Third; + return true; } #endif diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt index f2b2b6b..100f12a 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt @@ -949,28 +949,34 @@ namespace MyTestNameSpace public static bool HasAnyFlags(this global::MyTestNameSpace.AnotherEnum value, global::MyTestNameSpace.AnotherEnum otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 2; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// Writes each defined single-bit flag set in into . + /// Undefined bits and the zero/None value are excluded from the output. /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this global::MyTestNameSpace.AnotherEnum value, global::System.Span buffer) + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::MyTestNameSpace.AnotherEnum value, global::System.Span buffer, out int count) { - var count = 0; - var v = (int)value; - while (v != 0) + if (buffer.Length < DistinctFlagCount) { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = (int)(v & -v); - buffer[count++] = (global::MyTestNameSpace.AnotherEnum)flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= (int)(v - 1); + count = 0; + return false; } - return count; + count = 0; + if ((value & global::MyTestNameSpace.AnotherEnum.Second) == global::MyTestNameSpace.AnotherEnum.Second) + buffer[count++] = global::MyTestNameSpace.AnotherEnum.Second; + if ((value & global::MyTestNameSpace.AnotherEnum.Third) == global::MyTestNameSpace.AnotherEnum.Third) + buffer[count++] = global::MyTestNameSpace.AnotherEnum.Third; + return true; } #endif diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=False.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=False.verified.txt new file mode 100644 index 0000000..502bcb8 --- /dev/null +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=False.verified.txt @@ -0,0 +1,866 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by the NetEscapades.EnumGenerators source generator +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#nullable enable + +namespace Something.Blah +{ +#pragma warning disable CS0612 // Ignore usages of obsolete members or enums +#pragma warning disable CS0618 // Ignore usages of obsolete members or enums + /// + /// Extension methods for + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("NetEscapades.EnumGenerators", "FIXED_VERSION")] + public static partial class ShortName + { + /// + /// The number of members in the enum. + /// This is a non-distinct count of defined names. + /// + public const int Length = 4; + + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::Something.Blah.ShortName All = global::Something.Blah.ShortName.None | global::Something.Blah.ShortName.First | global::Something.Blah.ShortName.Second | global::Something.Blah.ShortName.Both; + + /// + /// Returns the string representation of the value. + /// Directly equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// The string representation of the value, the same as that returned by ToString() + public static string ToStringFast(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => nameof(global::Something.Blah.ShortName.None), + global::Something.Blah.ShortName.First => nameof(global::Something.Blah.ShortName.First), + global::Something.Blah.ShortName.Second => nameof(global::Something.Blah.ShortName.Second), + global::Something.Blah.ShortName.Both => nameof(global::Something.Blah.ShortName.Both), + _ => value.ToString(), + }; + + /// + /// Returns the string representation of the value. + /// Directly equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// The options to use when serializing the enum + /// The string representation of the value, using the provided options. +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static string ToStringFast(this global::Something.Blah.ShortName value, global::NetEscapades.EnumGenerators.SerializationOptions options) + => options.Transform switch + { + global::NetEscapades.EnumGenerators.SerializationTransform.LowerInvariant => value.ToStringFastLowerInvariant(), + global::NetEscapades.EnumGenerators.SerializationTransform.UpperInvariant => value.ToStringFastUpperInvariant(), + _ => value.ToStringFast(), + }; + + private static string ToStringFastLowerInvariant(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => "none", + global::Something.Blah.ShortName.First => "first", + global::Something.Blah.ShortName.Second => "second", + global::Something.Blah.ShortName.Both => "both", + _ => value.ToString().ToLowerInvariant(), + }; + + private static string ToStringFastUpperInvariant(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => "NONE", + global::Something.Blah.ShortName.First => "FIRST", + global::Something.Blah.ShortName.Second => "SECOND", + global::Something.Blah.ShortName.Both => "BOTH", + _ => value.ToString().ToUpperInvariant(), + }; + + /// + /// Returns the string representation of the value. + /// If the member is decorated with the attribute + /// then that value is returned. Otherwise returns uses the name of the member, + /// equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// If uses the value provided in the + /// attribute as the string representation of the member. + /// If , always uses the name of the member, the same as if ToString() was called. + /// The string representation of the value + public static string ToStringFast(this global::Something.Blah.ShortName value, bool useMetadataAttributes) + => value.ToStringFast(); + + /// + /// Determines whether one or more bit fields are set in the current instance. + /// Equivalent to calling on . + /// + /// The value of the instance to investigate + /// The flag to check for + /// if the fields set in the flag are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasFlagFast(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName flag) + => flag == 0 ? true : (value & flag) == flag; + + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . + public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + + /// + /// Cast a value of to the underlying type (int). + /// This is mainly a convenience method. + /// + /// The value of cast to the underlying type. + public static int AsUnderlyingType(this global::Something.Blah.ShortName value) + { + return (int) value; + } + + /// + /// Returns a boolean telling whether the given enum value exists in the enumeration. + /// + /// The value to check if it's defined + /// if the value exists in the enumeration, otherwise + public static bool IsDefined(global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => true, + global::Something.Blah.ShortName.First => true, + global::Something.Blah.ShortName.Second => true, + global::Something.Blah.ShortName.Both => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration. + /// + /// The name to check if it's defined + /// if a member with the name exists in the enumeration, otherwise + public static bool IsDefined(string name) + => name switch + { + nameof(global::Something.Blah.ShortName.None) => true, + nameof(global::Something.Blah.ShortName.First) => true, + nameof(global::Something.Blah.ShortName.Second) => true, + nameof(global::Something.Blah.ShortName.Both) => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration, + /// or if a member decorated with + /// with the required name exists. + /// + /// The name to check if it's defined + /// If , + /// considers the value of + /// instead of the member name, otherwise ignores them + /// if a member with the name exists in the enumeration, or a member is decorated + /// with a [Display] attribute with the name, otherwise + public static bool IsDefined(string name, bool allowMatchingMetadataAttribute) + => IsDefined(name); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration + /// + /// The name to check if it's defined + /// if a member with the name exists in the enumeration, otherwise + public static bool IsDefined(in global::System.ReadOnlySpan name) + => name switch + { + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.None)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.First)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), global::System.StringComparison.Ordinal) => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration, + /// or optionally if a member decorated with + /// exists. Slower then the overload, but doesn't allocate memory./> + /// + /// The name to check if it's defined + /// If , considers the value of metadata attributes,otherwise ignores them + /// if a member with the name exists in the enumeration, or a member is decorated + /// with a [Display] attribute with the name, otherwise + public static bool IsDefined(in global::System.ReadOnlySpan name, bool allowMatchingMetadataAttribute) + => IsDefined(name); +#endif + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name) + => TryParse(name, out var value, new global::NetEscapades.EnumGenerators.EnumParseOptions()) ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + bool ignoreCase) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)) + ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in metadata attributes such as + /// [Display] attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)) ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + => TryParse( + name, + out var value, + options) ? value : ThrowValueNotFound(name); + +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] +#endif + private static global::Something.Blah.ShortName ThrowValueNotFound(string? name) + => throw new global::System.ArgumentException($"Requested value '{name}' was not found."); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value) + => TryParse(name, out value, new global::NetEscapades.EnumGenerators.EnumParseOptions()); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + bool ignoreCase) + => TryParse(name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , + /// considers the value included in attribute + /// when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + { + switch (name) + { + case string s when s.Equals(nameof(global::Something.Blah.ShortName.None), options.ComparisonType): + value = global::Something.Blah.ShortName.None; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.First), options.ComparisonType): + value = global::Something.Blah.ShortName.First; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.Second), options.ComparisonType): + value = global::Something.Blah.ShortName.Second; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.Both), options.ComparisonType): + value = global::Something.Blah.ShortName.Both; + return true; + case string s when options.EnableNumberParsing && int.TryParse(name, out var val): + value = (global::Something.Blah.ShortName)val; + return true; + default: + value = default; + return false; + } + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions()) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + bool ignoreCase) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, disable number parsing in the parameter. + +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + => TryParse( + name, + out var value, + options) ? value : ThrowValueNotFound(name); + +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] +#endif + private static global::Something.Blah.ShortName ThrowValueNotFound(in global::System.ReadOnlySpan name) +#if NET6_0_OR_GREATER + => throw new global::System.ArgumentException($"Requested value '{name}' was not found."); +#else + => throw new global::System.ArgumentException($"Requested value '{name.ToString()}' was not found."); +#endif + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName value) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions()); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName value, + bool ignoreCase) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName result, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out result, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, disable number parsing in the parameter. + +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static bool TryParse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName result, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + { + switch (name) + { + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.None)), options.ComparisonType): + result = global::Something.Blah.ShortName.None; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.First)), options.ComparisonType): + result = global::Something.Blah.ShortName.First; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), options.ComparisonType): + result = global::Something.Blah.ShortName.Second; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), options.ComparisonType): + result = global::Something.Blah.ShortName.Both; + return true; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + case global::System.ReadOnlySpan current when options.EnableNumberParsing && int.TryParse(name, out var numericResult): +#else + // TryParse with ReadOnlySpan is not available, so we have to call ToString() + case global::System.ReadOnlySpan current when options.EnableNumberParsing && int.TryParse(name.ToString(), out var numericResult): +#endif + result = (global::Something.Blah.ShortName)numericResult; + return true; + default: + result = default; + return false; + } + } +#endif + + /// + /// Retrieves an array of the values of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the values defined in + public static global::Something.Blah.ShortName[] GetValues() + => new[] + { + global::Something.Blah.ShortName.None, + global::Something.Blah.ShortName.First, + global::Something.Blah.ShortName.Second, + global::Something.Blah.ShortName.Both, + }; + + /// + /// Retrieves an array of the underlying-values of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the underlying-values defined in + public static int[] GetValuesAsUnderlyingType() + => new[] + { + (int) global::Something.Blah.ShortName.None, + (int) global::Something.Blah.ShortName.First, + (int) global::Something.Blah.ShortName.Second, + (int) global::Something.Blah.ShortName.Both, + }; + + /// + /// Retrieves an array of the names of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the names of the members defined in + public static string[] GetNames() + => new[] + { + nameof(global::Something.Blah.ShortName.None), + nameof(global::Something.Blah.ShortName.First), + nameof(global::Something.Blah.ShortName.Second), + nameof(global::Something.Blah.ShortName.Both), + }; + } +#pragma warning restore CS0612 // Ignore usages of obsolete members or enums +#pragma warning restore CS0618 // Ignore usages of obsolete members or enums +} \ No newline at end of file diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=True.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=True.verified.txt new file mode 100644 index 0000000..bf48af1 --- /dev/null +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=True.verified.txt @@ -0,0 +1,871 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by the NetEscapades.EnumGenerators source generator +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#nullable enable + +namespace Something.Blah +{ +#pragma warning disable CS0612 // Ignore usages of obsolete members or enums +#pragma warning disable CS0618 // Ignore usages of obsolete members or enums + /// + /// Extension methods for + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("NetEscapades.EnumGenerators", "FIXED_VERSION")] + public static partial class ShortName + { + /// + /// The number of members in the enum. + /// This is a non-distinct count of defined names. + /// + public const int Length = 4; + + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::Something.Blah.ShortName All = global::Something.Blah.ShortName.None | global::Something.Blah.ShortName.First | global::Something.Blah.ShortName.Second | global::Something.Blah.ShortName.Both; + + /// + /// Returns the string representation of the value. + /// Directly equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// The string representation of the value, the same as that returned by ToString() + public static string ToStringFast(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => nameof(global::Something.Blah.ShortName.None), + global::Something.Blah.ShortName.First => nameof(global::Something.Blah.ShortName.First), + global::Something.Blah.ShortName.Second => nameof(global::Something.Blah.ShortName.Second), + global::Something.Blah.ShortName.Both => nameof(global::Something.Blah.ShortName.Both), + _ => value.ToString(), + }; + + /// + /// Returns the string representation of the value. + /// Directly equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// The options to use when serializing the enum + /// The string representation of the value, using the provided options. +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static string ToStringFast(this global::Something.Blah.ShortName value, global::NetEscapades.EnumGenerators.SerializationOptions options) + => options.Transform switch + { + global::NetEscapades.EnumGenerators.SerializationTransform.LowerInvariant => value.ToStringFastLowerInvariant(), + global::NetEscapades.EnumGenerators.SerializationTransform.UpperInvariant => value.ToStringFastUpperInvariant(), + _ => value.ToStringFast(), + }; + + private static string ToStringFastLowerInvariant(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => "none", + global::Something.Blah.ShortName.First => "first", + global::Something.Blah.ShortName.Second => "second", + global::Something.Blah.ShortName.Both => "both", + _ => value.ToString().ToLowerInvariant(), + }; + + private static string ToStringFastUpperInvariant(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => "NONE", + global::Something.Blah.ShortName.First => "FIRST", + global::Something.Blah.ShortName.Second => "SECOND", + global::Something.Blah.ShortName.Both => "BOTH", + _ => value.ToString().ToUpperInvariant(), + }; + + /// + /// Returns the string representation of the value. + /// If the member is decorated with the attribute + /// then that value is returned. Otherwise returns uses the name of the member, + /// equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// If uses the value provided in the + /// attribute as the string representation of the member. + /// If , always uses the name of the member, the same as if ToString() was called. + /// The string representation of the value + public static string ToStringFast(this global::Something.Blah.ShortName value, bool useMetadataAttributes) + => value.ToStringFast(); + + /// + /// Determines whether one or more bit fields are set in the current instance. + /// Equivalent to calling on . + /// + /// The value of the instance to investigate + /// The flag to check for + /// if the fields set in the flag are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasFlagFast(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName flag) + => flag == 0 ? true : (value & flag) == flag; + + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . + public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + + /// + /// Cast a value of to the underlying type (int). + /// This is mainly a convenience method. + /// + /// The value of cast to the underlying type. + public static int AsUnderlyingType(this global::Something.Blah.ShortName value) + { + return (int) value; + } + + // C#14 Extension member syntax + extension(global::Something.Blah.ShortName) + { + + /// + /// Returns a boolean telling whether the given enum value exists in the enumeration. + /// + /// The value to check if it's defined + /// if the value exists in the enumeration, otherwise + public static bool IsDefined(global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => true, + global::Something.Blah.ShortName.First => true, + global::Something.Blah.ShortName.Second => true, + global::Something.Blah.ShortName.Both => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration. + /// + /// The name to check if it's defined + /// if a member with the name exists in the enumeration, otherwise + public static bool IsDefined(string name) + => name switch + { + nameof(global::Something.Blah.ShortName.None) => true, + nameof(global::Something.Blah.ShortName.First) => true, + nameof(global::Something.Blah.ShortName.Second) => true, + nameof(global::Something.Blah.ShortName.Both) => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration, + /// or if a member decorated with + /// with the required name exists. + /// + /// The name to check if it's defined + /// If , + /// considers the value of + /// instead of the member name, otherwise ignores them + /// if a member with the name exists in the enumeration, or a member is decorated + /// with a [Display] attribute with the name, otherwise + public static bool IsDefined(string name, bool allowMatchingMetadataAttribute) + => IsDefined(name); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration + /// + /// The name to check if it's defined + /// if a member with the name exists in the enumeration, otherwise + public static bool IsDefined(in global::System.ReadOnlySpan name) + => name switch + { + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.None)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.First)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), global::System.StringComparison.Ordinal) => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration, + /// or optionally if a member decorated with + /// exists. Slower then the overload, but doesn't allocate memory./> + /// + /// The name to check if it's defined + /// If , considers the value of metadata attributes,otherwise ignores them + /// if a member with the name exists in the enumeration, or a member is decorated + /// with a [Display] attribute with the name, otherwise + public static bool IsDefined(in global::System.ReadOnlySpan name, bool allowMatchingMetadataAttribute) + => IsDefined(name); +#endif + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name) + => TryParse(name, out var value, new global::NetEscapades.EnumGenerators.EnumParseOptions()) ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + bool ignoreCase) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)) + ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in metadata attributes such as + /// [Display] attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)) ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + => TryParse( + name, + out var value, + options) ? value : ThrowValueNotFound(name); + +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] +#endif + private static global::Something.Blah.ShortName ThrowValueNotFound(string? name) + => throw new global::System.ArgumentException($"Requested value '{name}' was not found."); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value) + => TryParse(name, out value, new global::NetEscapades.EnumGenerators.EnumParseOptions()); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + bool ignoreCase) + => TryParse(name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , + /// considers the value included in attribute + /// when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + { + switch (name) + { + case string s when s.Equals(nameof(global::Something.Blah.ShortName.None), options.ComparisonType): + value = global::Something.Blah.ShortName.None; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.First), options.ComparisonType): + value = global::Something.Blah.ShortName.First; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.Second), options.ComparisonType): + value = global::Something.Blah.ShortName.Second; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.Both), options.ComparisonType): + value = global::Something.Blah.ShortName.Both; + return true; + case string s when options.EnableNumberParsing && int.TryParse(name, out var val): + value = (global::Something.Blah.ShortName)val; + return true; + default: + value = default; + return false; + } + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions()) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + bool ignoreCase) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, disable number parsing in the parameter. + +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + => TryParse( + name, + out var value, + options) ? value : ThrowValueNotFound(name); + +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] +#endif + private static global::Something.Blah.ShortName ThrowValueNotFound(in global::System.ReadOnlySpan name) +#if NET6_0_OR_GREATER + => throw new global::System.ArgumentException($"Requested value '{name}' was not found."); +#else + => throw new global::System.ArgumentException($"Requested value '{name.ToString()}' was not found."); +#endif + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName value) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions()); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName value, + bool ignoreCase) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName result, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out result, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, disable number parsing in the parameter. + +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static bool TryParse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName result, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + { + switch (name) + { + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.None)), options.ComparisonType): + result = global::Something.Blah.ShortName.None; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.First)), options.ComparisonType): + result = global::Something.Blah.ShortName.First; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), options.ComparisonType): + result = global::Something.Blah.ShortName.Second; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), options.ComparisonType): + result = global::Something.Blah.ShortName.Both; + return true; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + case global::System.ReadOnlySpan current when options.EnableNumberParsing && int.TryParse(name, out var numericResult): +#else + // TryParse with ReadOnlySpan is not available, so we have to call ToString() + case global::System.ReadOnlySpan current when options.EnableNumberParsing && int.TryParse(name.ToString(), out var numericResult): +#endif + result = (global::Something.Blah.ShortName)numericResult; + return true; + default: + result = default; + return false; + } + } +#endif + + /// + /// Retrieves an array of the values of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the values defined in + public static global::Something.Blah.ShortName[] GetValues() + => new[] + { + global::Something.Blah.ShortName.None, + global::Something.Blah.ShortName.First, + global::Something.Blah.ShortName.Second, + global::Something.Blah.ShortName.Both, + }; + + /// + /// Retrieves an array of the underlying-values of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the underlying-values defined in + public static int[] GetValuesAsUnderlyingType() + => new[] + { + (int) global::Something.Blah.ShortName.None, + (int) global::Something.Blah.ShortName.First, + (int) global::Something.Blah.ShortName.Second, + (int) global::Something.Blah.ShortName.Both, + }; + + /// + /// Retrieves an array of the names of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the names of the members defined in + public static string[] GetNames() + => new[] + { + nameof(global::Something.Blah.ShortName.None), + nameof(global::Something.Blah.ShortName.First), + nameof(global::Something.Blah.ShortName.Second), + nameof(global::Something.Blah.ShortName.Both), + }; + } + } +#pragma warning restore CS0612 // Ignore usages of obsolete members or enums +#pragma warning restore CS0618 // Ignore usages of obsolete members or enums +} \ No newline at end of file diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt index b7b9ca1..db5d048 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt @@ -117,28 +117,32 @@ namespace Something.Blah public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 1; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// Writes each defined single-bit flag set in into . + /// Undefined bits and the zero/None value are excluded from the output. /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this global::Something.Blah.ShortName value, global::System.Span buffer) + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::Something.Blah.ShortName value, global::System.Span buffer, out int count) { - var count = 0; - var v = (int)value; - while (v != 0) + if (buffer.Length < DistinctFlagCount) { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = (int)(v & -v); - buffer[count++] = (global::Something.Blah.ShortName)flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= (int)(v - 1); + count = 0; + return false; } - return count; + count = 0; + if ((value & global::Something.Blah.ShortName.Second) == global::Something.Blah.ShortName.Second) + buffer[count++] = global::Something.Blah.ShortName.Second; + return true; } #endif diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt index a6c51b3..48ce105 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt @@ -117,28 +117,32 @@ namespace Something.Blah public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 1; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// - /// Decomposes the value into individual flags, writing each set bit as a separate value into the provided buffer. + /// Writes each defined single-bit flag set in into . + /// Undefined bits and the zero/None value are excluded from the output. /// - /// The value to decompose into individual flags - /// The buffer to write the individual flags into. Must be large enough to hold all set bits. - /// The number of individual flags written to the buffer. - public static int GetFlags(this global::Something.Blah.ShortName value, global::System.Span buffer) + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::Something.Blah.ShortName value, global::System.Span buffer, out int count) { - var count = 0; - var v = (int)value; - while (v != 0) + if (buffer.Length < DistinctFlagCount) { - // Isolate the lowest set bit: `v & -v` keeps only the rightmost 1-bit - // (e.g. 0b0110 & -0b0110 = 0b0010). - var flag = (int)(v & -v); - buffer[count++] = (global::Something.Blah.ShortName)flag; - // Clear the lowest set bit: `v & (v - 1)` turns off the rightmost 1-bit - // (e.g. 0b0110 & 0b0101 = 0b0100). Loop continues until all bits are cleared. - v &= (int)(v - 1); + count = 0; + return false; } - return count; + count = 0; + if ((value & global::Something.Blah.ShortName.Second) == global::Something.Blah.ShortName.Second) + buffer[count++] = global::Something.Blah.ShortName.Second; + return true; } #endif diff --git a/tests/NetEscapades.EnumGenerators.Tests/SourceGenerationHelperSnapshotTests.cs b/tests/NetEscapades.EnumGenerators.Tests/SourceGenerationHelperSnapshotTests.cs index d5ac5c9..8c07952 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/SourceGenerationHelperSnapshotTests.cs +++ b/tests/NetEscapades.EnumGenerators.Tests/SourceGenerationHelperSnapshotTests.cs @@ -116,6 +116,42 @@ public Task GeneratesFlagsEnumCorrectly(bool csharp14IsSupported) .UseParameters(csharp14IsSupported); } + [Theory] + [CombinatorialData] + public Task DoesNotGenerateTryGetFlagsForCompositeFlagsEnum(bool csharp14IsSupported) + { + // An enum with a composite member (non-zero, non-power-of-two) should not get TryGetFlags. + var value = new EnumToGenerate( + "ShortName", + "Something.Blah", + "Something.Blah.ShortName", + "int", + isPublic: true, + new List<(string, EnumValueOption)> + { + ("None", EnumValueOption.CreateWithoutAttributes(0)), + ("First", EnumValueOption.CreateWithoutAttributes(1)), + ("Second", EnumValueOption.CreateWithoutAttributes(2)), + ("Both", EnumValueOption.CreateWithoutAttributes(3)), + }, + hasFlags: true, + metadataSource: null); + + var result = SourceGenerationHelper.GenerateExtensionClass( + value, + csharp14IsSupported, + useCollectionExpressions: false, + DefaultMetadataSource, + hasRuntimeDependencies: true, + forceInternal: false, + hasOverloadResolutionPriority: true).Content; + + return Verifier.Verify(result) + .ScrubExpectedChanges() + .UseDirectory("Snapshots") + .UseParameters(csharp14IsSupported); + } + [Fact] public Task GeneratesForcedInternalEnumCorrectly() { From a8ade689c077ed9bb1bcc3a848ae6b9a263aab3e Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Mon, 11 May 2026 21:57:42 +0100 Subject: [PATCH 11/11] fixup! Add `GetFlags` method for [Flags] enums --- .../SourceGenerationHelper.cs | 36 +- .../FlagsEnumExtensionsTests.cs | 51 + ...ExtensionsForFlagsEnum_Params.verified.txt | 3 +- ...nGenerateForExternalFlagsEnum.verified.txt | 36 + ...SameNameInDifferentNamespaces.verified.txt | 6 +- ...rceptEnumInDifferentNamespace.verified.txt | 3 +- ...nterceptEnumInGlobalNamespace.verified.txt | 3 +- ...ptorTests.CanInterceptHasFlag.verified.txt | 3 +- ...tEnumMarkedAsNotInterceptable.verified.txt | 3 +- ...ts_csharp14IsSupported=False.verified.txt} | 28 +- ...its_csharp14IsSupported=True.verified.txt} | 28 +- ...tly_csharp14IsSupported=False.verified.txt | 3 +- ...ctly_csharp14IsSupported=True.verified.txt | 3 +- ...ber_csharp14IsSupported=False.verified.txt | 898 +++++++++++++++++ ...mber_csharp14IsSupported=True.verified.txt | 903 ++++++++++++++++++ .../SourceGenerationHelperSnapshotTests.cs | 41 +- 16 files changed, 1998 insertions(+), 50 deletions(-) rename tests/NetEscapades.EnumGenerators.Tests/Snapshots/{SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=False.verified.txt => SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsWhenMemberHasUndefinedBits_csharp14IsSupported=False.verified.txt} (98%) rename tests/NetEscapades.EnumGenerators.Tests/Snapshots/{SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=True.verified.txt => SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsWhenMemberHasUndefinedBits_csharp14IsSupported=True.verified.txt} (98%) create mode 100644 tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesTryGetFlagsWithCleanCompositeMember_csharp14IsSupported=False.verified.txt create mode 100644 tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesTryGetFlagsWithCleanCompositeMember_csharp14IsSupported=True.verified.txt diff --git a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs index 46dbecc..918c706 100644 --- a/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs +++ b/src/NetEscapades.EnumGenerators.Generators/SourceGenerationHelper.cs @@ -596,11 +596,12 @@ public static bool HasAnyFlags(this => otherFlags == 0 ? true : (value & otherFlags) != 0; """); - // Collect single-bit flag members. If any member is a non-zero composite (not a power of two), - // skip emitting TryGetFlags/DistinctFlagCount entirely — the strict rule keeps semantics deterministic. + // Collect single-bit flag members (powers of two) and build a mask of all defined bits. + // Composite members are allowed as long as every bit they set is also a defined single-bit flag. + // Members with bits outside the defined mask disqualify the enum from TryGetFlags emission. List<(string Name, ulong Value)>? singleBitFlags = null; - var hasComposite = false; var seenBitValues = new HashSet(); + ulong definedMask = 0; foreach (var member in enumToGenerate.Names) { var bits = ToUInt64BitPattern(member.Value.ConstantValue); @@ -609,19 +610,31 @@ public static bool HasAnyFlags(this // Zero-valued members (e.g. None) are allowed but not emitted. continue; } - if ((bits & (bits - 1)) != 0) - { - hasComposite = true; - break; - } - if (seenBitValues.Add(bits)) + if ((bits & (bits - 1)) == 0 && seenBitValues.Add(bits)) { singleBitFlags ??= new List<(string, ulong)>(); singleBitFlags.Add((member.Key, bits)); + definedMask |= bits; + } + } + + // Verify every non-zero member value is a subset of the defined-bit mask. + // Catches members with bits that don't correspond to any single-bit flag. + var hasInvalidValue = false; + if (singleBitFlags is { Count: > 0 }) + { + foreach (var member in enumToGenerate.Names) + { + var bits = ToUInt64BitPattern(member.Value.ConstantValue); + if (bits != 0 && (bits & ~definedMask) != 0) + { + hasInvalidValue = true; + break; + } } } - if (!hasComposite && singleBitFlags is { Count: > 0 }) + if (!hasInvalidValue && singleBitFlags is { Count: > 0 }) { singleBitFlags.Sort((a, b) => a.Value.CompareTo(b.Value)); @@ -643,7 +656,8 @@ public static bool HasAnyFlags(this #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// /// Writes each defined single-bit flag set in into . - /// Undefined bits and the zero/None value are excluded from the output. + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. /// /// The value to decompose into individual flags. /// The buffer to write the flags into. Must be at least elements. diff --git a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs index a77b731..21cfbb8 100644 --- a/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs +++ b/tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs @@ -186,6 +186,57 @@ public void HasAnyFlags(FlagsEnum value, FlagsEnum otherFlags, bool expected) value.HasAnyFlags(otherFlags).Should().Be(expected); } + [Fact] + public void DistinctFlagCountCountsSingleBitFlags() + { + // First, Second, Third, Fourth — ThirdAndFourth is a composite, None is zero + FlagsEnumExtensions.DistinctFlagCount.Should().Be(4); + } + +#if READONLYSPAN + public static TheoryData TryGetFlagsData => new() + { + // Single flag + { FlagsEnum.First, 1, new[] { FlagsEnum.First } }, + // Multiple flags — bit-value order + { FlagsEnum.First | FlagsEnum.Third, 2, new[] { FlagsEnum.First, FlagsEnum.Third } }, + // Composite member decomposes into its constituent single-bit flags + { FlagsEnum.ThirdAndFourth, 2, new[] { FlagsEnum.Third, FlagsEnum.Fourth } }, + // Combination of single-bit and composite — same as combining the bits + { FlagsEnum.First | FlagsEnum.ThirdAndFourth, 3, + new[] { FlagsEnum.First, FlagsEnum.Third, FlagsEnum.Fourth } }, + // None → empty + { FlagsEnum.None, 0, Array.Empty() }, + // Undefined bit (16) is silently dropped, defined bit (1) is kept + { (FlagsEnum)17, 1, new[] { FlagsEnum.First } }, + // Only undefined bits → empty + { (FlagsEnum)16, 0, Array.Empty() }, + // All defined flags set + { FlagsEnumExtensions.All, 4, + new[] { FlagsEnum.First, FlagsEnum.Second, FlagsEnum.Third, FlagsEnum.Fourth } }, + }; + + [Theory] + [MemberData(nameof(TryGetFlagsData))] + public void TryGetFlags(FlagsEnum value, int expectedCount, FlagsEnum[] expectedFlags) + { + Span buffer = stackalloc FlagsEnum[FlagsEnumExtensions.DistinctFlagCount]; + var result = value.TryGetFlags(buffer, out var count); + result.Should().BeTrue(); + count.Should().Be(expectedCount); + buffer.Slice(0, count).ToArray().Should().Equal(expectedFlags); + } + + [Fact] + public void TryGetFlags_BufferSmallerThanDistinctFlagCount_ReturnsFalse() + { + Span buffer = stackalloc FlagsEnum[FlagsEnumExtensions.DistinctFlagCount - 1]; + var result = FlagsEnum.First.TryGetFlags(buffer, out var count); + result.Should().BeFalse(); + count.Should().Be(0); + } +#endif + private PackageEnumParseOptions Map(EnumParseOptions options) => new(comparisonType: options.ComparisonType, allowMatchingMetadataAttribute: options.AllowMatchingMetadataAttribute, diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt index c894226..120bc4b 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt @@ -129,7 +129,8 @@ namespace MyTestNameSpace #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// /// Writes each defined single-bit flag set in into . - /// Undefined bits and the zero/None value are excluded from the output. + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. /// /// The value to decompose into individual flags. /// The buffer to write the flags into. Must be at least elements. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt index f65cef0..3e06968 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt @@ -129,6 +129,42 @@ namespace System.IO public static bool HasAnyFlags(this global::System.IO.FileShare value, global::System.IO.FileShare otherFlags) => otherFlags == 0 ? true : (value & otherFlags) != 0; + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 4; + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Writes each defined single-bit flag set in into . + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. + /// + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::System.IO.FileShare value, global::System.Span buffer, out int count) + { + if (buffer.Length < DistinctFlagCount) + { + count = 0; + return false; + } + count = 0; + if ((value & global::System.IO.FileShare.Read) == global::System.IO.FileShare.Read) + buffer[count++] = global::System.IO.FileShare.Read; + if ((value & global::System.IO.FileShare.Write) == global::System.IO.FileShare.Write) + buffer[count++] = global::System.IO.FileShare.Write; + if ((value & global::System.IO.FileShare.Delete) == global::System.IO.FileShare.Delete) + buffer[count++] = global::System.IO.FileShare.Delete; + if ((value & global::System.IO.FileShare.Inheritable) == global::System.IO.FileShare.Inheritable) + buffer[count++] = global::System.IO.FileShare.Inheritable; + return true; + } +#endif + /// /// Cast a value of to the underlying type (int). /// This is mainly a convenience method. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt index d7cbe9b..991f911 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt @@ -130,7 +130,8 @@ namespace Foo #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// /// Writes each defined single-bit flag set in into . - /// Undefined bits and the zero/None value are excluded from the output. + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. /// /// The value to decompose into individual flags. /// The buffer to write the flags into. Must be at least elements. @@ -1020,7 +1021,8 @@ namespace Bar #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// /// Writes each defined single-bit flag set in into . - /// Undefined bits and the zero/None value are excluded from the output. + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. /// /// The value to decompose into individual flags. /// The buffer to write the flags into. Must be at least elements. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt index 08f7b84..f255908 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt @@ -130,7 +130,8 @@ namespace Bar #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// /// Writes each defined single-bit flag set in into . - /// Undefined bits and the zero/None value are excluded from the output. + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. /// /// The value to decompose into individual flags. /// The buffer to write the flags into. Must be at least elements. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt index 2e89a72..38501d7 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt @@ -128,7 +128,8 @@ #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// /// Writes each defined single-bit flag set in into . - /// Undefined bits and the zero/None value are excluded from the output. + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. /// /// The value to decompose into individual flags. /// The buffer to write the flags into. Must be at least elements. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt index fbe4069..2a130d4 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.CanInterceptHasFlag.verified.txt @@ -130,7 +130,8 @@ namespace MyTestNameSpace #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// /// Writes each defined single-bit flag set in into . - /// Undefined bits and the zero/None value are excluded from the output. + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. /// /// The value to decompose into individual flags. /// The buffer to write the flags into. Must be at least elements. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt index 100f12a..3cc8238 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt @@ -958,7 +958,8 @@ namespace MyTestNameSpace #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// /// Writes each defined single-bit flag set in into . - /// Undefined bits and the zero/None value are excluded from the output. + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. /// /// The value to decompose into individual flags. /// The buffer to write the flags into. Must be at least elements. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=False.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsWhenMemberHasUndefinedBits_csharp14IsSupported=False.verified.txt similarity index 98% rename from tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=False.verified.txt rename to tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsWhenMemberHasUndefinedBits_csharp14IsSupported=False.verified.txt index 502bcb8..b1eb187 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=False.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsWhenMemberHasUndefinedBits_csharp14IsSupported=False.verified.txt @@ -28,7 +28,7 @@ namespace Something.Blah /// /// A bitwise OR combination of all defined values in the enum. /// - public const global::Something.Blah.ShortName All = global::Something.Blah.ShortName.None | global::Something.Blah.ShortName.First | global::Something.Blah.ShortName.Second | global::Something.Blah.ShortName.Both; + public const global::Something.Blah.ShortName All = global::Something.Blah.ShortName.None | global::Something.Blah.ShortName.First | global::Something.Blah.ShortName.Second | global::Something.Blah.ShortName.Odd; /// /// Returns the string representation of the value. @@ -42,7 +42,7 @@ namespace Something.Blah global::Something.Blah.ShortName.None => nameof(global::Something.Blah.ShortName.None), global::Something.Blah.ShortName.First => nameof(global::Something.Blah.ShortName.First), global::Something.Blah.ShortName.Second => nameof(global::Something.Blah.ShortName.Second), - global::Something.Blah.ShortName.Both => nameof(global::Something.Blah.ShortName.Both), + global::Something.Blah.ShortName.Odd => nameof(global::Something.Blah.ShortName.Odd), _ => value.ToString(), }; @@ -71,7 +71,7 @@ namespace Something.Blah global::Something.Blah.ShortName.None => "none", global::Something.Blah.ShortName.First => "first", global::Something.Blah.ShortName.Second => "second", - global::Something.Blah.ShortName.Both => "both", + global::Something.Blah.ShortName.Odd => "odd", _ => value.ToString().ToLowerInvariant(), }; @@ -81,7 +81,7 @@ namespace Something.Blah global::Something.Blah.ShortName.None => "NONE", global::Something.Blah.ShortName.First => "FIRST", global::Something.Blah.ShortName.Second => "SECOND", - global::Something.Blah.ShortName.Both => "BOTH", + global::Something.Blah.ShortName.Odd => "ODD", _ => value.ToString().ToUpperInvariant(), }; @@ -144,7 +144,7 @@ namespace Something.Blah global::Something.Blah.ShortName.None => true, global::Something.Blah.ShortName.First => true, global::Something.Blah.ShortName.Second => true, - global::Something.Blah.ShortName.Both => true, + global::Something.Blah.ShortName.Odd => true, _ => false, }; @@ -159,7 +159,7 @@ namespace Something.Blah nameof(global::Something.Blah.ShortName.None) => true, nameof(global::Something.Blah.ShortName.First) => true, nameof(global::Something.Blah.ShortName.Second) => true, - nameof(global::Something.Blah.ShortName.Both) => true, + nameof(global::Something.Blah.ShortName.Odd) => true, _ => false, }; @@ -189,7 +189,7 @@ namespace Something.Blah global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.None)), global::System.StringComparison.Ordinal) => true, global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.First)), global::System.StringComparison.Ordinal) => true, global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), global::System.StringComparison.Ordinal) => true, - global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Odd)), global::System.StringComparison.Ordinal) => true, _ => false, }; @@ -406,8 +406,8 @@ namespace Something.Blah case string s when s.Equals(nameof(global::Something.Blah.ShortName.Second), options.ComparisonType): value = global::Something.Blah.ShortName.Second; return true; - case string s when s.Equals(nameof(global::Something.Blah.ShortName.Both), options.ComparisonType): - value = global::Something.Blah.ShortName.Both; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.Odd), options.ComparisonType): + value = global::Something.Blah.ShortName.Odd; return true; case string s when options.EnableNumberParsing && int.TryParse(name, out var val): value = (global::Something.Blah.ShortName)val; @@ -795,8 +795,8 @@ namespace Something.Blah case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), options.ComparisonType): result = global::Something.Blah.ShortName.Second; return true; - case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), options.ComparisonType): - result = global::Something.Blah.ShortName.Both; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Odd)), options.ComparisonType): + result = global::Something.Blah.ShortName.Odd; return true; #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER case global::System.ReadOnlySpan current when options.EnableNumberParsing && int.TryParse(name, out var numericResult): @@ -826,7 +826,7 @@ namespace Something.Blah global::Something.Blah.ShortName.None, global::Something.Blah.ShortName.First, global::Something.Blah.ShortName.Second, - global::Something.Blah.ShortName.Both, + global::Something.Blah.ShortName.Odd, }; /// @@ -842,7 +842,7 @@ namespace Something.Blah (int) global::Something.Blah.ShortName.None, (int) global::Something.Blah.ShortName.First, (int) global::Something.Blah.ShortName.Second, - (int) global::Something.Blah.ShortName.Both, + (int) global::Something.Blah.ShortName.Odd, }; /// @@ -858,7 +858,7 @@ namespace Something.Blah nameof(global::Something.Blah.ShortName.None), nameof(global::Something.Blah.ShortName.First), nameof(global::Something.Blah.ShortName.Second), - nameof(global::Something.Blah.ShortName.Both), + nameof(global::Something.Blah.ShortName.Odd), }; } #pragma warning restore CS0612 // Ignore usages of obsolete members or enums diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=True.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsWhenMemberHasUndefinedBits_csharp14IsSupported=True.verified.txt similarity index 98% rename from tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=True.verified.txt rename to tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsWhenMemberHasUndefinedBits_csharp14IsSupported=True.verified.txt index bf48af1..d9ce603 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsForCompositeFlagsEnum_csharp14IsSupported=True.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.DoesNotGenerateTryGetFlagsWhenMemberHasUndefinedBits_csharp14IsSupported=True.verified.txt @@ -28,7 +28,7 @@ namespace Something.Blah /// /// A bitwise OR combination of all defined values in the enum. /// - public const global::Something.Blah.ShortName All = global::Something.Blah.ShortName.None | global::Something.Blah.ShortName.First | global::Something.Blah.ShortName.Second | global::Something.Blah.ShortName.Both; + public const global::Something.Blah.ShortName All = global::Something.Blah.ShortName.None | global::Something.Blah.ShortName.First | global::Something.Blah.ShortName.Second | global::Something.Blah.ShortName.Odd; /// /// Returns the string representation of the value. @@ -42,7 +42,7 @@ namespace Something.Blah global::Something.Blah.ShortName.None => nameof(global::Something.Blah.ShortName.None), global::Something.Blah.ShortName.First => nameof(global::Something.Blah.ShortName.First), global::Something.Blah.ShortName.Second => nameof(global::Something.Blah.ShortName.Second), - global::Something.Blah.ShortName.Both => nameof(global::Something.Blah.ShortName.Both), + global::Something.Blah.ShortName.Odd => nameof(global::Something.Blah.ShortName.Odd), _ => value.ToString(), }; @@ -71,7 +71,7 @@ namespace Something.Blah global::Something.Blah.ShortName.None => "none", global::Something.Blah.ShortName.First => "first", global::Something.Blah.ShortName.Second => "second", - global::Something.Blah.ShortName.Both => "both", + global::Something.Blah.ShortName.Odd => "odd", _ => value.ToString().ToLowerInvariant(), }; @@ -81,7 +81,7 @@ namespace Something.Blah global::Something.Blah.ShortName.None => "NONE", global::Something.Blah.ShortName.First => "FIRST", global::Something.Blah.ShortName.Second => "SECOND", - global::Something.Blah.ShortName.Both => "BOTH", + global::Something.Blah.ShortName.Odd => "ODD", _ => value.ToString().ToUpperInvariant(), }; @@ -148,7 +148,7 @@ namespace Something.Blah global::Something.Blah.ShortName.None => true, global::Something.Blah.ShortName.First => true, global::Something.Blah.ShortName.Second => true, - global::Something.Blah.ShortName.Both => true, + global::Something.Blah.ShortName.Odd => true, _ => false, }; @@ -163,7 +163,7 @@ namespace Something.Blah nameof(global::Something.Blah.ShortName.None) => true, nameof(global::Something.Blah.ShortName.First) => true, nameof(global::Something.Blah.ShortName.Second) => true, - nameof(global::Something.Blah.ShortName.Both) => true, + nameof(global::Something.Blah.ShortName.Odd) => true, _ => false, }; @@ -193,7 +193,7 @@ namespace Something.Blah global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.None)), global::System.StringComparison.Ordinal) => true, global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.First)), global::System.StringComparison.Ordinal) => true, global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), global::System.StringComparison.Ordinal) => true, - global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Odd)), global::System.StringComparison.Ordinal) => true, _ => false, }; @@ -410,8 +410,8 @@ namespace Something.Blah case string s when s.Equals(nameof(global::Something.Blah.ShortName.Second), options.ComparisonType): value = global::Something.Blah.ShortName.Second; return true; - case string s when s.Equals(nameof(global::Something.Blah.ShortName.Both), options.ComparisonType): - value = global::Something.Blah.ShortName.Both; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.Odd), options.ComparisonType): + value = global::Something.Blah.ShortName.Odd; return true; case string s when options.EnableNumberParsing && int.TryParse(name, out var val): value = (global::Something.Blah.ShortName)val; @@ -799,8 +799,8 @@ namespace Something.Blah case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), options.ComparisonType): result = global::Something.Blah.ShortName.Second; return true; - case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), options.ComparisonType): - result = global::Something.Blah.ShortName.Both; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Odd)), options.ComparisonType): + result = global::Something.Blah.ShortName.Odd; return true; #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER case global::System.ReadOnlySpan current when options.EnableNumberParsing && int.TryParse(name, out var numericResult): @@ -830,7 +830,7 @@ namespace Something.Blah global::Something.Blah.ShortName.None, global::Something.Blah.ShortName.First, global::Something.Blah.ShortName.Second, - global::Something.Blah.ShortName.Both, + global::Something.Blah.ShortName.Odd, }; /// @@ -846,7 +846,7 @@ namespace Something.Blah (int) global::Something.Blah.ShortName.None, (int) global::Something.Blah.ShortName.First, (int) global::Something.Blah.ShortName.Second, - (int) global::Something.Blah.ShortName.Both, + (int) global::Something.Blah.ShortName.Odd, }; /// @@ -862,7 +862,7 @@ namespace Something.Blah nameof(global::Something.Blah.ShortName.None), nameof(global::Something.Blah.ShortName.First), nameof(global::Something.Blah.ShortName.Second), - nameof(global::Something.Blah.ShortName.Both), + nameof(global::Something.Blah.ShortName.Odd), }; } } diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt index db5d048..3f5cafe 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=False.verified.txt @@ -126,7 +126,8 @@ namespace Something.Blah #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// /// Writes each defined single-bit flag set in into . - /// Undefined bits and the zero/None value are excluded from the output. + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. /// /// The value to decompose into individual flags. /// The buffer to write the flags into. Must be at least elements. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt index 48ce105..fac82ea 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly_csharp14IsSupported=True.verified.txt @@ -126,7 +126,8 @@ namespace Something.Blah #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY /// /// Writes each defined single-bit flag set in into . - /// Undefined bits and the zero/None value are excluded from the output. + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. /// /// The value to decompose into individual flags. /// The buffer to write the flags into. Must be at least elements. diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesTryGetFlagsWithCleanCompositeMember_csharp14IsSupported=False.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesTryGetFlagsWithCleanCompositeMember_csharp14IsSupported=False.verified.txt new file mode 100644 index 0000000..ca11283 --- /dev/null +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesTryGetFlagsWithCleanCompositeMember_csharp14IsSupported=False.verified.txt @@ -0,0 +1,898 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by the NetEscapades.EnumGenerators source generator +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#nullable enable + +namespace Something.Blah +{ +#pragma warning disable CS0612 // Ignore usages of obsolete members or enums +#pragma warning disable CS0618 // Ignore usages of obsolete members or enums + /// + /// Extension methods for + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("NetEscapades.EnumGenerators", "FIXED_VERSION")] + public static partial class ShortName + { + /// + /// The number of members in the enum. + /// This is a non-distinct count of defined names. + /// + public const int Length = 4; + + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::Something.Blah.ShortName All = global::Something.Blah.ShortName.None | global::Something.Blah.ShortName.First | global::Something.Blah.ShortName.Second | global::Something.Blah.ShortName.Both; + + /// + /// Returns the string representation of the value. + /// Directly equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// The string representation of the value, the same as that returned by ToString() + public static string ToStringFast(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => nameof(global::Something.Blah.ShortName.None), + global::Something.Blah.ShortName.First => nameof(global::Something.Blah.ShortName.First), + global::Something.Blah.ShortName.Second => nameof(global::Something.Blah.ShortName.Second), + global::Something.Blah.ShortName.Both => nameof(global::Something.Blah.ShortName.Both), + _ => value.ToString(), + }; + + /// + /// Returns the string representation of the value. + /// Directly equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// The options to use when serializing the enum + /// The string representation of the value, using the provided options. +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static string ToStringFast(this global::Something.Blah.ShortName value, global::NetEscapades.EnumGenerators.SerializationOptions options) + => options.Transform switch + { + global::NetEscapades.EnumGenerators.SerializationTransform.LowerInvariant => value.ToStringFastLowerInvariant(), + global::NetEscapades.EnumGenerators.SerializationTransform.UpperInvariant => value.ToStringFastUpperInvariant(), + _ => value.ToStringFast(), + }; + + private static string ToStringFastLowerInvariant(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => "none", + global::Something.Blah.ShortName.First => "first", + global::Something.Blah.ShortName.Second => "second", + global::Something.Blah.ShortName.Both => "both", + _ => value.ToString().ToLowerInvariant(), + }; + + private static string ToStringFastUpperInvariant(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => "NONE", + global::Something.Blah.ShortName.First => "FIRST", + global::Something.Blah.ShortName.Second => "SECOND", + global::Something.Blah.ShortName.Both => "BOTH", + _ => value.ToString().ToUpperInvariant(), + }; + + /// + /// Returns the string representation of the value. + /// If the member is decorated with the attribute + /// then that value is returned. Otherwise returns uses the name of the member, + /// equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// If uses the value provided in the + /// attribute as the string representation of the member. + /// If , always uses the name of the member, the same as if ToString() was called. + /// The string representation of the value + public static string ToStringFast(this global::Something.Blah.ShortName value, bool useMetadataAttributes) + => value.ToStringFast(); + + /// + /// Determines whether one or more bit fields are set in the current instance. + /// Equivalent to calling on . + /// + /// The value of the instance to investigate + /// The flag to check for + /// if the fields set in the flag are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasFlagFast(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName flag) + => flag == 0 ? true : (value & flag) == flag; + + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . + public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 2; + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Writes each defined single-bit flag set in into . + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. + /// + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::Something.Blah.ShortName value, global::System.Span buffer, out int count) + { + if (buffer.Length < DistinctFlagCount) + { + count = 0; + return false; + } + count = 0; + if ((value & global::Something.Blah.ShortName.First) == global::Something.Blah.ShortName.First) + buffer[count++] = global::Something.Blah.ShortName.First; + if ((value & global::Something.Blah.ShortName.Second) == global::Something.Blah.ShortName.Second) + buffer[count++] = global::Something.Blah.ShortName.Second; + return true; + } +#endif + + /// + /// Cast a value of to the underlying type (int). + /// This is mainly a convenience method. + /// + /// The value of cast to the underlying type. + public static int AsUnderlyingType(this global::Something.Blah.ShortName value) + { + return (int) value; + } + + /// + /// Returns a boolean telling whether the given enum value exists in the enumeration. + /// + /// The value to check if it's defined + /// if the value exists in the enumeration, otherwise + public static bool IsDefined(global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => true, + global::Something.Blah.ShortName.First => true, + global::Something.Blah.ShortName.Second => true, + global::Something.Blah.ShortName.Both => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration. + /// + /// The name to check if it's defined + /// if a member with the name exists in the enumeration, otherwise + public static bool IsDefined(string name) + => name switch + { + nameof(global::Something.Blah.ShortName.None) => true, + nameof(global::Something.Blah.ShortName.First) => true, + nameof(global::Something.Blah.ShortName.Second) => true, + nameof(global::Something.Blah.ShortName.Both) => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration, + /// or if a member decorated with + /// with the required name exists. + /// + /// The name to check if it's defined + /// If , + /// considers the value of + /// instead of the member name, otherwise ignores them + /// if a member with the name exists in the enumeration, or a member is decorated + /// with a [Display] attribute with the name, otherwise + public static bool IsDefined(string name, bool allowMatchingMetadataAttribute) + => IsDefined(name); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration + /// + /// The name to check if it's defined + /// if a member with the name exists in the enumeration, otherwise + public static bool IsDefined(in global::System.ReadOnlySpan name) + => name switch + { + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.None)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.First)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), global::System.StringComparison.Ordinal) => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration, + /// or optionally if a member decorated with + /// exists. Slower then the overload, but doesn't allocate memory./> + /// + /// The name to check if it's defined + /// If , considers the value of metadata attributes,otherwise ignores them + /// if a member with the name exists in the enumeration, or a member is decorated + /// with a [Display] attribute with the name, otherwise + public static bool IsDefined(in global::System.ReadOnlySpan name, bool allowMatchingMetadataAttribute) + => IsDefined(name); +#endif + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name) + => TryParse(name, out var value, new global::NetEscapades.EnumGenerators.EnumParseOptions()) ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + bool ignoreCase) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)) + ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in metadata attributes such as + /// [Display] attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)) ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + => TryParse( + name, + out var value, + options) ? value : ThrowValueNotFound(name); + +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] +#endif + private static global::Something.Blah.ShortName ThrowValueNotFound(string? name) + => throw new global::System.ArgumentException($"Requested value '{name}' was not found."); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value) + => TryParse(name, out value, new global::NetEscapades.EnumGenerators.EnumParseOptions()); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + bool ignoreCase) + => TryParse(name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , + /// considers the value included in attribute + /// when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + { + switch (name) + { + case string s when s.Equals(nameof(global::Something.Blah.ShortName.None), options.ComparisonType): + value = global::Something.Blah.ShortName.None; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.First), options.ComparisonType): + value = global::Something.Blah.ShortName.First; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.Second), options.ComparisonType): + value = global::Something.Blah.ShortName.Second; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.Both), options.ComparisonType): + value = global::Something.Blah.ShortName.Both; + return true; + case string s when options.EnableNumberParsing && int.TryParse(name, out var val): + value = (global::Something.Blah.ShortName)val; + return true; + default: + value = default; + return false; + } + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions()) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + bool ignoreCase) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, disable number parsing in the parameter. + +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + => TryParse( + name, + out var value, + options) ? value : ThrowValueNotFound(name); + +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] +#endif + private static global::Something.Blah.ShortName ThrowValueNotFound(in global::System.ReadOnlySpan name) +#if NET6_0_OR_GREATER + => throw new global::System.ArgumentException($"Requested value '{name}' was not found."); +#else + => throw new global::System.ArgumentException($"Requested value '{name.ToString()}' was not found."); +#endif + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName value) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions()); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName value, + bool ignoreCase) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName result, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out result, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, disable number parsing in the parameter. + +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static bool TryParse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName result, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + { + switch (name) + { + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.None)), options.ComparisonType): + result = global::Something.Blah.ShortName.None; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.First)), options.ComparisonType): + result = global::Something.Blah.ShortName.First; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), options.ComparisonType): + result = global::Something.Blah.ShortName.Second; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), options.ComparisonType): + result = global::Something.Blah.ShortName.Both; + return true; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + case global::System.ReadOnlySpan current when options.EnableNumberParsing && int.TryParse(name, out var numericResult): +#else + // TryParse with ReadOnlySpan is not available, so we have to call ToString() + case global::System.ReadOnlySpan current when options.EnableNumberParsing && int.TryParse(name.ToString(), out var numericResult): +#endif + result = (global::Something.Blah.ShortName)numericResult; + return true; + default: + result = default; + return false; + } + } +#endif + + /// + /// Retrieves an array of the values of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the values defined in + public static global::Something.Blah.ShortName[] GetValues() + => new[] + { + global::Something.Blah.ShortName.None, + global::Something.Blah.ShortName.First, + global::Something.Blah.ShortName.Second, + global::Something.Blah.ShortName.Both, + }; + + /// + /// Retrieves an array of the underlying-values of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the underlying-values defined in + public static int[] GetValuesAsUnderlyingType() + => new[] + { + (int) global::Something.Blah.ShortName.None, + (int) global::Something.Blah.ShortName.First, + (int) global::Something.Blah.ShortName.Second, + (int) global::Something.Blah.ShortName.Both, + }; + + /// + /// Retrieves an array of the names of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the names of the members defined in + public static string[] GetNames() + => new[] + { + nameof(global::Something.Blah.ShortName.None), + nameof(global::Something.Blah.ShortName.First), + nameof(global::Something.Blah.ShortName.Second), + nameof(global::Something.Blah.ShortName.Both), + }; + } +#pragma warning restore CS0612 // Ignore usages of obsolete members or enums +#pragma warning restore CS0618 // Ignore usages of obsolete members or enums +} \ No newline at end of file diff --git a/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesTryGetFlagsWithCleanCompositeMember_csharp14IsSupported=True.verified.txt b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesTryGetFlagsWithCleanCompositeMember_csharp14IsSupported=True.verified.txt new file mode 100644 index 0000000..ebb6bc0 --- /dev/null +++ b/tests/NetEscapades.EnumGenerators.Tests/Snapshots/SourceGenerationHelperSnapshotTests.GeneratesTryGetFlagsWithCleanCompositeMember_csharp14IsSupported=True.verified.txt @@ -0,0 +1,903 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by the NetEscapades.EnumGenerators source generator +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#nullable enable + +namespace Something.Blah +{ +#pragma warning disable CS0612 // Ignore usages of obsolete members or enums +#pragma warning disable CS0618 // Ignore usages of obsolete members or enums + /// + /// Extension methods for + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("NetEscapades.EnumGenerators", "FIXED_VERSION")] + public static partial class ShortName + { + /// + /// The number of members in the enum. + /// This is a non-distinct count of defined names. + /// + public const int Length = 4; + + /// + /// A bitwise OR combination of all defined values in the enum. + /// + public const global::Something.Blah.ShortName All = global::Something.Blah.ShortName.None | global::Something.Blah.ShortName.First | global::Something.Blah.ShortName.Second | global::Something.Blah.ShortName.Both; + + /// + /// Returns the string representation of the value. + /// Directly equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// The string representation of the value, the same as that returned by ToString() + public static string ToStringFast(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => nameof(global::Something.Blah.ShortName.None), + global::Something.Blah.ShortName.First => nameof(global::Something.Blah.ShortName.First), + global::Something.Blah.ShortName.Second => nameof(global::Something.Blah.ShortName.Second), + global::Something.Blah.ShortName.Both => nameof(global::Something.Blah.ShortName.Both), + _ => value.ToString(), + }; + + /// + /// Returns the string representation of the value. + /// Directly equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// The options to use when serializing the enum + /// The string representation of the value, using the provided options. +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static string ToStringFast(this global::Something.Blah.ShortName value, global::NetEscapades.EnumGenerators.SerializationOptions options) + => options.Transform switch + { + global::NetEscapades.EnumGenerators.SerializationTransform.LowerInvariant => value.ToStringFastLowerInvariant(), + global::NetEscapades.EnumGenerators.SerializationTransform.UpperInvariant => value.ToStringFastUpperInvariant(), + _ => value.ToStringFast(), + }; + + private static string ToStringFastLowerInvariant(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => "none", + global::Something.Blah.ShortName.First => "first", + global::Something.Blah.ShortName.Second => "second", + global::Something.Blah.ShortName.Both => "both", + _ => value.ToString().ToLowerInvariant(), + }; + + private static string ToStringFastUpperInvariant(this global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => "NONE", + global::Something.Blah.ShortName.First => "FIRST", + global::Something.Blah.ShortName.Second => "SECOND", + global::Something.Blah.ShortName.Both => "BOTH", + _ => value.ToString().ToUpperInvariant(), + }; + + /// + /// Returns the string representation of the value. + /// If the member is decorated with the attribute + /// then that value is returned. Otherwise returns uses the name of the member, + /// equivalent to calling ToString() on . + /// + /// The value to retrieve the string value for + /// If uses the value provided in the + /// attribute as the string representation of the member. + /// If , always uses the name of the member, the same as if ToString() was called. + /// The string representation of the value + public static string ToStringFast(this global::Something.Blah.ShortName value, bool useMetadataAttributes) + => value.ToStringFast(); + + /// + /// Determines whether one or more bit fields are set in the current instance. + /// Equivalent to calling on . + /// + /// The value of the instance to investigate + /// The flag to check for + /// if the fields set in the flag are also set in the current instance; otherwise . + /// If the underlying value of is zero, the method returns true. + /// This is consistent with the behaviour of + public static bool HasFlagFast(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName flag) + => flag == 0 ? true : (value & flag) == flag; + + /// + /// Determines whether any of the bit fields are set in the current instance. + /// + /// The value of the instance to investigate + /// The flags to check for + /// if any of the fields set in are + /// also set in ; otherwise . + /// If the underlying value of is zero, the method + /// always returns , consistent with the behaviour of . + public static bool HasAnyFlags(this global::Something.Blah.ShortName value, global::Something.Blah.ShortName otherFlags) + => otherFlags == 0 ? true : (value & otherFlags) != 0; + + /// + /// The number of distinct single-bit flag members defined in the enum. + /// This is the buffer size required by . + /// + public const int DistinctFlagCount = 2; + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Writes each defined single-bit flag set in into . + /// Composite members (e.g. a member defined as A | B) are decomposed into their constituent single-bit flags. + /// The zero/None value never appears in the output. + /// + /// The value to decompose into individual flags. + /// The buffer to write the flags into. Must be at least elements. + /// The number of flags written to . Set to 0 if the method returns . + /// if the buffer was large enough; if it was smaller than . + public static bool TryGetFlags(this global::Something.Blah.ShortName value, global::System.Span buffer, out int count) + { + if (buffer.Length < DistinctFlagCount) + { + count = 0; + return false; + } + count = 0; + if ((value & global::Something.Blah.ShortName.First) == global::Something.Blah.ShortName.First) + buffer[count++] = global::Something.Blah.ShortName.First; + if ((value & global::Something.Blah.ShortName.Second) == global::Something.Blah.ShortName.Second) + buffer[count++] = global::Something.Blah.ShortName.Second; + return true; + } +#endif + + /// + /// Cast a value of to the underlying type (int). + /// This is mainly a convenience method. + /// + /// The value of cast to the underlying type. + public static int AsUnderlyingType(this global::Something.Blah.ShortName value) + { + return (int) value; + } + + // C#14 Extension member syntax + extension(global::Something.Blah.ShortName) + { + + /// + /// Returns a boolean telling whether the given enum value exists in the enumeration. + /// + /// The value to check if it's defined + /// if the value exists in the enumeration, otherwise + public static bool IsDefined(global::Something.Blah.ShortName value) + => value switch + { + global::Something.Blah.ShortName.None => true, + global::Something.Blah.ShortName.First => true, + global::Something.Blah.ShortName.Second => true, + global::Something.Blah.ShortName.Both => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration. + /// + /// The name to check if it's defined + /// if a member with the name exists in the enumeration, otherwise + public static bool IsDefined(string name) + => name switch + { + nameof(global::Something.Blah.ShortName.None) => true, + nameof(global::Something.Blah.ShortName.First) => true, + nameof(global::Something.Blah.ShortName.Second) => true, + nameof(global::Something.Blah.ShortName.Both) => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration, + /// or if a member decorated with + /// with the required name exists. + /// + /// The name to check if it's defined + /// If , + /// considers the value of + /// instead of the member name, otherwise ignores them + /// if a member with the name exists in the enumeration, or a member is decorated + /// with a [Display] attribute with the name, otherwise + public static bool IsDefined(string name, bool allowMatchingMetadataAttribute) + => IsDefined(name); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration + /// + /// The name to check if it's defined + /// if a member with the name exists in the enumeration, otherwise + public static bool IsDefined(in global::System.ReadOnlySpan name) + => name switch + { + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.None)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.First)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), global::System.StringComparison.Ordinal) => true, + global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), global::System.StringComparison.Ordinal) => true, + _ => false, + }; + + /// + /// Returns a boolean telling whether an enum with the given name exists in the enumeration, + /// or optionally if a member decorated with + /// exists. Slower then the overload, but doesn't allocate memory./> + /// + /// The name to check if it's defined + /// If , considers the value of metadata attributes,otherwise ignores them + /// if a member with the name exists in the enumeration, or a member is decorated + /// with a [Display] attribute with the name, otherwise + public static bool IsDefined(in global::System.ReadOnlySpan name, bool allowMatchingMetadataAttribute) + => IsDefined(name); +#endif + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name) + => TryParse(name, out var value, new global::NetEscapades.EnumGenerators.EnumParseOptions()) ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + bool ignoreCase) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)) + ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in metadata attributes such as + /// [Display] attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)) ? value : ThrowValueNotFound(name); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static global::Something.Blah.ShortName Parse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + => TryParse( + name, + out var value, + options) ? value : ThrowValueNotFound(name); + +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] +#endif + private static global::Something.Blah.ShortName ThrowValueNotFound(string? name) + => throw new global::System.ArgumentException($"Requested value '{name}' was not found."); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value) + => TryParse(name, out value, new global::NetEscapades.EnumGenerators.EnumParseOptions()); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + bool ignoreCase) + => TryParse(name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , + /// considers the value included in attribute + /// when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)); + + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static bool TryParse( +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + string? name, + out global::Something.Blah.ShortName value, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + { + switch (name) + { + case string s when s.Equals(nameof(global::Something.Blah.ShortName.None), options.ComparisonType): + value = global::Something.Blah.ShortName.None; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.First), options.ComparisonType): + value = global::Something.Blah.ShortName.First; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.Second), options.ComparisonType): + value = global::Something.Blah.ShortName.Second; + return true; + case string s when s.Equals(nameof(global::Something.Blah.ShortName.Both), options.ComparisonType): + value = global::Something.Blah.ShortName.Both; + return true; + case string s when options.EnableNumberParsing && int.TryParse(name, out var val): + value = (global::Something.Blah.ShortName)val; + return true; + default: + value = default; + return false; + } + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions()) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + bool ignoreCase) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The case-sensitive string representation of the enumeration name or underlying value to convert + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// An object of type whose + /// value is represented by + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out var value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)) + ? value : ThrowValueNotFound(name); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, disable number parsing in the parameter. + +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static global::Something.Blah.ShortName Parse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// + /// The string representation of the enumeration name or underlying value to convert + /// Options that control how the string value should be parsed. + /// An object of type whose + /// value is represented by +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static global::Something.Blah.ShortName Parse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + => TryParse( + name, + out var value, + options) ? value : ThrowValueNotFound(name); + +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] +#endif + private static global::Something.Blah.ShortName ThrowValueNotFound(in global::System.ReadOnlySpan name) +#if NET6_0_OR_GREATER + => throw new global::System.ArgumentException($"Requested value '{name}' was not found."); +#else + => throw new global::System.ArgumentException($"Requested value '{name.ToString()}' was not found."); +#endif + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName value) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions()); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName value, + bool ignoreCase) + => TryParse( + name, + out value, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal)); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, call the + /// overload, and disable number parsing. + public static bool TryParse( +#else + /// + /// Converts the span representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The span representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// to read value in case insensitive mode; to read value in case sensitive mode. + /// If , considers the value included in + /// attribute when parsing, otherwise only considers the member names. + /// if the value parameter was converted successfully; otherwise, . + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName result, + bool ignoreCase, + bool allowMatchingMetadataAttribute) + => TryParse( + name, + out result, + new global::NetEscapades.EnumGenerators.EnumParseOptions( + ignoreCase ? global::System.StringComparison.OrdinalIgnoreCase : global::System.StringComparison.Ordinal, + allowMatchingMetadataAttribute: allowMatchingMetadataAttribute)); + +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER && NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . + /// WARNING: This API will allocate a when + /// is not explicitly defined in when attempting to parse as a number. + /// To avoid this allocation, only call this overload when you know the value will exist. + /// Alternatively, disable number parsing in the parameter. + +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static bool TryParse( +#else + /// + /// Converts the string representation of the name or numeric value of + /// an to the equivalent instance. + /// The return value indicates whether the conversion succeeded. + /// + /// The string representation of the enumeration name or underlying value to convert + /// When this method returns, contains an object of type + /// whose + /// value is represented by if the parse operation succeeds. + /// If the parse operation fails, contains the default value of the underlying type + /// of . This parameter is passed uninitialized. + /// Options that control how the string value should be parsed. + /// if the value parameter was converted successfully; otherwise, . +#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + + public static bool TryParse( +#endif +#if NETCOREAPP3_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] +#endif + in global::System.ReadOnlySpan name, + out global::Something.Blah.ShortName result, + global::NetEscapades.EnumGenerators.EnumParseOptions options) + { + switch (name) + { + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.None)), options.ComparisonType): + result = global::Something.Blah.ShortName.None; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.First)), options.ComparisonType): + result = global::Something.Blah.ShortName.First; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Second)), options.ComparisonType): + result = global::Something.Blah.ShortName.Second; + return true; + case global::System.ReadOnlySpan current when global::System.MemoryExtensions.Equals(current, global::System.MemoryExtensions.AsSpan(nameof(global::Something.Blah.ShortName.Both)), options.ComparisonType): + result = global::Something.Blah.ShortName.Both; + return true; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + case global::System.ReadOnlySpan current when options.EnableNumberParsing && int.TryParse(name, out var numericResult): +#else + // TryParse with ReadOnlySpan is not available, so we have to call ToString() + case global::System.ReadOnlySpan current when options.EnableNumberParsing && int.TryParse(name.ToString(), out var numericResult): +#endif + result = (global::Something.Blah.ShortName)numericResult; + return true; + default: + result = default; + return false; + } + } +#endif + + /// + /// Retrieves an array of the values of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the values defined in + public static global::Something.Blah.ShortName[] GetValues() + => new[] + { + global::Something.Blah.ShortName.None, + global::Something.Blah.ShortName.First, + global::Something.Blah.ShortName.Second, + global::Something.Blah.ShortName.Both, + }; + + /// + /// Retrieves an array of the underlying-values of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the underlying-values defined in + public static int[] GetValuesAsUnderlyingType() + => new[] + { + (int) global::Something.Blah.ShortName.None, + (int) global::Something.Blah.ShortName.First, + (int) global::Something.Blah.ShortName.Second, + (int) global::Something.Blah.ShortName.Both, + }; + + /// + /// Retrieves an array of the names of the members defined in + /// . + /// Note that this returns a new array with every invocation, so + /// should be cached if appropriate. + /// + /// An array of the names of the members defined in + public static string[] GetNames() + => new[] + { + nameof(global::Something.Blah.ShortName.None), + nameof(global::Something.Blah.ShortName.First), + nameof(global::Something.Blah.ShortName.Second), + nameof(global::Something.Blah.ShortName.Both), + }; + } + } +#pragma warning restore CS0612 // Ignore usages of obsolete members or enums +#pragma warning restore CS0618 // Ignore usages of obsolete members or enums +} \ No newline at end of file diff --git a/tests/NetEscapades.EnumGenerators.Tests/SourceGenerationHelperSnapshotTests.cs b/tests/NetEscapades.EnumGenerators.Tests/SourceGenerationHelperSnapshotTests.cs index 8c07952..f86f201 100644 --- a/tests/NetEscapades.EnumGenerators.Tests/SourceGenerationHelperSnapshotTests.cs +++ b/tests/NetEscapades.EnumGenerators.Tests/SourceGenerationHelperSnapshotTests.cs @@ -118,9 +118,46 @@ public Task GeneratesFlagsEnumCorrectly(bool csharp14IsSupported) [Theory] [CombinatorialData] - public Task DoesNotGenerateTryGetFlagsForCompositeFlagsEnum(bool csharp14IsSupported) + public Task DoesNotGenerateTryGetFlagsWhenMemberHasUndefinedBits(bool csharp14IsSupported) { - // An enum with a composite member (non-zero, non-power-of-two) should not get TryGetFlags. + // A member whose value contains a bit not corresponding to any single-bit flag disqualifies emission. + // Here Odd=5 = 1|4, but 4 is not defined as a single-bit flag, so no TryGetFlags is generated. + var value = new EnumToGenerate( + "ShortName", + "Something.Blah", + "Something.Blah.ShortName", + "int", + isPublic: true, + new List<(string, EnumValueOption)> + { + ("None", EnumValueOption.CreateWithoutAttributes(0)), + ("First", EnumValueOption.CreateWithoutAttributes(1)), + ("Second", EnumValueOption.CreateWithoutAttributes(2)), + ("Odd", EnumValueOption.CreateWithoutAttributes(5)), + }, + hasFlags: true, + metadataSource: null); + + var result = SourceGenerationHelper.GenerateExtensionClass( + value, + csharp14IsSupported, + useCollectionExpressions: false, + DefaultMetadataSource, + hasRuntimeDependencies: true, + forceInternal: false, + hasOverloadResolutionPriority: true).Content; + + return Verifier.Verify(result) + .ScrubExpectedChanges() + .UseDirectory("Snapshots") + .UseParameters(csharp14IsSupported); + } + + [Theory] + [CombinatorialData] + public Task GeneratesTryGetFlagsWithCleanCompositeMember(bool csharp14IsSupported) + { + // A composite member that's a clean combination of single-bit flags is fine; TryGetFlags is emitted. var value = new EnumToGenerate( "ShortName", "Something.Blah",