From 871daf0b7f9bffd27f293444b8e1aefda6a37be9 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Mon, 12 Apr 2021 15:51:16 -0700 Subject: [PATCH 1/5] Add JSON source generator --- eng/Versions.props | 4 + .../JsonIgnoreCondition.cs | 7 +- .../JsonNumberHandling.cs | 14 + .../System.Text.Json/System.Text.Json.sln | 32 + .../System.Text.Json/ref/System.Text.Json.cs | 10 +- .../source_generation/src/ClassType.cs | 21 + .../source_generation/src/CollectionType.cs | 19 + .../source_generation/src/IsExternalInit.cs | 10 + .../src/JsonSerializableSyntaxReceiver.cs | 24 + .../src/JsonSourceGenerator.cs | 84 +++ .../src/JsonSourceGeneratorHelper.Generate.cs | 672 ++++++++++++++++++ .../src/JsonSourceGeneratorHelper.cs | 499 +++++++++++++ .../src/ObjectConstructionStrategy.cs | 24 + .../source_generation/src/PropertyMetadata.cs | 73 ++ .../src/ReflectionUtils/AssemblyWrapper.cs | 55 ++ .../ReflectionUtils/ConstructorInfoWrapper.cs | 93 +++ .../CustomAttributeDataWrapper.cs | 42 ++ .../src/ReflectionUtils/FieldInfoWrapper.cs | 95 +++ .../src/ReflectionUtils/MemberInfoWrapper.cs | 53 ++ .../ReflectionUtils/MetadataLoadContext.cs | 113 +++ .../src/ReflectionUtils/MethodInfoWrapper.cs | 138 ++++ .../ReflectionUtils/ParameterInfoWrapper.cs | 35 + .../ReflectionUtils/PropertyInfoWrapper.cs | 95 +++ .../ReflectionUtils/ReflectionExtensions.cs | 34 + .../src/ReflectionUtils/RoslynExtensions.cs | 33 + .../src/ReflectionUtils/TypeExtensions.cs | 85 +++ .../src/ReflectionUtils/TypeWrapper.cs | 490 +++++++++++++ .../System.Text.Json.SourceGeneration.csproj | 48 ++ .../source_generation/src/TypeMetadata.cs | 82 +++ .../tests/JsonSourceGeneratorTests.cs | 452 ++++++++++++ ...em.Text.Json.SourceGeneration.Tests.csproj | 22 + .../source_generation/tests/TestClasses.cs | 128 ++++ .../unit_tests/CompilationHelper.cs | 198 ++++++ .../JsonSourceGeneratorDiagnosticsTests.cs | 154 ++++ .../unit_tests/JsonSourceGeneratorTests.cs | 310 ++++++++ ...ext.Json.SourceGeneration.UnitTests.csproj | 27 + .../unit_tests/TypeWrapperTests.cs | 190 +++++ .../src/Resources/Strings.resx | 8 +- .../src/System.Text.Json.csproj | 4 +- .../src/System/Text/Json/JsonHelpers.cs | 15 + .../Serialization/JsonSerializer.Read.Span.cs | 4 + .../JsonSerializer.Read.Stream.cs | 4 + .../JsonSerializer.Read.String.cs | 134 +++- .../JsonSerializer.Read.Utf8JsonReader.cs | 4 + .../JsonSerializer.Write.ByteArray.cs | 2 + .../JsonSerializer.Write.Stream.cs | 2 + .../JsonSerializer.Write.String.cs | 84 +++ .../JsonSerializer.Write.Utf8JsonWriter.cs | 2 + .../JsonSerializerOptions.Converters.cs | 54 +- .../Serialization/JsonSerializerOptions.cs | 25 +- .../JsonMetadataServices.Converters.cs | 19 +- .../Metadata/JsonMetadataServices.cs | 23 +- .../Metadata/JsonPropertyInfo.cs | 10 +- .../Metadata/JsonPropertyInfoOfT.cs | 50 +- .../Metadata/JsonTypeInfoInternalOfT.cs | 6 +- .../Text/Json/Serialization/ReadStack.cs | 6 + .../Text/Json/Serialization/WriteStack.cs | 4 + .../Text/Json/ThrowHelper.Serialization.cs | 21 + .../MetadataServicesTests.cs | 49 +- .../tests/Serialization/Null.WriteTests.cs | 2 +- .../Serialization/NumberHandlingTests.cs | 3 + .../tests/Serialization/OptionsTests.cs | 1 + src/libraries/src.proj | 2 + 63 files changed, 4911 insertions(+), 92 deletions(-) rename src/libraries/System.Text.Json/{src/System/Text/Json/Serialization => Common}/JsonIgnoreCondition.cs (93%) rename src/libraries/System.Text.Json/{src/System/Text/Json/Serialization => Common}/JsonNumberHandling.cs (86%) create mode 100644 src/libraries/System.Text.Json/source_generation/src/ClassType.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/CollectionType.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/IsExternalInit.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/JsonSerializableSyntaxReceiver.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/JsonSourceGenerator.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.Generate.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ObjectConstructionStrategy.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/PropertyMetadata.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/AssemblyWrapper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ConstructorInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/CustomAttributeDataWrapper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/FieldInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MemberInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MetadataLoadContext.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MethodInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ParameterInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/PropertyInfoWrapper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ReflectionExtensions.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/RoslynExtensions.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeExtensions.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeWrapper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/src/System.Text.Json.SourceGeneration.csproj create mode 100644 src/libraries/System.Text.Json/source_generation/src/TypeMetadata.cs create mode 100644 src/libraries/System.Text.Json/source_generation/tests/JsonSourceGeneratorTests.cs create mode 100644 src/libraries/System.Text.Json/source_generation/tests/System.Text.Json.SourceGeneration.Tests.csproj create mode 100644 src/libraries/System.Text.Json/source_generation/tests/TestClasses.cs create mode 100644 src/libraries/System.Text.Json/source_generation/unit_tests/CompilationHelper.cs create mode 100644 src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs create mode 100644 src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorTests.cs create mode 100644 src/libraries/System.Text.Json/source_generation/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj create mode 100644 src/libraries/System.Text.Json/source_generation/unit_tests/TypeWrapperTests.cs diff --git a/eng/Versions.props b/eng/Versions.props index b23a6f1d9017df..ed14d21579bdb4 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -177,6 +177,10 @@ 9.0.1-alpha.1.21212.1 9.0.1-alpha.1.21212.1 9.0.1-alpha.1.21212.1 + + 3.10.0-1.final + 3.3.2 + 3.10.0-1.final diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreCondition.cs b/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs similarity index 93% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreCondition.cs rename to src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs index f54b45f97cbf9a..90b09e392af009 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreCondition.cs +++ b/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs @@ -3,6 +3,9 @@ namespace System.Text.Json.Serialization { +#if BUILDING_SOURCE_GENERATOR + internal +#else /// /// When specified on , /// determines when properties and fields across the type graph are ignored. @@ -10,7 +13,9 @@ namespace System.Text.Json.Serialization /// a property is ignored during serialization and deserialization. This option /// overrides the setting on . /// - public enum JsonIgnoreCondition + public +#endif + enum JsonIgnoreCondition { /// /// Property is never ignored during serialization or deserialization. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNumberHandling.cs b/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs similarity index 86% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNumberHandling.cs rename to src/libraries/System.Text.Json/Common/JsonNumberHandling.cs index c8eabaad5089c7..f98f41949b03a2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNumberHandling.cs +++ b/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs @@ -3,6 +3,16 @@ namespace System.Text.Json.Serialization { +#if BUILDING_SOURCE_GENERATOR + // cref references below are not present when compiling this assembly. + internal enum JsonNumberHandling + { + Strict = 0x0, + AllowReadingFromString = 0x1, + WriteAsString = 0x2, + AllowNamedFloatingPointLiterals = 0x4 + } +#else /// /// Determines how handles numbers when serializing and deserializing. /// @@ -13,6 +23,7 @@ public enum JsonNumberHandling /// Numbers will only be read from tokens and will only be written as JSON numbers (without quotes). /// Strict = 0x0, + /// /// Numbers can be read from tokens. /// Does not prevent numbers from being read from token. @@ -20,10 +31,12 @@ public enum JsonNumberHandling /// Leading or trailing trivia within the string token, including whitespace, is not allowed. /// AllowReadingFromString = 0x1, + /// /// Numbers will be written as JSON strings (with quotes), not as JSON numbers. /// WriteAsString = 0x2, + /// /// The "NaN", "Infinity", and "-Infinity" tokens can be read as /// floating-point constants, and the and values for these @@ -34,4 +47,5 @@ public enum JsonNumberHandling /// AllowNamedFloatingPointLiterals = 0x4 } +#endif } diff --git a/src/libraries/System.Text.Json/System.Text.Json.sln b/src/libraries/System.Text.Json/System.Text.Json.sln index cf494364f9866e..e426c9095e1fcf 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.sln +++ b/src/libraries/System.Text.Json/System.Text.Json.sln @@ -35,6 +35,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{EC8CE194-261 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{676B6044-FA47-4B7D-AEC2-FA94DB23A423}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "source_generation", "source_generation", "{A403699B-1BB2-4B23-AE05-97608C23EB27}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0DBD3DDF-16F1-4E7D-80A5-F9D74C1219B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration", "source_generation\src\System.Text.Json.SourceGeneration.csproj", "{24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C4C10F31-F6DF-40DB-B892-CDFDD4DD9091}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.Tests", "source_generation\tests\System.Text.Json.SourceGeneration.Tests.csproj", "{9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit_tests", "unit_tests", "{7B11F28A-2732-49CF-8000-682FBDE606A7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.UnitTests", "source_generation\unit_tests\System.Text.Json.SourceGeneration.UnitTests.csproj", "{CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}" +EndProject Global GlobalSection(NestedProjects) = preSolution {102945CA-3736-4B2C-8E68-242A0B247F2B} = {3C544454-BD8B-44F4-A174-B61F18957613} @@ -52,6 +66,12 @@ Global {7909EB27-0D6E-46E6-B9F9-8A1EFD557018} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423} {9BCCDA15-8907-4AE3-8871-2F17775DDE4C} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423} {1285FF43-F491-4BE0-B92C-37DA689CBD4B} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423} + {0DBD3DDF-16F1-4E7D-80A5-F9D74C1219B7} = {A403699B-1BB2-4B23-AE05-97608C23EB27} + {24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF} = {0DBD3DDF-16F1-4E7D-80A5-F9D74C1219B7} + {C4C10F31-F6DF-40DB-B892-CDFDD4DD9091} = {A403699B-1BB2-4B23-AE05-97608C23EB27} + {9DE9B0B4-8721-4A6A-B73C-59D89E5212B9} = {C4C10F31-F6DF-40DB-B892-CDFDD4DD9091} + {7B11F28A-2732-49CF-8000-682FBDE606A7} = {A403699B-1BB2-4B23-AE05-97608C23EB27} + {CE147231-41C1-4FFE-A1CB-0BC3DCCD980A} = {7B11F28A-2732-49CF-8000-682FBDE606A7} EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -118,6 +138,18 @@ Global {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Debug|Any CPU.Build.0 = Debug|Any CPU {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Release|Any CPU.ActiveCfg = Release|Any CPU {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Release|Any CPU.Build.0 = Release|Any CPU + {24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}.Release|Any CPU.Build.0 = Release|Any CPU + {9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}.Release|Any CPU.Build.0 = Release|Any CPU + {CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 768fdb6fea3f03..22769efd27ed9a 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -196,6 +196,8 @@ public static partial class JsonSerializer public static TValue? Deserialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(System.ReadOnlySpan utf8Json, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static TValue? Deserialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(System.ReadOnlySpan json, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static TValue? Deserialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string json, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static TValue? Deserialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string json, System.Text.Json.Serialization.JsonSerializerContext jsonSerializerContext) { throw null; } + public static TValue? Deserialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string json, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo) { throw null; } public static TValue? Deserialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(ref System.Text.Json.Utf8JsonReader reader, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static string Serialize(object? value, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] System.Type inputType, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static void Serialize(System.Text.Json.Utf8JsonWriter writer, object? value, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] System.Type inputType, System.Text.Json.JsonSerializerOptions? options = null) { } @@ -205,6 +207,8 @@ public static void Serialize(System.Text.Json.Utf8JsonWriter writer, object? val public static byte[] SerializeToUtf8Bytes<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(TValue value, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } public static void Serialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(System.Text.Json.Utf8JsonWriter writer, TValue value, System.Text.Json.JsonSerializerOptions? options = null) { } public static string Serialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(TValue value, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } + public static string Serialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(TValue value, System.Text.Json.Serialization.JsonSerializerContext jsonSerializerContext) { throw null; } + public static string Serialize<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo) { throw null; } } public enum JsonSerializerDefaults { @@ -790,10 +794,10 @@ public static partial class JsonMetadataServices public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateDictionaryInfo(System.Text.Json.JsonSerializerOptions options, System.Func createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where TCollection : System.Collections.Generic.Dictionary where TKey : notnull { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateListInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where TCollection : System.Collections.Generic.List { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateObjectInfo() where T : notnull { throw null; } - public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter? converter, System.Func? getter, System.Action? setter, System.Text.Json.Serialization.JsonIgnoreCondition ignoreCondition, System.Text.Json.Serialization.JsonNumberHandling numberHandling, string propertyName, System.Text.Json.JsonEncodedText jsonPropertyName) { throw null; } - public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateValueInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) where TConverterReturn : T { throw null; } + public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter? converter, System.Func? getter, System.Action? setter, System.Text.Json.Serialization.JsonIgnoreCondition ignoreCondition, System.Text.Json.Serialization.JsonNumberHandling numberHandling, string propertyName, string? jsonPropertyName, byte[]? encodedName) { throw null; } + public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateValueInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; } public static System.Text.Json.Serialization.JsonConverter GetEnumConverter(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; } - public static System.Text.Json.Serialization.JsonConverter GetNullableConverter(System.Text.Json.Serialization.JsonConverter underlyingTypeconverter) where T : struct { throw null; } + public static System.Text.Json.Serialization.JsonConverter GetNullableConverter(System.Text.Json.Serialization.Metadata.JsonTypeInfo underlyingTypeInfo) where T : struct { throw null; } public static void InitializeObjectInfo(System.Text.Json.Serialization.Metadata.JsonTypeInfo info, System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Func propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where T : notnull { } } public abstract partial class JsonPropertyInfo diff --git a/src/libraries/System.Text.Json/source_generation/src/ClassType.cs b/src/libraries/System.Text.Json/source_generation/src/ClassType.cs new file mode 100644 index 00000000000000..fa6aab3d2573bd --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ClassType.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Text.Json.SourceGeneration +{ + internal enum ClassType + { + TypeUnsupportedBySourceGen = 0, + Object = 1, + KnownType = 2, + TypeWithDesignTimeProvidedCustomConverter = 3, + Enumerable = 4, + Dictionary = 5, + Nullable = 6, + Enum = 7, + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/CollectionType.cs b/src/libraries/System.Text.Json/source_generation/src/CollectionType.cs new file mode 100644 index 00000000000000..6f736698f47b8f --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/CollectionType.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Text.Json.SourceGeneration +{ + internal enum CollectionType + { + NotApplicable = 0, + Array = 1, + List = 2, + IEnumerable = 3, + IList = 4, + Dictionary = 5 + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/IsExternalInit.cs b/src/libraries/System.Text.Json/source_generation/src/IsExternalInit.cs new file mode 100644 index 00000000000000..626c3edde3ddc9 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/IsExternalInit.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ + /// + /// Dummy class so C# init-only properties can compile on NetStandard. + /// + public sealed class IsExternalInit { } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/JsonSerializableSyntaxReceiver.cs b/src/libraries/System.Text.Json/source_generation/src/JsonSerializableSyntaxReceiver.cs new file mode 100644 index 00000000000000..171aadca0b0722 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/JsonSerializableSyntaxReceiver.cs @@ -0,0 +1,24 @@ +// 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 Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace System.Text.Json.SourceGeneration +{ + public class JsonSerializableSyntaxReceiver : ISyntaxReceiver + { + public List CompilationUnits { get; } = new(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is CompilationUnitSyntax compilationUnit) + { + CompilationUnits.Add(compilationUnit); + } + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/source_generation/src/JsonSourceGenerator.cs new file mode 100644 index 00000000000000..56d5f87e6c0c56 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/JsonSourceGenerator.cs @@ -0,0 +1,84 @@ +// 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 Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace System.Text.Json.SourceGeneration +{ + [Generator] + public sealed class JsonSourceGenerator : ISourceGenerator + { + public Dictionary? SerializableTypes { get; private set; } + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new JsonSerializableSyntaxReceiver()); + } + + public void Execute(GeneratorExecutionContext executionContext) + { + JsonSerializableSyntaxReceiver receiver = (JsonSerializableSyntaxReceiver)executionContext.SyntaxReceiver; + MetadataLoadContext metadataLoadContext = new(executionContext.Compilation); + + TypeExtensions.NullableOfTType = metadataLoadContext.Resolve(typeof(Nullable<>)); + + // Discover serializable types indicated by JsonSerializableAttribute. + foreach (CompilationUnitSyntax compilationUnit in receiver.CompilationUnits) + { + SemanticModel compilationSemanticModel = executionContext.Compilation.GetSemanticModel(compilationUnit.SyntaxTree); + + foreach (AttributeListSyntax attributeListSyntax in compilationUnit.AttributeLists) + { + AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.Single(); + IMethodSymbol attributeSymbol = compilationSemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol; + + if (attributeSymbol?.ToString().StartsWith("System.Text.Json.Serialization.JsonSerializableAttribute") == true) + { + // Get JsonSerializableAttribute arguments. + IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); + + int argumentCount = attributeArguments.Count(); + + // Compiler shouldn't allow invalid signature for the JsonSerializable attribute. + Debug.Assert(argumentCount == 1 || argumentCount == 2); + + // Obtain the one `Type` argument that must be present in the constructor of the attribute. + AttributeArgumentSyntax typeArgumentNode = (AttributeArgumentSyntax)attributeArguments.First(); + TypeOfExpressionSyntax typeNode = (TypeOfExpressionSyntax)typeArgumentNode.ChildNodes().Single(); + ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single(); + ITypeSymbol typeSymbol = compilationSemanticModel.GetTypeInfo(typeNameSyntax).ConvertedType; + + // TODO: parse the TypeInfoPropertyName arg if present. + + Type type = new TypeWrapper(typeSymbol, metadataLoadContext); + + if (type.Namespace == "") + { + // typeof() reference where the type's name isn't fully qualified. + // The compilation is not valid and user needs to fix their code. + // The compiler will notify the user so we don't have to. + return; + } + + (SerializableTypes ??= new Dictionary())[type.FullName] = type; + } + } + } + + if (SerializableTypes == null) + { + return; + } + + Debug.Assert(SerializableTypes.Count >= 1); + JsonSourceGeneratorHelper helper = new(executionContext, metadataLoadContext, SerializableTypes); + helper.GenerateSerializationMetadata(); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.Generate.cs b/src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.Generate.cs new file mode 100644 index 00000000000000..0403eded2643b8 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.Generate.cs @@ -0,0 +1,672 @@ +// 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.Text.Json.Serialization; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace System.Text.Json.SourceGeneration +{ + internal sealed partial class JsonSourceGeneratorHelper + { + private readonly string _generationNamespace; + + // TODO: consider public option for this. + private bool _honorRuntimeProvidedCustomConverters = true; + + private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter"; + + private const string JsonContextDeclarationSource = "internal partial class JsonContext : JsonSerializerContext"; + + private const string OptionsInstanceVariableName = "Options"; + + private const string PropInitFuncVarName = "PropInitFunc"; + + private const string JsonMetadataServicesClassName = "JsonMetadataServices"; + + private const string CreateValueInfoMethodName = "CreateValueInfo"; + + private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor( + "SYSLIB2021", + "Did not generate serialization metadata for type", + "Did not generate serialization metadata for type {0}", + "JSON source generation", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "Error message: {2}"); + + /// + /// Types that we have initiated serialization metadata generation for. A type may be discoverable in the object graph, + /// but not reachable for serialization (e.g. it is [JsonIgnore]'d); thus we maintain a separate cache. + /// + private readonly HashSet _typesWithMetadataGenerated = new(); + + private void GenerateTypeMetadata(TypeMetadata typeMetadata) + { + Debug.Assert(typeMetadata != null); + + if (_typesWithMetadataGenerated.Contains(typeMetadata)) + { + return; + } + + _typesWithMetadataGenerated.Add(typeMetadata); + + string metadataFileName = $"{typeMetadata.FriendlyName}.g.cs"; + + switch (typeMetadata.ClassType) + { + case ClassType.KnownType: + { + _executionContext.AddSource( + metadataFileName, + SourceText.From(GenerateForTypeWithKnownConverter(typeMetadata), Encoding.UTF8)); + } + break; + case ClassType.TypeWithDesignTimeProvidedCustomConverter: + { + _executionContext.AddSource( + metadataFileName, + SourceText.From(GenerateForTypeWithUnknownConverter(typeMetadata), Encoding.UTF8)); + } + break; + case ClassType.Nullable: + { + _executionContext.AddSource( + metadataFileName, + SourceText.From(GenerateForNullable(typeMetadata), Encoding.UTF8)); + + GenerateTypeMetadata(typeMetadata.NullableUnderlyingTypeMetadata); + } + break; + case ClassType.Enum: + { + _executionContext.AddSource( + metadataFileName, + SourceText.From(GenerateForEnum(typeMetadata), Encoding.UTF8)); + } + break; + case ClassType.Enumerable: + { + _executionContext.AddSource( + metadataFileName, + SourceText.From(GenerateForCollection(typeMetadata), Encoding.UTF8)); + + GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata); + } + break; + case ClassType.Dictionary: + { + _executionContext.AddSource( + metadataFileName, + SourceText.From(GenerateForCollection(typeMetadata), Encoding.UTF8)); + + GenerateTypeMetadata(typeMetadata.CollectionKeyTypeMetadata); + GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata); + } + break; + case ClassType.Object: + { + _executionContext.AddSource( + metadataFileName, + SourceText.From(GenerateForObject(typeMetadata), Encoding.UTF8)); + + if (typeMetadata.PropertiesMetadata != null) + { + foreach (PropertyMetadata metadata in typeMetadata.PropertiesMetadata) + { + GenerateTypeMetadata(metadata.TypeMetadata); + } + } + } + break; + case ClassType.TypeUnsupportedBySourceGen: + { + _executionContext.ReportDiagnostic( + Diagnostic.Create(TypeNotSupported, Location.None, new string[] { typeMetadata.CompilableName })); + return; + } + default: + { + throw new InvalidOperationException(); + } + } + } + + private string GenerateForTypeWithKnownConverter(TypeMetadata typeMetadata) + { + string typeCompilableName = typeMetadata.CompilableName; + string typeFriendlyName = typeMetadata.FriendlyName; + + string metadataInitSource = $@"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesClassName}.{typeFriendlyName}Converter);"; + + return GenerateForType(typeMetadata, metadataInitSource); + } + + private string GenerateForTypeWithUnknownConverter(TypeMetadata typeMetadata) + { + string typeCompilableName = typeMetadata.CompilableName; + string typeFriendlyName = typeMetadata.FriendlyName; + + StringBuilder sb = new(); + + string metadataInitSource = $@"JsonConverter converter = {typeMetadata.ConverterInstantiationLogic}; + // TODO: consider moving this verification source to common helper. + Type typeToConvert = typeof({typeCompilableName}); + if (!converter.CanConvert(typeToConvert)) + {{ + Type underlyingType = Nullable.GetUnderlyingType(typeToConvert); + if (underlyingType != null && converter.CanConvert(underlyingType)) + {{ + JsonConverter actualConverter = converter; + + if (converter is JsonConverterFactory converterFactory) + {{ + actualConverter = converterFactory.CreateConverter(underlyingType, {OptionsInstanceVariableName}); + + if (actualConverter == null || actualConverter is JsonConverterFactory) + {{ + throw new InvalidOperationException($""JsonConverterFactory '{{converter}} cannot return a 'null' or 'JsonConverterFactory' value.""); + }} + }} + + // Allow nullable handling to forward to the underlying type's converter. + converter = {JsonMetadataServicesClassName}.GetNullableConverter<{typeCompilableName}>((JsonConverter<{typeCompilableName}>)actualConverter); + }} + else + {{ + throw new InvalidOperationException($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'.""); + }} + }} + + _{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);"; + + return GenerateForType(typeMetadata, metadataInitSource); + } + + private string GenerateForNullable(TypeMetadata typeMetadata) + { + string typeCompilableName = typeMetadata.CompilableName; + string typeFriendlyName = typeMetadata.FriendlyName; + + TypeMetadata? underlyingTypeMetadata = typeMetadata.NullableUnderlyingTypeMetadata; + Debug.Assert(underlyingTypeMetadata != null); + string underlyingTypeCompilableName = underlyingTypeMetadata.CompilableName; + string underlyingTypeFriendlyName = underlyingTypeMetadata.FriendlyName; + string underlyingTypeInfoNamedArg = underlyingTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen + ? "underlyingTypeInfo: null" + : $"underlyingTypeInfo: {underlyingTypeFriendlyName}"; + + string metadataInitSource = @$"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}( + {OptionsInstanceVariableName}, + {JsonMetadataServicesClassName}.GetNullableConverter<{underlyingTypeCompilableName}>({underlyingTypeInfoNamedArg})); +"; + + return GenerateForType(typeMetadata, metadataInitSource); + } + + private string GenerateForEnum(TypeMetadata typeMetadata) + { + string typeCompilableName = typeMetadata.CompilableName; + string typeFriendlyName = typeMetadata.FriendlyName; + + string metadataInitSource = $"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, new EnumConverter<{typeCompilableName}>({OptionsInstanceVariableName}));"; + + return GenerateForType(typeMetadata, metadataInitSource); + } + + private string GenerateForCollection(TypeMetadata typeMetadata) + { + string typeCompilableName = typeMetadata.CompilableName; + string typeFriendlyName = typeMetadata.FriendlyName; + + // Key metadata + TypeMetadata? collectionKeyTypeMetadata = typeMetadata.CollectionKeyTypeMetadata; + Debug.Assert(!(typeMetadata.CollectionType == CollectionType.Dictionary && collectionKeyTypeMetadata == null)); + string? keyTypeCompilableName = collectionKeyTypeMetadata?.CompilableName; + string? keyTypeReadableName = collectionKeyTypeMetadata?.FriendlyName; + + string? keyTypeMetadataPropertyName; + if (typeMetadata.ClassType != ClassType.Dictionary) + { + keyTypeMetadataPropertyName = "null"; + } + else + { + keyTypeMetadataPropertyName = collectionKeyTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen + ? "null" + : $"this.{keyTypeReadableName}"; + } + + // Value metadata + TypeMetadata? collectionValueTypeMetadata = typeMetadata.CollectionValueTypeMetadata; + Debug.Assert(collectionValueTypeMetadata != null); + string valueTypeCompilableName = collectionValueTypeMetadata.CompilableName; + string valueTypeReadableName = collectionValueTypeMetadata.FriendlyName; + + string valueTypeMetadataPropertyName = collectionValueTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen + ? "null" + : $"this.{valueTypeReadableName}"; + + string numberHandlingArg = $"{GetNumberHandlingAsStr(typeMetadata.NumberHandling)}"; + + CollectionType collectionType = typeMetadata.CollectionType; + string collectionTypeInfoValue = collectionType switch + { + CollectionType.Array => $"{JsonMetadataServicesClassName}.CreateArrayInfo<{valueTypeCompilableName}>({OptionsInstanceVariableName}, {valueTypeMetadataPropertyName}, {numberHandlingArg})", + CollectionType.List => $"{JsonMetadataServicesClassName}.CreateListInfo<{typeCompilableName}, {valueTypeCompilableName}>({OptionsInstanceVariableName}, () => new System.Collections.Generic.List<{valueTypeCompilableName}>(), {valueTypeMetadataPropertyName}, {numberHandlingArg})", + CollectionType.Dictionary => $"{JsonMetadataServicesClassName}.CreateDictionaryInfo<{typeCompilableName}, {keyTypeCompilableName!}, {valueTypeCompilableName}>({OptionsInstanceVariableName}, () => new System.Collections.Generic.Dictionary<{keyTypeCompilableName}, {valueTypeCompilableName}>(), {keyTypeMetadataPropertyName!}, {valueTypeMetadataPropertyName}, {numberHandlingArg})", + _ => throw new NotSupportedException() + }; + + string metadataInitSource = @$"_{typeFriendlyName} = {collectionTypeInfoValue};"; + return GenerateForType(typeMetadata, metadataInitSource); + } + + private string GenerateForObject(TypeMetadata typeMetadata) + { + string typeCompilableName = typeMetadata.CompilableName; + string typeFriendlyName = typeMetadata.FriendlyName; + + string createObjectFuncTypeArg = typeMetadata.ConstructionStrategy == ObjectConstructionStrategy.ParameterlessConstructor + ? $"createObjectFunc: static () => new {typeMetadata.CompilableName}()" + : "createObjectFunc: null"; + + List? properties = typeMetadata.PropertiesMetadata; + + StringBuilder sb = new(); + + sb.Append($@"JsonTypeInfo<{typeCompilableName}> objectInfo = {JsonMetadataServicesClassName}.CreateObjectInfo<{typeCompilableName}>(); + _{typeFriendlyName} = objectInfo; +"); + + string propInitFuncVarName = $"{typeFriendlyName}{PropInitFuncVarName}"; + + sb.Append($@" + {JsonMetadataServicesClassName}.InitializeObjectInfo( + objectInfo, + {OptionsInstanceVariableName}, + {createObjectFuncTypeArg}, + {propInitFuncVarName}, + {GetNumberHandlingAsStr(typeMetadata.NumberHandling)});"); + + string metadataInitSource = sb.ToString(); + string? propInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitFuncVarName, properties); + + return GenerateForType(typeMetadata, metadataInitSource, propInitFuncSource); + } + + private string GeneratePropMetadataInitFunc( + bool declaringTypeIsValueType, + string propInitFuncVarName, + List? properties) + { + const string PropVarName = "properties"; + const string JsonContextVarName = "jsonContext"; + const string JsonPropertyInfoTypeName = "JsonPropertyInfo"; + + string propertyArrayInstantiationValue = properties == null + ? $"System.Array.Empty<{JsonPropertyInfoTypeName}>()" + : $"new {JsonPropertyInfoTypeName}[{properties.Count}]"; + + StringBuilder sb = new(); + + sb.Append($@"private static {JsonPropertyInfoTypeName}[] {propInitFuncVarName}(JsonSerializerContext context) + {{ + JsonContext {JsonContextVarName} = (JsonContext)context; + JsonSerializerOptions options = context.Options; + + {JsonPropertyInfoTypeName}[] {PropVarName} = {propertyArrayInstantiationValue}; +"); + + if (properties != null) + { + for (int i = 0; i < properties.Count; i++) + { + PropertyMetadata memberMetadata = properties[i]; + + TypeMetadata memberTypeMetadata = memberMetadata.TypeMetadata; + + string clrPropertyName = memberMetadata.ClrName; + + string declaringTypeCompilableName = memberMetadata.DeclaringTypeCompilableName; + + string memberTypeFriendlyName = memberTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen + ? "null" + : $"{JsonContextVarName}.{memberTypeMetadata.FriendlyName}"; + + string typeTypeInfoNamedArg = $"propertyTypeInfo: {memberTypeFriendlyName}"; + + string jsonPropertyNameNamedArg = memberMetadata.JsonPropertyName != null + ? @$"jsonPropertyName: ""{memberMetadata.JsonPropertyName}""" + : "jsonPropertyName: null"; + + string getterNamedArg = memberMetadata.HasGetter + ? $"getter: static (obj) => {{ return (({declaringTypeCompilableName})obj).{clrPropertyName}; }}" + : "getter: null"; + + string setterNamedArg; + if (memberMetadata.HasSetter) + { + string propMutation = declaringTypeIsValueType + ? @$"{{ Unsafe.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value; }}" + : $@"{{ (({declaringTypeCompilableName})obj).{clrPropertyName} = value; }}"; + + setterNamedArg = $"setter: static (obj, value) => {propMutation}"; + } + else + { + setterNamedArg = "setter: null"; + } + + JsonIgnoreCondition? ignoreCondition = memberMetadata.IgnoreCondition; + string ignoreConditionNamedArg = ignoreCondition.HasValue + ? $"ignoreCondition: JsonIgnoreCondition.{ignoreCondition.Value}" + : "ignoreCondition: default"; + + string encodedNameArg; + if (!ContainsNonAscii(clrPropertyName)) + { + byte[] name = Encoding.UTF8.GetBytes(memberMetadata.JsonPropertyName ?? clrPropertyName); + string nameBytes = string.Join(", ", name.Select(b => $"{b}")); + encodedNameArg = "encodedName: new byte[] {" + nameBytes + "}"; + } + else + { + encodedNameArg = "encodedName: default"; + } + + string converterNamedArg = memberMetadata.ConverterInstantiationLogic == null + ? "converter: null" + : $"converter: {memberMetadata.ConverterInstantiationLogic}"; + + string memberTypeCompilableName = memberTypeMetadata.CompilableName; + + sb.Append($@" + {PropVarName}[{i}] = {JsonMetadataServicesClassName}.CreatePropertyInfo<{memberTypeCompilableName}>( + options, + isProperty: {memberMetadata.IsProperty.ToString().ToLowerInvariant()}, + declaringType: typeof({memberMetadata.DeclaringTypeCompilableName}), + {typeTypeInfoNamedArg}, + {converterNamedArg}, + {getterNamedArg}, + {setterNamedArg}, + {ignoreConditionNamedArg}, + numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)}, + propertyName: ""{clrPropertyName}"", + {jsonPropertyNameNamedArg}, + {encodedNameArg}); + "); + } + } + + sb.Append(@$" + return {PropVarName}; + }}"); + + return sb.ToString(); + + static bool ContainsNonAscii(string str) + { + foreach (char c in str) + { + if (c > 127) + { + return true; + } + } + return false; + } + } + + private string GenerateForType(TypeMetadata typeMetadata, string metadataInitSource, string? additionalSource = null) + { + string typeCompilableName = typeMetadata.CompilableName; + string typeFriendlyName = typeMetadata.FriendlyName; + + return @$"{GetUsingStatementsString(typeMetadata)} + +namespace {_generationNamespace} +{{ + {JsonContextDeclarationSource} + {{ + private JsonTypeInfo<{typeCompilableName}> _{typeFriendlyName}; + public JsonTypeInfo<{typeCompilableName}> {typeFriendlyName} + {{ + get + {{ + if (_{typeFriendlyName} == null) + {{ + {WrapWithCheckForCustomConverterIfRequired(metadataInitSource, typeCompilableName, typeFriendlyName, GetNumberHandlingAsStr(typeMetadata.NumberHandling))} + }} + + return _{typeFriendlyName}; + }} + }}{additionalSource} + }} +}} +"; + } + + private string WrapWithCheckForCustomConverterIfRequired(string source, string typeCompilableName, string typeFriendlyName, string numberHandlingNamedArg) + { + if (!_honorRuntimeProvidedCustomConverters) + { + return source; + } + + return @$"JsonConverter customConverter; + if ({OptionsInstanceVariableName}.Converters.Count > 0 && (customConverter = {RuntimeCustomConverterFetchingMethodName}(typeof({typeCompilableName}))) != null) + {{ + _{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, customConverter); + }} + else + {{ + {source.Replace(Environment.NewLine, $"{Environment.NewLine} ")} + }}"; + } + + // Base source generation context partial class. + private string BaseJsonContextImplementation() + { + StringBuilder sb = new(); + sb.Append(@$"using System.Text.Json; +using System.Text.Json.Serialization; + +namespace {_generationNamespace} +{{ + {JsonContextDeclarationSource} + {{ + private static JsonContext s_default; + public static JsonContext Default => s_default ??= new JsonContext(new JsonSerializerOptions()); + + public JsonContext() : base(null) + {{ + }} + + public JsonContext(JsonSerializerOptions options) : base(options) + {{ + }} + + {GetFetchLogicForRuntimeSpecifiedCustomConverter()} + }} +}} +"); + + return sb.ToString(); + } + + private string GetFetchLogicForRuntimeSpecifiedCustomConverter() + { + if (!_honorRuntimeProvidedCustomConverters) + { + return ""; + } + + return @$"private JsonConverter {RuntimeCustomConverterFetchingMethodName}(System.Type type) + {{ + System.Collections.Generic.IList converters = {OptionsInstanceVariableName}.Converters; + + for (int i = 0; i < converters.Count; i++) + {{ + JsonConverter converter = converters[i]; + + if (converter.CanConvert(type)) + {{ + if (converter is JsonConverterFactory factory) + {{ + converter = factory.CreateConverter(type, {OptionsInstanceVariableName}); + if (converter == null || converter is JsonConverterFactory) + {{ + throw new System.InvalidOperationException($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance.""); + }} + }} + + return converter; + }} + }} + + return null; + }}"; + } + + private string GetGetTypeInfoImplementation() + { + StringBuilder sb = new(); + + HashSet usingStatements = new(); + + List rootTypeMetadata = new(); + + foreach (Type type in _rootSerializableTypes.Values) + { + TypeMetadata metadata = _typeMetadataCache[type]; + rootTypeMetadata.Add(metadata); + usingStatements.UnionWith(GetUsingStatements(metadata)); + } + + sb.Append(@$"{GetUsingStatementsString(usingStatements)} + +namespace {_generationNamespace} +{{ + {JsonContextDeclarationSource} + {{ + public override JsonTypeInfo GetTypeInfo(System.Type type) + {{"); + + // TODO: Make this Dictionary-lookup-based if _handledType.Count > 64. + foreach (TypeMetadata typeMetadata in rootTypeMetadata) + { + if (typeMetadata.ClassType != ClassType.TypeUnsupportedBySourceGen) + { + sb.Append($@" + if (type == typeof({typeMetadata.Type.GetUniqueCompilableTypeName()})) + {{ + return this.{typeMetadata.FriendlyName}; + }} +"); + } + } + + sb.Append(@" + return null!; + } + } +} +"); + + return sb.ToString(); + } + + private static string GetUsingStatementsString(TypeMetadata typeMetadata) + { + HashSet usingStatements = GetUsingStatements(typeMetadata); + return GetUsingStatementsString(usingStatements); + } + + private static string GetUsingStatementsString(HashSet usingStatements) + { + string[] usingsArr = usingStatements.ToArray(); + Array.Sort(usingsArr); + return string.Join("\n", usingsArr); + } + + private static HashSet GetUsingStatements(TypeMetadata typeMetadata) + { + HashSet usingStatements = new(); + + // Add library usings. + usingStatements.Add(FormatAsUsingStatement("System.Runtime.CompilerServices")); + usingStatements.Add(FormatAsUsingStatement("System.Text.Json")); + usingStatements.Add(FormatAsUsingStatement("System.Text.Json.Serialization")); + usingStatements.Add(FormatAsUsingStatement("System.Text.Json.Serialization.Metadata")); + + // Add imports to root type. + usingStatements.Add(FormatAsUsingStatement(typeMetadata.Type.Namespace)); + + switch (typeMetadata.ClassType) + { + case ClassType.Nullable: + { + AddUsingStatementsForType(typeMetadata.NullableUnderlyingTypeMetadata!); + } + break; + case ClassType.Enumerable: + { + AddUsingStatementsForType(typeMetadata.CollectionValueTypeMetadata); + } + break; + case ClassType.Dictionary: + { + AddUsingStatementsForType(typeMetadata.CollectionKeyTypeMetadata); + AddUsingStatementsForType(typeMetadata.CollectionValueTypeMetadata); + } + break; + case ClassType.Object: + { + if (typeMetadata.PropertiesMetadata != null) + { + foreach (PropertyMetadata metadata in typeMetadata.PropertiesMetadata) + { + AddUsingStatementsForType(metadata.TypeMetadata); + } + } + } + break; + default: + break; + } + + void AddUsingStatementsForType(TypeMetadata typeMetadata) + { + usingStatements.Add(FormatAsUsingStatement(typeMetadata.Type.Namespace)); + + if (typeMetadata.CollectionKeyTypeMetadata != null) + { + Debug.Assert(typeMetadata.CollectionValueTypeMetadata != null); + usingStatements.Add(FormatAsUsingStatement(typeMetadata.CollectionKeyTypeMetadata.Type.Namespace)); + } + + if (typeMetadata.CollectionValueTypeMetadata != null) + { + usingStatements.Add(FormatAsUsingStatement(typeMetadata.CollectionValueTypeMetadata.Type.Namespace)); + } + } + + return usingStatements; + } + + private static string FormatAsUsingStatement(string @namespace) => $"using {@namespace};"; + + private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) => + numberHandling.HasValue + ? $"(JsonNumberHandling){(int)numberHandling.Value}" + : "default"; + + private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>"; + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.cs b/src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.cs new file mode 100644 index 00000000000000..7428126cda4102 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.cs @@ -0,0 +1,499 @@ +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using System.Text.Json.Serialization; + +namespace System.Text.Json.SourceGeneration +{ + internal sealed partial class JsonSourceGeneratorHelper + { + private readonly Type _ienumerableType; + private readonly Type _listOfTType; + private readonly Type _dictionaryType; + + private readonly Type _booleanType; + private readonly Type _byteArrayType; + private readonly Type _charType; + private readonly Type _dateTimeType; + private readonly Type _dateTimeOffsetType; + private readonly Type _guidType; + private readonly Type _stringType; + private readonly Type _uriType; + private readonly Type _versionType; + + private readonly HashSet _numberTypes = new(); + + private readonly HashSet _knownTypes = new(); + + // Contains used JsonTypeInfo identifiers. + private readonly HashSet _usedFriendlyTypeNames = new(); + + /// + /// Type information for member types in input object graphs. + /// + private readonly Dictionary _typeMetadataCache = new(); + + /// + /// Types that were specified with System.Text.Json.Serialization.JsonSerializableAttribute. + /// + private readonly Dictionary _rootSerializableTypes; + + private readonly GeneratorExecutionContext _executionContext; + + private readonly MetadataLoadContext _metadataLoadContext; + + private const string JsonConverterAttributeFullName = "System.Text.Json.Serialization.JsonConverterAttribute"; + + public JsonSourceGeneratorHelper( + GeneratorExecutionContext executionContext, + MetadataLoadContext metadataLoadContext, + Dictionary rootSerializableTypes) + { + _generationNamespace = $"{executionContext.Compilation.AssemblyName}.JsonSourceGeneration"; + _executionContext = executionContext; + _metadataLoadContext = metadataLoadContext; + _rootSerializableTypes = rootSerializableTypes; + + _ienumerableType = metadataLoadContext.Resolve(typeof(IEnumerable)); + _listOfTType = metadataLoadContext.Resolve(typeof(List<>)); + _dictionaryType = metadataLoadContext.Resolve(typeof(Dictionary<,>)); + + _booleanType = metadataLoadContext.Resolve(typeof(bool)); + _byteArrayType = metadataLoadContext.Resolve(typeof(byte[])); + _charType = metadataLoadContext.Resolve(typeof(char)); + _dateTimeType = metadataLoadContext.Resolve(typeof(DateTime)); + _dateTimeOffsetType = metadataLoadContext.Resolve(typeof(DateTimeOffset)); + _guidType = metadataLoadContext.Resolve(typeof(Guid)); + + _stringType = metadataLoadContext.Resolve(typeof(string)); + _uriType = metadataLoadContext.Resolve(typeof(Uri)); + _versionType = metadataLoadContext.Resolve(typeof(Version)); + + PopulateKnownTypes(metadataLoadContext); + } + + public void GenerateSerializationMetadata() + { + Debug.Assert(_rootSerializableTypes != null); + Debug.Assert(_rootSerializableTypes.Count > 0); + + foreach (KeyValuePair pair in _rootSerializableTypes) + { + Type type = pair.Value; + TypeMetadata typeMetadata = GetOrAddTypeMetadata(type); + GenerateTypeMetadata(typeMetadata); + } + + // Add base default instance source. + _executionContext.AddSource("JsonContext.g.cs", SourceText.From(BaseJsonContextImplementation(), Encoding.UTF8)); + + // Add GetJsonTypeInfo override implementation. + _executionContext.AddSource("JsonContext.GetJsonTypeInfo.g.cs", SourceText.From(GetGetTypeInfoImplementation(), Encoding.UTF8)); + } + + private TypeMetadata GetOrAddTypeMetadata(Type type) + { + if (_typeMetadataCache.TryGetValue(type, out TypeMetadata? typeMetadata)) + { + return typeMetadata!; + } + + // Add metadata to cache now to prevent stack overflow when the same type is found somewhere else in the object graph. + typeMetadata = new(); + _typeMetadataCache[type] = typeMetadata; + + ClassType classType; + Type? collectionKeyType = null; + Type? collectionValueType = null; + Type? nullableUnderlyingType = null; + List? propertiesMetadata = null; + CollectionType collectionType = CollectionType.NotApplicable; + ObjectConstructionStrategy constructionStrategy = default; + JsonNumberHandling? numberHandling = null; + bool containsOnlyPrimitives = true; + + bool foundDesignTimeCustomConverter = false; + string? converterInstatiationLogic = null; + + IList attributeDataList = CustomAttributeData.GetCustomAttributes(type); + foreach (CustomAttributeData attributeData in attributeDataList) + { + Type attributeType = attributeData.AttributeType; + if (attributeType.FullName == "System.Text.Json.Serialization.JsonNumberHandlingAttribute") + { + IList ctorArgs = attributeData.ConstructorArguments; + numberHandling = (JsonNumberHandling)ctorArgs[0].Value; + continue; + } + else if (!foundDesignTimeCustomConverter && attributeType.GetCompatibleBaseClass(JsonConverterAttributeFullName) != null) + { + foundDesignTimeCustomConverter = true; + converterInstatiationLogic = GetConverterInstantiationLogic(attributeData); + } + } + + if (foundDesignTimeCustomConverter) + { + classType = converterInstatiationLogic != null + ? ClassType.TypeWithDesignTimeProvidedCustomConverter + : ClassType.TypeUnsupportedBySourceGen; + } + else if (_knownTypes.Contains(type)) + { + classType = ClassType.KnownType; + } + else if (type.IsNullableValueType(out nullableUnderlyingType)) + { + Debug.Assert(nullableUnderlyingType != null); + classType = ClassType.Nullable; + } + else if (type.IsEnum) + { + classType = ClassType.Enum; + } + else if (_ienumerableType.IsAssignableFrom(type)) + { + // Only T[], List, and Dictionary are supported. + + if (type.IsArray) + { + classType = ClassType.Enumerable; + collectionType = CollectionType.Array; + collectionValueType = type.GetElementType(); + } + else if (!type.IsGenericType) + { + classType = ClassType.TypeUnsupportedBySourceGen; + } + else + { + Type genericTypeDef = type.GetGenericTypeDefinition(); + Type[] genericTypeArgs = type.GetGenericArguments(); + + if (genericTypeDef == _listOfTType) + { + classType = ClassType.Enumerable; + collectionType = CollectionType.List; + collectionValueType = genericTypeArgs[0]; + } + else if (genericTypeDef == _dictionaryType) + { + classType = ClassType.Dictionary; + collectionType = CollectionType.Dictionary; + collectionKeyType = genericTypeArgs[0]; + collectionValueType = genericTypeArgs[1]; + } + else + { + classType = ClassType.TypeUnsupportedBySourceGen; + } + } + } + else + { + classType = ClassType.Object; + + if (type.GetConstructor(Type.EmptyTypes) != null && !type.IsAbstract && !type.IsInterface) + { + // TODO: support parameterized ctors. + constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; + } + + Dictionary? ignoredMembers = null; + + for (Type? currentType = type; currentType != null; currentType = currentType.BaseType) + { + const BindingFlags bindingFlags = + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.DeclaredOnly; + + foreach (Reflection.PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) + { + PropertyMetadata metadata = GetPropertyMetadata(propertyInfo); + + // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d. + if (propertyInfo.GetIndexParameters().Length > 0 || PropertyIsOverridenAndIgnored(metadata, ignoredMembers)) + { + continue; + } + + string key = metadata.JsonPropertyName ?? metadata.ClrName; + + if (metadata.HasGetter || metadata.HasSetter) + { + (propertiesMetadata ??= new()).Add(metadata); + } + + if (containsOnlyPrimitives && !IsPrimitive(propertyInfo.PropertyType)) + { + containsOnlyPrimitives = false; + } + } + + foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) + { + PropertyMetadata metadata = GetPropertyMetadata(fieldInfo); + + if (PropertyIsOverridenAndIgnored(metadata, ignoredMembers)) + { + continue; + } + + if (metadata.HasGetter || metadata.HasSetter) + { + (propertiesMetadata ??= new()).Add(metadata); + } + } + } + } + + string compilableName = type.GetUniqueCompilableTypeName(); + string friendlyName = type.GetFriendlyTypeName(); + + if (_usedFriendlyTypeNames.Contains(friendlyName)) + { + friendlyName = type.GetUniqueFriendlyTypeName(); + } + else + { + _usedFriendlyTypeNames.Add(friendlyName); + } + + typeMetadata.Initialize( + compilableName, + friendlyName, + type, + classType, + isValueType: type.IsValueType, + numberHandling, + propertiesMetadata, + collectionType, + collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeMetadata(collectionKeyType) : null, + collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeMetadata(collectionValueType) : null, + constructionStrategy, + nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeMetadata(nullableUnderlyingType) : null, + converterInstatiationLogic, + containsOnlyPrimitives); + + return typeMetadata; + } + + private static bool PropertyIsOverridenAndIgnored(PropertyMetadata currentMemberMetadata, Dictionary? ignoredMembers) + { + if (ignoredMembers == null || !ignoredMembers.TryGetValue(currentMemberMetadata.ClrName, out PropertyMetadata? ignoredMemberMetadata)) + { + return false; + } + + return currentMemberMetadata.TypeMetadata.Type == ignoredMemberMetadata.TypeMetadata.Type && + PropertyIsVirtual(currentMemberMetadata) && + PropertyIsVirtual(ignoredMemberMetadata); + } + + private static bool PropertyIsVirtual(PropertyMetadata? propertyMetadata) + { + return propertyMetadata != null && (propertyMetadata.GetterIsVirtual == true || propertyMetadata.SetterIsVirtual == true); + } + + private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo) + { + IList attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo); + + bool hasJsonInclude = false; + JsonIgnoreCondition? ignoreCondition = null; + JsonNumberHandling? numberHandling = null; + string? jsonPropertyName = null; + + bool foundDesignTimeCustomConverter = false; + string? converterInstantiationLogic = null; + + foreach (CustomAttributeData attributeData in attributeDataList) + { + Type attributeType = attributeData.AttributeType; + + if (!foundDesignTimeCustomConverter && attributeType.GetCompatibleBaseClass(JsonConverterAttributeFullName) != null) + { + foundDesignTimeCustomConverter = true; + converterInstantiationLogic = GetConverterInstantiationLogic(attributeData); + } + else if (attributeType.Assembly.FullName == "System.Text.Json") + { + switch (attributeData.AttributeType.FullName) + { + case "System.Text.Json.Serialization.JsonIgnoreAttribute": + { + IList namedArgs = attributeData.NamedArguments; + + if (namedArgs.Count == 0) + { + ignoreCondition = JsonIgnoreCondition.Always; + } + else if (namedArgs.Count == 1 && + namedArgs[0].MemberInfo.MemberType == MemberTypes.Property && + ((Reflection.PropertyInfo)namedArgs[0].MemberInfo).PropertyType.FullName == "System.Text.Json.Serialization.JsonIgnoreCondition") + { + ignoreCondition = (JsonIgnoreCondition)namedArgs[0].TypedValue.Value; + } + } + break; + case "System.Text.Json.Serialization.JsonIncludeAttribute": + { + hasJsonInclude = true; + } + break; + case "System.Text.Json.Serialization.JsonNumberHandlingAttribute": + { + IList ctorArgs = attributeData.ConstructorArguments; + if (ctorArgs.Count != 1) + { + throw new InvalidOperationException($"Invalid use of 'JsonNumberHandlingAttribute' detected on '{memberInfo.DeclaringType}.{memberInfo.Name}'."); + } + + numberHandling = (JsonNumberHandling)ctorArgs[0].Value; + } + break; + case "System.Text.Json.Serialization.JsonPropertyNameAttribute": + { + IList ctorArgs = attributeData.ConstructorArguments; + if (ctorArgs.Count != 1 || ctorArgs[0].ArgumentType != _stringType) + { + throw new InvalidOperationException($"Invalid use of 'JsonPropertyNameAttribute' detected on '{memberInfo.DeclaringType}.{memberInfo.Name}'."); + } + + jsonPropertyName = (string)ctorArgs[0].Value; + // Null check here is done at runtime within JsonSerializer. + } + break; + default: + break; + } + } + } + + Type memberCLRType; + bool hasGetter; + bool hasSetter; + bool getterIsVirtual = false; + bool setterIsVirtual = false; + + switch (memberInfo) + { + case Reflection.PropertyInfo propertyInfo: + { + MethodInfo setMethod = propertyInfo.SetMethod; + + memberCLRType = propertyInfo.PropertyType; + hasGetter = PropertyAccessorCanBeReferenced(propertyInfo.GetMethod, hasJsonInclude); + hasSetter = PropertyAccessorCanBeReferenced(setMethod, hasJsonInclude) && !setMethod.IsInitOnly(); + getterIsVirtual = propertyInfo.GetMethod?.IsVirtual == true; + setterIsVirtual = propertyInfo.SetMethod?.IsVirtual == true; + } + break; + case FieldInfo fieldInfo: + { + Debug.Assert(fieldInfo.IsPublic); + + memberCLRType = fieldInfo.FieldType; + hasGetter = true; + hasSetter = !fieldInfo.IsInitOnly; + } + break; + default: + throw new InvalidOperationException(); + } + + return new PropertyMetadata + { + ClrName = memberInfo.Name, + IsProperty = memberInfo.MemberType == MemberTypes.Property, + JsonPropertyName = jsonPropertyName, + HasGetter = hasGetter, + HasSetter = hasSetter, + GetterIsVirtual = getterIsVirtual, + SetterIsVirtual = setterIsVirtual, + IgnoreCondition = ignoreCondition, + NumberHandling = numberHandling, + HasJsonInclude = hasJsonInclude, + TypeMetadata = GetOrAddTypeMetadata(memberCLRType), + DeclaringTypeCompilableName = memberInfo.DeclaringType.GetUniqueCompilableTypeName(), + ConverterInstantiationLogic = converterInstantiationLogic + }; + } + + private static bool PropertyAccessorCanBeReferenced(MethodInfo? memberAccessor, bool hasJsonInclude) => + (memberAccessor != null && !memberAccessor.IsPrivate) && (memberAccessor.IsPublic || hasJsonInclude); + + private string? GetConverterInstantiationLogic(CustomAttributeData attributeData) + { + if (attributeData.AttributeType.FullName != JsonConverterAttributeFullName) + { + return null; + } + + Type converterType = new TypeWrapper((ITypeSymbol)attributeData.ConstructorArguments[0].Value, _metadataLoadContext); + + if (converterType == null || converterType.GetConstructor(Type.EmptyTypes) == null || converterType.IsNestedPrivate) + { + return null; + } + + return $"new {converterType.GetUniqueCompilableTypeName()}()"; + } + + private void PopulateNumberTypes(MetadataLoadContext metadataLoadContext) + { + Debug.Assert(_numberTypes != null); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(byte))); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(decimal))); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(double))); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(short))); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(sbyte))); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(int))); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(long))); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(float))); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(ushort))); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(uint))); + _numberTypes.Add(metadataLoadContext.Resolve(typeof(ulong))); + } + + private void PopulateKnownTypes(MetadataLoadContext metadataLoadContext) + { + PopulateNumberTypes(metadataLoadContext); + + Debug.Assert(_knownTypes != null); + Debug.Assert(_numberTypes != null); + + _knownTypes.UnionWith(_numberTypes); + _knownTypes.Add(_booleanType); + _knownTypes.Add(_byteArrayType); + _knownTypes.Add(_charType); + _knownTypes.Add(_dateTimeType); + _knownTypes.Add(_dateTimeOffsetType); + _knownTypes.Add(_guidType); + _knownTypes.Add(metadataLoadContext.Resolve(typeof(object))); + _knownTypes.Add(_stringType); + + // System.Private.Uri may not be loaded in input compilation. + if (_uriType != null) + { + _knownTypes.Add(_uriType); + } + + _knownTypes.Add(metadataLoadContext.Resolve(typeof(Version))); + } + + private bool IsPrimitive(Type type) + => _knownTypes.Contains(type) && type != _uriType && type != _versionType; + + private bool IsStringBasedType(Type type) + => type == _stringType || type == _dateTimeType || type == _dateTimeOffsetType || type == _guidType; + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ObjectConstructionStrategy.cs b/src/libraries/System.Text.Json/source_generation/src/ObjectConstructionStrategy.cs new file mode 100644 index 00000000000000..03705d27075a35 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ObjectConstructionStrategy.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Text.Json.SourceGeneration +{ + /// + /// Indicates which kind of constructor an object is to be created with. + /// + internal enum ObjectConstructionStrategy + { + /// + /// Object is abstract or an interface. + /// + NotApplicable = 0, + /// + /// Object should be created with a parameterless constructor. + /// + ParameterlessConstructor = 1, + /// + /// Object should be created with a parameterized constructor. + /// + ParameterizedConstructor = 2, + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/PropertyMetadata.cs b/src/libraries/System.Text.Json/source_generation/src/PropertyMetadata.cs new file mode 100644 index 00000000000000..68a5a408e83131 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/PropertyMetadata.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Text.Json.Serialization; + +namespace System.Text.Json.SourceGeneration +{ + [DebuggerDisplay("Name={Name}, Type={TypeMetadata}")] + internal class PropertyMetadata + { + /// + /// The CLR name of the property. + /// + public string ClrName { get; init; } + + /// + /// Is this a property or a field? + /// + public bool IsProperty { get; init; } + + /// + /// The property name specified via JsonPropertyNameAttribute, if available. + /// + public string? JsonPropertyName { get; init; } + + /// + /// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified) + /// getter that can be referenced in generated source code. + /// + public bool HasGetter { get; init; } + + /// + /// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified) + /// setter that can be referenced in generated source code. + /// + public bool HasSetter { get; init; } + + public bool GetterIsVirtual { get; init; } + + public bool SetterIsVirtual { get; init; } + + /// + /// The for the property. + /// + public JsonIgnoreCondition? IgnoreCondition { get; init; } + + /// + /// The for the property. + /// + public JsonNumberHandling? NumberHandling { get; init; } + + /// + /// Whether the property has the JsonIncludeAttribute. If so, non-public accessors can be used for (de)serialziation. + /// + public bool HasJsonInclude { get; init; } + + /// + /// Metadata for the property's type. + /// + public TypeMetadata TypeMetadata { get; init; } + + /// + /// Compilable name of the property's declaring type. + /// + public string DeclaringTypeCompilableName { get; init; } + + /// + /// Source code to instantiate design-time specified custom converter. + /// + public string? ConverterInstantiationLogic { get; init; } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/AssemblyWrapper.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/AssemblyWrapper.cs new file mode 100644 index 00000000000000..e5b105f4222ea9 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/AssemblyWrapper.cs @@ -0,0 +1,55 @@ +// 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 Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class AssemblyWrapper : Assembly + { + private readonly MetadataLoadContext _metadataLoadContext; + + public AssemblyWrapper(IAssemblySymbol assembly, MetadataLoadContext metadataLoadContext) + { + Symbol = assembly; + _metadataLoadContext = metadataLoadContext; + } + + internal IAssemblySymbol Symbol { get; } + + public override string FullName => Symbol.Identity.Name; + + public override Type[] GetExportedTypes() + { + return GetTypes(); + } + + public override Type[] GetTypes() + { + var types = new List(); + var stack = new Stack(); + stack.Push(Symbol.GlobalNamespace); + while (stack.Count > 0) + { + INamespaceSymbol current = stack.Pop(); + + foreach (INamedTypeSymbol type in current.GetTypeMembers()) + { + types.Add(type.AsType(_metadataLoadContext)); + } + + foreach (INamespaceSymbol ns in current.GetNamespaceMembers()) + { + stack.Push(ns); + } + } + return types.ToArray(); + } + + public override Type GetType(string name) + { + return Symbol.GetTypeByMetadataName(name)!.AsType(_metadataLoadContext); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ConstructorInfoWrapper.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ConstructorInfoWrapper.cs new file mode 100644 index 00000000000000..28721b5dcf93d6 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ConstructorInfoWrapper.cs @@ -0,0 +1,93 @@ +// 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.Globalization; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class ConstructorInfoWrapper : ConstructorInfo + { + private readonly IMethodSymbol _ctor; + private readonly MetadataLoadContext _metadataLoadContext; + + public ConstructorInfoWrapper(IMethodSymbol ctor, MetadataLoadContext metadataLoadContext) + { + _ctor = ctor; + _metadataLoadContext = metadataLoadContext; + } + + public override Type DeclaringType => _ctor.ContainingType.AsType(_metadataLoadContext); + + public override MethodAttributes Attributes => throw new NotImplementedException(); + + public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException(); + + public override string Name => _ctor.Name; + + public override Type ReflectedType => throw new NotImplementedException(); + + public override bool IsGenericMethod => _ctor.IsGenericMethod; + + public override Type[] GetGenericArguments() + { + var typeArguments = new List(); + foreach (ITypeSymbol t in _ctor.TypeArguments) + { + typeArguments.Add(t.AsType(_metadataLoadContext)); + } + return typeArguments.ToArray(); + } + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _ctor.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override MethodImplAttributes GetMethodImplementationFlags() + { + throw new NotImplementedException(); + } + + public override ParameterInfo[] GetParameters() + { + var parameters = new List(); + foreach (IParameterSymbol p in _ctor.Parameters) + { + parameters.Add(new ParameterInfoWrapper(p, _metadataLoadContext)); + } + return parameters.ToArray(); + } + + public override object Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/CustomAttributeDataWrapper.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/CustomAttributeDataWrapper.cs new file mode 100644 index 00000000000000..5b53e0e8dad1a4 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/CustomAttributeDataWrapper.cs @@ -0,0 +1,42 @@ +// 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.Linq; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class CustomAttributeDataWrapper : CustomAttributeData + { + public CustomAttributeDataWrapper(AttributeData a, MetadataLoadContext metadataLoadContext) + { + var namedArguments = new List(); + foreach (KeyValuePair na in a.NamedArguments) + { + var member = a.AttributeClass!.GetMembers(na.Key).First(); + + MemberInfo memberInfo = member is IPropertySymbol + ? new PropertyInfoWrapper((IPropertySymbol)member, metadataLoadContext) + : new FieldInfoWrapper((IFieldSymbol)member, metadataLoadContext); + + namedArguments.Add(new CustomAttributeNamedArgument(memberInfo, na.Value.Value)); + } + + var constructorArguments = new List(); + foreach (TypedConstant ca in a.ConstructorArguments) + { + constructorArguments.Add(new CustomAttributeTypedArgument(ca.Type.AsType(metadataLoadContext), ca.Value)); + } + Constructor = new ConstructorInfoWrapper(a.AttributeConstructor!, metadataLoadContext); + NamedArguments = namedArguments; + ConstructorArguments = constructorArguments; + } + + public override ConstructorInfo Constructor { get; } + + public override IList NamedArguments { get; } + + public override IList ConstructorArguments { get; } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/FieldInfoWrapper.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/FieldInfoWrapper.cs new file mode 100644 index 00000000000000..f32aca6fd9bc33 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/FieldInfoWrapper.cs @@ -0,0 +1,95 @@ +// 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 Microsoft.CodeAnalysis; +using System.Globalization; + +namespace System.Reflection +{ + internal class FieldInfoWrapper : FieldInfo + { + private readonly IFieldSymbol _field; + private readonly MetadataLoadContext _metadataLoadContext; + public FieldInfoWrapper(IFieldSymbol parameter, MetadataLoadContext metadataLoadContext) + { + _field = parameter; + _metadataLoadContext = metadataLoadContext; + } + + private FieldAttributes? _attributes; + + public override FieldAttributes Attributes + { + get + { + if (!_attributes.HasValue) + { + _attributes = default(FieldAttributes); + + if (_field.IsStatic) + { + _attributes |= FieldAttributes.Static; + } + + switch (_field.DeclaredAccessibility) + { + case Accessibility.Public: + _attributes |= FieldAttributes.Public; + break; + case Accessibility.Private: + _attributes |= FieldAttributes.Private; + break; + } + } + + return _attributes.Value; + } + } + + public override RuntimeFieldHandle FieldHandle => throw new NotImplementedException(); + + public override Type FieldType => _field.Type.AsType(_metadataLoadContext); + + public override Type DeclaringType => _field.ContainingType.AsType(_metadataLoadContext); + + public override string Name => _field.Name; + + public override Type ReflectedType => throw new NotImplementedException(); + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotImplementedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + + public override object GetValue(object obj) + { + throw new NotImplementedException(); + } + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _field.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + + public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MemberInfoWrapper.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MemberInfoWrapper.cs new file mode 100644 index 00000000000000..2a9283931bd29f --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MemberInfoWrapper.cs @@ -0,0 +1,53 @@ +// 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 Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class MemberInfoWrapper : MemberInfo + { + private readonly ISymbol _member; + private readonly MetadataLoadContext _metadataLoadContext; + + public MemberInfoWrapper(ISymbol member, MetadataLoadContext metadataLoadContext) + { + _member = member; + _metadataLoadContext = metadataLoadContext; + } + + public override Type DeclaringType => _member.ContainingType.AsType(_metadataLoadContext); + + public override MemberTypes MemberType => throw new NotImplementedException(); + + public override string Name => _member.Name; + + public override Type ReflectedType => throw new NotImplementedException(); + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _member.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MetadataLoadContext.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MetadataLoadContext.cs new file mode 100644 index 00000000000000..90007ef70b29bf --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MetadataLoadContext.cs @@ -0,0 +1,113 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + public class MetadataLoadContext + { + private readonly Dictionary _assemblies = new Dictionary(StringComparer.OrdinalIgnoreCase); + + private readonly Compilation _compilation; + + private IAssemblySymbol? _collectionsAssemblySymbol; + + public MetadataLoadContext(Compilation compilation) + { + _compilation = compilation; + Dictionary assemblies = compilation.References + .OfType() + .ToDictionary( + r => string.IsNullOrWhiteSpace(r.FilePath) ? + new AssemblyName(r.Display) : AssemblyName.GetAssemblyName(r.FilePath), + r => (IAssemblySymbol)compilation.GetAssemblyOrModuleSymbol(r)!); + + foreach (var item in assemblies) + { + string key = item.Key.Name; + _assemblies[key] = item.Value!; + + if (_collectionsAssemblySymbol == null && key == "System.Collections") + { + _collectionsAssemblySymbol = item.Value!; + } + } + + CoreAssembly = new AssemblyWrapper(compilation.GetTypeByMetadataName("System.Object")!.ContainingAssembly, this); + MainAssembly = new AssemblyWrapper(compilation.Assembly, this); + } + + public Type Resolve() => Resolve(typeof(T)); + + public Type? Resolve(Type type) + { + string assemblyName = type.Assembly.GetName().Name; + IAssemblySymbol assemblySymbol; + + if (assemblyName == "System.Private.CoreLib" || assemblyName == "mscorlib" || assemblyName == "System.Runtime" || assemblyName == "System.Private.Uri") + { + Type resolvedType = ResolveFromAssembly(type, CoreAssembly.Symbol); + if (resolvedType != null) + { + return resolvedType; + } + + if (_collectionsAssemblySymbol != null && typeof(IEnumerable).IsAssignableFrom(type)) + { + resolvedType = ResolveFromAssembly(type, _collectionsAssemblySymbol); + if (resolvedType != null) + { + return resolvedType; + } + } + } + + CustomAttributeData? typeForwardedFrom = type.GetCustomAttributeData(typeof(TypeForwardedFromAttribute)); + if (typeForwardedFrom != null) + { + assemblyName = typeForwardedFrom.GetConstructorArgument(0); + } + + if (!_assemblies.TryGetValue(new AssemblyName(assemblyName).Name, out assemblySymbol)) + { + return null; + } + + return ResolveFromAssembly(type, assemblySymbol); + } + + private Type? ResolveFromAssembly(Type type, IAssemblySymbol assemblySymbol) + { + if (type.IsArray) + { + var typeSymbol = assemblySymbol.GetTypeByMetadataName(type.GetElementType().FullName); + if (typeSymbol == null) + { + return null!; + } + + return _compilation.CreateArrayTypeSymbol(typeSymbol).AsType(this); + } + + // Resolve the full name + return assemblySymbol.GetTypeByMetadataName(type.FullName)!.AsType(this); + } + + private AssemblyWrapper CoreAssembly { get; } + public Assembly MainAssembly { get; } + + internal Assembly LoadFromAssemblyName(string fullName) + { + if (_assemblies.TryGetValue(new AssemblyName(fullName).Name, out var assembly)) + { + return new AssemblyWrapper(assembly, this); + } + return null!; + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MethodInfoWrapper.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MethodInfoWrapper.cs new file mode 100644 index 00000000000000..1a2acd79d4afea --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MethodInfoWrapper.cs @@ -0,0 +1,138 @@ +// 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.Data; +using System.Globalization; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class MethodInfoWrapper : MethodInfo + { + private readonly IMethodSymbol _method; + private readonly MetadataLoadContext _metadataLoadContext; + + public MethodInfoWrapper(IMethodSymbol method, MetadataLoadContext metadataLoadContext) + { + _method = method; + _metadataLoadContext = metadataLoadContext; + } + + public override ICustomAttributeProvider ReturnTypeCustomAttributes => throw new NotImplementedException(); + + private MethodAttributes? _attributes; + + public override MethodAttributes Attributes + { + get + { + if (!_attributes.HasValue) + { + _attributes = default(MethodAttributes); + + if (_method.IsAbstract) + { + _attributes |= MethodAttributes.Abstract; + } + + if (_method.IsStatic) + { + _attributes |= MethodAttributes.Static; + } + + if (_method.IsVirtual) + { + _attributes |= MethodAttributes.Virtual; + } + + switch (_method.DeclaredAccessibility) + { + case Accessibility.Public: + _attributes |= MethodAttributes.Public; + break; + case Accessibility.Private: + _attributes |= MethodAttributes.Private; + break; + } + } + + return _attributes.Value; + } + } + + public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException(); + + public override Type DeclaringType => _method.ContainingType.AsType(_metadataLoadContext); + + public override Type ReturnType => _method.ReturnType.AsType(_metadataLoadContext); + + public override string Name => _method.Name; + + public override bool IsGenericMethod => _method.IsGenericMethod; + + public bool IsInitOnly => _method.IsInitOnly; + + public override Type ReflectedType => throw new NotImplementedException(); + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _method.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + + public override MethodInfo GetBaseDefinition() + { + throw new NotImplementedException(); + } + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override Type[] GetGenericArguments() + { + var typeArguments = new List(); + foreach (ITypeSymbol t in _method.TypeArguments) + { + typeArguments.Add(t.AsType(_metadataLoadContext)); + } + return typeArguments.ToArray(); + } + + public override MethodImplAttributes GetMethodImplementationFlags() + { + throw new NotImplementedException(); + } + + public override ParameterInfo[] GetParameters() + { + var parameters = new List(); + foreach (IParameterSymbol p in _method.Parameters) + { + parameters.Add(new ParameterInfoWrapper(p, _metadataLoadContext)); + } + return parameters.ToArray(); + } + + public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ParameterInfoWrapper.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ParameterInfoWrapper.cs new file mode 100644 index 00000000000000..52da666c669024 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ParameterInfoWrapper.cs @@ -0,0 +1,35 @@ +// 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 Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + public class ParameterInfoWrapper : ParameterInfo + { + private readonly IParameterSymbol _parameter; + + private readonly MetadataLoadContext _metadataLoadContext; + + public ParameterInfoWrapper(IParameterSymbol parameter, MetadataLoadContext metadataLoadContext) + { + _parameter = parameter; + _metadataLoadContext = metadataLoadContext; + } + + public override Type ParameterType => _parameter.Type.AsType(_metadataLoadContext); + + public override string Name => _parameter.Name; + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _parameter.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/PropertyInfoWrapper.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/PropertyInfoWrapper.cs new file mode 100644 index 00000000000000..73f69ee271331b --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/PropertyInfoWrapper.cs @@ -0,0 +1,95 @@ +// 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.Globalization; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class PropertyInfoWrapper : PropertyInfo + { + private readonly IPropertySymbol _property; + private MetadataLoadContext _metadataLoadContext; + + public PropertyInfoWrapper(IPropertySymbol property, MetadataLoadContext metadataLoadContext) + { + _property = property; + _metadataLoadContext = metadataLoadContext; + } + + public override PropertyAttributes Attributes => throw new NotImplementedException(); + + public override bool CanRead => _property.GetMethod != null; + + public override bool CanWrite => _property.SetMethod != null; + + public override Type PropertyType => _property.Type.AsType(_metadataLoadContext); + + public override Type DeclaringType => _property.ContainingType.AsType(_metadataLoadContext); + + public override string Name => _property.Name; + + public override Type ReflectedType => throw new NotImplementedException(); + + public override MethodInfo[] GetAccessors(bool nonPublic) + { + throw new NotImplementedException(); + } + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _property.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + + public override MethodInfo GetGetMethod(bool nonPublic) + { + return _property.GetMethod!.AsMethodInfo(_metadataLoadContext); + } + + public override ParameterInfo[] GetIndexParameters() + { + var parameters = new List(); + foreach (IParameterSymbol p in _property.Parameters) + { + parameters.Add(new ParameterInfoWrapper(p, _metadataLoadContext)); + } + return parameters.ToArray(); + } + + public override MethodInfo GetSetMethod(bool nonPublic) + { + return _property.SetMethod!.AsMethodInfo(_metadataLoadContext); + } + + public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + + public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ReflectionExtensions.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ReflectionExtensions.cs new file mode 100644 index 00000000000000..428d78dbfd707f --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ReflectionExtensions.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Reflection; + +namespace System.Reflection +{ + internal static class ReflectionExtensions + { + public static CustomAttributeData GetCustomAttributeData(this MemberInfo memberInfo, Type type) + { + return memberInfo.CustomAttributes.FirstOrDefault(a => type.IsAssignableFrom(a.AttributeType)); + } + + public static TValue GetConstructorArgument(this CustomAttributeData customAttributeData, int index) + { + return index < customAttributeData.ConstructorArguments.Count ? (TValue)customAttributeData.ConstructorArguments[index].Value! : default!; + } + + public static bool IsInitOnly(this MethodInfo method) + { + MethodInfoWrapper? methodInfoWrapper = method as MethodInfoWrapper; + + if (methodInfoWrapper == null) + { + throw new ArgumentException("Expected a MethodInfoWrapper instance.", nameof(method)); + } + + return methodInfoWrapper.IsInitOnly; + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/RoslynExtensions.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/RoslynExtensions.cs new file mode 100644 index 00000000000000..8904adb02bca34 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/RoslynExtensions.cs @@ -0,0 +1,33 @@ +// 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 Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + public static class RoslynExtensions + { + public static Type AsType(this ITypeSymbol typeSymbol, MetadataLoadContext metadataLoadContext) + { + if (typeSymbol == null) + { + return null; + } + + return new TypeWrapper(typeSymbol, metadataLoadContext); + } + + public static MethodInfo AsMethodInfo(this IMethodSymbol methodSymbol, MetadataLoadContext metadataLoadContext) => (methodSymbol == null ? null : new MethodInfoWrapper(methodSymbol, metadataLoadContext))!; + + public static IEnumerable BaseTypes(this INamedTypeSymbol typeSymbol) + { + var t = typeSymbol; + while (t != null) + { + yield return t; + t = t.BaseType; + } + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeExtensions.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeExtensions.cs new file mode 100644 index 00000000000000..e717eacf743631 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeExtensions.cs @@ -0,0 +1,85 @@ +// 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.Text; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal static class TypeExtensions + { + public static string GetUniqueCompilableTypeName(this Type type) => GetCompilableTypeName(type, type.FullName); + + public static string GetCompilableTypeName(this Type type) => GetCompilableTypeName(type, type.Name); + + private static string GetCompilableTypeName(Type type, string name) + { + if (!type.IsGenericType) + { + return name.Replace('+', '.'); + } + + // TODO: Guard upstream against open generics. + Debug.Assert(!type.ContainsGenericParameters); + + int backTickIndex = name.IndexOf('`'); + string baseName = name.Substring(0, backTickIndex).Replace('+', '.'); + + return $"{baseName}<{string.Join(",", type.GetGenericArguments().Select(arg => GetUniqueCompilableTypeName(arg)))}>"; + } + + public static string GetUniqueFriendlyTypeName(this Type type) + { + return GetFriendlyTypeName(type.GetUniqueCompilableTypeName()); + } + + public static string GetFriendlyTypeName(this Type type) + { + return GetFriendlyTypeName(type.GetCompilableTypeName()); + } + + private static string GetFriendlyTypeName(string compilableName) + { + return compilableName.Replace(".", "").Replace("<", "").Replace(">", "").Replace(",", "").Replace("[]", "Array"); + } + + public static Type NullableOfTType { get; set; } + + public static bool IsNullableValueType(this Type type, out Type? underlyingType) + { + Debug.Assert(NullableOfTType != null); + + // TODO: log bug because Nullable.GetUnderlyingType doesn't work due to + // https://github.com/dotnet/runtimelab/blob/7472c863db6ec5ddab7f411ddb134a6e9f3c105f/src/libraries/System.Private.CoreLib/src/System/Nullable.cs#L124 + // i.e. type.GetGenericTypeDefinition() will never equal typeof(Nullable<>), as expected in that code segment. + if (type.IsGenericType && type.GetGenericTypeDefinition() == NullableOfTType) + { + underlyingType = type.GetGenericArguments()[0]; + return true; + } + + underlyingType = null; + return false; + } + + public static Type? GetCompatibleBaseClass(this Type type, string baseTypeFullName) + { + Type? baseTypeToCheck = type; + + while (baseTypeToCheck != null && baseTypeToCheck != typeof(object)) + { + if (baseTypeToCheck.FullName == baseTypeFullName) + { + return baseTypeToCheck; + } + + baseTypeToCheck = baseTypeToCheck.BaseType; + } + + return null; + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeWrapper.cs b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeWrapper.cs new file mode 100644 index 00000000000000..fd62a91ca467d9 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeWrapper.cs @@ -0,0 +1,490 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace System.Reflection +{ + internal class TypeWrapper : Type + { + private readonly ITypeSymbol _typeSymbol; + + private readonly MetadataLoadContext _metadataLoadContext; + + private INamedTypeSymbol? _namedTypeSymbol; + + private IArrayTypeSymbol? _arrayTypeSymbol; + + private Type _elementType; + + public TypeWrapper(ITypeSymbol namedTypeSymbol, MetadataLoadContext metadataLoadContext) + { + _typeSymbol = namedTypeSymbol; + _metadataLoadContext = metadataLoadContext; + _namedTypeSymbol = _typeSymbol as INamedTypeSymbol; + _arrayTypeSymbol = _typeSymbol as IArrayTypeSymbol; + } + + public override Assembly Assembly => new AssemblyWrapper(_typeSymbol.ContainingAssembly, _metadataLoadContext); + + private string? _assemblyQualifiedName; + + public override string AssemblyQualifiedName + { + get + { + if (_assemblyQualifiedName == null) + { + StringBuilder sb = new(); + + AssemblyIdentity identity = _typeSymbol.ContainingAssembly.Identity; + + sb.Append(FullName); + + sb.Append(", "); + sb.Append(identity.Name); + + sb.Append(", Version="); + sb.Append(identity.Version); + + if (string.IsNullOrWhiteSpace(identity.CultureName)) + { + sb.Append(", Culture=neutral"); + } + + sb.Append(", PublicKeyToken="); + ImmutableArray publicKeyToken = identity.PublicKeyToken; + if (publicKeyToken.Length > 0) + { + foreach (byte b in publicKeyToken) + { + sb.Append(b.ToString("x2")); + } + } + else + { + sb.Append("null"); + } + + _assemblyQualifiedName = sb.ToString(); + } + + return _assemblyQualifiedName; + } + } + + public override Type BaseType => _typeSymbol.BaseType!.AsType(_metadataLoadContext); + + private string? _fullName; + + public override string FullName + { + get + { + if (_fullName == null) + { + StringBuilder sb = new(); + + if (this.IsNullableValueType(out Type? underlyingType)) + { + sb.Append("System.Nullable`1[["); + sb.Append(underlyingType.AssemblyQualifiedName); + sb.Append("]]"); + } + else + { + sb.Append(Name); + + for (ISymbol currentSymbol = _typeSymbol.ContainingSymbol; currentSymbol != null && currentSymbol.Kind != SymbolKind.Namespace; currentSymbol = currentSymbol.ContainingSymbol) + { + sb.Insert(0, $"{currentSymbol.Name}+"); + } + + if (!string.IsNullOrWhiteSpace(Namespace)) + { + sb.Insert(0, $"{Namespace}."); + } + } + + _fullName = sb.ToString(); + } + + return _fullName; + } + } + + public override Guid GUID => Guid.Empty; + + public override Module Module => throw new NotImplementedException(); + + public override string Namespace => + IsArray ? + GetElementType().Namespace : + _typeSymbol.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining))!; + + public override Type UnderlyingSystemType => this; + + public override string Name + { + get + { + if (_arrayTypeSymbol == null) + { + return _typeSymbol.MetadataName; + } + + Type elementType = GetElementType(); + return elementType.Name + "[]"; + } + } + + private Type _enumType; + + public override bool IsEnum + { + get + { + _enumType ??= _metadataLoadContext.Resolve(typeof(Enum)); + return IsSubclassOf(_enumType); + } + } + + public override bool IsGenericType => _namedTypeSymbol?.IsGenericType == true; + + public override bool ContainsGenericParameters => _namedTypeSymbol?.IsUnboundGenericType == true; + + public override bool IsGenericTypeDefinition => base.IsGenericTypeDefinition; + + public INamespaceSymbol GetNamespaceSymbol => _typeSymbol.ContainingNamespace; + + public override Type[] GetGenericArguments() + { + var args = new List(); + foreach (ITypeSymbol item in _namedTypeSymbol.TypeArguments) + { + args.Add(item.AsType(_metadataLoadContext)); + } + return args.ToArray(); + } + + public override Type GetGenericTypeDefinition() + { + return _namedTypeSymbol.ConstructedFrom.AsType(_metadataLoadContext); + } + + public override IList GetCustomAttributesData() + { + var attributes = new List(); + foreach (AttributeData a in _typeSymbol.GetAttributes()) + { + attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext)); + } + return attributes; + } + + public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) + { + var ctors = new List(); + foreach (IMethodSymbol c in _namedTypeSymbol.Constructors) + { + ctors.Add(new ConstructorInfoWrapper(c, _metadataLoadContext)); + } + return ctors.ToArray(); + } + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override Type GetElementType() + { + _elementType ??= _arrayTypeSymbol?.ElementType.AsType(_metadataLoadContext)!; + return _elementType; + } + + public override EventInfo GetEvent(string name, BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + public override EventInfo[] GetEvents(BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + public override FieldInfo GetField(string name, BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + public override FieldInfo[] GetFields(BindingFlags bindingAttr) + { + var fields = new List(); + foreach (ISymbol item in _typeSymbol.GetMembers()) + { + // Associated Symbol checks the field is not a backingfield. + if (item is IFieldSymbol field && field.AssociatedSymbol == null && !field.IsReadOnly) + { + if ((item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public) + { + fields.Add(new FieldInfoWrapper(field, _metadataLoadContext)); + } + } + } + return fields.ToArray(); + } + + public override Type GetInterface(string name, bool ignoreCase) + { + throw new NotImplementedException(); + } + + public override Type[] GetInterfaces() + { + var interfaces = new List(); + foreach (INamedTypeSymbol i in _typeSymbol.Interfaces) + { + interfaces.Add(i.AsType(_metadataLoadContext)); + } + return interfaces.ToArray(); + } + + public override MemberInfo[] GetMembers(BindingFlags bindingAttr) + { + var members = new List(); + foreach (ISymbol m in _typeSymbol.GetMembers()) + { + members.Add(new MemberInfoWrapper(m, _metadataLoadContext)); + } + return members.ToArray(); + } + + public override MethodInfo[] GetMethods(BindingFlags bindingAttr) + { + var methods = new List(); + foreach (ISymbol m in _typeSymbol.GetMembers()) + { + // TODO: Efficiency + if (m is IMethodSymbol method && !_namedTypeSymbol.Constructors.Contains(method)) + { + methods.Add(method.AsMethodInfo(_metadataLoadContext)); + } + } + return methods.ToArray(); + } + + public override Type GetNestedType(string name, BindingFlags bindingAttr) + { + throw new NotImplementedException(); + } + + public override Type[] GetNestedTypes(BindingFlags bindingAttr) + { + var nestedTypes = new List(); + foreach (INamedTypeSymbol type in _typeSymbol.GetTypeMembers()) + { + nestedTypes.Add(type.AsType(_metadataLoadContext)); + } + return nestedTypes.ToArray(); + } + + // TODO: make sure to use bindingAttr for correctness. Current implementation assumes public and non-static. + public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) + { + var properties = new List(); + + foreach (ISymbol item in _typeSymbol.GetMembers()) + { + if (item is IPropertySymbol property) + { + if ((item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public) + { + properties.Add(new PropertyInfoWrapper(property, _metadataLoadContext)); + } + } + } + + return properties.ToArray(); + } + + public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } + + private TypeAttributes? _typeAttributes; + + protected override TypeAttributes GetAttributeFlagsImpl() + { + if (!_typeAttributes.HasValue) + { + _typeAttributes = default(TypeAttributes); + + if (_typeSymbol.IsAbstract) + { + _typeAttributes |= TypeAttributes.Abstract; + } + + if (_typeSymbol.TypeKind == TypeKind.Interface) + { + _typeAttributes |= TypeAttributes.Interface; + } + + if (_typeSymbol.ContainingType != null && _typeSymbol.DeclaredAccessibility == Accessibility.Private) + { + _typeAttributes |= TypeAttributes.NestedPrivate; + } + } + + return _typeAttributes.Value; + } + + protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) + { + foreach (ConstructorInfo constructor in GetConstructors(bindingAttr)) + { + ParameterInfo[] parameters = constructor.GetParameters(); + + if (parameters.Length == types.Length) + { + bool mismatched = false; + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i].ParameterType != types[i]) + { + mismatched = true; + break; + } + } + + if (!mismatched) + { + return constructor; + } + } + } + + return null; + } + + protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) + { + throw new NotImplementedException(); + } + + protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) + { + // TODO: peformance; caching; honor bindingAttr + foreach (PropertyInfo propertyInfo in GetProperties(bindingAttr)) + { + if (propertyInfo.Name == name) + { + return propertyInfo; + } + } + + return null!; + } + + protected override bool HasElementTypeImpl() + { + throw new NotImplementedException(); + } + + protected override bool IsArrayImpl() + { + return _arrayTypeSymbol != null; + } + + private Type _valueType; + + protected override bool IsValueTypeImpl() + { + _valueType ??= _metadataLoadContext.Resolve(typeof(ValueType)); + return IsSubclassOf(_valueType); + } + + protected override bool IsByRefImpl() + { + throw new NotImplementedException(); + } + + protected override bool IsCOMObjectImpl() + { + throw new NotImplementedException(); + } + + protected override bool IsPointerImpl() + { + throw new NotImplementedException(); + } + + protected override bool IsPrimitiveImpl() + { + throw new NotImplementedException(); + } + + public override bool IsAssignableFrom(Type c) + { + if (c is TypeWrapper tr) + { + return tr._typeSymbol.AllInterfaces.Contains(_typeSymbol, SymbolEqualityComparer.Default) || + (tr._namedTypeSymbol != null && tr._namedTypeSymbol.BaseTypes().Contains(_typeSymbol, SymbolEqualityComparer.Default)); + } + else if (_metadataLoadContext.Resolve(c) is TypeWrapper trr) + { + return trr._typeSymbol.AllInterfaces.Contains(_typeSymbol, SymbolEqualityComparer.Default) || + (trr._namedTypeSymbol != null && trr._namedTypeSymbol.BaseTypes().Contains(_typeSymbol, SymbolEqualityComparer.Default)); + } + return false; + } + +#pragma warning disable RS1024 // Compare symbols correctly + public override int GetHashCode() => _typeSymbol.GetHashCode(); +#pragma warning restore RS1024 // Compare symbols correctly + + public override bool Equals(object o) + { + if (o is TypeWrapper tw) + { + return _typeSymbol.Equals(tw._typeSymbol, SymbolEqualityComparer.Default); + } + else if (o is Type t && _metadataLoadContext.Resolve(t) is TypeWrapper tww) + { + return _typeSymbol.Equals(tww._typeSymbol, SymbolEqualityComparer.Default); + } + + return base.Equals(o); + } + + public override bool Equals(Type o) + { + if (o is TypeWrapper tw) + { + return _typeSymbol.Equals(tw._typeSymbol, SymbolEqualityComparer.Default); + } + else if (_metadataLoadContext.Resolve(o) is TypeWrapper tww) + { + return _typeSymbol.Equals(tww._typeSymbol, SymbolEqualityComparer.Default); + } + return base.Equals(o); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/source_generation/src/System.Text.Json.SourceGeneration.csproj new file mode 100644 index 00000000000000..45afe9bc389e4a --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/System.Text.Json.SourceGeneration.csproj @@ -0,0 +1,48 @@ + + + + netstandard2.0 + false + enable + + + + $(DefineConstants);BUILDING_SOURCE_GENERATOR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libraries/System.Text.Json/source_generation/src/TypeMetadata.cs b/src/libraries/System.Text.Json/source_generation/src/TypeMetadata.cs new file mode 100644 index 00000000000000..7f9ea6385f2150 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/src/TypeMetadata.cs @@ -0,0 +1,82 @@ +// 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.Text.Json.Serialization; + +namespace System.Text.Json.SourceGeneration +{ + [DebuggerDisplay("Type={Type}, ClassType={ClassType}")] + internal class TypeMetadata + { + private bool _hasBeenInitialized; + + public string CompilableName { get; private set; } + + public string FriendlyName { get; private set; } + + public Type Type { get; private set; } + + public ClassType ClassType { get; private set; } + + public bool IsValueType { get; private set; } + + public JsonNumberHandling? NumberHandling { get; private set; } + + public List? PropertiesMetadata { get; private set; } + + public CollectionType CollectionType { get; private set; } + + public TypeMetadata? CollectionKeyTypeMetadata { get; private set; } + + public TypeMetadata? CollectionValueTypeMetadata { get; private set; } + + public ObjectConstructionStrategy ConstructionStrategy { get; private set; } + + public TypeMetadata? NullableUnderlyingTypeMetadata { get; private set; } + + public string? ConverterInstantiationLogic { get; private set; } + + public bool ContainsOnlyPrimitives { get; private set; } + + public void Initialize( + string compilableName, + string friendlyName, + Type type, + ClassType classType, + bool isValueType, + JsonNumberHandling? numberHandling, + List? propertiesMetadata, + CollectionType collectionType, + TypeMetadata? collectionKeyTypeMetadata, + TypeMetadata? collectionValueTypeMetadata, + ObjectConstructionStrategy constructionStrategy, + TypeMetadata? nullableUnderlyingTypeMetadata, + string? converterInstantiationLogic, + bool containsOnlyPrimitives) + { + if (_hasBeenInitialized) + { + throw new InvalidOperationException("Type metadata has already been initialized."); + } + + _hasBeenInitialized = true; + + CompilableName = compilableName; + FriendlyName = friendlyName; + Type = type; + ClassType = classType; + IsValueType = isValueType; + NumberHandling = numberHandling; + PropertiesMetadata = propertiesMetadata; + CollectionType = collectionType; + CollectionKeyTypeMetadata = collectionKeyTypeMetadata; + CollectionValueTypeMetadata = collectionValueTypeMetadata; + ConstructionStrategy = constructionStrategy; + NullableUnderlyingTypeMetadata = nullableUnderlyingTypeMetadata; + ConverterInstantiationLogic = converterInstantiationLogic; + ContainsOnlyPrimitives = containsOnlyPrimitives; + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/source_generation/tests/JsonSourceGeneratorTests.cs new file mode 100644 index 00000000000000..440197ae55ca70 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/tests/JsonSourceGeneratorTests.cs @@ -0,0 +1,452 @@ +// 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.Linq; +using System.Text.Json.Serialization; +using System.Text.Json.SourceGeneration.Tests; +using System.Text.Json.SourceGeneration.Tests.JsonSourceGeneration; +using Xunit; + +[assembly: JsonSerializable(typeof(JsonSerializerSourceGeneratorTests.MyNestedClass))] +[assembly: JsonSerializable(typeof(JsonSerializerSourceGeneratorTests.MyNestedClass.MyNestedNestedClass))] +[assembly: JsonSerializable(typeof(object[]))] +[assembly: JsonSerializable(typeof(string))] + +namespace System.Text.Json.SourceGeneration.Tests +{ + public static class JsonSerializerSourceGeneratorTests + { + [Fact] + public static void RoundTripLocation() + { + Location expected = CreateLocation(); + + // Location is renamed to SystemTextJsonSourceGenerationTestsLocation given there + // is another type with the name Location, that the generator processed first + // (in this case due to JsonSerializableAttribute ordering). + // A warning to the user is displayed with this detail at compile time. + string json = JsonSerializer.Serialize(expected, JsonContext.Default.SystemTextJsonSourceGenerationTestsLocation); + Location obj = JsonSerializer.Deserialize(json, JsonContext.Default.SystemTextJsonSourceGenerationTestsLocation); + VerifyLocation(expected, obj); + } + + [Fact] + public static void RoundTripIndexViewModel() + { + IndexViewModel expected = CreateIndexViewModel(); + + string json = JsonSerializer.Serialize(expected, JsonContext.Default.IndexViewModel); + IndexViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Default.IndexViewModel); + + VerifyIndexViewModel(expected, obj); + } + + [Fact] + public static void RoundTripCampaignSummaryViewModel() + { + CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(expected, JsonContext.Default.CampaignSummaryViewModel); + CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Default.CampaignSummaryViewModel); + + VerifyCampaignSummaryViewModel(expected, obj); + } + + [Fact] + public static void RoundTripActiveOrUpcomingEvent() + { + ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent(); + + string json = JsonSerializer.Serialize(expected, JsonContext.Default.ActiveOrUpcomingEvent); + ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, JsonContext.Default.ActiveOrUpcomingEvent); + + VerifyActiveOrUpcomingEvent(expected, obj); + } + + [Fact] + public static void RoundTripCollectionsDictionary() + { + WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs(); + + string json = JsonSerializer.Serialize(expected, JsonContext.Default.WeatherForecastWithPOCOs); + WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, JsonContext.Default.WeatherForecastWithPOCOs); + + VerifyWeatherForecastWithPOCOs(expected, obj); + } + + [Fact] + public static void RoundTripEmptyPoco() + { + EmptyPoco expected = CreateEmptyPoco(); + + string json = JsonSerializer.Serialize(expected, JsonContext.Default.EmptyPoco); + EmptyPoco obj = JsonSerializer.Deserialize(json, JsonContext.Default.EmptyPoco); + + VerifyEmptyPoco(expected, obj); + } + + [Fact] + public static void RoundTripTypeNameClash() + { + RepeatedTypes.Location expected = CreateRepeatedLocation(); + + string json = JsonSerializer.Serialize(expected, JsonContext.Default.Location); + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, JsonContext.Default.Location); + + VerifyRepeatedLocation(expected, obj); + } + + private static Location CreateLocation() + { + return new Location + { + Id = 1234, + Address1 = "The Street Name", + Address2 = "20/11", + City = "The City", + State = "The State", + PostalCode = "abc-12", + Name = "Nonexisting", + PhoneNumber = "+0 11 222 333 44", + Country = "The Greatest" + }; + } + + private static void VerifyLocation(Location expected, Location obj) + { + Assert.Equal(expected.Address1, obj.Address1); + Assert.Equal(expected.Address2, obj.Address2); + Assert.Equal(expected.City, obj.City); + Assert.Equal(expected.State, obj.State); + Assert.Equal(expected.PostalCode, obj.PostalCode); + Assert.Equal(expected.Name, obj.Name); + Assert.Equal(expected.PhoneNumber, obj.PhoneNumber); + Assert.Equal(expected.Country, obj.Country); + } + + private static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent() + { + return new ActiveOrUpcomingEvent + { + Id = 10, + CampaignManagedOrganizerName = "Name FamiltyName", + CampaignName = "The very new campaing", + Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", + EndDate = DateTime.UtcNow.AddYears(1), + Name = "Just a name", + ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", + StartDate = DateTime.UtcNow + }; + } + + private static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, ActiveOrUpcomingEvent obj) + { + Assert.Equal(expected.CampaignManagedOrganizerName, obj.CampaignManagedOrganizerName); + Assert.Equal(expected.CampaignName, obj.CampaignName); + Assert.Equal(expected.Description, obj.Description); + Assert.Equal(expected.EndDate, obj.EndDate); + Assert.Equal(expected.Id, obj.Id); + Assert.Equal(expected.ImageUrl, obj.ImageUrl); + Assert.Equal(expected.Name, obj.Name); + Assert.Equal(expected.StartDate, obj.StartDate); + } + + private static CampaignSummaryViewModel CreateCampaignSummaryViewModel() + { + return new CampaignSummaryViewModel + { + Description = "Very nice campaing", + Headline = "The Headline", + Id = 234235, + OrganizationName = "The Company XYZ", + ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", + Title = "Promoting Open Source" + }; + } + + private static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expected, CampaignSummaryViewModel obj) + { + Assert.Equal(expected.Description, obj.Description); + Assert.Equal(expected.Headline, obj.Headline); + Assert.Equal(expected.Id, obj.Id); + Assert.Equal(expected.ImageUrl, obj.ImageUrl); + Assert.Equal(expected.OrganizationName, obj.OrganizationName); + Assert.Equal(expected.Title, obj.Title); + } + + private static IndexViewModel CreateIndexViewModel() + { + return new IndexViewModel + { + IsNewAccount = false, + FeaturedCampaign = new CampaignSummaryViewModel + { + Description = "Very nice campaing", + Headline = "The Headline", + Id = 234235, + OrganizationName = "The Company XYZ", + ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", + Title = "Promoting Open Source" + }, + ActiveOrUpcomingEvents = Enumerable.Repeat( + new ActiveOrUpcomingEvent + { + Id = 10, + CampaignManagedOrganizerName = "Name FamiltyName", + CampaignName = "The very new campaing", + Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", + EndDate = DateTime.UtcNow.AddYears(1), + Name = "Just a name", + ImageUrl = "https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", + StartDate = DateTime.UtcNow + }, + count: 20).ToList() + }; + } + + private static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel obj) + { + Assert.Equal(expected.ActiveOrUpcomingEvents.Count, obj.ActiveOrUpcomingEvents.Count); + for (int i = 0; i < expected.ActiveOrUpcomingEvents.Count; i++) + { + VerifyActiveOrUpcomingEvent(expected.ActiveOrUpcomingEvents[i], obj.ActiveOrUpcomingEvents[i]); + } + + VerifyCampaignSummaryViewModel(expected.FeaturedCampaign, obj.FeaturedCampaign); + Assert.Equal(expected.HasFeaturedCampaign, obj.HasFeaturedCampaign); + Assert.Equal(expected.IsNewAccount, obj.IsNewAccount); + } + + private static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs() + { + return new WeatherForecastWithPOCOs + { + Date = DateTime.Parse("2019-08-01T00:00:00-07:00"), + TemperatureCelsius = 25, + Summary = "Hot", + DatesAvailable = new List + { + DateTimeOffset.Parse("2019-08-01T00:00:00-07:00"), + DateTimeOffset.Parse("2019-08-02T00:00:00-07:00"), + }, + TemperatureRanges = new Dictionary { + { + "Cold", + new HighLowTemps + { + High = 20, + Low = -10, + } + }, + { + "Hot", + new HighLowTemps + { + High = 60, + Low = 20, + } + }, + }, + SummaryWords = new string[] { "Cool", "Windy", "Humid" }, + }; + } + + private static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs expected, WeatherForecastWithPOCOs obj) + { + Assert.Equal(expected.Date, obj.Date); + Assert.Equal(expected.TemperatureCelsius, obj.TemperatureCelsius); + Assert.Equal(expected.Summary, obj.Summary); + Assert.Equal(expected.DatesAvailable.Count, obj.DatesAvailable.Count); + for (int i = 0; i < expected.DatesAvailable.Count; i++) + { + Assert.Equal(expected.DatesAvailable[i], obj.DatesAvailable[i]); + } + List> expectedTemperatureRanges = expected.TemperatureRanges.OrderBy(kv => kv.Key).ToList(); + List> objTemperatureRanges = obj.TemperatureRanges.OrderBy(kv => kv.Key).ToList(); + Assert.Equal(expectedTemperatureRanges.Count, objTemperatureRanges.Count); + for (int i = 0; i < expectedTemperatureRanges.Count; i++) + { + Assert.Equal(expectedTemperatureRanges[i].Key, objTemperatureRanges[i].Key); + Assert.Equal(expectedTemperatureRanges[i].Value.Low, objTemperatureRanges[i].Value.Low); + Assert.Equal(expectedTemperatureRanges[i].Value.High, objTemperatureRanges[i].Value.High); + } + Assert.Equal(expected.SummaryWords.Length, obj.SummaryWords.Length); + for (int i = 0; i < expected.SummaryWords.Length; i++) + { + Assert.Equal(expected.SummaryWords[i], obj.SummaryWords[i]); + } + } + + private static RepeatedTypes.Location CreateRepeatedLocation() + { + return new RepeatedTypes.Location + { + FakeId = 1234, + FakeAddress1 = "The Street Name", + FakeAddress2 = "20/11", + FakeCity = "The City", + FakeState = "The State", + FakePostalCode = "abc-12", + FakeName = "Nonexisting", + FakePhoneNumber = "+0 11 222 333 44", + FakeCountry = "The Greatest" + }; + } + + private static void VerifyRepeatedLocation(RepeatedTypes.Location expected, RepeatedTypes.Location obj) + { + Assert.Equal(expected.FakeAddress1, obj.FakeAddress1); + Assert.Equal(expected.FakeAddress2, obj.FakeAddress2); + Assert.Equal(expected.FakeCity, obj.FakeCity); + Assert.Equal(expected.FakeState, obj.FakeState); + Assert.Equal(expected.FakePostalCode, obj.FakePostalCode); + Assert.Equal(expected.FakeName, obj.FakeName); + Assert.Equal(expected.FakePhoneNumber, obj.FakePhoneNumber); + Assert.Equal(expected.FakeCountry, obj.FakeCountry); + } + + private static EmptyPoco CreateEmptyPoco() => new EmptyPoco(); + + private static void VerifyEmptyPoco(EmptyPoco expected, EmptyPoco obj) + { + Assert.NotNull(expected); + Assert.NotNull(obj); + } + + [Fact] + public static void NestedSameTypeWorks() + { + MyType myType = new() { Type = new() }; + string json = JsonSerializer.Serialize(myType, JsonContext.Default.MyType); + myType = JsonSerializer.Deserialize(json, JsonContext.Default.MyType); + Assert.Equal(json, JsonSerializer.Serialize(myType, JsonContext.Default.MyType)); + + MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; + json = JsonSerializer.Serialize(myType2, JsonContext.Default.MyType2); + myType2 = JsonSerializer.Deserialize(json, JsonContext.Default.MyType2); + Assert.Equal(json, JsonSerializer.Serialize(myType2, JsonContext.Default.MyType2)); + } + + [Fact] + public static void SerializeObjectArray() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, JsonContext.Default.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, JsonContext.Default.ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), JsonContext.Default.IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), JsonContext.Default.CampaignSummaryViewModel)); + } + + [Fact] + public static void SerializeObjectArray_WithCustomOptions() + { + IndexViewModel index = CreateIndexViewModel(); + CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); + + JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + JsonContext context = new(options); + + string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, context.ObjectArray); + + JsonElement indexAsJsonElement = (JsonElement)arr[0]; + JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), context.IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), context.CampaignSummaryViewModel)); + } + + [Fact] + public static void SerializeObjectArray_SimpleTypes_WithCustomOptions() + { + JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + JsonContext context = new JsonContext(options); + + string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, context); + object[] arr = JsonSerializer.Deserialize(json, context); + + JsonElement hello = (JsonElement)arr[0]; + JsonElement world = (JsonElement)arr[1]; + Assert.Equal("\"Hello\"", hello.GetRawText()); + Assert.Equal("\"World\"", world.GetRawText()); + } + + [Fact] + public static void HandlesNestedTypes() + { + string json = @"{""MyInt"":5}"; + MyNestedClass obj = JsonSerializer.Deserialize(json, JsonContext.Default.MyNestedClass); + Assert.Equal(5, obj.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj, JsonContext.Default.MyNestedClass)); + + MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, JsonContext.Default.MyNestedNestedClass); + Assert.Equal(5, obj2.MyInt); + Assert.Equal(json, JsonSerializer.Serialize(obj2, JsonContext.Default.MyNestedNestedClass)); + } + + public class MyNestedClass + { + public int MyInt { get; set; } + + public class MyNestedNestedClass + { + public int MyInt { get; set; } + } + } + + [Fact] + public static void ConstructingFromOptionsKeepsReference() + { + JsonStringEnumConverter converter = new(); + JsonSerializerOptions options = new() + { + PropertyNameCaseInsensitive = true, + Converters = { converter } + }; + + JsonContext context = new(options); + Assert.Same(options, context.Options); + Assert.Equal(options.PropertyNameCaseInsensitive, context.Options.PropertyNameCaseInsensitive); + Assert.Same(converter, context.Options.Converters[0]); + } + + [Fact] + public static void JsonContextDefaultClonesDefaultOptions() + { + JsonContext context = JsonContext.Default; + Assert.Equal(0, context.Options.Converters.Count); + } + + [Fact] + public static void JsonContextOptionsNotMutableAfterConstruction() + { + JsonContext context = JsonContext.Default; + InvalidOperationException ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); + string exAsStr = ex.ToString(); + Assert.Contains("JsonSerializerOptions", exAsStr); + Assert.Contains("JsonSerializerContext", exAsStr); + + context = new JsonContext(new JsonSerializerOptions()); + ex = Assert.Throws(() => context.Options.PropertyNameCaseInsensitive = true); + exAsStr = ex.ToString(); + Assert.Contains("JsonSerializerOptions", exAsStr); + Assert.Contains("JsonSerializerContext", exAsStr); + } + + [Fact] + public static void ParameterizedConstructor() + { + string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), JsonContext.Default.HighLowTempsImmutable); + Assert.Contains(@"""High"":1", json); + Assert.Contains(@"""Low"":2", json); + + // Deserialization not supported for now. + Assert.Throws(() => JsonSerializer.Deserialize(json, JsonContext.Default.HighLowTempsImmutable)); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/tests/System.Text.Json.SourceGeneration.Tests.csproj b/src/libraries/System.Text.Json/source_generation/tests/System.Text.Json.SourceGeneration.Tests.csproj new file mode 100644 index 00000000000000..61e73c9b5a9192 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/tests/System.Text.Json.SourceGeneration.Tests.csproj @@ -0,0 +1,22 @@ + + + $(NetCoreAppCurrent);$(NetFrameworkCurrent) + true + 3.10.0-1.final + + + + + + + + + + + + + + + + + diff --git a/src/libraries/System.Text.Json/source_generation/tests/TestClasses.cs b/src/libraries/System.Text.Json/source_generation/tests/TestClasses.cs new file mode 100644 index 00000000000000..b5f4f84b356db5 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/tests/TestClasses.cs @@ -0,0 +1,128 @@ +// 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.Text.Json.Serialization; +using System.Text.Json.SourceGeneration.Tests; + +[assembly: JsonSerializable(typeof(System.Text.Json.SourceGeneration.Tests.RepeatedTypes.Location))] +[assembly: JsonSerializable(typeof(Location))] +// TODO: add tests for when same type is specified in ths manner. +[assembly: JsonSerializable(typeof(System.Text.Json.SourceGeneration.Tests.Location))] +[assembly: JsonSerializable(typeof(ActiveOrUpcomingEvent))] +[assembly: JsonSerializable(typeof(CampaignSummaryViewModel))] +[assembly: JsonSerializable(typeof(IndexViewModel))] +[assembly: JsonSerializable(typeof(WeatherForecastWithPOCOs))] +[assembly: JsonSerializable(typeof(EmptyPoco))] +// Ensure no errors when type of member in previously specified object graph is passed as input type to generator. +[assembly: JsonSerializable(typeof(HighLowTemps))] +[assembly: JsonSerializable(typeof(MyType))] +[assembly: JsonSerializable(typeof(MyType2))] +[assembly: JsonSerializable(typeof(MyIntermediateType))] +[assembly: JsonSerializable(typeof(HighLowTempsImmutable))] + +namespace System.Text.Json.SourceGeneration.Tests.RepeatedTypes +{ + public class Location + { + public int FakeId { get; set; } + public string FakeAddress1 { get; set; } + public string FakeAddress2 { get; set; } + public string FakeCity { get; set; } + public string FakeState { get; set; } + public string FakePostalCode { get; set; } + public string FakeName { get; set; } + public string FakePhoneNumber { get; set; } + public string FakeCountry { get; set; } + } +} + +namespace System.Text.Json.SourceGeneration.Tests +{ + public class Location + { + public int Id { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + public string City { get; set; } + public string State { get; set; } + public string PostalCode { get; set; } + public string Name { get; set; } + public string PhoneNumber { get; set; } + public string Country { get; set; } + } + + public class ActiveOrUpcomingEvent + { + public int Id { get; set; } + public string ImageUrl { get; set; } + public string Name { get; set; } + public string CampaignName { get; set; } + public string CampaignManagedOrganizerName { get; set; } + public string Description { get; set; } + public DateTimeOffset StartDate { get; set; } + public DateTimeOffset EndDate { get; set; } + } + + public class CampaignSummaryViewModel + { + public int Id { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public string ImageUrl { get; set; } + public string OrganizationName { get; set; } + public string Headline { get; set; } + } + + public class IndexViewModel + { + public List ActiveOrUpcomingEvents { get; set; } + public CampaignSummaryViewModel FeaturedCampaign { get; set; } + public bool IsNewAccount { get; set; } + public bool HasFeaturedCampaign => FeaturedCampaign != null; + } + + public class WeatherForecastWithPOCOs + { + public DateTimeOffset Date { get; set; } + public int TemperatureCelsius { get; set; } + public string Summary { get; set; } + public string SummaryField; + public List DatesAvailable { get; set; } + public Dictionary TemperatureRanges { get; set; } + public string[] SummaryWords { get; set; } + } + + public class HighLowTemps + { + public int High { get; set; } + public int Low { get; set; } + } + + public class HighLowTempsImmutable + { + public int High { get; } + public int Low { get; } + + public HighLowTempsImmutable(int high, int low) => (High, Low) = (high, low); + } + + public class EmptyPoco + { + } + + public class MyType + { + public MyType Type; + } + + public class MyType2 + { + public MyIntermediateType Type = new(); + } + + public class MyIntermediateType + { + public MyType Type = new(); + } +} diff --git a/src/libraries/System.Text.Json/source_generation/unit_tests/CompilationHelper.cs b/src/libraries/System.Text.Json/source_generation/unit_tests/CompilationHelper.cs new file mode 100644 index 00000000000000..32a436fde31cde --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/unit_tests/CompilationHelper.cs @@ -0,0 +1,198 @@ +// 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.Collections.Immutable; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace System.Text.Json.SourceGeneration.UnitTests +{ + public class CompilationHelper + { + public static Compilation CreateCompilation(string source, MetadataReference[] additionalReferences = null) + { + // Bypass System.Runtime error. + Assembly systemRuntimeAssembly = Assembly.Load("System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + Assembly systemCollectionsAssembly = Assembly.Load("System.Collections, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + string systemRuntimeAssemblyPath = systemRuntimeAssembly.Location; + string systemCollectionsAssemblyPath = systemCollectionsAssembly.Location; + + List references = new List { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(JsonSerializerOptions).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Type).Assembly.Location), + MetadataReference.CreateFromFile(typeof(KeyValuePair).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ContractNamespaceAttribute).Assembly.Location), + MetadataReference.CreateFromFile(systemRuntimeAssemblyPath), + MetadataReference.CreateFromFile(systemCollectionsAssemblyPath), + }; + + // Add additional references as needed. + if (additionalReferences != null) + { + foreach (MetadataReference reference in additionalReferences) + { + references.Add(reference); + } + } + + return CSharpCompilation.Create( + "TestAssembly", + syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source) }, + references: references.ToArray(), + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ); + } + + private static GeneratorDriver CreateDriver(Compilation compilation, params ISourceGenerator[] generators) + => CSharpGeneratorDriver.Create( + generators: ImmutableArray.Create(generators), + parseOptions: new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Parse)); + + public static Compilation RunGenerators(Compilation compilation, out ImmutableArray diagnostics, params ISourceGenerator[] generators) + { + CreateDriver(compilation, generators).RunGeneratorsAndUpdateCompilation(compilation, out Compilation outCompilation, out diagnostics); + return outCompilation; + } + + public static byte[] CreateAssemblyImage(Compilation compilation) + { + MemoryStream ms = new MemoryStream(); + var emitResult = compilation.Emit(ms); + if (!emitResult.Success) + { + throw new InvalidOperationException(); + } + return ms.ToArray(); + } + + public static Compilation CreateReferencedLocationCompilation() + { + string source = @" + namespace ReferencedAssembly + { + public class Location + { + public int Id { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + public string City { get; set; } + public string State { get; set; } + public string PostalCode { get; set; } + public string Name { get; set; } + public string PhoneNumber { get; set; } + public string Country { get; set; } + } + }"; + + return CreateCompilation(source); + } + + public static Compilation CreateCampaignSummaryViewModelCompilation() + { + string source = @" + namespace ReferencedAssembly + { + public class CampaignSummaryViewModel + { + public int Id { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public string ImageUrl { get; set; } + public string OrganizationName { get; set; } + public string Headline { get; set; } + } + }"; + + return CreateCompilation(source); + } + + public static Compilation CreateActiveOrUpcomingEventCompilation() + { + string source = @" + using System; + namespace ReferencedAssembly + { + public class ActiveOrUpcomingEvent + { + public int Id { get; set; } + public string ImageUrl { get; set; } + public string Name { get; set; } + public string CampaignName { get; set; } + public string CampaignManagedOrganizerName { get; set; } + public string Description { get; set; } + public DateTimeOffset StartDate { get; set; } + public DateTimeOffset EndDate { get; set; } + } + }"; + + return CreateCompilation(source); + } + + public static Compilation CreateReferencedHighLowTempsCompilation() + { + string source = @" + namespace ReferencedAssembly + { + public class HighLowTemps + { + public int High { get; set; } + public int Low { get; set; } + } + }"; + + return CreateCompilation(source); + } + + public static Compilation CreateRepeatedLocationsCompilation() + { + string source = @" + using System; + using System.Collections; + using System.Collections.Generic; + using System.Text.Json.Serialization; + + [assembly: JsonSerializable(typeof(Fake.Location))] + [assembly: JsonSerializable(typeof(HelloWorld.Location))] + + namespace Fake + { + public class Location + { + public int FakeId { get; set; } + public string FakeAddress1 { get; set; } + public string FakeAddress2 { get; set; } + public string FakeCity { get; set; } + public string FakeState { get; set; } + public string FakePostalCode { get; set; } + public string FakeName { get; set; } + public string FakePhoneNumber { get; set; } + public string FakeCountry { get; set; } + } + } + + namespace HelloWorld + { + public class Location + { + public int Id { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + public string City { get; set; } + public string State { get; set; } + public string PostalCode { get; set; } + public string Name { get; set; } + public string PhoneNumber { get; set; } + public string Country { get; set; } + } + }"; + + return CreateCompilation(source); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs new file mode 100644 index 00000000000000..a3b69d0355f5c4 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs @@ -0,0 +1,154 @@ +// 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.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace System.Text.Json.SourceGeneration.UnitTests +{ + public class JsonSourceGeneratorDiagnosticsTests + { + [Fact] + [ActiveIssue("Figure out issue with CampaignSummaryViewModel namespace.")] + public void SuccessfulSourceGeneration() + { + // Compile the referenced assembly first. + Compilation campaignCompilation = CompilationHelper.CreateCampaignSummaryViewModelCompilation(); + Compilation eventCompilation = CompilationHelper.CreateActiveOrUpcomingEventCompilation(); + + // Emit the image of the referenced assembly. + byte[] campaignImage = CompilationHelper.CreateAssemblyImage(campaignCompilation); + byte[] eventImage = CompilationHelper.CreateAssemblyImage(eventCompilation); + + // Main source for current compilation. + string source = @" + using System.Collections.Generic; + using System.Text.Json.Serialization; + using ReferencedAssembly; + + [assembly: JsonSerializable(typeof(JsonSourceGenerator.IndexViewModel)] + + namespace JsonSourceGenerator + { + public class IndexViewModel + { + public List ActiveOrUpcomingEvents { get; set; } + public CampaignSummaryViewModel FeaturedCampaign { get; set; } + public bool IsNewAccount { get; set; } + public bool HasFeaturedCampaign => FeaturedCampaign != null; + } + }"; + + MetadataReference[] additionalReferences = { + MetadataReference.CreateFromImage(campaignImage), + MetadataReference.CreateFromImage(eventImage), + }; + + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + + // Expected info logs. + string[] expectedInfoDiagnostics = new string[] { + "Generated serialization metadata for type System.Collections.Generic.List", + "Generated serialization metadata for type System.Int32", + "Generated serialization metadata for type System.String", + "Generated serialization metadata for type System.DateTimeOffset", + "Generated serialization metadata for type System.Boolean", + "Generated serialization metadata for type ReferencedAssembly.ActiveOrUpcomingEvent", + "Generated serialization metadata for type ReferencedAssembly.CampaignSummaryViewModel", + "Generated serialization metadata for type JsonSourceGenerator.IndexViewModel", + }; + + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, expectedInfoDiagnostics); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, new string[] { }); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, new string[] { }); + } + + [Fact] + [ActiveIssue("Figure out issue with CampaignSummaryViewModel namespace.")] + public void UnsuccessfulSourceGeneration() + { + // Compile the referenced assembly first. + Compilation campaignCompilation = CompilationHelper.CreateCampaignSummaryViewModelCompilation(); + Compilation eventCompilation = CompilationHelper.CreateActiveOrUpcomingEventCompilation(); + + // Emit the image of the referenced assembly. + byte[] campaignImage = CompilationHelper.CreateAssemblyImage(campaignCompilation); + byte[] eventImage = CompilationHelper.CreateAssemblyImage(eventCompilation); + + // Main source for current compilation. + string source = @" + using System.Collections.Generic; + using System.Text.Json.Serialization; + using ReferencedAssembly; + + [assembly: JsonSerializable(typeof(JsonSourceGenerator.IndexViewModel)] + + namespace JsonSourceGenerator + { + public class IndexViewModel + { + public ISet ActiveOrUpcomingEvents { get; set; } + public CampaignSummaryViewModel FeaturedCampaign { get; set; } + public bool IsNewAccount { get; set; } + public bool HasFeaturedCampaign => FeaturedCampaign != null; + } + }"; + + MetadataReference[] additionalReferences = { + MetadataReference.CreateFromImage(campaignImage), + MetadataReference.CreateFromImage(eventImage), + }; + + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + + // Expected success info logs. + string[] expectedInfoDiagnostics = new string[] { + "Generated serialization metadata for type JsonSourceGeneration.IndexViewModel", + "Generated serialization metadata for type System.Boolean", + "Generated serialization metadata for type ReferencedAssembly.CampaignSummaryViewModel" + }; + + // Expected warning logs. + string[] expectedWarningDiagnostics = new string[] { "Did not generate serialization metadata for type System.Collections.Generic.ISet" }; + + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, expectedInfoDiagnostics); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, new string[] { }); + } + + [Fact] + public void NameClashSourceGeneration() + { + Compilation compilation = CompilationHelper.CreateRepeatedLocationsCompilation(); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + } + + private void CheckDiagnosticMessages(ImmutableArray diagnostics, DiagnosticSeverity level, string[] expectedMessages) + { + string[] actualMessages = diagnostics.Where(diagnostic => diagnostic.Severity == level).Select(diagnostic => diagnostic.GetMessage()).ToArray(); + + // Can't depending on reflection order when generating type metadata. + Array.Sort(actualMessages); + Array.Sort(expectedMessages); + + Assert.Equal(expectedMessages, actualMessages); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorTests.cs new file mode 100644 index 00000000000000..3183872b0d46b5 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorTests.cs @@ -0,0 +1,310 @@ +// 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.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace System.Text.Json.SourceGeneration.UnitTests +{ + public class GeneratorTests + { + [Fact] + public void TypeDiscoveryPrimitivePOCO() + { + string source = @" + using System.Text.Json.Serialization; + + [assembly: JsonSerializable(typeof(HelloWorld.MyType))] + + namespace HelloWorld + { + public class MyType + { + public int PublicPropertyInt { get; set; } + public string PublicPropertyString { get; set; } + private int PrivatePropertyInt { get; set; } + private string PrivatePropertyString { get; set; } + + public double PublicDouble; + public char PublicChar; + private double PrivateDouble; + private char PrivateChar; + + public void MyMethod() { } + public void MySecondMethod() { } + + public void UsePrivates() + { + PrivateDouble = 0; + PrivateChar = ' '; + double d = PrivateDouble; + char c = PrivateChar; + } + } + }"; + + Compilation compilation = CompilationHelper.CreateCompilation(source); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray generatorDiags, generator); + + // Make sure compilation was successful. + CheckCompilationDiagnosticsErrors(generatorDiags); + CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics()); + + // Check base functionality of found types. + Assert.Equal(1, generator.SerializableTypes.Count); + Type myType= generator.SerializableTypes["HelloWorld.MyType"]; + Assert.Equal("HelloWorld.MyType", myType.FullName); + + // Check for received fields, properties and methods in created type. + string[] expectedPropertyNames = { "PublicPropertyInt", "PublicPropertyString",}; + string[] expectedFieldNames = { "PublicChar", "PublicDouble" }; + string[] expectedMethodNames = { "get_PrivatePropertyInt", "get_PrivatePropertyString", "get_PublicPropertyInt", "get_PublicPropertyString", "MyMethod", "MySecondMethod", "set_PrivatePropertyInt", "set_PrivatePropertyString", "set_PublicPropertyInt", "set_PublicPropertyString", "UsePrivates" }; + CheckFieldsPropertiesMethods("HelloWorld.MyType", ref generator, expectedFieldNames, expectedPropertyNames, expectedMethodNames); + } + + [Fact] + public void TypeDiscoveryPrimitiveExternalPOCO() + { + // Compile the referenced assembly first. + Compilation referencedCompilation = CompilationHelper.CreateReferencedLocationCompilation(); + + // Emit the image of the referenced assembly. + byte[] referencedImage = CompilationHelper.CreateAssemblyImage(referencedCompilation); + + string source = @" + using System.Text.Json.Serialization; + using ReferencedAssembly; + + [assembly: JsonSerializable(typeof(HelloWorld.MyType))] + [assembly: JsonSerializable(typeof(ReferencedAssembly.Location))] + + namespace HelloWorld + { + public class MyType + { + public int PublicPropertyInt { get; set; } + public string PublicPropertyString { get; set; } + private int PrivatePropertyInt { get; set; } + private string PrivatePropertyString { get; set; } + + public double PublicDouble; + public char PublicChar; + private double PrivateDouble; + private char PrivateChar; + + public void MyMethod() { } + public void MySecondMethod() { } + public void UsePrivates() + { + PrivateDouble = 0; + PrivateChar = ' '; + double x = PrivateDouble; + string s = PrivateChar.ToString(); + } + } + }"; + + MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) }; + + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray generatorDiags, generator); + + // Make sure compilation was successful. + CheckCompilationDiagnosticsErrors(generatorDiags); + CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics()); + + // Check base functionality of found types. + Assert.Equal(2, generator.SerializableTypes.Count); + Type myType = generator.SerializableTypes["HelloWorld.MyType"]; + Type notMyType = generator.SerializableTypes["ReferencedAssembly.Location"]; + + // Check for MyType. + Assert.Equal("HelloWorld.MyType", myType.FullName); + + // Check for received fields, properties and methods for MyType. + string[] expectedFieldNamesMyType = { "PublicChar", "PublicDouble" }; + string[] expectedPropertyNamesMyType = { "PublicPropertyInt", "PublicPropertyString" }; + string[] expectedMethodNamesMyType = { "get_PrivatePropertyInt", "get_PrivatePropertyString", "get_PublicPropertyInt", "get_PublicPropertyString", "MyMethod", "MySecondMethod", "set_PrivatePropertyInt", "set_PrivatePropertyString", "set_PublicPropertyInt", "set_PublicPropertyString", "UsePrivates" }; + CheckFieldsPropertiesMethods("HelloWorld.MyType", ref generator, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType); + + // Check for NotMyType. + Assert.Equal("ReferencedAssembly.Location", notMyType.FullName); + + // Check for received fields, properties and methods for NotMyType. + string[] expectedFieldNamesNotMyType = { }; + string[] expectedPropertyNamesNotMyType = { "Address1", "Address2", "City", "Country", "Id", "Name", "PhoneNumber", "PostalCode", "State" }; + string[] expectedMethodNamesNotMyType = { "get_Address1", "get_Address2", "get_City", "get_Country", "get_Id", "get_Name", "get_PhoneNumber", "get_PostalCode", "get_State", + "set_Address1", "set_Address2", "set_City", "set_Country", "set_Id", "set_Name", "set_PhoneNumber", "set_PostalCode", "set_State" }; + CheckFieldsPropertiesMethods("ReferencedAssembly.Location", ref generator, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType); + } + + [Fact] + public void TypeDiscoveryWithRenamedAttribute() + { + // Compile the referenced assembly first. + Compilation referencedCompilation = CompilationHelper.CreateReferencedLocationCompilation(); + + // Emit the image of the referenced assembly. + byte[] referencedImage = CompilationHelper.CreateAssemblyImage(referencedCompilation); + MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) }; + + string source = @" + using System.Text.Json.Serialization; + using ReferencedAssembly; + + using @JsonSerializable = System.Runtime.Serialization.ContractNamespaceAttribute; + using AliasedAttribute = System.Text.Json.Serialization.JsonSerializableAttribute; + + [assembly: AliasedAttribute(typeof(HelloWorld.MyType))] + [assembly: AliasedAttribute(typeof(ReferencedAssembly.Location))] + [module: @JsonSerializable(""my namespace"")] + + namespace HelloWorld + { + public class MyType + { + public int PublicPropertyInt { get; set; } + public string PublicPropertyString { get; set; } + private int PrivatePropertyInt { get; set; } + private string PrivatePropertyString { get; set; } + + public double PublicDouble; + public char PublicChar; + private double PrivateDouble; + private char PrivateChar; + + public void MyMethod() { } + public void MySecondMethod() { } + public void UsePrivates() + { + PrivateDouble = 0; + PrivateChar = ' '; + double d = PrivateDouble; + char c = PrivateChar; + } + } + }"; + + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + + // Make sure compilation was successful. + CheckCompilationDiagnosticsErrors(generatorDiags); + CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics()); + + // Check base functionality of found types. + Assert.Equal(2, generator.SerializableTypes.Count); + + // Check for MyType. + Assert.Equal("HelloWorld.MyType", generator.SerializableTypes["HelloWorld.MyType"].FullName); + + // Check for received fields, properties and methods for MyType. + string[] expectedFieldNamesMyType = { "PublicChar", "PublicDouble" }; + string[] expectedPropertyNamesMyType = { "PublicPropertyInt", "PublicPropertyString" }; + string[] expectedMethodNamesMyType = { "get_PrivatePropertyInt", "get_PrivatePropertyString", "get_PublicPropertyInt", "get_PublicPropertyString", "MyMethod", "MySecondMethod", "set_PrivatePropertyInt", "set_PrivatePropertyString", "set_PublicPropertyInt", "set_PublicPropertyString", "UsePrivates" }; + CheckFieldsPropertiesMethods("HelloWorld.MyType", ref generator, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType); + + // Check for NotMyType. + Assert.Equal("ReferencedAssembly.Location", generator.SerializableTypes["ReferencedAssembly.Location"].FullName); + + // Check for received fields, properties and methods for NotMyType. + string[] expectedFieldNamesNotMyType = { }; + string[] expectedPropertyNamesNotMyType = { "Address1", "Address2", "City", "Country", "Id", "Name", "PhoneNumber", "PostalCode", "State" }; + string[] expectedMethodNamesNotMyType = { "get_Address1", "get_Address2", "get_City", "get_Country", "get_Id", "get_Name", "get_PhoneNumber", "get_PostalCode", "get_State", + "set_Address1", "set_Address2", "set_City", "set_Country", "set_Id", "set_Name", "set_PhoneNumber", "set_PostalCode", "set_State" }; + CheckFieldsPropertiesMethods("ReferencedAssembly.Location", ref generator, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType ); + } + + // TODO: add test where there is a local invalid System.Text.Json.JsonSerializableAttribute (https://github.com/dotnet/runtimelab/issues/29) + + [Fact] + public void NameClashCompilation() + { + Compilation compilation = CompilationHelper.CreateRepeatedLocationsCompilation(); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + + // Make sure compilation was successful. + CheckCompilationDiagnosticsErrors(generatorDiags); + CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics()); + } + + [Fact] + public void CollectionDictionarySourceGeneration() + { + // Compile the referenced assembly first. + Compilation referencedCompilation = CompilationHelper.CreateReferencedHighLowTempsCompilation(); + + // Emit the image of the referenced assembly. + byte[] referencedImage = CompilationHelper.CreateAssemblyImage(referencedCompilation); + + string source = @" + using System; + using System.Collections; + using System.Collections.Generic; + using System.Text.Json.Serialization; + using ReferencedAssembly; + + [assembly: JsonSerializable(typeof(HelloWorld.WeatherForecastWithPOCOs))] + + namespace HelloWorld + { + public class WeatherForecastWithPOCOs + { + public DateTimeOffset Date { get; set; } + public int TemperatureCelsius { get; set; } + public string Summary { get; set; } + public string SummaryField; + public List DatesAvailable { get; set; } + public Dictionary TemperatureRanges { get; set; } + public string[] SummaryWords { get; set; } + } + }"; + + MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) }; + + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + + // Make sure compilation was successful. + + CheckCompilationDiagnosticsErrors(generatorDiags); + CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics()); + } + + private void CheckCompilationDiagnosticsErrors(ImmutableArray diagnostics) + { + Assert.Empty(diagnostics.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)); + } + + private void CheckFieldsPropertiesMethods(string typeName, ref JsonSourceGenerator generator, string[] expectedFields, string[] expectedProperties, string[] expectedMethods) + { + Type type = generator.SerializableTypes[typeName]; + string[] receivedFields = type.GetFields().Select(field => field.Name).OrderBy(s => s).ToArray(); + string[] receivedProperties = type.GetProperties().Select(property => property.Name).OrderBy(s => s).ToArray(); + string[] receivedMethods = type.GetMethods().Select(method => method.Name).OrderBy(s => s).ToArray(); + + Assert.Equal(expectedFields, receivedFields); + Assert.Equal(expectedProperties, receivedProperties); + Assert.Equal(expectedMethods, receivedMethods); + } + + // TODO: add test guarding against (de)serializing static classes. + } +} diff --git a/src/libraries/System.Text.Json/source_generation/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj b/src/libraries/System.Text.Json/source_generation/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj new file mode 100644 index 00000000000000..8ae212157c3618 --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj @@ -0,0 +1,27 @@ + + + $(NetCoreAppCurrent);$(NetFrameworkCurrent) + true + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libraries/System.Text.Json/source_generation/unit_tests/TypeWrapperTests.cs b/src/libraries/System.Text.Json/source_generation/unit_tests/TypeWrapperTests.cs new file mode 100644 index 00000000000000..6f16665ced954e --- /dev/null +++ b/src/libraries/System.Text.Json/source_generation/unit_tests/TypeWrapperTests.cs @@ -0,0 +1,190 @@ +// 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.Immutable; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace System.Text.Json.SourceGeneration.UnitTests +{ + public class TypeWrapperTests + { + [Fact] + public void MetadataLoadFilePathHandle() + { + // Create a MetadataReference from new code. + string referencedSource = @" + namespace ReferencedAssembly + { + public class ReferencedType + { + public int ReferencedPublicInt; + public double ReferencedPublicDouble; + } + }"; + + // Compile the referenced assembly first. + Compilation referencedCompilation = CompilationHelper.CreateCompilation(referencedSource); + + // Emit the image of the referenced assembly. + byte[] referencedImage; + using (MemoryStream ms = new MemoryStream()) + { + var emitResult = referencedCompilation.Emit(ms); + if (!emitResult.Success) + { + throw new InvalidOperationException(); + } + referencedImage = ms.ToArray(); + } + + string source = @" + using System.Text.Json.Serialization; + using ReferencedAssembly; + + [assembly: JsonSerializable(typeof(HelloWorld.MyType))] + [assembly: JsonSerializable(typeof(ReferencedAssembly.ReferencedType))] + + namespace HelloWorld + { + public class MyType + { + public void MyMethod() { } + public void MySecondMethod() { } + } + }"; + + MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) }; + + // Compilation using the referenced image should fail if out MetadataLoadContext does not handle. + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray generatorDiags, generator); + + // Make sure compilation was successful. + Assert.Empty(generatorDiags.Where(diag => diag.Severity.Equals(DiagnosticSeverity.Error))); + Assert.Empty(newCompilation.GetDiagnostics().Where(diag => diag.Severity.Equals(DiagnosticSeverity.Error))); + + // Should find both types since compilation above was successful. + Assert.Equal(2, generator.SerializableTypes.Count); + } + + [Fact] + public void CanGetAttributes() + { + string source = @" + using System; + using System.Text.Json.Serialization; + + [assembly: JsonSerializable(typeof(HelloWorld.MyType))] + + namespace HelloWorld + { + public class MyType + { + [JsonInclude] + public double PublicDouble; + + [JsonPropertyName(""PPublicDouble"")] + public char PublicChar; + + [JsonIgnore] + private double PrivateDouble; + + private char PrivateChar; + + public MyType() {{ }} + + [JsonConstructor] + public MyType(double d) {{ PrivateDouble = d; }} + + [JsonPropertyName(""TestName"")] + public int PublicPropertyInt { get; set; } + + [JsonExtensionData] + public string PublicPropertyString { get; set; } + + [JsonIgnore] + private int PrivatePropertyInt { get; set; } + + private string PrivatePropertyString { get; set; } + + [Obsolete(""Testing"", true)] + public void MyMethod() { } + + public void MySecondMethod() { } + } + }"; + + Compilation compilation = CompilationHelper.CreateCompilation(source); + + JsonSourceGenerator generator = new JsonSourceGenerator(); + + Compilation outCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray generatorDiags, generator); + + // Check base functionality of found types. + Assert.Equal(1, generator.SerializableTypes.Count); + Type foundType = generator.SerializableTypes.First().Value; + + Assert.Equal("HelloWorld.MyType", foundType.FullName); + + // Check for ConstructorInfoWrapper attribute usage. + (string, string[])[] receivedCtorsWithAttributeNames = foundType.GetConstructors().Select(ctor => (ctor.DeclaringType.FullName, ctor.GetCustomAttributesData().Cast().Select(attributeData => attributeData.AttributeType.Name).ToArray())).ToArray(); + Assert.Equal( + new (string, string[])[] { + ("HelloWorld.MyType", new string[] { }), + ("HelloWorld.MyType", new string[] { "JsonConstructorAttribute" }) + }, + receivedCtorsWithAttributeNames + ); + + // Check for MethodInfoWrapper attribute usage. + (string, string[])[] receivedMethodsWithAttributeNames = foundType.GetMethods().Select(method => (method.Name, method.GetCustomAttributesData().Cast().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray(); + Assert.Equal( + new (string, string[])[] { ("MyMethod", new string[] { "ObsoleteAttribute" }) }, + receivedMethodsWithAttributeNames + ); + + // Check for FieldInfoWrapper attribute usage. + (string, string[])[] receivedFieldsWithAttributeNames = foundType.GetFields().Select(field => (field.Name, field.GetCustomAttributesData().Cast().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray(); + Assert.Equal( + new (string, string[])[] { + ("PublicDouble", new string[] { "JsonIncludeAttribute" }), + ("PublicChar", new string[] { "JsonPropertyNameAttribute" }), + }, + receivedFieldsWithAttributeNames + ); + + // Check for PropertyInfoWrapper attribute usage. + (string, string[])[] receivedPropertyWithAttributeNames = foundType.GetProperties().Select(property => (property.Name, property.GetCustomAttributesData().Cast().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray(); + Assert.Equal( + new (string, string[])[] { + ("PublicPropertyInt", new string[] { "JsonPropertyNameAttribute" }), + ("PublicPropertyString", new string[] { "JsonExtensionDataAttribute" }), + }, + receivedPropertyWithAttributeNames + ); + + // Check for MemberInfoWrapper attribute usage. + (string, string[])[] receivedMembersWithAttributeNames = foundType.GetMembers().Select(member => (member.Name, member.GetCustomAttributesData().Cast().Select(attributeData => attributeData.AttributeType.Name).ToArray())).Where(x => x.Item2.Any()).ToArray(); + Assert.Equal( + new (string, string[])[] { + ("PublicDouble", new string[] { "JsonIncludeAttribute" }), + ("PublicChar", new string[] { "JsonPropertyNameAttribute" }), + ("PrivateDouble", new string[] { "JsonIgnoreAttribute" } ), + (".ctor", new string[] { "JsonConstructorAttribute" }), + ("PublicPropertyInt", new string[] { "JsonPropertyNameAttribute" }), + ("PublicPropertyString", new string[] { "JsonExtensionDataAttribute" }), + ("PrivatePropertyInt", new string[] { "JsonIgnoreAttribute" } ), + ("MyMethod", new string[] { "ObsoleteAttribute" }), + }, + receivedMembersWithAttributeNames + ); + } + } +} diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index ca33e77c630562..21899c4519e783 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -602,4 +602,10 @@ The generic type of the converter for property '{0}.{1}' must match with the specified converter type '{2}'. The converter must not be 'null'. - + + Built-in type converters have not been initialized. There is no converter available for type '{0}'. To root all built-in converters, use a 'JsonSerializerOptions'-based method of the 'JsonSerializer'. + + + Metadata for type '{0}' was not provided to the serializer. The serializer method used does not support reflection-based creation of serialization-related type metadata. If using source generation, ensure that all root types passed to the serializer have been indicated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically. + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index f9a96dbbadf545..887dba97065e82 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -21,6 +21,8 @@ + + @@ -168,9 +170,7 @@ - - diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs index a2a94f20607ce6..765b1513d0119a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; namespace System.Text.Json { @@ -159,5 +160,19 @@ public static void ValidateInt32MaxArrayLength(uint length) ThrowHelper.ThrowOutOfMemoryException(length); } } + + public static JsonTypeInfo GetJsonTypeInfo(JsonSerializerContext context, Type type) + { + Debug.Assert(context != null); + Debug.Assert(type != null); + + JsonTypeInfo? info = context.GetTypeInfo(type); + if (info == null) + { + ThrowHelper.ThrowInvalidOperationException_NoMetadataForType(type); + } + + return info; + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs index 642bd2302f8479..0a5cfcbfd3fdf8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs @@ -29,6 +29,8 @@ public static partial class JsonSerializer options = JsonSerializerOptions.s_defaultOptions; } + options.RootBuiltInConvertersAndTypeInfoCreator(); + var readerState = new JsonReaderState(options.GetReaderOptions()); var reader = new Utf8JsonReader(utf8Json, isFinalBlock: true, readerState); @@ -66,6 +68,8 @@ public static partial class JsonSerializer options = JsonSerializerOptions.s_defaultOptions; } + options.RootBuiltInConvertersAndTypeInfoCreator(); + var readerState = new JsonReaderState(options.GetReaderOptions()); var reader = new Utf8JsonReader(utf8Json, isFinalBlock: true, readerState); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs index fb943a7a50b042..a30dbe3ce79252 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs @@ -111,6 +111,8 @@ public static partial class JsonSerializer } options ??= JsonSerializerOptions.s_defaultOptions; + options.RootBuiltInConvertersAndTypeInfoCreator(); + return CreateAsyncEnumerableDeserializer(utf8Json, options, cancellationToken); static async IAsyncEnumerable CreateAsyncEnumerableDeserializer( @@ -154,6 +156,8 @@ static async IAsyncEnumerable CreateAsyncEnumerableDeserializer( CancellationToken cancellationToken) { options ??= JsonSerializerOptions.s_defaultOptions; + options.RootBuiltInConvertersAndTypeInfoCreator(); + var asyncState = new ReadAsyncBufferState(options.DefaultBufferSize); ReadStack readStack = default; readStack.Initialize(inputType, options, supportContinuation: true); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs index 0b90f40eca5800..d29eb4f3b761ae 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs @@ -4,6 +4,8 @@ using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; namespace System.Text.Json { @@ -166,13 +168,28 @@ public static partial class JsonSerializer private static TValue? Deserialize(ReadOnlySpan json, Type returnType, JsonSerializerOptions? options) { - const long ArrayPoolMaxSizeBeforeUsingNormalAlloc = 1024 * 1024; - if (options == null) { options = JsonSerializerOptions.s_defaultOptions; } + options.RootBuiltInConvertersAndTypeInfoCreator(); + + ReadStack state = default; + state.Initialize(returnType, options, supportContinuation: false); + + JsonConverter jsonConverter = state.Current.JsonPropertyInfo!.ConverterBase; + return Deserialize(jsonConverter, json, options, ref state); + } + + private static TValue? Deserialize( + JsonConverter jsonConverter, + ReadOnlySpan json, + JsonSerializerOptions options, + ref ReadStack state) + { + const long ArrayPoolMaxSizeBeforeUsingNormalAlloc = 1024 * 1024; + byte[]? tempArray = null; // For performance, avoid obtaining actual byte count unless memory usage is higher than the threshold. @@ -191,7 +208,7 @@ public static partial class JsonSerializer var readerState = new JsonReaderState(options.GetReaderOptions()); var reader = new Utf8JsonReader(utf8, isFinalBlock: true, readerState); - TValue? value = ReadCore(ref reader, returnType, options); + TValue? value = ReadCore(jsonConverter, ref reader, options, ref state); // The reader should have thrown if we have remaining bytes. Debug.Assert(reader.BytesConsumed == actualByteCount); @@ -207,5 +224,116 @@ public static partial class JsonSerializer } } } + + /// + /// Parse the text representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// JSON text to parse. + /// Metadata about the type to convert. + /// + /// is . + /// + /// -or- + /// + /// is . + /// + /// + /// The JSON is invalid. + /// + /// -or- + /// + /// is not compatible with the JSON. + /// + /// -or- + /// + /// There is remaining data in the string beyond a single JSON value. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// Using a is not as efficient as using the + /// UTF-8 methods since the implementation natively uses UTF-8. + /// + public static TValue? Deserialize<[DynamicallyAccessedMembers(JsonHelpers.MembersAccessedOnRead)] TValue>(string json, JsonTypeInfo jsonTypeInfo) + { + if (json == null) + { + throw new ArgumentNullException(nameof(json)); + } + + // null check for jsonTypeInfo occurs here. + return DeserializeUsingMetadata(json, jsonTypeInfo); + } + + /// + /// Parse the text representing a single JSON value into a . + /// + /// A representation of the JSON value. + /// JSON text to parse. + /// A metadata provider for serializable types. + /// + /// is . + /// + /// -or- + /// + /// is . + /// + /// + /// The JSON is invalid. + /// + /// -or- + /// + /// is not compatible with the JSON. + /// + /// -or- + /// + /// There is remaining data in the string beyond a single JSON value. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// The method of the provided + /// returns for the type to convert. + /// + /// Using a is not as efficient as using the + /// UTF-8 methods since the implementation natively uses UTF-8. + /// + public static TValue? Deserialize<[DynamicallyAccessedMembers(JsonHelpers.MembersAccessedOnRead)] TValue>(string json, JsonSerializerContext jsonSerializerContext) + { + if (json == null) + { + throw new ArgumentNullException(nameof(json)); + } + + if (jsonSerializerContext == null) + { + throw new ArgumentNullException(nameof(jsonSerializerContext)); + } + + return DeserializeUsingMetadata( + json, + JsonHelpers.GetJsonTypeInfo(jsonSerializerContext, typeof(TValue))); + } + + private static TValue? DeserializeUsingMetadata(string json, JsonTypeInfo? jsonTypeInfo) + { + // TODO: this would be when to fallback to regular warm-up code-paths. + // For validation during development, we don't expect this to be null. + if (jsonTypeInfo == null) + { + throw new ArgumentNullException(nameof(jsonTypeInfo)); + } + + ReadStack state = default; + state.Initialize(jsonTypeInfo); + + return Deserialize( + jsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase, + json.AsSpan(), + jsonTypeInfo.Options, + ref state); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Utf8JsonReader.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Utf8JsonReader.cs index 01e2ece54d64f8..66b1d14d69bcb7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Utf8JsonReader.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Utf8JsonReader.cs @@ -59,6 +59,8 @@ public static partial class JsonSerializer options = JsonSerializerOptions.s_defaultOptions; } + options.RootBuiltInConvertersAndTypeInfoCreator(); + ReadStack state = default; state.Initialize(typeof(TValue), options, supportContinuation: false); @@ -122,6 +124,8 @@ public static partial class JsonSerializer options = JsonSerializerOptions.s_defaultOptions; } + options.RootBuiltInConvertersAndTypeInfoCreator(); + ReadStack state = default; state.Initialize(returnType, options, supportContinuation: false); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs index 0508fbeafcbb9a..0398dda5b52223 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs @@ -66,6 +66,8 @@ private static byte[] WriteCoreBytes(in TValue value, Type inputType, Js options = JsonSerializerOptions.s_defaultOptions; } + options.RootBuiltInConvertersAndTypeInfoCreator(); + using (var output = new PooledByteBufferWriter(options.DefaultBufferSize)) { using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs index 2817248c455ec8..e555cdde3395c8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs @@ -103,6 +103,8 @@ private static async Task WriteAsyncCore( options = JsonSerializerOptions.s_defaultOptions; } + options.RootBuiltInConvertersAndTypeInfoCreator(); + JsonWriterOptions writerOptions = options.GetWriterOptions(); using (var bufferWriter = new PooledByteBufferWriter(options.DefaultBufferSize)) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs index 6c6d84f505b516..1943ea52253f5a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; namespace System.Text.Json { @@ -69,6 +71,8 @@ private static string Serialize(in TValue value, Type inputType, JsonSer options = JsonSerializerOptions.s_defaultOptions; } + options.RootBuiltInConvertersAndTypeInfoCreator(); + using (var output = new PooledByteBufferWriter(options.DefaultBufferSize)) { using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) @@ -79,5 +83,85 @@ private static string Serialize(in TValue value, Type inputType, JsonSer return JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span); } } + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// Metadata about the type to convert. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// is . + /// + /// Using a is not as efficient as using UTF-8 + /// encoding since the implementation internally uses UTF-8. See also + /// and . + /// + public static string Serialize<[DynamicallyAccessedMembers(MembersAccessedOnWrite)] TValue>(TValue value, JsonTypeInfo jsonTypeInfo) + { + return SerializeUsingMetadata(value, jsonTypeInfo); + } + + /// + /// Convert the provided value into a . + /// + /// A representation of the value. + /// The value to convert. + /// A metadata provider for serializable types. + /// + /// There is no compatible + /// for or its serializable members. + /// + /// + /// The method of the provided + /// returns for the type to convert. + /// + /// Using a is not as efficient as using UTF-8 + /// encoding since the implementation internally uses UTF-8. See also + /// and . + /// + public static string Serialize<[DynamicallyAccessedMembers(MembersAccessedOnWrite)] TValue>(TValue value, JsonSerializerContext jsonSerializerContext) + { + if (jsonSerializerContext == null) + { + throw new ArgumentNullException(nameof(jsonSerializerContext)); + } + + Type type = typeof(TValue) == typeof(object) && value != null + ? value.GetType() + : typeof(TValue); + + return SerializeUsingMetadata( + value, + JsonHelpers.GetJsonTypeInfo(jsonSerializerContext, type)); + } + + private static string SerializeUsingMetadata(in TValue value, JsonTypeInfo? jsonTypeInfo) + { + if (jsonTypeInfo == null) + { + throw new ArgumentNullException(nameof(jsonTypeInfo)); + } + + JsonSerializerOptions options = jsonTypeInfo.Options; + + WriteStack state = default; + state.Initialize(jsonTypeInfo, options, supportContinuation: false); + + using (var output = new PooledByteBufferWriter(options.DefaultBufferSize)) + { + using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) + { + JsonConverter? jsonConverter = jsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase; + WriteCore(jsonConverter, writer, value, options, ref state); + } + + return JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span); + } + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs index 7b13213ba6afde..6ffa8c6d329971 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs @@ -73,6 +73,8 @@ private static void Serialize(Utf8JsonWriter writer, in TValue value, Ty options = JsonSerializerOptions.s_defaultOptions; } + options.RootBuiltInConvertersAndTypeInfoCreator(); + if (writer == null) { throw new ArgumentNullException(nameof(writer)); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs index 6c508b4b2b55b3..228f8b28eb1be6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs @@ -17,28 +17,36 @@ namespace System.Text.Json public sealed partial class JsonSerializerOptions { // The global list of built-in simple converters. - private static readonly Dictionary s_defaultSimpleConverters = GetDefaultSimpleConverters(); + private static Dictionary? s_defaultSimpleConverters; // The global list of built-in converters that override CanConvert(). - private static readonly JsonConverter[] s_defaultFactoryConverters = new JsonConverter[] - { - // Check for disallowed types. - new DisallowedTypeConverterFactory(), - // Nullable converter should always be next since it forwards to any nullable type. - new NullableConverterFactory(), - new EnumConverterFactory(), - new JsonNodeConverterFactory(), - // IAsyncEnumerable takes precedence over IEnumerable. - new IAsyncEnumerableConverterFactory(), - // IEnumerable should always be second to last since they can convert any IEnumerable. - new IEnumerableConverterFactory(), - // Object should always be last since it converts any type. - new ObjectConverterFactory() - }; + private static JsonConverter[]? s_defaultFactoryConverters; // The cached converters (custom or built-in). private readonly ConcurrentDictionary _converters = new ConcurrentDictionary(); + internal void RootBuiltInConvertersAndTypeInfoCreator() + { + s_defaultSimpleConverters ??= GetDefaultSimpleConverters(); + s_defaultFactoryConverters ??= new JsonConverter[] + { + // Check for disallowed types. + new DisallowedTypeConverterFactory(), + // Nullable converter should always be next since it forwards to any nullable type. + new NullableConverterFactory(), + new EnumConverterFactory(), + new JsonNodeConverterFactory(), + // IAsyncEnumerable takes precedence over IEnumerable. + new IAsyncEnumerableConverterFactory(), + // IEnumerable should always be second to last since they can convert any IEnumerable. + new IEnumerableConverterFactory(), + // Object should always be last since it converts any type. + new ObjectConverterFactory() + }; + + _typeInfoCreationFunc = static (type, options) => new JsonTypeInfo(type, options); + } + private static Dictionary GetDefaultSimpleConverters() { const int NumberOfSimpleConverters = 22; @@ -154,6 +162,9 @@ public JsonConverter GetConverter(Type typeToConvert) return converter; } + // Priority 1: If there is a JsonSerializerContext, fetch the converter from there. + converter = _context?.GetTypeInfo(typeToConvert)?.PropertyInfoForTypeInfo!.ConverterBase; + // Priority 2: Attempt to get custom converter added at runtime. // Currently there is not a way at runtime to override the [JsonConverter] when applied to a property. foreach (JsonConverter item in Converters) @@ -180,6 +191,17 @@ public JsonConverter GetConverter(Type typeToConvert) // Priority 4: Attempt to get built-in converter. if (converter == null) { + if (s_defaultSimpleConverters == null || s_defaultFactoryConverters == null) + { + // (De)serialization using serializer's options-based methods has not yet occurred, so the built-in converters are not rooted. + // Even though source-gen code paths do not call this method , we do not root all the + // built-in converters here since we fetch converters for any type included for source generation from the binded context (Priority 1). + Debug.Assert(s_defaultSimpleConverters == null); + Debug.Assert(s_defaultFactoryConverters == null); + ThrowHelper.ThrowNotSupportedException_BuiltInConvertersNotRooted(typeToConvert); + return null!; + } + if (s_defaultSimpleConverters.TryGetValue(typeToConvert, out JsonConverter? foundConverter)) { Debug.Assert(foundConverter != null); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index a7e9bff84a55d7..769f9621cce7c3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -27,6 +27,10 @@ public sealed partial class JsonSerializerOptions // Although this may be written by multiple threads, 'volatile' was not added since any local affinity is fine. private JsonTypeInfo? _lastClass { get; set; } + internal JsonSerializerContext? _context; + + private Func? _typeInfoCreationFunc = null!; + // For any new option added, adding it to the options copied in the copy constructor below must be considered. private MemberAccessor? _memberAccessorStrategy; @@ -50,8 +54,6 @@ public sealed partial class JsonSerializerOptions private bool _propertyNameCaseInsensitive; private bool _writeIndented; - internal JsonSerializerContext? _context; - /// /// Constructs a new instance. /// @@ -575,12 +577,29 @@ internal JsonTypeInfo GetOrAddClass(Type type) // https://github.com/dotnet/runtime/issues/32357 if (!_classes.TryGetValue(type, out JsonTypeInfo? result)) { - result = _classes.GetOrAdd(type, new JsonTypeInfo(type, this)); + result = _classes.GetOrAdd(type, GetClassFromContextOrCreate(type)); } return result; } + internal JsonTypeInfo GetClassFromContextOrCreate(Type type) + { + JsonTypeInfo? info = _context?.GetTypeInfo(type); + if (info != null) + { + return info; + } + + if (_typeInfoCreationFunc == null) + { + ThrowHelper.ThrowNotSupportedException_NoMetadataForType(type); + return null!; + } + + return _typeInfoCreationFunc(type, this); + } + /// /// Return the TypeInfo for root API calls. /// This has a LRU cache that is intended only for public API calls that specify the root type. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs index c25991a1d1b165..72afc850d35741 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs @@ -150,9 +150,22 @@ public static JsonConverter GetEnumConverter(JsonSerializerOptions options /// Creates a instance that converts values. /// /// - /// + /// /// - public static JsonConverter GetNullableConverter(JsonConverter underlyingTypeconverter) where T : struct - => new NullableConverter(underlyingTypeconverter ?? throw new ArgumentNullException(nameof(underlyingTypeconverter))); + public static JsonConverter GetNullableConverter(JsonTypeInfo underlyingTypeInfo) where T : struct + { + if (underlyingTypeInfo == null) + { + throw new ArgumentNullException(nameof(underlyingTypeInfo)); + } + + JsonConverter? underlyingConverter = underlyingTypeInfo.PropertyInfoForTypeInfo?.ConverterBase as JsonConverter; + if (underlyingConverter == null) + { + throw new InvalidOperationException(SR.Format(SR.SerializationConverterNotCompatible, underlyingConverter, typeof(T))); + } + + return new NullableConverter(underlyingConverter); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs index 59a497e94de548..22757dbcbdcc3a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs @@ -28,7 +28,8 @@ public static JsonPropertyInfo CreatePropertyInfo( JsonIgnoreCondition ignoreCondition, JsonNumberHandling numberHandling, string propertyName, - JsonEncodedText jsonPropertyName) + string? jsonPropertyName, + byte[]? encodedName) { if (options == null) { @@ -71,7 +72,8 @@ public static JsonPropertyInfo CreatePropertyInfo( ignoreCondition, numberHandling, propertyName, - jsonPropertyName); + jsonPropertyName, + encodedName); return jsonPropertyInfo; } @@ -131,14 +133,23 @@ public static void InitializeObjectInfo( /// Creates metadata for a primitive or a type with a custom converter. /// /// The generic type definition. - /// The generic type definition. /// A instance representing the type. - public static JsonTypeInfo CreateValueInfo(JsonSerializerOptions options, JsonConverter converter) - where TConverterReturn : T + public static JsonTypeInfo CreateValueInfo(JsonSerializerOptions options, JsonConverter converter) { JsonTypeInfo info = new JsonTypeInfoInternal(options); - info.PropertyInfoForTypeInfo = JsonPropertyInfo.CreateForSourceGenTypeInfo(typeof(T), runtimeTypeInfo: info, converter, options); + info.PropertyInfoForTypeInfo = CreateJsonPropertyInfoForClassInfo(typeof(T), info, converter, options); return info; } + + internal static JsonPropertyInfo CreateJsonPropertyInfoForClassInfo( + Type type, + JsonTypeInfo typeInfo, + JsonConverter converter, + JsonSerializerOptions options) + { + JsonPropertyInfo propertyInfo = converter.CreateJsonPropertyInfo(); + propertyInfo.InitializeForTypeInfo(type, typeInfo, converter, options); + return propertyInfo; + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs index 5b955752d6e9a0..35c62715819cea 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs @@ -203,7 +203,7 @@ internal void DetermineIgnoreCondition(JsonIgnoreCondition? ignoreCondition) internal void DetermineNumberHandlingForTypeInfo(JsonNumberHandling? numberHandling) { - if (numberHandling != null && !ConverterBase.IsInternalConverter) + if (numberHandling != null && numberHandling != JsonNumberHandling.Strict && !ConverterBase.IsInternalConverter) { ThrowHelper.ThrowInvalidOperationException_NumberHandlingOnPropertyInvalid(this); } @@ -243,7 +243,7 @@ internal void DetermineNumberHandlingForProperty( NumberHandling = handling; } - else if (propertyNumberHandling.HasValue) + else if (propertyNumberHandling.HasValue && propertyNumberHandling != JsonNumberHandling.Strict) { ThrowHelper.ThrowInvalidOperationException_NumberHandlingOnPropertyInvalid(this); } @@ -322,6 +322,12 @@ internal virtual void Initialize( Options = options; } + internal abstract void InitializeForTypeInfo( + Type declaredType, + JsonTypeInfo runtimeTypeInfo, + JsonConverter converter, + JsonSerializerOptions options); + internal bool IgnoreDefaultValuesOnRead { get; private set; } internal bool IgnoreDefaultValuesOnWrite { get; private set; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs index 3b0d3ee408b07a..1270d1a119c904 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs @@ -124,32 +124,30 @@ internal void InitializeForSourceGen( JsonIgnoreCondition ignoreCondition, JsonNumberHandling numberHandling, string propertyName, - JsonEncodedText jsonPropertyName) + string? jsonPropertyName, + byte[]? encodedName) { Options = options; ClrName = propertyName; - byte[] encodedName = jsonPropertyName._utf8Value; - string encodedNameAsStr = jsonPropertyName._value; - // Property name settings. if (encodedName != null && options.PropertyNamingPolicy == null && options.Encoder == null) { - NameAsString = encodedNameAsStr; + NameAsString = jsonPropertyName ?? propertyName; NameAsUtf8Bytes = encodedName; - int nameLength = encodedName.Length; - EscapedNameSection = new byte[nameLength + 3]; + int nameSectionLength = encodedName.Length + 3; + EscapedNameSection = new byte[nameSectionLength]; EscapedNameSection[0] = (byte)'"'; encodedName.CopyTo(EscapedNameSection, 1); - EscapedNameSection[nameLength - 2] = (byte)'"'; - EscapedNameSection[nameLength - 1] = (byte)':'; + EscapedNameSection[nameSectionLength - 2] = (byte)'"'; + EscapedNameSection[nameSectionLength - 1] = (byte)':'; } else { - if (encodedNameAsStr != null) + if (jsonPropertyName != null) { - NameAsString = encodedNameAsStr; + NameAsString = jsonPropertyName; } else if (options.PropertyNamingPolicy == null) { @@ -203,27 +201,25 @@ internal void InitializeForSourceGen( /// Create a for a given Type. /// See . /// - internal static JsonPropertyInfo CreateForSourceGenTypeInfo( - Type declaredPropertyType, + internal override void InitializeForTypeInfo( + Type declaredType, JsonTypeInfo runtimeTypeInfo, JsonConverter converter, JsonSerializerOptions options) { - JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfo(); - jsonPropertyInfo.DeclaredPropertyType = declaredPropertyType; - jsonPropertyInfo.RuntimePropertyType = declaredPropertyType; - jsonPropertyInfo.ConverterStrategy = converter.ConverterStrategy; - jsonPropertyInfo.RuntimeTypeInfo = runtimeTypeInfo; - jsonPropertyInfo.ConverterBase = converter; - jsonPropertyInfo.Options = options; - jsonPropertyInfo.IsForTypeInfo = true; - jsonPropertyInfo.HasGetter = true; - jsonPropertyInfo.HasSetter = true; + DeclaredPropertyType = declaredType; + RuntimePropertyType = declaredType; + ConverterStrategy = converter.ConverterStrategy; + RuntimeTypeInfo = runtimeTypeInfo; + ConverterBase = converter; + Options = options; + IsForTypeInfo = true; + HasGetter = true; + HasSetter = true; // TODO (perf): can we pre-compute some of these values during source gen? - jsonPropertyInfo._converterIsExternalAndPolymorphic = !converter.IsInternalConverter && declaredPropertyType != converter.TypeToConvert; - jsonPropertyInfo.PropertyTypeCanBeNull = declaredPropertyType.CanBeNull(); - jsonPropertyInfo._propertyTypeEqualsTypeToConvert = typeof(T) == declaredPropertyType; - return jsonPropertyInfo; + _converterIsExternalAndPolymorphic = !converter.IsInternalConverter && declaredType != converter.TypeToConvert; + PropertyTypeCanBeNull = declaredType.CanBeNull(); + _propertyTypeEqualsTypeToConvert = typeof(T) == declaredType; } internal override JsonConverter ConverterBase diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs index 55780e468093ba..38fe706f81d32a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs @@ -39,7 +39,7 @@ public JsonTypeInfoInternal( ElementType = converter.ElementType; ElementTypeInfo = elementInfo ?? throw new ArgumentNullException(nameof(elementInfo)); NumberHandling = numberHandling; - PropertyInfoForTypeInfo = JsonPropertyInfo.CreateForSourceGenTypeInfo(Type, runtimeTypeInfo: this, converter, Options); + PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options); SetCreateObjectFunc(createObjectFunc); } @@ -59,7 +59,7 @@ public JsonTypeInfoInternal( ElementType = converter.ElementType; ElementTypeInfo = valueInfo ?? throw new ArgumentNullException(nameof(valueInfo)); NumberHandling = numberHandling; - PropertyInfoForTypeInfo = JsonPropertyInfo.CreateForSourceGenTypeInfo(Type, runtimeTypeInfo: this, converter, Options); + PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options); SetCreateObjectFunc(createObjectFunc); } @@ -80,7 +80,7 @@ public void InitializeAsObject( JsonConverter converter = new ObjectSourceGenConverter(); #pragma warning restore CS8714 - PropertyInfoForTypeInfo = JsonPropertyInfo.CreateForSourceGenTypeInfo(Type, runtimeTypeInfo: this, converter, options); + PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options); NumberHandling = numberHandling; PropInitFunc = propInitFunc; SetCreateObjectFunc(createObjectFunc); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs index b8347036c28af7..2f0347f68332ae 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs @@ -83,6 +83,11 @@ private void AddCurrent() public void Initialize(Type type, JsonSerializerOptions options, bool supportContinuation) { JsonTypeInfo jsonTypeInfo = options.GetOrAddClassForRootType(type); + Initialize(jsonTypeInfo, supportContinuation); + } + + internal void Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation = false) + { Current.JsonTypeInfo = jsonTypeInfo; // The initial JsonPropertyInfo will be used to obtain the converter. @@ -90,6 +95,7 @@ public void Initialize(Type type, JsonSerializerOptions options, bool supportCon Current.NumberHandling = Current.JsonPropertyInfo.NumberHandling; + JsonSerializerOptions options = jsonTypeInfo.Options; bool preserveReferences = options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve; if (preserveReferences) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 1efbbe5fb83889..41ac243f19fe04 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -89,7 +89,11 @@ private void AddCurrent() public JsonConverter Initialize(Type type, JsonSerializerOptions options, bool supportContinuation) { JsonTypeInfo jsonTypeInfo = options.GetOrAddClassForRootType(type); + return Initialize(jsonTypeInfo, options, supportContinuation); + } + internal JsonConverter Initialize(JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options, bool supportContinuation) + { Current.JsonTypeInfo = jsonTypeInfo; Current.DeclaredJsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo; Current.NumberHandling = Current.DeclaredJsonPropertyInfo.NumberHandling; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 9fa5df182b52b9..70f455dbe45fda 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -678,5 +678,26 @@ public static void ThrowInvalidOperationException_JsonSerializerOptionsAlreadyBo { throw new InvalidOperationException(SR.Format(SR.OptionsAlreadyBoundToContext)); } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowNotSupportedException_BuiltInConvertersNotRooted(Type type) + { + throw new NotSupportedException(SR.Format(SR.BuiltInConvertersNotRooted, type)); + } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowNotSupportedException_NoMetadataForType(Type type) + { + throw new NotSupportedException(SR.Format(SR.NoMetadataForType, type)); + } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidOperationException_NoMetadataForType(Type type) + { + throw new InvalidOperationException(SR.Format(SR.NoMetadataForType, type)); + } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/MetadataServicesTests/MetadataServicesTests.cs b/src/libraries/System.Text.Json/tests/Serialization/MetadataServicesTests/MetadataServicesTests.cs index 3d17baa21ab660..74d3c641ceb1b9 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/MetadataServicesTests/MetadataServicesTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/MetadataServicesTests/MetadataServicesTests.cs @@ -22,14 +22,15 @@ public static void CreatePropertyInfo() options: null, isProperty: true, declaringType: typeof(Point), - propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), + propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), converter: null, getter: null, setter: null, ignoreCondition: default, numberHandling: default, propertyName: "MyInt", - jsonPropertyName: default)); + jsonPropertyName: null, + encodedName: null)); Assert.Contains("options", ane.ToString()); // Null declaring type @@ -37,14 +38,15 @@ public static void CreatePropertyInfo() options: options, isProperty: true, declaringType: null, - propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), + propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), converter: null, getter: null, setter: null, ignoreCondition: default, numberHandling: default, propertyName: "MyInt", - jsonPropertyName: default)); + jsonPropertyName: null, + encodedName: null)); Assert.Contains("declaringType", ane.ToString()); // Null property type info @@ -59,7 +61,8 @@ public static void CreatePropertyInfo() ignoreCondition: default, numberHandling: default, propertyName: "MyInt", - jsonPropertyName: default)); + jsonPropertyName: null, + encodedName: null)); Assert.Contains("propertyTypeInfo", ane.ToString()); // Null property name @@ -67,14 +70,15 @@ public static void CreatePropertyInfo() options: options, isProperty: true, declaringType: typeof(Point), - propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), + propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), converter: null, getter: null, setter: null, ignoreCondition: default, numberHandling: default, propertyName: null, - jsonPropertyName: default)); + jsonPropertyName: null, + encodedName: null)); Assert.Contains("propertyName", ane.ToString()); // Invalid converter @@ -83,14 +87,15 @@ public static void CreatePropertyInfo() isProperty: true, declaringType: typeof(Point), // Converter invalid because you'd need to create with JsonMetadataServices.CreatePropertyInfo instead. - propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, new DerivedClassConverter()), + propertyTypeInfo: JsonMetadataServices.CreateValueInfo(options, new DerivedClassConverter()), converter: null, getter: null, setter: null, ignoreCondition: default, numberHandling: default, propertyName: "MyProp", - jsonPropertyName: default)); + jsonPropertyName: null, + encodedName: null)); string ioeAsStr = ioe.ToString(); Assert.Contains("Point.MyProp", ioeAsStr); Assert.Contains("MyClass", ioeAsStr); @@ -119,7 +124,7 @@ public static void CreateObjectInfo() // Info is not for object converter strategy ArgumentException ae = Assert.Throws(() => JsonMetadataServices.InitializeObjectInfo( - info: JsonMetadataServices.CreateValueInfo(options, new DerivedClassConverter()), + info: JsonMetadataServices.CreateValueInfo(options, new DerivedClassConverter()), options: options, createObjectFunc: null, propInitFunc: (context) => Array.Empty(), @@ -151,13 +156,13 @@ public static void CreateValueInfo() JsonSerializerOptions options = new(); // Use converter that returns same type. - Assert.NotNull(JsonMetadataServices.CreateValueInfo(options, new ClassConverter())); + Assert.NotNull(JsonMetadataServices.CreateValueInfo(options, new ClassConverter())); // Use converter that returns derived type. - Assert.NotNull(JsonMetadataServices.CreateValueInfo(options, new DerivedClassConverter())); + Assert.NotNull(JsonMetadataServices.CreateValueInfo(options, new DerivedClassConverter())); // Null options - ArgumentNullException ex = Assert.Throws(() => JsonMetadataServices.CreateValueInfo(options: null, new DerivedClassConverter())); + ArgumentNullException ex = Assert.Throws(() => JsonMetadataServices.CreateValueInfo(options: null, new DerivedClassConverter())); Assert.Contains("options", ex.ToString()); } @@ -169,7 +174,7 @@ public static void CreateArrayInfo() // Null options ArgumentNullException ane = Assert.Throws(() => JsonMetadataServices.CreateArrayInfo( options: null, - elementInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), + elementInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), numberHandling: default)); Assert.Contains("options", ane.ToString()); @@ -190,7 +195,7 @@ public static void CreateListInfo() ArgumentNullException ane = Assert.Throws(() => JsonMetadataServices.CreateListInfo, int>( options: null, createObjectFunc: null, - elementInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), + elementInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), numberHandling: default)); Assert.Contains("options", ane.ToString()); @@ -212,8 +217,8 @@ public static void CreateDictionaryInfo() ArgumentNullException ane = Assert.Throws(() => JsonMetadataServices.CreateDictionaryInfo, string, int>( options: null, createObjectFunc: null, - keyInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.StringConverter), - valueInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), + keyInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.StringConverter), + valueInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), numberHandling: default)); Assert.Contains("options", ane.ToString()); @@ -222,7 +227,7 @@ public static void CreateDictionaryInfo() options: options, createObjectFunc: null, keyInfo: null, - valueInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), + valueInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.Int32Converter), numberHandling: default)); Assert.Contains("valueInfo", ane.ToString()); @@ -230,7 +235,7 @@ public static void CreateDictionaryInfo() ane = Assert.Throws(() => JsonMetadataServices.CreateDictionaryInfo, string, int>( options: options, createObjectFunc: null, - keyInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.StringConverter), + keyInfo: JsonMetadataServices.CreateValueInfo(options, JsonMetadataServices.StringConverter), valueInfo: null, numberHandling: default)); Assert.Contains("valueInfo", ane.ToString()); @@ -259,8 +264,10 @@ public static void GetEnumConverter() [Fact] public static void GetNullableConverter() { - JsonConverter enumConverter = JsonMetadataServices.GetEnumConverter(new JsonSerializerOptions()); - JsonConverter nullableConverter = JsonMetadataServices.GetNullableConverter(enumConverter); + JsonSerializerOptions options = new(); + JsonConverter enumConverter = JsonMetadataServices.GetEnumConverter(options); + JsonTypeInfo enumInfo = JsonMetadataServices.CreateValueInfo(options, enumConverter); + JsonConverter nullableConverter = JsonMetadataServices.GetNullableConverter(enumInfo); Assert.NotNull(nullableConverter); Assert.Throws(() => JsonMetadataServices.GetNullableConverter(null!)); } diff --git a/src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs index 49a8a86fd056d0..4b2537bd2cdfc8 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs @@ -185,7 +185,7 @@ public static void NullObjectOutput() } { - string output = JsonSerializer.Serialize(null, null); + string output = JsonSerializer.Serialize(null, options: null); Assert.Equal("null", output); } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/NumberHandlingTests.cs b/src/libraries/System.Text.Json/tests/Serialization/NumberHandlingTests.cs index ed58392341ef61..fb6dca863e0ff3 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/NumberHandlingTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/NumberHandlingTests.cs @@ -1483,6 +1483,7 @@ public class MyCustomDictionaryWrapper } [Fact] + [ActiveIssue("Need to tweak number handling option registration following code-gen support.")] public static void Attribute_NotAllowed_On_NonNumber_NonCollection_Property() { string json = @""; @@ -1504,6 +1505,7 @@ public class ClassWith_NumberHandlingOn_ObjectProperty } [Fact] + [ActiveIssue("Need to tweak number handling option registration following code-gen support.")] public static void Attribute_NotAllowed_On_Property_WithCustomConverter() { string json = @""; @@ -1526,6 +1528,7 @@ public class ClassWith_NumberHandlingOn_Property_WithCustomConverter } [Fact] + [ActiveIssue("Need to tweak number handling option registration following code-gen support.")] public static void Attribute_NotAllowed_On_Type_WithCustomConverter() { string json = @""; diff --git a/src/libraries/System.Text.Json/tests/Serialization/OptionsTests.cs b/src/libraries/System.Text.Json/tests/Serialization/OptionsTests.cs index 53c7fdd1dd91d2..e917dbba846921 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/OptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/OptionsTests.cs @@ -407,6 +407,7 @@ public static void Options_GetConverter_GivesCorrectDefaultConverterAndReadWrite } [Fact] + [ActiveIssue("KeyValuePair converter is not a primitive JsonConverter, so there's no way to properly flow the ReadStack state in the direct call to the serializer.")] public static void Options_GetConverter_GivesCorrectKeyValuePairConverter() { GenericConverterTestHelper>( diff --git a/src/libraries/src.proj b/src/libraries/src.proj index c0738c440374f3..499e121002b701 100644 --- a/src/libraries/src.proj +++ b/src/libraries/src.proj @@ -7,6 +7,8 @@ <_allSrc Include="$(MSBuildThisFileDirectory)*\src\*.csproj" Exclude="@(ProjectExclusions)" /> + + <_allSrc Include="$(MSBuildThisFileDirectory)System.Text.Json\source_generation\src\System.Text.Json.SourceGeneration.csproj" /> Date: Thu, 15 Apr 2021 16:35:37 -0700 Subject: [PATCH 2/5] Address review feedback --- .../Common/JsonIgnoreCondition.cs | 6 +- .../Common/JsonNumberHandling.cs | 19 +-- .../System.Text.Json/System.Text.Json.sln | 8 +- .../src/ClassType.cs | 0 .../src/CollectionType.cs | 0 .../src/IsExternalInit.cs | 0 .../gen/src/JsonSerializableSyntaxReceiver.cs | 22 ++++ .../gen/src/JsonSourceGenerator.cs | 117 +++++++++++++++++ .../src/JsonSourceGeneratorHelper.Generate.cs | 77 +++++------ .../src/JsonSourceGeneratorHelper.cs | 122 +++++++----------- .../src/ObjectConstructionStrategy.cs | 0 .../src/PropertyMetadata.cs | 0 .../src/Reflection}/AssemblyWrapper.cs | 7 +- .../src/Reflection}/ConstructorInfoWrapper.cs | 7 +- .../Reflection}/CustomAttributeDataWrapper.cs | 5 +- .../src/Reflection}/FieldInfoWrapper.cs | 7 +- .../src/Reflection}/MemberInfoWrapper.cs | 7 +- .../MetadataLoadContextInternal.cs} | 7 +- .../src/Reflection}/MethodInfoWrapper.cs | 8 +- .../src/Reflection}/ParameterInfoWrapper.cs | 9 +- .../src/Reflection}/PropertyInfoWrapper.cs | 7 +- .../src/Reflection}/ReflectionExtensions.cs | 3 +- .../src/Reflection}/RoslynExtensions.cs | 9 +- .../src/Reflection}/TypeExtensions.cs | 10 +- .../src/Reflection}/TypeWrapper.cs | 8 +- .../System.Text.Json.SourceGeneration.csproj | 33 +++-- .../src/TypeMetadata.cs | 0 .../tests/JsonSourceGeneratorTests.cs | 16 +-- ...em.Text.Json.SourceGeneration.Tests.csproj | 2 +- .../tests/TestClasses.cs | 4 +- .../unit_tests/CompilationHelper.cs | 46 +++++++ .../JsonSourceGeneratorDiagnosticsTests.cs | 14 +- .../unit_tests/JsonSourceGeneratorTests.cs | 0 ...ext.Json.SourceGeneration.UnitTests.csproj | 2 +- .../unit_tests/TypeWrapperTests.cs | 0 .../pkg/System.Text.Json.pkgproj | 1 + .../System.Text.Json/ref/System.Text.Json.cs | 8 +- .../src/JsonSerializableSyntaxReceiver.cs | 24 ---- .../src/JsonSourceGenerator.cs | 84 ------------ .../JsonSerializer.Read.String.cs | 22 ++-- .../JsonSerializer.Write.String.cs | 16 ++- .../JsonSerializerOptions.Converters.cs | 2 +- .../tests/Serialization/OptionsTests.cs | 3 +- src/libraries/src.proj | 4 +- 44 files changed, 404 insertions(+), 342 deletions(-) rename src/libraries/System.Text.Json/{source_generation => gen}/src/ClassType.cs (100%) rename src/libraries/System.Text.Json/{source_generation => gen}/src/CollectionType.cs (100%) rename src/libraries/System.Text.Json/{source_generation => gen}/src/IsExternalInit.cs (100%) create mode 100644 src/libraries/System.Text.Json/gen/src/JsonSerializableSyntaxReceiver.cs create mode 100644 src/libraries/System.Text.Json/gen/src/JsonSourceGenerator.cs rename src/libraries/System.Text.Json/{source_generation => gen}/src/JsonSourceGeneratorHelper.Generate.cs (92%) rename src/libraries/System.Text.Json/{source_generation => gen}/src/JsonSourceGeneratorHelper.cs (81%) rename src/libraries/System.Text.Json/{source_generation => gen}/src/ObjectConstructionStrategy.cs (100%) rename src/libraries/System.Text.Json/{source_generation => gen}/src/PropertyMetadata.cs (100%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/AssemblyWrapper.cs (88%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/ConstructorInfoWrapper.cs (93%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/CustomAttributeDataWrapper.cs (93%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/FieldInfoWrapper.cs (93%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/MemberInfoWrapper.cs (89%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils/MetadataLoadContext.cs => gen/src/Reflection/MetadataLoadContextInternal.cs} (95%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/MethodInfoWrapper.cs (95%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/ParameterInfoWrapper.cs (79%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/PropertyInfoWrapper.cs (94%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/ReflectionExtensions.cs (95%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/RoslynExtensions.cs (70%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/TypeExtensions.cs (90%) rename src/libraries/System.Text.Json/{source_generation/src/ReflectionUtils => gen/src/Reflection}/TypeWrapper.cs (98%) rename src/libraries/System.Text.Json/{source_generation => gen}/src/System.Text.Json.SourceGeneration.csproj (56%) rename src/libraries/System.Text.Json/{source_generation => gen}/src/TypeMetadata.cs (100%) rename src/libraries/System.Text.Json/{source_generation => gen}/tests/JsonSourceGeneratorTests.cs (97%) rename src/libraries/System.Text.Json/{source_generation => gen}/tests/System.Text.Json.SourceGeneration.Tests.csproj (80%) rename src/libraries/System.Text.Json/{source_generation => gen}/tests/TestClasses.cs (95%) rename src/libraries/System.Text.Json/{source_generation => gen}/unit_tests/CompilationHelper.cs (81%) rename src/libraries/System.Text.Json/{source_generation => gen}/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs (89%) rename src/libraries/System.Text.Json/{source_generation => gen}/unit_tests/JsonSourceGeneratorTests.cs (100%) rename src/libraries/System.Text.Json/{source_generation => gen}/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj (89%) rename src/libraries/System.Text.Json/{source_generation => gen}/unit_tests/TypeWrapperTests.cs (100%) delete mode 100644 src/libraries/System.Text.Json/source_generation/src/JsonSerializableSyntaxReceiver.cs delete mode 100644 src/libraries/System.Text.Json/source_generation/src/JsonSourceGenerator.cs diff --git a/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs b/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs index 90b09e392af009..f93ea31aaa4e53 100644 --- a/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs +++ b/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs @@ -3,9 +3,6 @@ namespace System.Text.Json.Serialization { -#if BUILDING_SOURCE_GENERATOR - internal -#else /// /// When specified on , /// determines when properties and fields across the type graph are ignored. @@ -13,6 +10,9 @@ namespace System.Text.Json.Serialization /// a property is ignored during serialization and deserialization. This option /// overrides the setting on . /// +#if BUILDING_SOURCE_GENERATOR + internal +#else public #endif enum JsonIgnoreCondition diff --git a/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs b/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs index f98f41949b03a2..ea1d68fe33bba4 100644 --- a/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs +++ b/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs @@ -3,21 +3,17 @@ namespace System.Text.Json.Serialization { -#if BUILDING_SOURCE_GENERATOR - // cref references below are not present when compiling this assembly. - internal enum JsonNumberHandling - { - Strict = 0x0, - AllowReadingFromString = 0x1, - WriteAsString = 0x2, - AllowNamedFloatingPointLiterals = 0x4 - } -#else /// /// Determines how handles numbers when serializing and deserializing. /// [Flags] - public enum JsonNumberHandling +#if BUILDING_SOURCE_GENERATOR + // cref references below are not present when compiling this assembly. + internal +#else + public +#endif + enum JsonNumberHandling { /// /// Numbers will only be read from tokens and will only be written as JSON numbers (without quotes). @@ -47,5 +43,4 @@ public enum JsonNumberHandling /// AllowNamedFloatingPointLiterals = 0x4 } -#endif } diff --git a/src/libraries/System.Text.Json/System.Text.Json.sln b/src/libraries/System.Text.Json/System.Text.Json.sln index e426c9095e1fcf..498eb09b54e185 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.sln +++ b/src/libraries/System.Text.Json/System.Text.Json.sln @@ -35,19 +35,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{EC8CE194-261 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{676B6044-FA47-4B7D-AEC2-FA94DB23A423}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "source_generation", "source_generation", "{A403699B-1BB2-4B23-AE05-97608C23EB27}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{A403699B-1BB2-4B23-AE05-97608C23EB27}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0DBD3DDF-16F1-4E7D-80A5-F9D74C1219B7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration", "source_generation\src\System.Text.Json.SourceGeneration.csproj", "{24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration", "gen\src\System.Text.Json.SourceGeneration.csproj", "{24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C4C10F31-F6DF-40DB-B892-CDFDD4DD9091}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.Tests", "source_generation\tests\System.Text.Json.SourceGeneration.Tests.csproj", "{9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.Tests", "gen\tests\System.Text.Json.SourceGeneration.Tests.csproj", "{9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit_tests", "unit_tests", "{7B11F28A-2732-49CF-8000-682FBDE606A7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.UnitTests", "source_generation\unit_tests\System.Text.Json.SourceGeneration.UnitTests.csproj", "{CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.UnitTests", "gen\unit_tests\System.Text.Json.SourceGeneration.UnitTests.csproj", "{CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}" EndProject Global GlobalSection(NestedProjects) = preSolution diff --git a/src/libraries/System.Text.Json/source_generation/src/ClassType.cs b/src/libraries/System.Text.Json/gen/src/ClassType.cs similarity index 100% rename from src/libraries/System.Text.Json/source_generation/src/ClassType.cs rename to src/libraries/System.Text.Json/gen/src/ClassType.cs diff --git a/src/libraries/System.Text.Json/source_generation/src/CollectionType.cs b/src/libraries/System.Text.Json/gen/src/CollectionType.cs similarity index 100% rename from src/libraries/System.Text.Json/source_generation/src/CollectionType.cs rename to src/libraries/System.Text.Json/gen/src/CollectionType.cs diff --git a/src/libraries/System.Text.Json/source_generation/src/IsExternalInit.cs b/src/libraries/System.Text.Json/gen/src/IsExternalInit.cs similarity index 100% rename from src/libraries/System.Text.Json/source_generation/src/IsExternalInit.cs rename to src/libraries/System.Text.Json/gen/src/IsExternalInit.cs diff --git a/src/libraries/System.Text.Json/gen/src/JsonSerializableSyntaxReceiver.cs b/src/libraries/System.Text.Json/gen/src/JsonSerializableSyntaxReceiver.cs new file mode 100644 index 00000000000000..2600cafe6aa3c8 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/src/JsonSerializableSyntaxReceiver.cs @@ -0,0 +1,22 @@ +// 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 Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace System.Text.Json.SourceGeneration +{ + internal class JsonSerializableSyntaxReceiver : ISyntaxReceiver + { + public List CompilationUnits { get; } = new(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is CompilationUnitSyntax compilationUnit) + { + CompilationUnits.Add(compilationUnit); + } + } + } +} diff --git a/src/libraries/System.Text.Json/gen/src/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/src/JsonSourceGenerator.cs new file mode 100644 index 00000000000000..7e7f421f142911 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/src/JsonSourceGenerator.cs @@ -0,0 +1,117 @@ +// 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.Text.Json.SourceGeneration.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace System.Text.Json.SourceGeneration +{ + /// + /// Generates source code to optimize serialization and deserialization with JsonSerializer. + /// + [Generator] + public sealed class JsonSourceGenerator : ISourceGenerator + { + private JsonSourceGeneratorHelper? _helper; + + /// + /// Helper for unit tests. + /// TODO: refactor this after adopting emitter/parser pattern. + /// + public Dictionary? SerializableTypes => _helper.GetSerializableTypes(); + + /// + /// Registers a syntax resolver to receive compilation units. + /// + /// + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new JsonSerializableSyntaxReceiver()); + } + + /// + /// Generates source code to optimize serialization and deserialization with JsonSerializer. + /// + /// + public void Execute(GeneratorExecutionContext executionContext) + { + Compilation compilation = executionContext.Compilation; + + const string JsonSerializableAttributeName = "System.Text.Json.Serialization.JsonSerializableAttribute"; + INamedTypeSymbol jsonSerializableAttribute = compilation.GetTypeByMetadataName(JsonSerializableAttributeName); + if (jsonSerializableAttribute == null) + { + return; + } + + JsonSerializableSyntaxReceiver receiver = (JsonSerializableSyntaxReceiver)executionContext.SyntaxReceiver; + MetadataLoadContextInternal metadataLoadContext = new(compilation); + + TypeExtensions.NullableOfTType = metadataLoadContext.Resolve(typeof(Nullable<>)); + + JsonSourceGeneratorHelper helper = new(executionContext, metadataLoadContext); + _helper = helper; + + // Discover serializable types indicated by JsonSerializableAttribute. + foreach (CompilationUnitSyntax compilationUnit in receiver.CompilationUnits) + { + SemanticModel compilationSemanticModel = executionContext.Compilation.GetSemanticModel(compilationUnit.SyntaxTree); + + foreach (AttributeListSyntax attributeListSyntax in compilationUnit.AttributeLists) + { + AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.Single(); + IMethodSymbol attributeSymbol = compilationSemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol; + + if (attributeSymbol == null || !jsonSerializableAttribute.Equals(attributeSymbol.ContainingType, SymbolEqualityComparer.Default)) + { + // Not the right attribute. + continue; + } + + // Get JsonSerializableAttribute arguments. + IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); + + int argumentCount = attributeArguments.Count(); + + // Compiler shouldn't allow invalid signature for the JsonSerializable attribute. + Debug.Assert(argumentCount == 1 || argumentCount == 2); + + // Obtain the one `Type` argument that must be present in the constructor of the attribute. + AttributeArgumentSyntax typeArgumentNode = (AttributeArgumentSyntax)attributeArguments.First(); + TypeOfExpressionSyntax typeNode = (TypeOfExpressionSyntax)typeArgumentNode.ChildNodes().Single(); + ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single(); + ITypeSymbol typeSymbol = compilationSemanticModel.GetTypeInfo(typeNameSyntax).ConvertedType; + + string? typeInfoPropertyName = null; + // Obtain the optional TypeInfoPropertyName boolean property on the attribute, if present. + if (argumentCount == 2) + { + AttributeArgumentSyntax stringArgumentNode = (AttributeArgumentSyntax)attributeArguments.ElementAt(1); + IEnumerable childNodes = stringArgumentNode.ChildNodes(); + SyntaxNode stringValueNode = childNodes.ElementAt(1); + typeInfoPropertyName = stringValueNode.GetFirstToken().ValueText; + } + + Type type = new TypeWrapper(typeSymbol, metadataLoadContext); + if (type.Namespace == "") + { + // typeof() reference where the type's name isn't fully qualified. + // The compilation is not valid and user needs to fix their code. + // The compiler will notify the user so we don't have to. + return; + } + + helper.RegisterRootSerializableType(type, typeInfoPropertyName); + } + } + + helper.GenerateSerializationMetadata(); + } + } +} diff --git a/src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.Generate.cs b/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.Generate.cs similarity index 92% rename from src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.Generate.cs rename to src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.Generate.cs index 0403eded2643b8..c80ea908a6015c 100644 --- a/src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.Generate.cs +++ b/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.Generate.cs @@ -4,8 +4,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Reflection; using System.Text.Json.Serialization; +using System.Text.Json.SourceGeneration.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -30,6 +30,8 @@ internal sealed partial class JsonSourceGeneratorHelper private const string CreateValueInfoMethodName = "CreateValueInfo"; + // TODO: Enable localization for these descriptors. + private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor( "SYSLIB2021", "Did not generate serialization metadata for type", @@ -39,6 +41,15 @@ internal sealed partial class JsonSourceGeneratorHelper isEnabledByDefault: true, description: "Error message: {2}"); + private static DiagnosticDescriptor DuplicateTypeName { get; } = new DiagnosticDescriptor( + "SYSLIB2022", + "Duplicate type name", + "There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.", + "JSON source generation", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "Error message: {2}"); + /// /// Types that we have initiated serialization metadata generation for. A type may be discoverable in the object graph, /// but not reachable for serialization (e.g. it is [JsonIgnore]'d); thus we maintain a separate cache. @@ -56,54 +67,42 @@ private void GenerateTypeMetadata(TypeMetadata typeMetadata) _typesWithMetadataGenerated.Add(typeMetadata); - string metadataFileName = $"{typeMetadata.FriendlyName}.g.cs"; + string source; switch (typeMetadata.ClassType) { case ClassType.KnownType: { - _executionContext.AddSource( - metadataFileName, - SourceText.From(GenerateForTypeWithKnownConverter(typeMetadata), Encoding.UTF8)); + source = GenerateForTypeWithKnownConverter(typeMetadata); } break; case ClassType.TypeWithDesignTimeProvidedCustomConverter: { - _executionContext.AddSource( - metadataFileName, - SourceText.From(GenerateForTypeWithUnknownConverter(typeMetadata), Encoding.UTF8)); + source = GenerateForTypeWithUnknownConverter(typeMetadata); } break; case ClassType.Nullable: { - _executionContext.AddSource( - metadataFileName, - SourceText.From(GenerateForNullable(typeMetadata), Encoding.UTF8)); + source = GenerateForNullable(typeMetadata); GenerateTypeMetadata(typeMetadata.NullableUnderlyingTypeMetadata); } break; case ClassType.Enum: { - _executionContext.AddSource( - metadataFileName, - SourceText.From(GenerateForEnum(typeMetadata), Encoding.UTF8)); + source = GenerateForEnum(typeMetadata); } break; case ClassType.Enumerable: { - _executionContext.AddSource( - metadataFileName, - SourceText.From(GenerateForCollection(typeMetadata), Encoding.UTF8)); + source = GenerateForCollection(typeMetadata); GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata); } break; case ClassType.Dictionary: { - _executionContext.AddSource( - metadataFileName, - SourceText.From(GenerateForCollection(typeMetadata), Encoding.UTF8)); + source = GenerateForCollection(typeMetadata); GenerateTypeMetadata(typeMetadata.CollectionKeyTypeMetadata); GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata); @@ -111,9 +110,7 @@ private void GenerateTypeMetadata(TypeMetadata typeMetadata) break; case ClassType.Object: { - _executionContext.AddSource( - metadataFileName, - SourceText.From(GenerateForObject(typeMetadata), Encoding.UTF8)); + source = GenerateForObject(typeMetadata); if (typeMetadata.PropertiesMetadata != null) { @@ -135,6 +132,15 @@ private void GenerateTypeMetadata(TypeMetadata typeMetadata) throw new InvalidOperationException(); } } + + try + { + _executionContext.AddSource($"{typeMetadata.FriendlyName}.cs", SourceText.From(source, Encoding.UTF8)); + } + catch (ArgumentException) + { + _executionContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeMetadata.FriendlyName })); + } } private string GenerateForTypeWithKnownConverter(TypeMetadata typeMetadata) @@ -315,7 +321,8 @@ private string GeneratePropMetadataInitFunc( StringBuilder sb = new(); - sb.Append($@"private static {JsonPropertyInfoTypeName}[] {propInitFuncVarName}(JsonSerializerContext context) + sb.Append($@" + private static {JsonPropertyInfoTypeName}[] {propInitFuncVarName}(JsonSerializerContext context) {{ JsonContext {JsonContextVarName} = (JsonContext)context; JsonSerializerOptions options = context.Options; @@ -369,7 +376,7 @@ private string GeneratePropMetadataInitFunc( : "ignoreCondition: default"; string encodedNameArg; - if (!ContainsNonAscii(clrPropertyName)) + if (!NeedsEscaping(clrPropertyName)) { byte[] name = Encoding.UTF8.GetBytes(memberMetadata.JsonPropertyName ?? clrPropertyName); string nameBytes = string.Join(", ", name.Select(b => $"{b}")); @@ -410,15 +417,16 @@ private string GeneratePropMetadataInitFunc( return sb.ToString(); - static bool ContainsNonAscii(string str) + static bool NeedsEscaping(string str) { foreach (char c in str) { - if (c > 127) + if (c > 127 || c == '\'' || c == '\\') { return true; } } + return false; } } @@ -511,6 +519,7 @@ private string GetFetchLogicForRuntimeSpecifiedCustomConverter() {{ System.Collections.Generic.IList converters = {OptionsInstanceVariableName}.Converters; + // TODO: use a dictionary if count > ~15. for (int i = 0; i < converters.Count; i++) {{ JsonConverter converter = converters[i]; @@ -540,12 +549,8 @@ private string GetGetTypeInfoImplementation() HashSet usingStatements = new(); - List rootTypeMetadata = new(); - - foreach (Type type in _rootSerializableTypes.Values) + foreach (TypeMetadata metadata in _rootSerializableTypes.Values) { - TypeMetadata metadata = _typeMetadataCache[type]; - rootTypeMetadata.Add(metadata); usingStatements.UnionWith(GetUsingStatements(metadata)); } @@ -559,14 +564,14 @@ public override JsonTypeInfo GetTypeInfo(System.Type type) {{"); // TODO: Make this Dictionary-lookup-based if _handledType.Count > 64. - foreach (TypeMetadata typeMetadata in rootTypeMetadata) + foreach (TypeMetadata metadata in _rootSerializableTypes.Values) { - if (typeMetadata.ClassType != ClassType.TypeUnsupportedBySourceGen) + if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen) { sb.Append($@" - if (type == typeof({typeMetadata.Type.GetUniqueCompilableTypeName()})) + if (type == typeof({metadata.Type.GetUniqueCompilableTypeName()})) {{ - return this.{typeMetadata.FriendlyName}; + return this.{metadata.FriendlyName}; }} "); } diff --git a/src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.cs b/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.cs similarity index 81% rename from src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.cs rename to src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.cs index 7428126cda4102..0ba08db791825c 100644 --- a/src/libraries/System.Text.Json/source_generation/src/JsonSourceGeneratorHelper.cs +++ b/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.cs @@ -8,6 +8,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; using System.Text.Json.Serialization; +using System.Text.Json.SourceGeneration.Reflection; +using System.Linq; namespace System.Text.Json.SourceGeneration { @@ -31,9 +33,6 @@ internal sealed partial class JsonSourceGeneratorHelper private readonly HashSet _knownTypes = new(); - // Contains used JsonTypeInfo identifiers. - private readonly HashSet _usedFriendlyTypeNames = new(); - /// /// Type information for member types in input object graphs. /// @@ -42,23 +41,33 @@ internal sealed partial class JsonSourceGeneratorHelper /// /// Types that were specified with System.Text.Json.Serialization.JsonSerializableAttribute. /// - private readonly Dictionary _rootSerializableTypes; + private Dictionary _rootSerializableTypes; private readonly GeneratorExecutionContext _executionContext; - private readonly MetadataLoadContext _metadataLoadContext; + private readonly MetadataLoadContextInternal _metadataLoadContext; + + private const string SystemTextJsonNamespace = "System.Text.Json"; private const string JsonConverterAttributeFullName = "System.Text.Json.Serialization.JsonConverterAttribute"; + private const string JsonIgnoreAttributeFullName = "System.Text.Json.Serialization.JsonIgnoreAttribute"; + + private const string JsonIgnoreConditionFullName = "System.Text.Json.Serialization.JsonIgnoreCondition"; + + private const string JsonIncludeAttributeFullName = "System.Text.Json.Serialization.JsonIncludeAttribute"; + + private const string JsonNumberHandlingAttributeFullName = "System.Text.Json.Serialization.JsonNumberHandlingAttribute"; + + private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute"; + public JsonSourceGeneratorHelper( GeneratorExecutionContext executionContext, - MetadataLoadContext metadataLoadContext, - Dictionary rootSerializableTypes) + MetadataLoadContextInternal metadataLoadContext) { _generationNamespace = $"{executionContext.Compilation.AssemblyName}.JsonSourceGeneration"; _executionContext = executionContext; _metadataLoadContext = metadataLoadContext; - _rootSerializableTypes = rootSerializableTypes; _ienumerableType = metadataLoadContext.Resolve(typeof(IEnumerable)); _listOfTType = metadataLoadContext.Resolve(typeof(List<>)); @@ -70,7 +79,6 @@ public JsonSourceGeneratorHelper( _dateTimeType = metadataLoadContext.Resolve(typeof(DateTime)); _dateTimeOffsetType = metadataLoadContext.Resolve(typeof(DateTimeOffset)); _guidType = metadataLoadContext.Resolve(typeof(Guid)); - _stringType = metadataLoadContext.Resolve(typeof(string)); _uriType = metadataLoadContext.Resolve(typeof(Uri)); _versionType = metadataLoadContext.Resolve(typeof(Version)); @@ -78,15 +86,22 @@ public JsonSourceGeneratorHelper( PopulateKnownTypes(metadataLoadContext); } + public void RegisterRootSerializableType(Type type, string? typeInfoPropertyName) + { + _rootSerializableTypes ??= new Dictionary(); + _rootSerializableTypes[type.FullName] = GetOrAddTypeMetadata(type, typeInfoPropertyName); + } + public void GenerateSerializationMetadata() { - Debug.Assert(_rootSerializableTypes != null); - Debug.Assert(_rootSerializableTypes.Count > 0); + if (_rootSerializableTypes?.Count < 1) + { + return; + } - foreach (KeyValuePair pair in _rootSerializableTypes) + foreach (KeyValuePair pair in _rootSerializableTypes) { - Type type = pair.Value; - TypeMetadata typeMetadata = GetOrAddTypeMetadata(type); + TypeMetadata typeMetadata = pair.Value; GenerateTypeMetadata(typeMetadata); } @@ -97,7 +112,7 @@ public void GenerateSerializationMetadata() _executionContext.AddSource("JsonContext.GetJsonTypeInfo.g.cs", SourceText.From(GetGetTypeInfoImplementation(), Encoding.UTF8)); } - private TypeMetadata GetOrAddTypeMetadata(Type type) + private TypeMetadata GetOrAddTypeMetadata(Type type, string? typeInfoPropertyName = null) { if (_typeMetadataCache.TryGetValue(type, out TypeMetadata? typeMetadata)) { @@ -205,8 +220,6 @@ private TypeMetadata GetOrAddTypeMetadata(Type type) constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; } - Dictionary? ignoredMembers = null; - for (Type? currentType = type; currentType != null; currentType = currentType.BaseType) { const BindingFlags bindingFlags = @@ -215,12 +228,12 @@ private TypeMetadata GetOrAddTypeMetadata(Type type) BindingFlags.NonPublic | BindingFlags.DeclaredOnly; - foreach (Reflection.PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) + foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) { PropertyMetadata metadata = GetPropertyMetadata(propertyInfo); - // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d. - if (propertyInfo.GetIndexParameters().Length > 0 || PropertyIsOverridenAndIgnored(metadata, ignoredMembers)) + // Ignore indexers. + if (propertyInfo.GetIndexParameters().Length > 0) { continue; } @@ -242,11 +255,6 @@ private TypeMetadata GetOrAddTypeMetadata(Type type) { PropertyMetadata metadata = GetPropertyMetadata(fieldInfo); - if (PropertyIsOverridenAndIgnored(metadata, ignoredMembers)) - { - continue; - } - if (metadata.HasGetter || metadata.HasSetter) { (propertiesMetadata ??= new()).Add(metadata); @@ -255,21 +263,9 @@ private TypeMetadata GetOrAddTypeMetadata(Type type) } } - string compilableName = type.GetUniqueCompilableTypeName(); - string friendlyName = type.GetFriendlyTypeName(); - - if (_usedFriendlyTypeNames.Contains(friendlyName)) - { - friendlyName = type.GetUniqueFriendlyTypeName(); - } - else - { - _usedFriendlyTypeNames.Add(friendlyName); - } - typeMetadata.Initialize( - compilableName, - friendlyName, + compilableName: type.GetUniqueCompilableTypeName(), + friendlyName: typeInfoPropertyName ?? type.GetFriendlyTypeName(), type, classType, isValueType: type.IsValueType, @@ -286,23 +282,6 @@ private TypeMetadata GetOrAddTypeMetadata(Type type) return typeMetadata; } - private static bool PropertyIsOverridenAndIgnored(PropertyMetadata currentMemberMetadata, Dictionary? ignoredMembers) - { - if (ignoredMembers == null || !ignoredMembers.TryGetValue(currentMemberMetadata.ClrName, out PropertyMetadata? ignoredMemberMetadata)) - { - return false; - } - - return currentMemberMetadata.TypeMetadata.Type == ignoredMemberMetadata.TypeMetadata.Type && - PropertyIsVirtual(currentMemberMetadata) && - PropertyIsVirtual(ignoredMemberMetadata); - } - - private static bool PropertyIsVirtual(PropertyMetadata? propertyMetadata) - { - return propertyMetadata != null && (propertyMetadata.GetterIsVirtual == true || propertyMetadata.SetterIsVirtual == true); - } - private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo) { IList attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo); @@ -324,11 +303,11 @@ private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo) foundDesignTimeCustomConverter = true; converterInstantiationLogic = GetConverterInstantiationLogic(attributeData); } - else if (attributeType.Assembly.FullName == "System.Text.Json") + else if (attributeType.Assembly.FullName == SystemTextJsonNamespace) { switch (attributeData.AttributeType.FullName) { - case "System.Text.Json.Serialization.JsonIgnoreAttribute": + case JsonIgnoreAttributeFullName: { IList namedArgs = attributeData.NamedArguments; @@ -338,36 +317,26 @@ private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo) } else if (namedArgs.Count == 1 && namedArgs[0].MemberInfo.MemberType == MemberTypes.Property && - ((Reflection.PropertyInfo)namedArgs[0].MemberInfo).PropertyType.FullName == "System.Text.Json.Serialization.JsonIgnoreCondition") + ((PropertyInfo)namedArgs[0].MemberInfo).PropertyType.FullName == JsonIgnoreConditionFullName) { ignoreCondition = (JsonIgnoreCondition)namedArgs[0].TypedValue.Value; } } break; - case "System.Text.Json.Serialization.JsonIncludeAttribute": + case JsonIncludeAttributeFullName: { hasJsonInclude = true; } break; - case "System.Text.Json.Serialization.JsonNumberHandlingAttribute": + case JsonNumberHandlingAttributeFullName: { IList ctorArgs = attributeData.ConstructorArguments; - if (ctorArgs.Count != 1) - { - throw new InvalidOperationException($"Invalid use of 'JsonNumberHandlingAttribute' detected on '{memberInfo.DeclaringType}.{memberInfo.Name}'."); - } - numberHandling = (JsonNumberHandling)ctorArgs[0].Value; } break; - case "System.Text.Json.Serialization.JsonPropertyNameAttribute": + case JsonPropertyNameAttributeFullName: { IList ctorArgs = attributeData.ConstructorArguments; - if (ctorArgs.Count != 1 || ctorArgs[0].ArgumentType != _stringType) - { - throw new InvalidOperationException($"Invalid use of 'JsonPropertyNameAttribute' detected on '{memberInfo.DeclaringType}.{memberInfo.Name}'."); - } - jsonPropertyName = (string)ctorArgs[0].Value; // Null check here is done at runtime within JsonSerializer. } @@ -386,7 +355,7 @@ private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo) switch (memberInfo) { - case Reflection.PropertyInfo propertyInfo: + case PropertyInfo propertyInfo: { MethodInfo setMethod = propertyInfo.SetMethod; @@ -448,7 +417,7 @@ private static bool PropertyAccessorCanBeReferenced(MethodInfo? memberAccessor, return $"new {converterType.GetUniqueCompilableTypeName()}()"; } - private void PopulateNumberTypes(MetadataLoadContext metadataLoadContext) + private void PopulateNumberTypes(MetadataLoadContextInternal metadataLoadContext) { Debug.Assert(_numberTypes != null); _numberTypes.Add(metadataLoadContext.Resolve(typeof(byte))); @@ -464,7 +433,7 @@ private void PopulateNumberTypes(MetadataLoadContext metadataLoadContext) _numberTypes.Add(metadataLoadContext.Resolve(typeof(ulong))); } - private void PopulateKnownTypes(MetadataLoadContext metadataLoadContext) + private void PopulateKnownTypes(MetadataLoadContextInternal metadataLoadContext) { PopulateNumberTypes(metadataLoadContext); @@ -493,7 +462,6 @@ private void PopulateKnownTypes(MetadataLoadContext metadataLoadContext) private bool IsPrimitive(Type type) => _knownTypes.Contains(type) && type != _uriType && type != _versionType; - private bool IsStringBasedType(Type type) - => type == _stringType || type == _dateTimeType || type == _dateTimeOffsetType || type == _guidType; + public Dictionary GetSerializableTypes() => _rootSerializableTypes?.ToDictionary(p => p.Key, p => p.Value.Type); } } diff --git a/src/libraries/System.Text.Json/source_generation/src/ObjectConstructionStrategy.cs b/src/libraries/System.Text.Json/gen/src/ObjectConstructionStrategy.cs similarity index 100% rename from src/libraries/System.Text.Json/source_generation/src/ObjectConstructionStrategy.cs rename to src/libraries/System.Text.Json/gen/src/ObjectConstructionStrategy.cs diff --git a/src/libraries/System.Text.Json/source_generation/src/PropertyMetadata.cs b/src/libraries/System.Text.Json/gen/src/PropertyMetadata.cs similarity index 100% rename from src/libraries/System.Text.Json/source_generation/src/PropertyMetadata.cs rename to src/libraries/System.Text.Json/gen/src/PropertyMetadata.cs diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/AssemblyWrapper.cs b/src/libraries/System.Text.Json/gen/src/Reflection/AssemblyWrapper.cs similarity index 88% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/AssemblyWrapper.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/AssemblyWrapper.cs index e5b105f4222ea9..d5ed28e4800a17 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/AssemblyWrapper.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/AssemblyWrapper.cs @@ -2,15 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { internal class AssemblyWrapper : Assembly { - private readonly MetadataLoadContext _metadataLoadContext; + private readonly MetadataLoadContextInternal _metadataLoadContext; - public AssemblyWrapper(IAssemblySymbol assembly, MetadataLoadContext metadataLoadContext) + public AssemblyWrapper(IAssemblySymbol assembly, MetadataLoadContextInternal metadataLoadContext) { Symbol = assembly; _metadataLoadContext = metadataLoadContext; diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ConstructorInfoWrapper.cs b/src/libraries/System.Text.Json/gen/src/Reflection/ConstructorInfoWrapper.cs similarity index 93% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ConstructorInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/ConstructorInfoWrapper.cs index 28721b5dcf93d6..979d32c8f7c99e 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ConstructorInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/ConstructorInfoWrapper.cs @@ -3,16 +3,17 @@ using System.Collections.Generic; using System.Globalization; +using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { internal class ConstructorInfoWrapper : ConstructorInfo { private readonly IMethodSymbol _ctor; - private readonly MetadataLoadContext _metadataLoadContext; + private readonly MetadataLoadContextInternal _metadataLoadContext; - public ConstructorInfoWrapper(IMethodSymbol ctor, MetadataLoadContext metadataLoadContext) + public ConstructorInfoWrapper(IMethodSymbol ctor, MetadataLoadContextInternal metadataLoadContext) { _ctor = ctor; _metadataLoadContext = metadataLoadContext; diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/CustomAttributeDataWrapper.cs b/src/libraries/System.Text.Json/gen/src/Reflection/CustomAttributeDataWrapper.cs similarity index 93% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/CustomAttributeDataWrapper.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/CustomAttributeDataWrapper.cs index 5b53e0e8dad1a4..75b31db587a8f6 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/CustomAttributeDataWrapper.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/CustomAttributeDataWrapper.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.Linq; +using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { internal class CustomAttributeDataWrapper : CustomAttributeData { - public CustomAttributeDataWrapper(AttributeData a, MetadataLoadContext metadataLoadContext) + public CustomAttributeDataWrapper(AttributeData a, MetadataLoadContextInternal metadataLoadContext) { var namedArguments = new List(); foreach (KeyValuePair na in a.NamedArguments) diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/FieldInfoWrapper.cs b/src/libraries/System.Text.Json/gen/src/Reflection/FieldInfoWrapper.cs similarity index 93% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/FieldInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/FieldInfoWrapper.cs index f32aca6fd9bc33..cf36ceacbeee55 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/FieldInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/FieldInfoWrapper.cs @@ -2,16 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Reflection; using Microsoft.CodeAnalysis; using System.Globalization; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { internal class FieldInfoWrapper : FieldInfo { private readonly IFieldSymbol _field; - private readonly MetadataLoadContext _metadataLoadContext; - public FieldInfoWrapper(IFieldSymbol parameter, MetadataLoadContext metadataLoadContext) + private readonly MetadataLoadContextInternal _metadataLoadContext; + public FieldInfoWrapper(IFieldSymbol parameter, MetadataLoadContextInternal metadataLoadContext) { _field = parameter; _metadataLoadContext = metadataLoadContext; diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MemberInfoWrapper.cs b/src/libraries/System.Text.Json/gen/src/Reflection/MemberInfoWrapper.cs similarity index 89% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MemberInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/MemberInfoWrapper.cs index 2a9283931bd29f..1b4de7811fd741 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MemberInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/MemberInfoWrapper.cs @@ -2,16 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { internal class MemberInfoWrapper : MemberInfo { private readonly ISymbol _member; - private readonly MetadataLoadContext _metadataLoadContext; + private readonly MetadataLoadContextInternal _metadataLoadContext; - public MemberInfoWrapper(ISymbol member, MetadataLoadContext metadataLoadContext) + public MemberInfoWrapper(ISymbol member, MetadataLoadContextInternal metadataLoadContext) { _member = member; _metadataLoadContext = metadataLoadContext; diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MetadataLoadContext.cs b/src/libraries/System.Text.Json/gen/src/Reflection/MetadataLoadContextInternal.cs similarity index 95% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MetadataLoadContext.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/MetadataLoadContextInternal.cs index 90007ef70b29bf..885885325fe53b 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MetadataLoadContext.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/MetadataLoadContextInternal.cs @@ -4,12 +4,13 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { - public class MetadataLoadContext + internal class MetadataLoadContextInternal { private readonly Dictionary _assemblies = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -17,7 +18,7 @@ public class MetadataLoadContext private IAssemblySymbol? _collectionsAssemblySymbol; - public MetadataLoadContext(Compilation compilation) + public MetadataLoadContextInternal(Compilation compilation) { _compilation = compilation; Dictionary assemblies = compilation.References diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MethodInfoWrapper.cs b/src/libraries/System.Text.Json/gen/src/Reflection/MethodInfoWrapper.cs similarity index 95% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MethodInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/MethodInfoWrapper.cs index 1a2acd79d4afea..44eecf0b5919db 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/MethodInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/MethodInfoWrapper.cs @@ -2,18 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Data; using System.Globalization; +using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { internal class MethodInfoWrapper : MethodInfo { private readonly IMethodSymbol _method; - private readonly MetadataLoadContext _metadataLoadContext; + private readonly MetadataLoadContextInternal _metadataLoadContext; - public MethodInfoWrapper(IMethodSymbol method, MetadataLoadContext metadataLoadContext) + public MethodInfoWrapper(IMethodSymbol method, MetadataLoadContextInternal metadataLoadContext) { _method = method; _metadataLoadContext = metadataLoadContext; diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ParameterInfoWrapper.cs b/src/libraries/System.Text.Json/gen/src/Reflection/ParameterInfoWrapper.cs similarity index 79% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ParameterInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/ParameterInfoWrapper.cs index 52da666c669024..b8891251c7e627 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ParameterInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/ParameterInfoWrapper.cs @@ -2,17 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { - public class ParameterInfoWrapper : ParameterInfo + internal class ParameterInfoWrapper : ParameterInfo { private readonly IParameterSymbol _parameter; - private readonly MetadataLoadContext _metadataLoadContext; + private readonly MetadataLoadContextInternal _metadataLoadContext; - public ParameterInfoWrapper(IParameterSymbol parameter, MetadataLoadContext metadataLoadContext) + public ParameterInfoWrapper(IParameterSymbol parameter, MetadataLoadContextInternal metadataLoadContext) { _parameter = parameter; _metadataLoadContext = metadataLoadContext; diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/PropertyInfoWrapper.cs b/src/libraries/System.Text.Json/gen/src/Reflection/PropertyInfoWrapper.cs similarity index 94% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/PropertyInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/PropertyInfoWrapper.cs index 73f69ee271331b..294c6dd1059165 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/PropertyInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/PropertyInfoWrapper.cs @@ -3,16 +3,17 @@ using System.Collections.Generic; using System.Globalization; +using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { internal class PropertyInfoWrapper : PropertyInfo { private readonly IPropertySymbol _property; - private MetadataLoadContext _metadataLoadContext; + private MetadataLoadContextInternal _metadataLoadContext; - public PropertyInfoWrapper(IPropertySymbol property, MetadataLoadContext metadataLoadContext) + public PropertyInfoWrapper(IPropertySymbol property, MetadataLoadContextInternal metadataLoadContext) { _property = property; _metadataLoadContext = metadataLoadContext; diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ReflectionExtensions.cs b/src/libraries/System.Text.Json/gen/src/Reflection/ReflectionExtensions.cs similarity index 95% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ReflectionExtensions.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/ReflectionExtensions.cs index 428d78dbfd707f..07dce68fcbb899 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/ReflectionExtensions.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/ReflectionExtensions.cs @@ -1,11 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Linq; using System.Reflection; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { internal static class ReflectionExtensions { diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/src/Reflection/RoslynExtensions.cs similarity index 70% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/RoslynExtensions.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/RoslynExtensions.cs index 8904adb02bca34..610e7fe4eef6aa 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/RoslynExtensions.cs @@ -2,13 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { - public static class RoslynExtensions + internal static class RoslynExtensions { - public static Type AsType(this ITypeSymbol typeSymbol, MetadataLoadContext metadataLoadContext) + public static Type AsType(this ITypeSymbol typeSymbol, MetadataLoadContextInternal metadataLoadContext) { if (typeSymbol == null) { @@ -18,7 +19,7 @@ public static Type AsType(this ITypeSymbol typeSymbol, MetadataLoadContext metad return new TypeWrapper(typeSymbol, metadataLoadContext); } - public static MethodInfo AsMethodInfo(this IMethodSymbol methodSymbol, MetadataLoadContext metadataLoadContext) => (methodSymbol == null ? null : new MethodInfoWrapper(methodSymbol, metadataLoadContext))!; + public static MethodInfo AsMethodInfo(this IMethodSymbol methodSymbol, MetadataLoadContextInternal metadataLoadContext) => (methodSymbol == null ? null : new MethodInfoWrapper(methodSymbol, metadataLoadContext))!; public static IEnumerable BaseTypes(this INamedTypeSymbol typeSymbol) { diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeExtensions.cs b/src/libraries/System.Text.Json/gen/src/Reflection/TypeExtensions.cs similarity index 90% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeExtensions.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/TypeExtensions.cs index e717eacf743631..4bfb71a9ce8717 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeExtensions.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/TypeExtensions.cs @@ -1,13 +1,10 @@ // 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.Text; -using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { internal static class TypeExtensions { @@ -31,11 +28,6 @@ private static string GetCompilableTypeName(Type type, string name) return $"{baseName}<{string.Join(",", type.GetGenericArguments().Select(arg => GetUniqueCompilableTypeName(arg)))}>"; } - public static string GetUniqueFriendlyTypeName(this Type type) - { - return GetFriendlyTypeName(type.GetUniqueCompilableTypeName()); - } - public static string GetFriendlyTypeName(this Type type) { return GetFriendlyTypeName(type.GetCompilableTypeName()); diff --git a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeWrapper.cs b/src/libraries/System.Text.Json/gen/src/Reflection/TypeWrapper.cs similarity index 98% rename from src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeWrapper.cs rename to src/libraries/System.Text.Json/gen/src/Reflection/TypeWrapper.cs index fd62a91ca467d9..2ffc4725922ab6 100644 --- a/src/libraries/System.Text.Json/source_generation/src/ReflectionUtils/TypeWrapper.cs +++ b/src/libraries/System.Text.Json/gen/src/Reflection/TypeWrapper.cs @@ -6,16 +6,16 @@ using System.Collections.Immutable; using System.Globalization; using System.Linq; -using System.Text; +using System.Reflection; using Microsoft.CodeAnalysis; -namespace System.Reflection +namespace System.Text.Json.SourceGeneration.Reflection { internal class TypeWrapper : Type { private readonly ITypeSymbol _typeSymbol; - private readonly MetadataLoadContext _metadataLoadContext; + private readonly MetadataLoadContextInternal _metadataLoadContext; private INamedTypeSymbol? _namedTypeSymbol; @@ -23,7 +23,7 @@ internal class TypeWrapper : Type private Type _elementType; - public TypeWrapper(ITypeSymbol namedTypeSymbol, MetadataLoadContext metadataLoadContext) + public TypeWrapper(ITypeSymbol namedTypeSymbol, MetadataLoadContextInternal metadataLoadContext) { _typeSymbol = namedTypeSymbol; _metadataLoadContext = metadataLoadContext; diff --git a/src/libraries/System.Text.Json/source_generation/src/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/gen/src/System.Text.Json.SourceGeneration.csproj similarity index 56% rename from src/libraries/System.Text.Json/source_generation/src/System.Text.Json.SourceGeneration.csproj rename to src/libraries/System.Text.Json/gen/src/System.Text.Json.SourceGeneration.csproj index 45afe9bc389e4a..b1ff9ed3b9b2ad 100644 --- a/src/libraries/System.Text.Json/source_generation/src/System.Text.Json.SourceGeneration.csproj +++ b/src/libraries/System.Text.Json/gen/src/System.Text.Json.SourceGeneration.csproj @@ -1,9 +1,10 @@ - - + netstandard2.0 false enable + + CS1574 @@ -12,7 +13,6 @@ - @@ -29,20 +29,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + - diff --git a/src/libraries/System.Text.Json/source_generation/src/TypeMetadata.cs b/src/libraries/System.Text.Json/gen/src/TypeMetadata.cs similarity index 100% rename from src/libraries/System.Text.Json/source_generation/src/TypeMetadata.cs rename to src/libraries/System.Text.Json/gen/src/TypeMetadata.cs diff --git a/src/libraries/System.Text.Json/source_generation/tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/gen/tests/JsonSourceGeneratorTests.cs similarity index 97% rename from src/libraries/System.Text.Json/source_generation/tests/JsonSourceGeneratorTests.cs rename to src/libraries/System.Text.Json/gen/tests/JsonSourceGeneratorTests.cs index 440197ae55ca70..e045aa7a02b2d2 100644 --- a/src/libraries/System.Text.Json/source_generation/tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/gen/tests/JsonSourceGeneratorTests.cs @@ -22,12 +22,8 @@ public static void RoundTripLocation() { Location expected = CreateLocation(); - // Location is renamed to SystemTextJsonSourceGenerationTestsLocation given there - // is another type with the name Location, that the generator processed first - // (in this case due to JsonSerializableAttribute ordering). - // A warning to the user is displayed with this detail at compile time. - string json = JsonSerializer.Serialize(expected, JsonContext.Default.SystemTextJsonSourceGenerationTestsLocation); - Location obj = JsonSerializer.Deserialize(json, JsonContext.Default.SystemTextJsonSourceGenerationTestsLocation); + string json = JsonSerializer.Serialize(expected, JsonContext.Default.Location); + Location obj = JsonSerializer.Deserialize(json, JsonContext.Default.Location); VerifyLocation(expected, obj); } @@ -91,8 +87,8 @@ public static void RoundTripTypeNameClash() { RepeatedTypes.Location expected = CreateRepeatedLocation(); - string json = JsonSerializer.Serialize(expected, JsonContext.Default.Location); - RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, JsonContext.Default.Location); + string json = JsonSerializer.Serialize(expected, JsonContext.Default.RepeatedLocation); + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, JsonContext.Default.RepeatedLocation); VerifyRepeatedLocation(expected, obj); } @@ -367,8 +363,8 @@ public static void SerializeObjectArray_SimpleTypes_WithCustomOptions() JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; JsonContext context = new JsonContext(options); - string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, context); - object[] arr = JsonSerializer.Deserialize(json, context); + string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), context); + object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), context); JsonElement hello = (JsonElement)arr[0]; JsonElement world = (JsonElement)arr[1]; diff --git a/src/libraries/System.Text.Json/source_generation/tests/System.Text.Json.SourceGeneration.Tests.csproj b/src/libraries/System.Text.Json/gen/tests/System.Text.Json.SourceGeneration.Tests.csproj similarity index 80% rename from src/libraries/System.Text.Json/source_generation/tests/System.Text.Json.SourceGeneration.Tests.csproj rename to src/libraries/System.Text.Json/gen/tests/System.Text.Json.SourceGeneration.Tests.csproj index 61e73c9b5a9192..773aade26f9921 100644 --- a/src/libraries/System.Text.Json/source_generation/tests/System.Text.Json.SourceGeneration.Tests.csproj +++ b/src/libraries/System.Text.Json/gen/tests/System.Text.Json.SourceGeneration.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/libraries/System.Text.Json/source_generation/tests/TestClasses.cs b/src/libraries/System.Text.Json/gen/tests/TestClasses.cs similarity index 95% rename from src/libraries/System.Text.Json/source_generation/tests/TestClasses.cs rename to src/libraries/System.Text.Json/gen/tests/TestClasses.cs index b5f4f84b356db5..d5ebf88645f2c6 100644 --- a/src/libraries/System.Text.Json/source_generation/tests/TestClasses.cs +++ b/src/libraries/System.Text.Json/gen/tests/TestClasses.cs @@ -5,10 +5,8 @@ using System.Text.Json.Serialization; using System.Text.Json.SourceGeneration.Tests; -[assembly: JsonSerializable(typeof(System.Text.Json.SourceGeneration.Tests.RepeatedTypes.Location))] [assembly: JsonSerializable(typeof(Location))] -// TODO: add tests for when same type is specified in ths manner. -[assembly: JsonSerializable(typeof(System.Text.Json.SourceGeneration.Tests.Location))] +[assembly: JsonSerializable(typeof(System.Text.Json.SourceGeneration.Tests.RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")] [assembly: JsonSerializable(typeof(ActiveOrUpcomingEvent))] [assembly: JsonSerializable(typeof(CampaignSummaryViewModel))] [assembly: JsonSerializable(typeof(IndexViewModel))] diff --git a/src/libraries/System.Text.Json/source_generation/unit_tests/CompilationHelper.cs b/src/libraries/System.Text.Json/gen/unit_tests/CompilationHelper.cs similarity index 81% rename from src/libraries/System.Text.Json/source_generation/unit_tests/CompilationHelper.cs rename to src/libraries/System.Text.Json/gen/unit_tests/CompilationHelper.cs index 32a436fde31cde..2c092817d3ea54 100644 --- a/src/libraries/System.Text.Json/source_generation/unit_tests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/gen/unit_tests/CompilationHelper.cs @@ -194,5 +194,51 @@ public class Location return CreateCompilation(source); } + + public static Compilation CreateRepeatedLocationsWithResolutionCompilation() + { + string source = @" + using System; + using System.Collections; + using System.Collections.Generic; + using System.Text.Json.Serialization; + + [assembly: JsonSerializable(typeof(Fake.Location))] + [assembly: JsonSerializable(typeof(HelloWorld.Location), TypeInfoPropertyName = ""RepeatedLocation"")] + + namespace Fake + { + public class Location + { + public int FakeId { get; set; } + public string FakeAddress1 { get; set; } + public string FakeAddress2 { get; set; } + public string FakeCity { get; set; } + public string FakeState { get; set; } + public string FakePostalCode { get; set; } + public string FakeName { get; set; } + public string FakePhoneNumber { get; set; } + public string FakeCountry { get; set; } + } + } + + namespace HelloWorld + { + public class Location + { + public int Id { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + public string City { get; set; } + public string State { get; set; } + public string PostalCode { get; set; } + public string Name { get; set; } + public string PhoneNumber { get; set; } + public string Country { get; set; } + } + }"; + + return CreateCompilation(source); + } } } diff --git a/src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs similarity index 89% rename from src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs rename to src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs index a3b69d0355f5c4..447b6e5e3a80b0 100644 --- a/src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs +++ b/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs @@ -129,12 +129,22 @@ public class IndexViewModel [Fact] public void NameClashSourceGeneration() { + // Without resolution. Compilation compilation = CompilationHelper.CreateRepeatedLocationsCompilation(); - JsonSourceGenerator generator = new JsonSourceGenerator(); - CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + string[] expectedWarningDiagnostics = new string[] { "There are multiple types named Location. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision." }; + + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + + // With resolution. + compilation = CompilationHelper.CreateRepeatedLocationsWithResolutionCompilation(); + generator = new JsonSourceGenerator(); + CompilationHelper.RunGenerators(compilation, out generatorDiags, generator); + CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); diff --git a/src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorTests.cs similarity index 100% rename from src/libraries/System.Text.Json/source_generation/unit_tests/JsonSourceGeneratorTests.cs rename to src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorTests.cs diff --git a/src/libraries/System.Text.Json/source_generation/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj b/src/libraries/System.Text.Json/gen/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj similarity index 89% rename from src/libraries/System.Text.Json/source_generation/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj rename to src/libraries/System.Text.Json/gen/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj index 8ae212157c3618..15016b41746de0 100644 --- a/src/libraries/System.Text.Json/source_generation/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj +++ b/src/libraries/System.Text.Json/gen/unit_tests/System.Text.Json.SourceGeneration.UnitTests.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/libraries/System.Text.Json/source_generation/unit_tests/TypeWrapperTests.cs b/src/libraries/System.Text.Json/gen/unit_tests/TypeWrapperTests.cs similarity index 100% rename from src/libraries/System.Text.Json/source_generation/unit_tests/TypeWrapperTests.cs rename to src/libraries/System.Text.Json/gen/unit_tests/TypeWrapperTests.cs diff --git a/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj b/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj index c02e237aea1ea3..23e6dced988fe4 100644 --- a/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj +++ b/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj @@ -5,6 +5,7 @@ net461;netcoreapp2.0;uap10.0.16299;$(AllXamarinFrameworks) + - <_allSrc Include="$(MSBuildThisFileDirectory)System.Text.Json\source_generation\src\System.Text.Json.SourceGeneration.csproj" /> + Date: Sun, 18 Apr 2021 18:16:02 -0700 Subject: [PATCH 3/5] Address review feedback --- eng/Versions.props | 4 - .../Common/JsonIgnoreCondition.cs | 2 +- .../Common/JsonNumberHandling.cs | 1 - .../gen/src/IsExternalInit.cs | 2 +- .../gen/src/JsonSerializableSyntaxReceiver.cs | 2 +- .../gen/src/JsonSourceGenerator.cs | 51 +++++++----- .../src/JsonSourceGeneratorHelper.Generate.cs | 32 +------- .../gen/src/JsonSourceGeneratorHelper.cs | 4 +- .../System.Text.Json.SourceGeneration.csproj | 3 +- .../gen/tests/JsonSourceGeneratorTests.cs | 12 +-- ...em.Text.Json.SourceGeneration.Tests.csproj | 3 +- .../gen/unit_tests/CompilationHelper.cs | 27 ++++++- .../JsonSourceGeneratorDiagnosticsTests.cs | 68 +++++++++++----- .../unit_tests/JsonSourceGeneratorTests.cs | 78 ++++++++++++++++++- .../System.Text.Json/ref/System.Text.Json.cs | 2 +- .../JsonSerializer.Read.String.cs | 2 - .../JsonSerializerOptions.Converters.cs | 2 +- .../Metadata/JsonMetadataServices.cs | 6 +- .../Metadata/JsonPropertyInfoOfT.cs | 42 ++++------ .../MetadataServicesTests.cs | 15 ++-- .../TestClasses/TestClasses.Constructor.cs | 14 ++-- 21 files changed, 228 insertions(+), 144 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index ed14d21579bdb4..b23a6f1d9017df 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -177,10 +177,6 @@ 9.0.1-alpha.1.21212.1 9.0.1-alpha.1.21212.1 9.0.1-alpha.1.21212.1 - - 3.10.0-1.final - 3.3.2 - 3.10.0-1.final diff --git a/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs b/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs index f93ea31aaa4e53..5f2595961a868f 100644 --- a/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs +++ b/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs @@ -7,7 +7,7 @@ namespace System.Text.Json.Serialization /// When specified on , /// determines when properties and fields across the type graph are ignored. /// When specified on , controls whether - /// a property is ignored during serialization and deserialization. This option + /// a property or field is ignored during serialization and deserialization. This option /// overrides the setting on . /// #if BUILDING_SOURCE_GENERATOR diff --git a/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs b/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs index ea1d68fe33bba4..8dafef22a1982b 100644 --- a/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs +++ b/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs @@ -8,7 +8,6 @@ namespace System.Text.Json.Serialization /// [Flags] #if BUILDING_SOURCE_GENERATOR - // cref references below are not present when compiling this assembly. internal #else public diff --git a/src/libraries/System.Text.Json/gen/src/IsExternalInit.cs b/src/libraries/System.Text.Json/gen/src/IsExternalInit.cs index 626c3edde3ddc9..d5984b4be38351 100644 --- a/src/libraries/System.Text.Json/gen/src/IsExternalInit.cs +++ b/src/libraries/System.Text.Json/gen/src/IsExternalInit.cs @@ -6,5 +6,5 @@ namespace System.Runtime.CompilerServices /// /// Dummy class so C# init-only properties can compile on NetStandard. /// - public sealed class IsExternalInit { } + internal sealed class IsExternalInit { } } diff --git a/src/libraries/System.Text.Json/gen/src/JsonSerializableSyntaxReceiver.cs b/src/libraries/System.Text.Json/gen/src/JsonSerializableSyntaxReceiver.cs index 2600cafe6aa3c8..f60f4cfb686f9d 100644 --- a/src/libraries/System.Text.Json/gen/src/JsonSerializableSyntaxReceiver.cs +++ b/src/libraries/System.Text.Json/gen/src/JsonSerializableSyntaxReceiver.cs @@ -7,7 +7,7 @@ namespace System.Text.Json.SourceGeneration { - internal class JsonSerializableSyntaxReceiver : ISyntaxReceiver + internal sealed class JsonSerializableSyntaxReceiver : ISyntaxReceiver { public List CompilationUnits { get; } = new(); diff --git a/src/libraries/System.Text.Json/gen/src/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/src/JsonSourceGenerator.cs index 7e7f421f142911..ef2412f6aaf901 100644 --- a/src/libraries/System.Text.Json/gen/src/JsonSourceGenerator.cs +++ b/src/libraries/System.Text.Json/gen/src/JsonSourceGenerator.cs @@ -2,7 +2,6 @@ // 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.Text.Json.SourceGeneration.Reflection; @@ -22,7 +21,6 @@ public sealed class JsonSourceGenerator : ISourceGenerator /// /// Helper for unit tests. - /// TODO: refactor this after adopting emitter/parser pattern. /// public Dictionary? SerializableTypes => _helper.GetSerializableTypes(); @@ -65,7 +63,7 @@ public void Execute(GeneratorExecutionContext executionContext) foreach (AttributeListSyntax attributeListSyntax in compilationUnit.AttributeLists) { - AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.Single(); + AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.First(); IMethodSymbol attributeSymbol = compilationSemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol; if (attributeSymbol == null || !jsonSerializableAttribute.Equals(attributeSymbol.ContainingType, SymbolEqualityComparer.Default)) @@ -77,32 +75,45 @@ public void Execute(GeneratorExecutionContext executionContext) // Get JsonSerializableAttribute arguments. IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); - int argumentCount = attributeArguments.Count(); - - // Compiler shouldn't allow invalid signature for the JsonSerializable attribute. - Debug.Assert(argumentCount == 1 || argumentCount == 2); + ITypeSymbol? typeSymbol = null; + string? typeInfoPropertyName = null; - // Obtain the one `Type` argument that must be present in the constructor of the attribute. - AttributeArgumentSyntax typeArgumentNode = (AttributeArgumentSyntax)attributeArguments.First(); - TypeOfExpressionSyntax typeNode = (TypeOfExpressionSyntax)typeArgumentNode.ChildNodes().Single(); - ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single(); - ITypeSymbol typeSymbol = compilationSemanticModel.GetTypeInfo(typeNameSyntax).ConvertedType; + int i = 0; + foreach (AttributeArgumentSyntax node in attributeArguments) + { + if (i == 0) + { + TypeOfExpressionSyntax? typeNode = node.ChildNodes().Single() as TypeOfExpressionSyntax; + if (typeNode != null) + { + ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single(); + typeSymbol = compilationSemanticModel.GetTypeInfo(typeNameSyntax).ConvertedType; + } + } + else if (i == 1) + { + // Obtain the optional TypeInfoPropertyName string property on the attribute, if present. + SyntaxNode? typeInfoPropertyNameNode = node.ChildNodes().ElementAtOrDefault(1); + if (typeInfoPropertyNameNode != null) + { + typeInfoPropertyName = typeInfoPropertyNameNode.GetFirstToken().ValueText; + } + } + + i++; + } - string? typeInfoPropertyName = null; - // Obtain the optional TypeInfoPropertyName boolean property on the attribute, if present. - if (argumentCount == 2) + if (typeSymbol == null) { - AttributeArgumentSyntax stringArgumentNode = (AttributeArgumentSyntax)attributeArguments.ElementAt(1); - IEnumerable childNodes = stringArgumentNode.ChildNodes(); - SyntaxNode stringValueNode = childNodes.ElementAt(1); - typeInfoPropertyName = stringValueNode.GetFirstToken().ValueText; + continue; } + Type type = new TypeWrapper(typeSymbol, metadataLoadContext); if (type.Namespace == "") { // typeof() reference where the type's name isn't fully qualified. - // The compilation is not valid and user needs to fix their code. + // The compilation is not valid and the user needs to fix their code. // The compiler will notify the user so we don't have to. return; } diff --git a/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.Generate.cs b/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.Generate.cs index c80ea908a6015c..ce3cf43de9cee3 100644 --- a/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.Generate.cs +++ b/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.Generate.cs @@ -16,6 +16,8 @@ internal sealed partial class JsonSourceGeneratorHelper private readonly string _generationNamespace; // TODO: consider public option for this. + // Converter-honoring logic generation can be simplified + // if we don't plan to have a feature around this. private bool _honorRuntimeProvidedCustomConverters = true; private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter"; @@ -30,8 +32,6 @@ internal sealed partial class JsonSourceGeneratorHelper private const string CreateValueInfoMethodName = "CreateValueInfo"; - // TODO: Enable localization for these descriptors. - private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor( "SYSLIB2021", "Did not generate serialization metadata for type", @@ -375,18 +375,6 @@ private string GeneratePropMetadataInitFunc( ? $"ignoreCondition: JsonIgnoreCondition.{ignoreCondition.Value}" : "ignoreCondition: default"; - string encodedNameArg; - if (!NeedsEscaping(clrPropertyName)) - { - byte[] name = Encoding.UTF8.GetBytes(memberMetadata.JsonPropertyName ?? clrPropertyName); - string nameBytes = string.Join(", ", name.Select(b => $"{b}")); - encodedNameArg = "encodedName: new byte[] {" + nameBytes + "}"; - } - else - { - encodedNameArg = "encodedName: default"; - } - string converterNamedArg = memberMetadata.ConverterInstantiationLogic == null ? "converter: null" : $"converter: {memberMetadata.ConverterInstantiationLogic}"; @@ -405,8 +393,7 @@ private string GeneratePropMetadataInitFunc( {ignoreConditionNamedArg}, numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)}, propertyName: ""{clrPropertyName}"", - {jsonPropertyNameNamedArg}, - {encodedNameArg}); + {jsonPropertyNameNamedArg}); "); } } @@ -416,19 +403,6 @@ private string GeneratePropMetadataInitFunc( }}"); return sb.ToString(); - - static bool NeedsEscaping(string str) - { - foreach (char c in str) - { - if (c > 127 || c == '\'' || c == '\\') - { - return true; - } - } - - return false; - } } private string GenerateForType(TypeMetadata typeMetadata, string metadataInitSource, string? additionalSource = null) diff --git a/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.cs b/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.cs index 0ba08db791825c..10f1dd2f011a6e 100644 --- a/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.cs +++ b/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.cs @@ -94,7 +94,7 @@ public void RegisterRootSerializableType(Type type, string? typeInfoPropertyName public void GenerateSerializationMetadata() { - if (_rootSerializableTypes?.Count < 1) + if (_rootSerializableTypes == null || _rootSerializableTypes.Count == 0) { return; } @@ -462,6 +462,6 @@ private void PopulateKnownTypes(MetadataLoadContextInternal metadataLoadContext) private bool IsPrimitive(Type type) => _knownTypes.Contains(type) && type != _uriType && type != _versionType; - public Dictionary GetSerializableTypes() => _rootSerializableTypes?.ToDictionary(p => p.Key, p => p.Value.Type); + public Dictionary? GetSerializableTypes() => _rootSerializableTypes?.ToDictionary(p => p.Key, p => p.Value.Type); } } diff --git a/src/libraries/System.Text.Json/gen/src/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/gen/src/System.Text.Json.SourceGeneration.csproj index b1ff9ed3b9b2ad..157dd2733269d1 100644 --- a/src/libraries/System.Text.Json/gen/src/System.Text.Json.SourceGeneration.csproj +++ b/src/libraries/System.Text.Json/gen/src/System.Text.Json.SourceGeneration.csproj @@ -3,7 +3,7 @@ netstandard2.0 false enable - + CS1574 @@ -13,6 +13,7 @@ + diff --git a/src/libraries/System.Text.Json/gen/tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/gen/tests/JsonSourceGeneratorTests.cs index e045aa7a02b2d2..5b8c9e2f3423e8 100644 --- a/src/libraries/System.Text.Json/gen/tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/gen/tests/JsonSourceGeneratorTests.cs @@ -126,8 +126,8 @@ private static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent() return new ActiveOrUpcomingEvent { Id = 10, - CampaignManagedOrganizerName = "Name FamiltyName", - CampaignName = "The very new campaing", + CampaignManagedOrganizerName = "Name FamilyName", + CampaignName = "The very new campaign", Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", EndDate = DateTime.UtcNow.AddYears(1), Name = "Just a name", @@ -152,7 +152,7 @@ private static CampaignSummaryViewModel CreateCampaignSummaryViewModel() { return new CampaignSummaryViewModel { - Description = "Very nice campaing", + Description = "Very nice campaign", Headline = "The Headline", Id = 234235, OrganizationName = "The Company XYZ", @@ -178,7 +178,7 @@ private static IndexViewModel CreateIndexViewModel() IsNewAccount = false, FeaturedCampaign = new CampaignSummaryViewModel { - Description = "Very nice campaing", + Description = "Very nice campaign", Headline = "The Headline", Id = 234235, OrganizationName = "The Company XYZ", @@ -189,8 +189,8 @@ private static IndexViewModel CreateIndexViewModel() new ActiveOrUpcomingEvent { Id = 10, - CampaignManagedOrganizerName = "Name FamiltyName", - CampaignName = "The very new campaing", + CampaignManagedOrganizerName = "Name FamilyName", + CampaignName = "The very new campaign", Description = "The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", EndDate = DateTime.UtcNow.AddYears(1), Name = "Just a name", diff --git a/src/libraries/System.Text.Json/gen/tests/System.Text.Json.SourceGeneration.Tests.csproj b/src/libraries/System.Text.Json/gen/tests/System.Text.Json.SourceGeneration.Tests.csproj index 773aade26f9921..b83120156748e6 100644 --- a/src/libraries/System.Text.Json/gen/tests/System.Text.Json.SourceGeneration.Tests.csproj +++ b/src/libraries/System.Text.Json/gen/tests/System.Text.Json.SourceGeneration.Tests.csproj @@ -1,8 +1,7 @@ - + $(NetCoreAppCurrent);$(NetFrameworkCurrent) true - 3.10.0-1.final diff --git a/src/libraries/System.Text.Json/gen/unit_tests/CompilationHelper.cs b/src/libraries/System.Text.Json/gen/unit_tests/CompilationHelper.cs index 2c092817d3ea54..da4370619fcd4e 100644 --- a/src/libraries/System.Text.Json/gen/unit_tests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/gen/unit_tests/CompilationHelper.cs @@ -4,16 +4,22 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Xunit; namespace System.Text.Json.SourceGeneration.UnitTests { public class CompilationHelper { - public static Compilation CreateCompilation(string source, MetadataReference[] additionalReferences = null) + public static Compilation CreateCompilation( + string source, + MetadataReference[] additionalReferences = null, + string assemblyName = "TestAssembly", + bool includeSTJ = true) { // Bypass System.Runtime error. Assembly systemRuntimeAssembly = Assembly.Load("System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); @@ -24,7 +30,6 @@ public static Compilation CreateCompilation(string source, MetadataReference[] a List references = new List { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(JsonSerializerOptions).Assembly.Location), MetadataReference.CreateFromFile(typeof(Type).Assembly.Location), MetadataReference.CreateFromFile(typeof(KeyValuePair).Assembly.Location), MetadataReference.CreateFromFile(typeof(ContractNamespaceAttribute).Assembly.Location), @@ -32,6 +37,11 @@ public static Compilation CreateCompilation(string source, MetadataReference[] a MetadataReference.CreateFromFile(systemCollectionsAssemblyPath), }; + if (includeSTJ) + { + references.Add(MetadataReference.CreateFromFile(typeof(JsonSerializerOptions).Assembly.Location)); + } + // Add additional references as needed. if (additionalReferences != null) { @@ -42,7 +52,7 @@ public static Compilation CreateCompilation(string source, MetadataReference[] a } return CSharpCompilation.Create( - "TestAssembly", + assemblyName, syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source) }, references: references.ToArray(), options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) @@ -240,5 +250,16 @@ public class Location return CreateCompilation(source); } + + internal static void CheckDiagnosticMessages(ImmutableArray diagnostics, DiagnosticSeverity level, string[] expectedMessages) + { + string[] actualMessages = diagnostics.Where(diagnostic => diagnostic.Severity == level).Select(diagnostic => diagnostic.GetMessage()).ToArray(); + + // Can't depending on reflection order when generating type metadata. + Array.Sort(actualMessages); + Array.Sort(expectedMessages); + + Assert.Equal(expectedMessages, actualMessages); + } } } diff --git a/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs index 447b6e5e3a80b0..046c40127fe7ec 100644 --- a/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs +++ b/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorDiagnosticsTests.cs @@ -64,9 +64,9 @@ public class IndexViewModel "Generated serialization metadata for type JsonSourceGenerator.IndexViewModel", }; - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, expectedInfoDiagnostics); - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, new string[] { }); - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, new string[] { }); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, expectedInfoDiagnostics); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, new string[] { }); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, new string[] { }); } [Fact] @@ -121,9 +121,9 @@ public class IndexViewModel // Expected warning logs. string[] expectedWarningDiagnostics = new string[] { "Did not generate serialization metadata for type System.Collections.Generic.ISet" }; - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, expectedInfoDiagnostics); - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, new string[] { }); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, expectedInfoDiagnostics); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, new string[] { }); } [Fact] @@ -136,29 +136,61 @@ public void NameClashSourceGeneration() string[] expectedWarningDiagnostics = new string[] { "There are multiple types named Location. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision." }; - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); // With resolution. compilation = CompilationHelper.CreateRepeatedLocationsWithResolutionCompilation(); generator = new JsonSourceGenerator(); CompilationHelper.RunGenerators(compilation, out generatorDiags, generator); - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); - CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); } - private void CheckDiagnosticMessages(ImmutableArray diagnostics, DiagnosticSeverity level, string[] expectedMessages) + [Fact] + public void ProgramsThatDontUseGeneratorCompile() { - string[] actualMessages = diagnostics.Where(diagnostic => diagnostic.Severity == level).Select(diagnostic => diagnostic.GetMessage()).ToArray(); + // No STJ usage. + string source = @"using System; + +public class Program +{ + public static void Main() + { + Console.WriteLine(""Hello World""); + + } +} +"; + Compilation compilation = CompilationHelper.CreateCompilation(source); + JsonSourceGenerator generator = new JsonSourceGenerator(); + CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); - // Can't depending on reflection order when generating type metadata. - Array.Sort(actualMessages); - Array.Sort(expectedMessages); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + + // With STJ usage. + source = @"using System.Text.Json; + +public class Program +{ + public static void Main() + { + JsonSerializer.Serialize(""Hello World""); + } +} +"; + compilation = CompilationHelper.CreateCompilation(source); + generator = new JsonSourceGenerator(); + CompilationHelper.RunGenerators(compilation, out generatorDiags, generator); - Assert.Equal(expectedMessages, actualMessages); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); } } } diff --git a/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorTests.cs index 3183872b0d46b5..945c79f044cff8 100644 --- a/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/gen/unit_tests/JsonSourceGeneratorTests.cs @@ -1,6 +1,7 @@ // 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.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; @@ -226,7 +227,82 @@ public void UsePrivates() CheckFieldsPropertiesMethods("ReferencedAssembly.Location", ref generator, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType ); } - // TODO: add test where there is a local invalid System.Text.Json.JsonSerializableAttribute (https://github.com/dotnet/runtimelab/issues/29) + [Theory] + [InlineData("System.Text.Json", true)] + [InlineData("System.Text.Json.Not", true)] + [InlineData("System.Text.Json", false)] + [InlineData("System.Text.Json.Not", false)] + public static void LocalJsonSerializableAttributeExpectedShape(string assemblyName, bool includeSTJ) + { + string source = @"using System; +using System.Text.Json.Serialization; + +[assembly: JsonSerializable(typeof(int))] +[assembly: JsonSerializable(typeof(string), TypeInfoPropertyName = ""Str"")] + +namespace System.Text.Json.Serialization +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class JsonSerializableAttribute : JsonAttribute + { + public string TypeInfoPropertyName { get; set; } + + public JsonSerializableAttribute(Type type) { } + } +}"; + + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences: null, assemblyName, includeSTJ); + JsonSourceGenerator generator = new JsonSourceGenerator(); + + CompilationHelper.RunGenerators(compilation, out ImmutableArray generatorDiags, generator); + + Dictionary types = generator.SerializableTypes; + if (includeSTJ) + { + Assert.Equal("System.Int32", types["System.Int32"].FullName); + Assert.Equal("System.String", types["System.String"].FullName); + } + else + { + Assert.Null(types); + } + + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + } + + [Theory] + [InlineData("System.Text.Json", true)] + [InlineData("System.Text.Json.Not", true)] + [InlineData("System.Text.Json", false)] + [InlineData("System.Text.Json.Not", false)] + public static void LocalJsonSerializableAttributeUnexpectedShape(string assemblyName, bool includeSTJ) + { + string source = @"using System; +using System.Text.Json.Serialization; + +[assembly: JsonSerializable(typeof(int))] + +namespace System.Text.Json.Serialization +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class JsonSerializableAttribute : JsonAttribute + { + public JsonSerializableAttribute(string typeInfoPropertyName, Type type) { } + } +}"; + + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences: null, assemblyName, includeSTJ); + JsonSourceGenerator generator = new JsonSourceGenerator(); + + CompilationHelper.RunGenerators(compilation, out ImmutableArray generatorDiags, generator); + Assert.Null(generator.SerializableTypes); + + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + } [Fact] public void NameClashCompilation() diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 70e14526c48bb8..481eddecc3134b 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -794,7 +794,7 @@ public static partial class JsonMetadataServices public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateDictionaryInfo(System.Text.Json.JsonSerializerOptions options, System.Func createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where TCollection : System.Collections.Generic.Dictionary where TKey : notnull { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateListInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where TCollection : System.Collections.Generic.List { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateObjectInfo() where T : notnull { throw null; } - public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter? converter, System.Func? getter, System.Action? setter, System.Text.Json.Serialization.JsonIgnoreCondition ignoreCondition, System.Text.Json.Serialization.JsonNumberHandling numberHandling, string propertyName, string? jsonPropertyName, byte[]? encodedName) { throw null; } + public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter? converter, System.Func? getter, System.Action? setter, System.Text.Json.Serialization.JsonIgnoreCondition ignoreCondition, System.Text.Json.Serialization.JsonNumberHandling numberHandling, string propertyName, string? jsonPropertyName) { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateValueInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; } public static System.Text.Json.Serialization.JsonConverter GetEnumConverter(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; } public static System.Text.Json.Serialization.JsonConverter GetNullableConverter(System.Text.Json.Serialization.Metadata.JsonTypeInfo underlyingTypeInfo) where T : struct { throw null; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs index 90d5615c8ddef0..6be743dacb6bfb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs @@ -325,8 +325,6 @@ public static partial class JsonSerializer private static TValue? DeserializeUsingMetadata(string json, JsonTypeInfo? jsonTypeInfo) { - // TODO: this would be when to fallback to regular warm-up code-paths. - // For validation during development, we don't expect this to be null. if (jsonTypeInfo == null) { throw new ArgumentNullException(nameof(jsonTypeInfo)); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs index 977dcd2d2e78d5..c0c26ed1461aa4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs @@ -163,7 +163,7 @@ public JsonConverter GetConverter(Type typeToConvert) } // Priority 1: If there is a JsonSerializerContext, fetch the converter from there. - converter = _context?.GetTypeInfo(typeToConvert)?.PropertyInfoForTypeInfo!.ConverterBase; + converter = _context?.GetTypeInfo(typeToConvert)?.PropertyInfoForTypeInfo?.ConverterBase; // Priority 2: Attempt to get custom converter added at runtime. // Currently there is not a way at runtime to override the [JsonConverter] when applied to a property. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs index 22757dbcbdcc3a..70d6abf8d90fe6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs @@ -28,8 +28,7 @@ public static JsonPropertyInfo CreatePropertyInfo( JsonIgnoreCondition ignoreCondition, JsonNumberHandling numberHandling, string propertyName, - string? jsonPropertyName, - byte[]? encodedName) + string? jsonPropertyName) { if (options == null) { @@ -72,8 +71,7 @@ public static JsonPropertyInfo CreatePropertyInfo( ignoreCondition, numberHandling, propertyName, - jsonPropertyName, - encodedName); + jsonPropertyName); return jsonPropertyInfo; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs index 1270d1a119c904..fd970b455f288c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs @@ -124,48 +124,32 @@ internal void InitializeForSourceGen( JsonIgnoreCondition ignoreCondition, JsonNumberHandling numberHandling, string propertyName, - string? jsonPropertyName, - byte[]? encodedName) + string? jsonPropertyName) { Options = options; ClrName = propertyName; // Property name settings. - if (encodedName != null && options.PropertyNamingPolicy == null && options.Encoder == null) + if (jsonPropertyName != null) { - NameAsString = jsonPropertyName ?? propertyName; - NameAsUtf8Bytes = encodedName; - - int nameSectionLength = encodedName.Length + 3; - EscapedNameSection = new byte[nameSectionLength]; - EscapedNameSection[0] = (byte)'"'; - encodedName.CopyTo(EscapedNameSection, 1); - EscapedNameSection[nameSectionLength - 2] = (byte)'"'; - EscapedNameSection[nameSectionLength - 1] = (byte)':'; + NameAsString = jsonPropertyName; + } + else if (options.PropertyNamingPolicy == null) + { + NameAsString = ClrName; } else { - if (jsonPropertyName != null) - { - NameAsString = jsonPropertyName; - } - else if (options.PropertyNamingPolicy == null) + NameAsString = options.PropertyNamingPolicy.ConvertName(ClrName); + if (NameAsString == null) { - NameAsString = ClrName; + ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(DeclaringType, this); } - else - { - NameAsString = options.PropertyNamingPolicy.ConvertName(ClrName); - if (NameAsString == null) - { - ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(DeclaringType, this); - } - } - - NameAsUtf8Bytes ??= Encoding.UTF8.GetBytes(NameAsString!); - EscapedNameSection ??= JsonHelpers.GetEscapedPropertyNameSection(NameAsUtf8Bytes, Options.Encoder); } + NameAsUtf8Bytes ??= Encoding.UTF8.GetBytes(NameAsString!); + EscapedNameSection ??= JsonHelpers.GetEscapedPropertyNameSection(NameAsUtf8Bytes, Options.Encoder); + if (ignoreCondition == JsonIgnoreCondition.Always) { IsIgnored = true; diff --git a/src/libraries/System.Text.Json/tests/Serialization/MetadataServicesTests/MetadataServicesTests.cs b/src/libraries/System.Text.Json/tests/Serialization/MetadataServicesTests/MetadataServicesTests.cs index 74d3c641ceb1b9..0e0818222cdb4a 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/MetadataServicesTests/MetadataServicesTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/MetadataServicesTests/MetadataServicesTests.cs @@ -29,8 +29,7 @@ public static void CreatePropertyInfo() ignoreCondition: default, numberHandling: default, propertyName: "MyInt", - jsonPropertyName: null, - encodedName: null)); + jsonPropertyName: null)); Assert.Contains("options", ane.ToString()); // Null declaring type @@ -45,8 +44,7 @@ public static void CreatePropertyInfo() ignoreCondition: default, numberHandling: default, propertyName: "MyInt", - jsonPropertyName: null, - encodedName: null)); + jsonPropertyName: null)); Assert.Contains("declaringType", ane.ToString()); // Null property type info @@ -61,8 +59,7 @@ public static void CreatePropertyInfo() ignoreCondition: default, numberHandling: default, propertyName: "MyInt", - jsonPropertyName: null, - encodedName: null)); + jsonPropertyName: null)); Assert.Contains("propertyTypeInfo", ane.ToString()); // Null property name @@ -77,8 +74,7 @@ public static void CreatePropertyInfo() ignoreCondition: default, numberHandling: default, propertyName: null, - jsonPropertyName: null, - encodedName: null)); + jsonPropertyName: null)); Assert.Contains("propertyName", ane.ToString()); // Invalid converter @@ -94,8 +90,7 @@ public static void CreatePropertyInfo() ignoreCondition: default, numberHandling: default, propertyName: "MyProp", - jsonPropertyName: null, - encodedName: null)); + jsonPropertyName: null)); string ioeAsStr = ioe.ToString(); Assert.Contains("Point.MyProp", ioeAsStr); Assert.Contains("MyClass", ioeAsStr); diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.Constructor.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.Constructor.cs index e4d25ea36926c5..0d2749c7b13ca6 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.Constructor.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.Constructor.cs @@ -1778,8 +1778,8 @@ public Parameterized_IndexViewModel_Immutable( ""Id"": 10, ""ImageUrl"": ""https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png"", ""Name"": ""Just a name"", - ""CampaignName"": ""The very new campaing"", - ""CampaignManagedOrganizerName"": ""Name FamiltyName"", + ""CampaignName"": ""The very new campaign"", + ""CampaignManagedOrganizerName"": ""Name FamilyName"", ""Description"": ""The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities."", ""StartDate"": ""2019-01-30T12:01:02+00:00"", ""EndDate"": ""2019-01-30T12:01:02+00:00"" @@ -1788,7 +1788,7 @@ public Parameterized_IndexViewModel_Immutable( ""FeaturedCampaign"": { ""Id"": 234235, ""Title"": ""Promoting Open Source"", - ""Description"": ""Very nice campaing"", + ""Description"": ""Very nice campaign"", ""ImageUrl"": ""https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png"", ""OrganizationName"": ""The Company XYZ"", ""Headline"": ""The Headline"" @@ -1807,15 +1807,15 @@ public void Verify() ActiveOrUpcomingEvent @event = ActiveOrUpcomingEvents.First(); Assert.Equal(10, @event.Id); - Assert.Equal("Name FamiltyName", @event.CampaignManagedOrganizerName); - Assert.Equal("The very new campaing", @event.CampaignName); + Assert.Equal("Name FamilyName", @event.CampaignManagedOrganizerName); + Assert.Equal("The very new campaign", @event.CampaignName); Assert.Equal("The .NET Foundation works with Microsoft and the broader industry to increase the exposure of open source projects in the .NET community and the .NET Foundation. The .NET Foundation provides access to these resources to projects and looks to promote the activities of our communities.", @event.Description); Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), @event.EndDate); Assert.Equal("Just a name", @event.Name); Assert.Equal("https://www.dotnetfoundation.org/theme/img/carousel/foundation-diagram-content.png", @event.ImageUrl); Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), @event.StartDate); - Assert.Equal("Very nice campaing", FeaturedCampaign.Description); + Assert.Equal("Very nice campaign", FeaturedCampaign.Description); Assert.Equal("The Headline", FeaturedCampaign.Headline); Assert.Equal(234235, FeaturedCampaign.Id); Assert.Equal("The Company XYZ", FeaturedCampaign.OrganizationName); @@ -2324,7 +2324,7 @@ public class MyEventsListerItem public static MyEventsListerItem Instance = new MyEventsListerItem { - Campaign = "A very nice campaing", + Campaign = "A very nice campaign", EndDate = DateTime.UtcNow.AddDays(7), EventId = 321, EventName = "wonderful name", From 78f11dc7699e8a3d1a4508d79510d5303f58f553 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Sun, 18 Apr 2021 21:20:31 -0700 Subject: [PATCH 4/5] Align project structure with src-gen conventions --- .../System.Text.Json/System.Text.Json.sln | 59 ++++++++----------- .../gen/{src => }/ClassType.cs | 0 .../gen/{src => }/CollectionType.cs | 0 .../gen/{src => }/IsExternalInit.cs | 0 .../JsonSerializableSyntaxReceiver.cs | 0 .../gen/{src => }/JsonSourceGenerator.cs | 0 .../JsonSourceGeneratorHelper.Generate.cs | 0 .../{src => }/JsonSourceGeneratorHelper.cs | 0 .../{src => }/ObjectConstructionStrategy.cs | 0 .../gen/{src => }/PropertyMetadata.cs | 0 .../{src => }/Reflection/AssemblyWrapper.cs | 0 .../Reflection/ConstructorInfoWrapper.cs | 0 .../Reflection/CustomAttributeDataWrapper.cs | 0 .../{src => }/Reflection/FieldInfoWrapper.cs | 0 .../{src => }/Reflection/MemberInfoWrapper.cs | 0 .../Reflection/MetadataLoadContextInternal.cs | 0 .../{src => }/Reflection/MethodInfoWrapper.cs | 0 .../Reflection/ParameterInfoWrapper.cs | 0 .../Reflection/PropertyInfoWrapper.cs | 0 .../Reflection/ReflectionExtensions.cs | 0 .../{src => }/Reflection/RoslynExtensions.cs | 0 .../{src => }/Reflection/TypeExtensions.cs | 0 .../gen/{src => }/Reflection/TypeWrapper.cs | 0 .../System.Text.Json.SourceGeneration.csproj | 4 +- .../gen/{src => }/TypeMetadata.cs | 0 .../pkg/System.Text.Json.pkgproj | 2 +- .../JsonSourceGeneratorTests.cs | 0 ...em.Text.Json.SourceGeneration.Tests.csproj | 2 +- .../TestClasses.cs | 0 .../CompilationHelper.cs | 0 .../JsonSourceGeneratorDiagnosticsTests.cs | 0 .../JsonSourceGeneratorTests.cs | 0 ...ext.Json.SourceGeneration.UnitTests.csproj | 2 +- .../TypeWrapperTests.cs | 0 .../BitStackTests.cs | 0 .../BufferFactory.cs | 0 .../BufferSegment.cs | 0 .../DebuggerTests.cs | 0 .../FixedSizedBufferWriter.cs | 0 .../InvalidBufferWriter.cs | 0 .../JsonBase64TestData.cs | 0 .../JsonDateTimeTestData.cs | 0 .../JsonDocumentTests.cs | 0 .../JsonElementCloneTests.cs | 0 .../JsonElementParseTests.cs | 0 .../JsonElementWriteTests.cs | 0 .../JsonEncodedTextTests.cs | 0 .../JsonGuidTestData.cs | 0 .../JsonNode/Common.cs | 0 .../JsonNode/DynamicTests.cs | 0 .../JsonNode/JsonArrayTests.cs | 0 .../JsonNode/JsonNodeOperatorTests.cs | 0 .../JsonNode/JsonNodeTests.cs | 0 .../JsonNode/JsonObjectTests.cs | 0 .../JsonNode/JsonValueTests.cs | 0 .../JsonNode/ParentPathRootTests.cs | 0 .../JsonNode/ParseTests.cs | 0 .../JsonNode/SerializerInteropTests.cs | 0 .../JsonNode/ToStringTests.cs | 0 .../JsonNumberTestData.cs | 0 .../JsonPropertyTests.cs | 0 .../JsonReaderStateAndOptionsTests.cs | 0 .../JsonTestHelper.cs | 0 .../JsonWriterOptionsTests.cs | 0 .../NewtonsoftTests/CamelCaseTests.cs | 0 .../CustomObjectConverterTests.cs | 0 .../NewtonsoftTests/DateTimeConverterTests.cs | 0 .../NewtonsoftTests/EnumConverterTests.cs | 0 .../ImmutableCollectionsTests.cs | 0 .../NewtonsoftTests/JsonSerializerTests.cs | 0 .../Resources/Strings.resx | 0 .../Serialization/Array.ReadTests.cs | 0 .../Serialization/Array.WriteTests.cs | 0 .../Serialization/CacheTests.cs | 0 .../Serialization/CamelCaseUnitTests.cs | 0 .../CollectionTests.AsyncEnumerable.cs | 0 .../CollectionTests.Concurrent.Write.cs | 0 .../CollectionTests.Concurrent.cs | 0 .../CollectionTests.Dictionary.KeyPolicy.cs | 0 ...CollectionTests.Dictionary.NonStringKey.cs | 0 .../CollectionTests.Dictionary.cs | 0 .../CollectionTests.Generic.Read.cs | 0 .../CollectionTests.Generic.Write.cs | 0 .../CollectionTests.Immutable.Read.cs | 0 .../CollectionTests.Immutable.Write.cs | 0 .../CollectionTests.KeyValuePair.cs | 0 .../CollectionTests.NonGeneric.Read.cs | 0 .../CollectionTests.NonGeneric.Write.cs | 0 .../CollectionTests.ObjectModel.Read.cs | 0 .../CollectionTests.ObjectModel.Write.cs | 0 .../CollectionTests.Specialized.Read.cs | 0 .../CollectionTests.Specialized.Write.cs | 0 .../ConstructorTests.AttributePresence.cs | 0 .../ConstructorTests.Cache.cs | 0 .../ConstructorTests.Exceptions.cs | 0 .../ConstructorTests.ParameterMatching.cs | 0 .../ConstructorTests.Stream.cs | 0 .../ContinuationTests.NullToken.cs | 0 .../Serialization/ContinuationTests.cs | 0 .../CustomConverterTests.Array.cs | 0 .../CustomConverterTests.Attribute.cs | 0 .../CustomConverterTests.BadConverters.cs | 0 .../CustomConverterTests.Callback.cs | 0 ...rTests.ContravariantDictionaryConverter.cs | 0 .../CustomConverterTests.DerivedTypes.cs | 0 ...mConverterTests.DictionaryEnumConverter.cs | 0 ...mConverterTests.DictionaryGuidConverter.cs | 0 ...terTests.DictionaryInt32StringConverter.cs | 0 ....DictionaryInt32StringKeyValueConverter.cs | 0 ...verterTests.DictionaryKeyValueConverter.cs | 0 ...stomConverterTests.Dynamic.Sample.Tests.cs | 0 .../CustomConverterTests.Dynamic.Sample.cs | 0 .../CustomConverterTests.Enum.cs | 0 .../CustomConverterTests.Exceptions.cs | 0 .../CustomConverterTests.HandleNull.cs | 0 .../CustomConverterTests.Int32.cs | 0 .../CustomConverterTests.InvalidCast.cs | 0 .../CustomConverterTests.List.cs | 0 .../CustomConverterTests.NullValueType.cs | 0 .../CustomConverterTests.NullableTypes.cs | 0 .../CustomConverterTests.Object.cs | 0 .../CustomConverterTests.Point.cs | 0 .../CustomConverterTests.Polymorphic.cs | 0 .../CustomConverterTests.ReadAhead.cs | 0 .../CustomConverterTests.ValueTypedMember.cs | 0 .../CustomConverterTests.cs | 0 .../Serialization/CyclicTests.cs | 0 .../Serialization/DeserializationWrapper.cs | 0 .../Serialization/DynamicTests.cs | 0 .../Serialization/EnumConverterTests.cs | 0 .../Serialization/EnumTests.cs | 0 .../Serialization/ExceptionTests.cs | 0 .../Serialization/ExtensionDataTests.cs | 0 .../Serialization/InvalidJsonTests.cs | 0 .../Serialization/InvalidTypeTests.cs | 0 .../Serialization/IsExternalInit.cs | 0 .../Serialization/JsonDocumentTests.cs | 0 .../Serialization/JsonElementTests.cs | 0 .../MetadataServicesTests.Options.cs | 0 .../MetadataServicesTests.cs | 0 .../Serialization/Null.ReadTests.cs | 0 .../Serialization/Null.WriteTests.cs | 0 .../Serialization/NullableTests.cs | 0 .../Serialization/NumberHandlingTests.cs | 0 .../Serialization/Object.ReadTests.cs | 0 .../Serialization/Object.WriteTests.cs | 0 .../Serialization/OptionsTests.cs | 0 .../Serialization/PolymorphicTests.cs | 0 .../Serialization/PropertyNameTests.cs | 0 ...pertyVisibilityTests.NonPublicAccessors.cs | 0 .../Serialization/PropertyVisibilityTests.cs | 0 .../PropertyVisiblityTests.InitOnly.cs | 0 .../Serialization/ReadScenarioTests.cs | 0 .../Serialization/ReadValueTests.cs | 0 .../ReferenceHandlerTests.Deserialize.cs | 0 .../ReferenceHandlerTests.IgnoreCycles.cs | 0 .../ReferenceHandlerTests.Serialize.cs | 0 .../Serialization/ReferenceHandlerTests.cs | 0 .../SampleTestData.OrderPayload.cs | 0 .../Serialization/SerializationWrapper.cs | 0 .../Serialization/SpanTests.cs | 0 .../Serialization/Stream.Collections.cs | 0 .../Stream.DeserializeAsyncEnumerable.cs | 0 .../Serialization/Stream.ReadTests.cs | 0 .../Serialization/Stream.WriteTests.cs | 0 .../TestClasses.ConcurrentCollections.cs | 0 .../TestClasses/TestClasses.Constructor.cs | 0 .../TestClasses.GenericCollections.cs | 0 .../TestClasses.ImmutableCollections.cs | 0 .../TestClasses.NonGenericCollections.cs | 0 .../TestClasses/TestClasses.Polymorphic.cs | 0 .../TestClasses.SimpleTestClass.cs | 0 .../TestClasses.SimpleTestClassWithFields.cs | 0 ...estClasses.SimpleTestClassWithNullables.cs | 0 .../TestClasses.SimpleTestClassWithObject.cs | 0 ...Classes.SimpleTestClassWithObjectArrays.cs | 0 ...Classes.SimpleTestClassWithSimpleObject.cs | 0 .../TestClasses.SimpleTestStruct.cs | 0 .../TestClasses.SimpleTestStructWithFields.cs | 0 .../TestClasses.ValueTypedMember.cs | 0 .../Serialization/TestClasses/TestClasses.cs | 0 .../Serialization/TestData.cs | 0 .../Serialization/Value.ReadTests.cs | 0 .../Serialization/Value.WriteTests.cs | 0 .../Serialization/WriteValueTests.cs | 0 .../System.Text.Json.Tests.csproj | 4 +- .../TestCaseType.cs | 0 .../TestClasses.ClassWithComplexObjects.cs | 0 .../TrimmingTests/Collections/Array.cs | 0 .../Collections/ConcurrentDictionary.cs | 0 .../Collections/ConcurrentQueue.cs | 0 .../Collections/ConcurrentStack.cs | 0 .../Collections/DictionaryOfTKeyTValue.cs | 0 .../TrimmingTests/Collections/HashSetOfT.cs | 0 .../TrimmingTests/Collections/Hashtable.cs | 0 .../TrimmingTests/Collections/ICollection.cs | 0 .../Collections/ICollectionOfT.cs | 0 .../TrimmingTests/Collections/IDictionary.cs | 0 .../Collections/IDictionaryOfTKeyTValue.cs | 0 .../TrimmingTests/Collections/IEnumerable.cs | 0 .../Collections/IEnumerableOfT.cs | 0 .../Collections/IImmutableDictionary.cs | 0 .../Collections/IImmutableList.cs | 0 .../Collections/IImmutableQueue.cs | 0 .../Collections/IImmutableSet.cs | 0 .../Collections/IImmutableStack.cs | 0 .../TrimmingTests/Collections/IList.cs | 0 .../TrimmingTests/Collections/IListOfT.cs | 0 .../IReadOnlyDictionaryOfTKeyTValue.cs | 0 .../TrimmingTests/Collections/ISetOfT.cs | 0 .../Collections/ImmutableArray.cs | 0 .../Collections/ImmutableDictionary.cs | 0 .../Collections/ImmutableHashSet.cs | 0 .../Collections/ImmutableList.cs | 0 .../Collections/ImmutableQueue.cs | 0 .../Collections/ImmutableSortedDictionary.cs | 0 .../Collections/ImmutableSortedSet.cs | 0 .../Collections/ImmutableStack.cs | 0 .../TrimmingTests/Collections/ListOfT.cs | 0 .../TrimmingTests/Collections/Queue.cs | 0 .../TrimmingTests/Collections/QueueOfT.cs | 0 .../TrimmingTests/Collections/Stack.cs | 0 .../TrimmingTests/Collections/StackOfT.cs | 0 .../TrimmingTests/EnumConverterTest.cs | 0 .../TrimmingTests/Helper.cs | 0 .../JsonConverterAttributeTest.cs | 0 .../TrimmingTests/ObjectConvertersTest.cs | 0 .../Deserialize.FromReader.BoxedObject.cs | 0 .../Deserialize.FromReader.TypedObject.cs | 0 .../Deserialize.FromSpan.BoxedObject.cs | 0 .../Deserialize.FromSpan.TypedObject.cs | 0 .../Deserialize.FromString.BoxedObject.cs | 0 .../Deserialize.FromString.TypedObject.cs | 0 ...DeserializeAsync.FromStream.BoxedObject.cs | 0 ...DeserializeAsync.FromStream.TypedObject.cs | 0 .../Serialize.ToByteArray.BoxedObject.cs | 0 .../Serialize.ToByteArray.TypedObject.cs | 0 ...rialize.ToString.BoxedObject.WithWriter.cs | 0 .../Serialize.ToString.BoxedObject.cs | 0 ...rialize.ToString.TypedObject.WithWriter.cs | 0 .../Serialize.ToString.TypedObject.cs | 0 .../SerializeAsync.ToStream.BoxedObject.cs | 0 .../SerializeAsync.ToStream.TypedObject.cs | 0 .../StackOrQueueNotRootedTest.cs | 0 .../System.Text.Json.TrimmingTests.proj | 0 .../Utf8JsonReaderTests.MultiSegment.cs | 0 .../Utf8JsonReaderTests.TryGet.Date.cs | 0 .../Utf8JsonReaderTests.TryGet.cs | 0 .../Utf8JsonReaderTests.ValueTextEquals.cs | 0 .../Utf8JsonReaderTests.cs | 0 .../Utf8JsonWriterTests.cs | 0 251 files changed, 32 insertions(+), 41 deletions(-) rename src/libraries/System.Text.Json/gen/{src => }/ClassType.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/CollectionType.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/IsExternalInit.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/JsonSerializableSyntaxReceiver.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/JsonSourceGenerator.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/JsonSourceGeneratorHelper.Generate.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/JsonSourceGeneratorHelper.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/ObjectConstructionStrategy.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/PropertyMetadata.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/AssemblyWrapper.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/ConstructorInfoWrapper.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/CustomAttributeDataWrapper.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/FieldInfoWrapper.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/MemberInfoWrapper.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/MetadataLoadContextInternal.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/MethodInfoWrapper.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/ParameterInfoWrapper.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/PropertyInfoWrapper.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/ReflectionExtensions.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/RoslynExtensions.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/TypeExtensions.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/Reflection/TypeWrapper.cs (100%) rename src/libraries/System.Text.Json/gen/{src => }/System.Text.Json.SourceGeneration.csproj (89%) rename src/libraries/System.Text.Json/gen/{src => }/TypeMetadata.cs (100%) rename src/libraries/System.Text.Json/{gen/tests => tests/System.Text.Json.SourceGeneration.Tests}/JsonSourceGeneratorTests.cs (100%) rename src/libraries/System.Text.Json/{gen/tests => tests/System.Text.Json.SourceGeneration.Tests}/System.Text.Json.SourceGeneration.Tests.csproj (79%) rename src/libraries/System.Text.Json/{gen/tests => tests/System.Text.Json.SourceGeneration.Tests}/TestClasses.cs (100%) rename src/libraries/System.Text.Json/{gen/unit_tests => tests/System.Text.Json.SourceGeneration.UnitTests}/CompilationHelper.cs (100%) rename src/libraries/System.Text.Json/{gen/unit_tests => tests/System.Text.Json.SourceGeneration.UnitTests}/JsonSourceGeneratorDiagnosticsTests.cs (100%) rename src/libraries/System.Text.Json/{gen/unit_tests => tests/System.Text.Json.SourceGeneration.UnitTests}/JsonSourceGeneratorTests.cs (100%) rename src/libraries/System.Text.Json/{gen/unit_tests => tests/System.Text.Json.SourceGeneration.UnitTests}/System.Text.Json.SourceGeneration.UnitTests.csproj (90%) rename src/libraries/System.Text.Json/{gen/unit_tests => tests/System.Text.Json.SourceGeneration.UnitTests}/TypeWrapperTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/BitStackTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/BufferFactory.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/BufferSegment.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/DebuggerTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/FixedSizedBufferWriter.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/InvalidBufferWriter.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonBase64TestData.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonDateTimeTestData.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonDocumentTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonElementCloneTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonElementParseTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonElementWriteTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonEncodedTextTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonGuidTestData.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/Common.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/DynamicTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/JsonArrayTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/JsonNodeOperatorTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/JsonNodeTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/JsonObjectTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/JsonValueTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/ParentPathRootTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/ParseTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/SerializerInteropTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNode/ToStringTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonNumberTestData.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonPropertyTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonReaderStateAndOptionsTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonTestHelper.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/JsonWriterOptionsTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/NewtonsoftTests/CamelCaseTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/NewtonsoftTests/CustomObjectConverterTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/NewtonsoftTests/DateTimeConverterTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/NewtonsoftTests/EnumConverterTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/NewtonsoftTests/ImmutableCollectionsTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/NewtonsoftTests/JsonSerializerTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Resources/Strings.resx (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Array.ReadTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Array.WriteTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CacheTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CamelCaseUnitTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.AsyncEnumerable.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Concurrent.Write.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Concurrent.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Dictionary.KeyPolicy.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Dictionary.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Generic.Read.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Generic.Write.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Immutable.Read.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Immutable.Write.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.KeyValuePair.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.NonGeneric.Read.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.NonGeneric.Write.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.ObjectModel.Read.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.ObjectModel.Write.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Specialized.Read.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CollectionTests/CollectionTests.Specialized.Write.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ConstructorTests/ConstructorTests.AttributePresence.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ConstructorTests/ConstructorTests.Cache.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ConstructorTests/ConstructorTests.Exceptions.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ConstructorTests/ConstructorTests.ParameterMatching.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ConstructorTests/ConstructorTests.Stream.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ContinuationTests.NullToken.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ContinuationTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Array.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Attribute.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.BadConverters.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Callback.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.ContravariantDictionaryConverter.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.DerivedTypes.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.DictionaryEnumConverter.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.DictionaryGuidConverter.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.DictionaryInt32StringConverter.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.DictionaryInt32StringKeyValueConverter.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.DictionaryKeyValueConverter.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.Tests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Enum.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Exceptions.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.HandleNull.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Int32.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.InvalidCast.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.List.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.NullValueType.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.NullableTypes.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Object.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Point.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.ReadAhead.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.ValueTypedMember.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CustomConverterTests/CustomConverterTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/CyclicTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/DeserializationWrapper.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/DynamicTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/EnumConverterTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/EnumTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ExceptionTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ExtensionDataTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/InvalidJsonTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/InvalidTypeTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/IsExternalInit.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/JsonDocumentTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/JsonElementTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/MetadataServicesTests/MetadataServicesTests.Options.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/MetadataServicesTests/MetadataServicesTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Null.ReadTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Null.WriteTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/NullableTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/NumberHandlingTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Object.ReadTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Object.WriteTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/OptionsTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/PolymorphicTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/PropertyNameTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/PropertyVisibilityTests.NonPublicAccessors.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/PropertyVisibilityTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/PropertyVisiblityTests.InitOnly.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ReadScenarioTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ReadValueTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ReferenceHandlerTests.Deserialize.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ReferenceHandlerTests.IgnoreCycles.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ReferenceHandlerTests.Serialize.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/ReferenceHandlerTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/SampleTestData.OrderPayload.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/SerializationWrapper.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/SpanTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Stream.Collections.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Stream.DeserializeAsyncEnumerable.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Stream.ReadTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Stream.WriteTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.ConcurrentCollections.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.Constructor.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.GenericCollections.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.ImmutableCollections.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.NonGenericCollections.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.Polymorphic.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.SimpleTestClass.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.SimpleTestClassWithFields.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.SimpleTestClassWithNullables.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.SimpleTestClassWithObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.SimpleTestClassWithObjectArrays.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.SimpleTestClassWithSimpleObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.SimpleTestStruct.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.SimpleTestStructWithFields.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.ValueTypedMember.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestClasses/TestClasses.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/TestData.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Value.ReadTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/Value.WriteTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Serialization/WriteValueTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/System.Text.Json.Tests.csproj (98%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TestCaseType.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TestClasses.ClassWithComplexObjects.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/Array.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ConcurrentDictionary.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ConcurrentQueue.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ConcurrentStack.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/DictionaryOfTKeyTValue.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/HashSetOfT.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/Hashtable.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ICollection.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ICollectionOfT.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IDictionary.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IDictionaryOfTKeyTValue.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IEnumerable.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IEnumerableOfT.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IImmutableDictionary.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IImmutableList.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IImmutableQueue.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IImmutableSet.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IImmutableStack.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IList.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IListOfT.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/IReadOnlyDictionaryOfTKeyTValue.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ISetOfT.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ImmutableArray.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ImmutableDictionary.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ImmutableHashSet.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ImmutableList.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ImmutableQueue.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ImmutableSortedDictionary.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ImmutableSortedSet.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ImmutableStack.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/ListOfT.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/Queue.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/QueueOfT.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/Stack.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Collections/StackOfT.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/EnumConverterTest.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/Helper.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/JsonConverterAttributeTest.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/ObjectConvertersTest.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Deserialize.FromReader.BoxedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Deserialize.FromReader.TypedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Deserialize.FromSpan.BoxedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Deserialize.FromSpan.TypedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Deserialize.FromString.BoxedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Deserialize.FromString.TypedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/DeserializeAsync.FromStream.BoxedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/DeserializeAsync.FromStream.TypedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Serialize.ToByteArray.BoxedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Serialize.ToByteArray.TypedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Serialize.ToString.BoxedObject.WithWriter.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Serialize.ToString.BoxedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Serialize.ToString.TypedObject.WithWriter.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/Serialize.ToString.TypedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/SerializeAsync.ToStream.BoxedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/SerializerEntryPoint/SerializeAsync.ToStream.TypedObject.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/StackOrQueueNotRootedTest.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/TrimmingTests/System.Text.Json.TrimmingTests.proj (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Utf8JsonReaderTests.MultiSegment.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Utf8JsonReaderTests.TryGet.Date.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Utf8JsonReaderTests.TryGet.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Utf8JsonReaderTests.ValueTextEquals.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Utf8JsonReaderTests.cs (100%) rename src/libraries/System.Text.Json/tests/{ => System.Text.Json.Tests}/Utf8JsonWriterTests.cs (100%) diff --git a/src/libraries/System.Text.Json/System.Text.Json.sln b/src/libraries/System.Text.Json/System.Text.Json.sln index 498eb09b54e185..433c378f98852a 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.sln +++ b/src/libraries/System.Text.Json/System.Text.Json.sln @@ -27,32 +27,25 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json", "ref\Sys EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json", "src\System.Text.Json.csproj", "{1285FF43-F491-4BE0-B92C-37DA689CBD4B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.Tests", "tests\System.Text.Json.Tests.csproj", "{607D1960-1428-40D5-8AC4-D98E3C9BCF47}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{3C544454-BD8B-44F4-A174-B61F18957613}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{EC8CE194-261A-4115-9582-E2DB1A25CAFB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{676B6044-FA47-4B7D-AEC2-FA94DB23A423}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{A403699B-1BB2-4B23-AE05-97608C23EB27}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0DBD3DDF-16F1-4E7D-80A5-F9D74C1219B7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration", "gen\src\System.Text.Json.SourceGeneration.csproj", "{24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{74017ACD-3AC1-4BB5-804B-D57E305FFBD9}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C4C10F31-F6DF-40DB-B892-CDFDD4DD9091}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration", "gen\System.Text.Json.SourceGeneration.csproj", "{6485EED4-C313-4551-9865-8ADCED603629}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.Tests", "gen\tests\System.Text.Json.SourceGeneration.Tests.csproj", "{9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.Tests", "tests\System.Text.Json.Tests\System.Text.Json.Tests.csproj", "{A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit_tests", "unit_tests", "{7B11F28A-2732-49CF-8000-682FBDE606A7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.Tests", "tests\System.Text.Json.SourceGeneration.Tests\System.Text.Json.SourceGeneration.Tests.csproj", "{33599A6C-F340-4E1B-9B4D-CB8946C22140}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.UnitTests", "gen\unit_tests\System.Text.Json.SourceGeneration.UnitTests.csproj", "{CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.UnitTests", "tests\System.Text.Json.SourceGeneration.UnitTests\System.Text.Json.SourceGeneration.UnitTests.csproj", "{18173CEC-895F-4F62-B7BB-B724457FEDCD}" EndProject Global GlobalSection(NestedProjects) = preSolution {102945CA-3736-4B2C-8E68-242A0B247F2B} = {3C544454-BD8B-44F4-A174-B61F18957613} - {607D1960-1428-40D5-8AC4-D98E3C9BCF47} = {3C544454-BD8B-44F4-A174-B61F18957613} {73D5739C-E382-4E22-A7D3-B82705C58C74} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB} {25C42754-B384-4842-8FA7-75D7A79ADF0D} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB} {4774F56D-16A8-4ABB-8C73-5F57609F1773} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB} @@ -66,12 +59,10 @@ Global {7909EB27-0D6E-46E6-B9F9-8A1EFD557018} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423} {9BCCDA15-8907-4AE3-8871-2F17775DDE4C} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423} {1285FF43-F491-4BE0-B92C-37DA689CBD4B} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423} - {0DBD3DDF-16F1-4E7D-80A5-F9D74C1219B7} = {A403699B-1BB2-4B23-AE05-97608C23EB27} - {24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF} = {0DBD3DDF-16F1-4E7D-80A5-F9D74C1219B7} - {C4C10F31-F6DF-40DB-B892-CDFDD4DD9091} = {A403699B-1BB2-4B23-AE05-97608C23EB27} - {9DE9B0B4-8721-4A6A-B73C-59D89E5212B9} = {C4C10F31-F6DF-40DB-B892-CDFDD4DD9091} - {7B11F28A-2732-49CF-8000-682FBDE606A7} = {A403699B-1BB2-4B23-AE05-97608C23EB27} - {CE147231-41C1-4FFE-A1CB-0BC3DCCD980A} = {7B11F28A-2732-49CF-8000-682FBDE606A7} + {6485EED4-C313-4551-9865-8ADCED603629} = {74017ACD-3AC1-4BB5-804B-D57E305FFBD9} + {A0178BAA-A1AF-4C69-8E4A-A700A2723DDC} = {3C544454-BD8B-44F4-A174-B61F18957613} + {33599A6C-F340-4E1B-9B4D-CB8946C22140} = {3C544454-BD8B-44F4-A174-B61F18957613} + {18173CEC-895F-4F62-B7BB-B724457FEDCD} = {3C544454-BD8B-44F4-A174-B61F18957613} EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -134,22 +125,22 @@ Global {1285FF43-F491-4BE0-B92C-37DA689CBD4B}.Debug|Any CPU.Build.0 = Debug|Any CPU {1285FF43-F491-4BE0-B92C-37DA689CBD4B}.Release|Any CPU.ActiveCfg = Release|Any CPU {1285FF43-F491-4BE0-B92C-37DA689CBD4B}.Release|Any CPU.Build.0 = Release|Any CPU - {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Debug|Any CPU.Build.0 = Debug|Any CPU - {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Release|Any CPU.ActiveCfg = Release|Any CPU - {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Release|Any CPU.Build.0 = Release|Any CPU - {24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24D86F06-B8C4-4A26-A6D4-1997FA0DD2FF}.Release|Any CPU.Build.0 = Release|Any CPU - {9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9DE9B0B4-8721-4A6A-B73C-59D89E5212B9}.Release|Any CPU.Build.0 = Release|Any CPU - {CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CE147231-41C1-4FFE-A1CB-0BC3DCCD980A}.Release|Any CPU.Build.0 = Release|Any CPU + {6485EED4-C313-4551-9865-8ADCED603629}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6485EED4-C313-4551-9865-8ADCED603629}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6485EED4-C313-4551-9865-8ADCED603629}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6485EED4-C313-4551-9865-8ADCED603629}.Release|Any CPU.Build.0 = Release|Any CPU + {A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}.Release|Any CPU.Build.0 = Release|Any CPU + {33599A6C-F340-4E1B-9B4D-CB8946C22140}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33599A6C-F340-4E1B-9B4D-CB8946C22140}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33599A6C-F340-4E1B-9B4D-CB8946C22140}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33599A6C-F340-4E1B-9B4D-CB8946C22140}.Release|Any CPU.Build.0 = Release|Any CPU + {18173CEC-895F-4F62-B7BB-B724457FEDCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18173CEC-895F-4F62-B7BB-B724457FEDCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18173CEC-895F-4F62-B7BB-B724457FEDCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18173CEC-895F-4F62-B7BB-B724457FEDCD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/libraries/System.Text.Json/gen/src/ClassType.cs b/src/libraries/System.Text.Json/gen/ClassType.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/ClassType.cs rename to src/libraries/System.Text.Json/gen/ClassType.cs diff --git a/src/libraries/System.Text.Json/gen/src/CollectionType.cs b/src/libraries/System.Text.Json/gen/CollectionType.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/CollectionType.cs rename to src/libraries/System.Text.Json/gen/CollectionType.cs diff --git a/src/libraries/System.Text.Json/gen/src/IsExternalInit.cs b/src/libraries/System.Text.Json/gen/IsExternalInit.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/IsExternalInit.cs rename to src/libraries/System.Text.Json/gen/IsExternalInit.cs diff --git a/src/libraries/System.Text.Json/gen/src/JsonSerializableSyntaxReceiver.cs b/src/libraries/System.Text.Json/gen/JsonSerializableSyntaxReceiver.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/JsonSerializableSyntaxReceiver.cs rename to src/libraries/System.Text.Json/gen/JsonSerializableSyntaxReceiver.cs diff --git a/src/libraries/System.Text.Json/gen/src/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/JsonSourceGenerator.cs rename to src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs diff --git a/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.Generate.cs b/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.Generate.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.Generate.cs rename to src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.Generate.cs diff --git a/src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.cs b/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/JsonSourceGeneratorHelper.cs rename to src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.cs diff --git a/src/libraries/System.Text.Json/gen/src/ObjectConstructionStrategy.cs b/src/libraries/System.Text.Json/gen/ObjectConstructionStrategy.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/ObjectConstructionStrategy.cs rename to src/libraries/System.Text.Json/gen/ObjectConstructionStrategy.cs diff --git a/src/libraries/System.Text.Json/gen/src/PropertyMetadata.cs b/src/libraries/System.Text.Json/gen/PropertyMetadata.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/PropertyMetadata.cs rename to src/libraries/System.Text.Json/gen/PropertyMetadata.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/AssemblyWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/AssemblyWrapper.cs rename to src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/ConstructorInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/ConstructorInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/CustomAttributeDataWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/CustomAttributeDataWrapper.cs rename to src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/FieldInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/FieldInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/MemberInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/MemberInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/MetadataLoadContextInternal.cs b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/MetadataLoadContextInternal.cs rename to src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/MethodInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/MethodInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/ParameterInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/ParameterInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/PropertyInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/PropertyInfoWrapper.cs rename to src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/ReflectionExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/ReflectionExtensions.cs rename to src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/RoslynExtensions.cs rename to src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/TypeExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/TypeExtensions.cs rename to src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs diff --git a/src/libraries/System.Text.Json/gen/src/Reflection/TypeWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/Reflection/TypeWrapper.cs rename to src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs diff --git a/src/libraries/System.Text.Json/gen/src/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj similarity index 89% rename from src/libraries/System.Text.Json/gen/src/System.Text.Json.SourceGeneration.csproj rename to src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj index 157dd2733269d1..92e70583da6722 100644 --- a/src/libraries/System.Text.Json/gen/src/System.Text.Json.SourceGeneration.csproj +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/src/libraries/System.Text.Json/gen/src/TypeMetadata.cs b/src/libraries/System.Text.Json/gen/TypeMetadata.cs similarity index 100% rename from src/libraries/System.Text.Json/gen/src/TypeMetadata.cs rename to src/libraries/System.Text.Json/gen/TypeMetadata.cs diff --git a/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj b/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj index 23e6dced988fe4..c714baa66768a7 100644 --- a/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj +++ b/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj @@ -5,7 +5,7 @@ net461;netcoreapp2.0;uap10.0.16299;$(AllXamarinFrameworks) - +