From d88fa6cd6e32015ba227def9b1582b8282ade9bf Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 14 Jun 2023 11:51:54 +0100 Subject: [PATCH 1/2] Make the soure generator format enums using their identifiers rather than numeric values. --- .../gen/Helpers/EnumFormatter.cs | 109 ++++++++++++++++++ .../gen/JsonSourceGenerator.Emitter.cs | 23 ++-- .../System.Text.Json.SourceGeneration.targets | 1 + 3 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 src/libraries/System.Text.Json/gen/Helpers/EnumFormatter.cs diff --git a/src/libraries/System.Text.Json/gen/Helpers/EnumFormatter.cs b/src/libraries/System.Text.Json/gen/Helpers/EnumFormatter.cs new file mode 100644 index 00000000000000..4ae6d987a063ec --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Helpers/EnumFormatter.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace System.Text.Json.SourceGeneration +{ + public static class EnumFormatter where TEnum : struct, Enum + { + private static readonly bool s_IsFlagsEnum = typeof(TEnum).GetCustomAttribute() != null; + private static readonly TEnum[] s_definedValues = (TEnum[])Enum.GetValues(typeof(TEnum)); + + public static string FormatEnumLiteral(string enumTypeName, TEnum value) + { + if (TryGetEnumComponents(value, out TEnum? singleValue, out List? flagComponents)) + { + if (singleValue != null) + { + return FormatDefinedValue(value); + } + else + { + Debug.Assert(flagComponents?.Count > 1); + return string.Join(" | ", flagComponents.Select(FormatDefinedValue)); + } + + string FormatDefinedValue(TEnum value) + { + return $"{enumTypeName}.{value}"; + } + } + + // Does not correspond to an enum value, format as numeric value. + int numericValue = GetNumericValue(value); + return numericValue >= 0 + ? $"({enumTypeName}){numericValue}" + : $"({enumTypeName})({numericValue})"; + } + + private static bool TryGetEnumComponents(TEnum value, out TEnum? singleValue, out List? flagComponents) + { + singleValue = null; + flagComponents = null; + + if (!s_IsFlagsEnum) + { + int idx = Array.IndexOf(s_definedValues, value); + if (idx < 0) + { + return false; + } + + singleValue = s_definedValues[idx]; + return true; + } + + int numericValue = GetNumericValue(value); + + foreach (TEnum definedValue in s_definedValues) + { + int definedNumericValue = GetNumericValue(definedValue); + bool isContainedInValue = definedNumericValue != 0 + ? (numericValue & definedNumericValue) == definedNumericValue + : numericValue == 0; + + if (isContainedInValue) + { + if (singleValue is null && flagComponents is null) + { + singleValue = definedValue; + } + else + { + if (flagComponents is null) + { + Debug.Assert(singleValue.HasValue); + flagComponents = new() { singleValue.Value }; + singleValue = null; + } + + flagComponents.Add(definedValue); + } + + numericValue &= ~definedNumericValue; + } + } + + // The enum contains bits that do not correspond to defined values. + // Discard accumulated state and return false. + if (numericValue != 0) + { + flagComponents = null; + singleValue = null; + } + + return singleValue != null || flagComponents != null; + } + + private static int GetNumericValue(TEnum value) + { + Debug.Assert(Type.GetTypeCode(typeof(TEnum)) is TypeCode.Int32, "only int-backed enums supported for now."); + return Unsafe.As(ref value); + } + } +} diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 068ee526206eaf..ec36d1292405e7 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -1211,23 +1211,16 @@ private SourceText GetPropertyNameInitialization(ContextGenerationSpec contextSp return CompleteSourceFileAndReturnText(writer); } - private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) => - numberHandling switch - { - null => "null", - >= 0 => $"({JsonNumberHandlingTypeRef}){(int)numberHandling.Value}", - < 0 => $"({JsonNumberHandlingTypeRef})({(int)numberHandling.Value})" - }; + private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) + => numberHandling.HasValue + ? EnumFormatter.FormatEnumLiteral(JsonNumberHandlingTypeRef, numberHandling.Value) + : "null"; - private static string GetObjectCreationHandlingAsStr(JsonObjectCreationHandling creationHandling) => - creationHandling >= 0 - ? $"({JsonObjectCreationHandlingTypeRef}){(int)creationHandling}" - : $"({JsonObjectCreationHandlingTypeRef})({(int)creationHandling})"; + private static string GetObjectCreationHandlingAsStr(JsonObjectCreationHandling creationHandling) + => EnumFormatter.FormatEnumLiteral(JsonObjectCreationHandlingTypeRef, creationHandling); - private static string GetUnmappedMemberHandlingAsStr(JsonUnmappedMemberHandling unmappedMemberHandling) => - unmappedMemberHandling >= 0 - ? $"({JsonUnmappedMemberHandlingTypeRef}){(int)unmappedMemberHandling}" - : $"({JsonUnmappedMemberHandlingTypeRef})({(int)unmappedMemberHandling})"; + private static string GetUnmappedMemberHandlingAsStr(JsonUnmappedMemberHandling unmappedMemberHandling) + => EnumFormatter.FormatEnumLiteral(JsonUnmappedMemberHandlingTypeRef, unmappedMemberHandling); private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>"; diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets index b7aa0a9f813a94..2861df1966cf07 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets @@ -49,6 +49,7 @@ + From d31a372bb689c3b8c466e48ae5059fb4845275fd Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 14 Jun 2023 17:32:30 +0100 Subject: [PATCH 2/2] Address feedback --- .../gen/Helpers/EnumFormatter.cs | 109 ------------------ .../gen/Helpers/SourceGeneratorHelpers.cs | 25 ++++ .../gen/JsonSourceGenerator.Emitter.cs | 6 +- .../System.Text.Json.SourceGeneration.targets | 2 +- 4 files changed, 29 insertions(+), 113 deletions(-) delete mode 100644 src/libraries/System.Text.Json/gen/Helpers/EnumFormatter.cs create mode 100644 src/libraries/System.Text.Json/gen/Helpers/SourceGeneratorHelpers.cs diff --git a/src/libraries/System.Text.Json/gen/Helpers/EnumFormatter.cs b/src/libraries/System.Text.Json/gen/Helpers/EnumFormatter.cs deleted file mode 100644 index 4ae6d987a063ec..00000000000000 --- a/src/libraries/System.Text.Json/gen/Helpers/EnumFormatter.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; - -namespace System.Text.Json.SourceGeneration -{ - public static class EnumFormatter where TEnum : struct, Enum - { - private static readonly bool s_IsFlagsEnum = typeof(TEnum).GetCustomAttribute() != null; - private static readonly TEnum[] s_definedValues = (TEnum[])Enum.GetValues(typeof(TEnum)); - - public static string FormatEnumLiteral(string enumTypeName, TEnum value) - { - if (TryGetEnumComponents(value, out TEnum? singleValue, out List? flagComponents)) - { - if (singleValue != null) - { - return FormatDefinedValue(value); - } - else - { - Debug.Assert(flagComponents?.Count > 1); - return string.Join(" | ", flagComponents.Select(FormatDefinedValue)); - } - - string FormatDefinedValue(TEnum value) - { - return $"{enumTypeName}.{value}"; - } - } - - // Does not correspond to an enum value, format as numeric value. - int numericValue = GetNumericValue(value); - return numericValue >= 0 - ? $"({enumTypeName}){numericValue}" - : $"({enumTypeName})({numericValue})"; - } - - private static bool TryGetEnumComponents(TEnum value, out TEnum? singleValue, out List? flagComponents) - { - singleValue = null; - flagComponents = null; - - if (!s_IsFlagsEnum) - { - int idx = Array.IndexOf(s_definedValues, value); - if (idx < 0) - { - return false; - } - - singleValue = s_definedValues[idx]; - return true; - } - - int numericValue = GetNumericValue(value); - - foreach (TEnum definedValue in s_definedValues) - { - int definedNumericValue = GetNumericValue(definedValue); - bool isContainedInValue = definedNumericValue != 0 - ? (numericValue & definedNumericValue) == definedNumericValue - : numericValue == 0; - - if (isContainedInValue) - { - if (singleValue is null && flagComponents is null) - { - singleValue = definedValue; - } - else - { - if (flagComponents is null) - { - Debug.Assert(singleValue.HasValue); - flagComponents = new() { singleValue.Value }; - singleValue = null; - } - - flagComponents.Add(definedValue); - } - - numericValue &= ~definedNumericValue; - } - } - - // The enum contains bits that do not correspond to defined values. - // Discard accumulated state and return false. - if (numericValue != 0) - { - flagComponents = null; - singleValue = null; - } - - return singleValue != null || flagComponents != null; - } - - private static int GetNumericValue(TEnum value) - { - Debug.Assert(Type.GetTypeCode(typeof(TEnum)) is TypeCode.Int32, "only int-backed enums supported for now."); - return Unsafe.As(ref value); - } - } -} diff --git a/src/libraries/System.Text.Json/gen/Helpers/SourceGeneratorHelpers.cs b/src/libraries/System.Text.Json/gen/Helpers/SourceGeneratorHelpers.cs new file mode 100644 index 00000000000000..26c5d3fb937738 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Helpers/SourceGeneratorHelpers.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using System.Collections.Generic; + +namespace System.Text.Json.SourceGeneration +{ + internal static class SourceGeneratorHelpers + { + private static readonly char[] s_enumSeparator = new char[] { ',' }; + + public static string FormatEnumLiteral(string fullyQualifiedName, TEnum value) where TEnum : struct, Enum + { + IEnumerable values = value.ToString().Split(s_enumSeparator, StringSplitOptions.RemoveEmptyEntries) + .Select(name => name.Trim()) + .Select(name => + int.TryParse(name, out _) + ? $"({fullyQualifiedName})({name})" + : $"{fullyQualifiedName}.{name}"); + + return string.Join(" | ", values); + } + } +} diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index ec36d1292405e7..2658b1df156406 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -1213,14 +1213,14 @@ private SourceText GetPropertyNameInitialization(ContextGenerationSpec contextSp private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) => numberHandling.HasValue - ? EnumFormatter.FormatEnumLiteral(JsonNumberHandlingTypeRef, numberHandling.Value) + ? SourceGeneratorHelpers.FormatEnumLiteral(JsonNumberHandlingTypeRef, numberHandling.Value) : "null"; private static string GetObjectCreationHandlingAsStr(JsonObjectCreationHandling creationHandling) - => EnumFormatter.FormatEnumLiteral(JsonObjectCreationHandlingTypeRef, creationHandling); + => SourceGeneratorHelpers.FormatEnumLiteral(JsonObjectCreationHandlingTypeRef, creationHandling); private static string GetUnmappedMemberHandlingAsStr(JsonUnmappedMemberHandling unmappedMemberHandling) - => EnumFormatter.FormatEnumLiteral(JsonUnmappedMemberHandlingTypeRef, unmappedMemberHandling); + => SourceGeneratorHelpers.FormatEnumLiteral(JsonUnmappedMemberHandlingTypeRef, unmappedMemberHandling); private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>"; diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets index 2861df1966cf07..06fccb03c2bce6 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets @@ -49,7 +49,7 @@ - +