diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index 47ec247968859..f12b4ef549192 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -108,3 +108,13 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL | __`SYSLIB1027`__ | *_`SYSLIB1024`-`SYSLIB1029` reserved for logging._* | | __`SYSLIB1028`__ | *_`SYSLIB1024`-`SYSLIB1029` reserved for logging._* | | __`SYSLIB1029`__ | *_`SYSLIB1024`-`SYSLIB1029` reserved for logging._* | +| __`SYSLIB1030`__ | [System.Text.Json.SourceGeneration] Did not generate serialization metadata for type. | +| __`SYSLIB1031`__ | [System.Text.Json.SourceGeneration] Duplicate type name. | +| __`SYSLIB1032`__ | *_`SYSLIB1032`-`SYSLIB1039` reserved for System.Text.Json.SourceGeneration._* | +| __`SYSLIB1033`__ | *_`SYSLIB1032`-`SYSLIB1039` reserved for System.Text.Json.SourceGeneration._* | +| __`SYSLIB1034`__ | *_`SYSLIB1032`-`SYSLIB1039` reserved for System.Text.Json.SourceGeneration._* | +| __`SYSLIB1035`__ | *_`SYSLIB1032`-`SYSLIB1039` reserved for System.Text.Json.SourceGeneration._* | +| __`SYSLIB1036`__ | *_`SYSLIB1032`-`SYSLIB1039` reserved for System.Text.Json.SourceGeneration._* | +| __`SYSLIB1037`__ | *_`SYSLIB1032`-`SYSLIB1039` reserved for System.Text.Json.SourceGeneration._* | +| __`SYSLIB1038`__ | *_`SYSLIB1032`-`SYSLIB1039` reserved for System.Text.Json.SourceGeneration._* | +| __`SYSLIB1039`__ | *_`SYSLIB1032`-`SYSLIB1039` reserved for System.Text.Json.SourceGeneration._* | diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IsExternalInit.cs b/src/libraries/Common/src/System/Runtime/CompilerServices/IsExternalInit.cs similarity index 82% rename from src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IsExternalInit.cs rename to src/libraries/Common/src/System/Runtime/CompilerServices/IsExternalInit.cs index f9973999dde93..0f1bb5d6672b0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IsExternalInit.cs +++ b/src/libraries/Common/src/System/Runtime/CompilerServices/IsExternalInit.cs @@ -10,7 +10,12 @@ namespace System.Runtime.CompilerServices /// This class should not be used by developers in source code. /// [EditorBrowsable(EditorBrowsableState.Never)] - public static class IsExternalInit +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + static class IsExternalInit { } } diff --git a/src/libraries/Microsoft.CSharp/tests/IsExternalInit.cs b/src/libraries/Microsoft.CSharp/tests/IsExternalInit.cs deleted file mode 100644 index 39d98e797e533..0000000000000 --- a/src/libraries/Microsoft.CSharp/tests/IsExternalInit.cs +++ /dev/null @@ -1,9 +0,0 @@ -// 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 -{ - public sealed class IsExternalInit - { - } -} diff --git a/src/libraries/Microsoft.CSharp/tests/Microsoft.CSharp.Tests.csproj b/src/libraries/Microsoft.CSharp/tests/Microsoft.CSharp.Tests.csproj index c115e0c43497f..fd58dca4e44f6 100644 --- a/src/libraries/Microsoft.CSharp/tests/Microsoft.CSharp.Tests.csproj +++ b/src/libraries/Microsoft.CSharp/tests/Microsoft.CSharp.Tests.csproj @@ -11,6 +11,7 @@ true + @@ -28,7 +29,6 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index b8f3489e46526..55e6938440c4a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -684,7 +684,6 @@ - @@ -1165,6 +1164,9 @@ Common\System\Diagnostics\CodeAnalysis\ExcludeFromCodeCoverageAttribute.cs + + Common\System\Runtime\CompilerServices\IsExternalInit.cs + Common\System\Runtime\Versioning\NonVersionableAttribute.cs diff --git a/src/libraries/System.Text.Json/gen/IsExternalInit.cs b/src/libraries/System.Text.Json/gen/IsExternalInit.cs deleted file mode 100644 index d5984b4be3835..0000000000000 --- a/src/libraries/System.Text.Json/gen/IsExternalInit.cs +++ /dev/null @@ -1,10 +0,0 @@ -// 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. - /// - internal sealed class IsExternalInit { } -} diff --git a/src/libraries/System.Text.Json/gen/JsonSerializableSyntaxReceiver.cs b/src/libraries/System.Text.Json/gen/JsonSerializableSyntaxReceiver.cs deleted file mode 100644 index f60f4cfb686f9..0000000000000 --- a/src/libraries/System.Text.Json/gen/JsonSerializableSyntaxReceiver.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace System.Text.Json.SourceGeneration -{ - internal sealed 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/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs new file mode 100644 index 0000000000000..a176de61b9a2f --- /dev/null +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -0,0 +1,682 @@ +// 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.Json.Serialization; +using System.Text.Json.SourceGeneration.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace System.Text.Json.SourceGeneration +{ + public sealed partial class JsonSourceGenerator + { + private sealed class Emitter + { + 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 const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration"; + + private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor( + id: "SYSLIB1030", + title: new LocalizableResourceString(nameof(SR.TypeNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.TypeNotSupportedMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + private static DiagnosticDescriptor DuplicateTypeName { get; } = new DiagnosticDescriptor( + id: "SYSLIB1031", + title: new LocalizableResourceString(nameof(SR.DuplicateTypeNameTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.DuplicateTypeNameMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + private readonly string _generationNamespace; + + // TODO (https://github.com/dotnet/runtime/issues/52218): consider public option for this. + // Converter-honoring logic generation can be simplified + // if we don't plan to have a feature around this. + private readonly bool _honorRuntimeProvidedCustomConverters = true; + + private readonly GeneratorExecutionContext _executionContext; + + /// + /// 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(); + + /// + /// Types that were specified with System.Text.Json.Serialization.JsonSerializableAttribute. + /// + private readonly Dictionary _rootSerializableTypes = null!; + + public Emitter(in GeneratorExecutionContext executionContext, Dictionary rootSerializableTypes) + { + _executionContext = executionContext; + _generationNamespace = $"{executionContext.Compilation.AssemblyName}.JsonSourceGeneration"; + _rootSerializableTypes = rootSerializableTypes; + } + + public void Emit() + { + foreach (KeyValuePair pair in _rootSerializableTypes) + { + TypeMetadata typeMetadata = pair.Value; + GenerateTypeMetadata(typeMetadata); + } + + // Add base default instance source. + _executionContext.AddSource("JsonContext.g.cs", SourceText.From(GetBaseJsonContextImplementation(), Encoding.UTF8)); + + // Add GetJsonTypeInfo override implementation. + _executionContext.AddSource("JsonContext.GetJsonTypeInfo.g.cs", SourceText.From(GetGetTypeInfoImplementation(), Encoding.UTF8)); + } + + private void GenerateTypeMetadata(TypeMetadata typeMetadata) + { + Debug.Assert(typeMetadata != null); + + if (_typesWithMetadataGenerated.Contains(typeMetadata)) + { + return; + } + + _typesWithMetadataGenerated.Add(typeMetadata); + + string source; + + switch (typeMetadata.ClassType) + { + case ClassType.KnownType: + { + source = GenerateForTypeWithKnownConverter(typeMetadata); + } + break; + case ClassType.TypeWithDesignTimeProvidedCustomConverter: + { + source = GenerateForTypeWithUnknownConverter(typeMetadata); + } + break; + case ClassType.Nullable: + { + source = GenerateForNullable(typeMetadata); + + GenerateTypeMetadata(typeMetadata.NullableUnderlyingTypeMetadata); + } + break; + case ClassType.Enum: + { + source = GenerateForEnum(typeMetadata); + } + break; + case ClassType.Enumerable: + { + source = GenerateForCollection(typeMetadata); + + GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata); + } + break; + case ClassType.Dictionary: + { + source = GenerateForCollection(typeMetadata); + + GenerateTypeMetadata(typeMetadata.CollectionKeyTypeMetadata); + GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata); + } + break; + case ClassType.Object: + { + source = GenerateForObject(typeMetadata); + + 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(); + } + } + + 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) + { + 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(); + + // TODO (https://github.com/dotnet/runtime/issues/52218): consider moving this verification source to common helper. + string metadataInitSource = $@"JsonConverter converter = {typeMetadata.ConverterInstantiationLogic}; + 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}, JsonMetadataServices.GetEnumConverter<{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 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}); + "); + } + } + + sb.Append(@$" + return {PropVarName}; + }}"); + + return sb.ToString(); + } + + 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} ")} + }}"; + } + + private string GetBaseJsonContextImplementation() + { + 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 ""; + } + + // TODO (https://github.com/dotnet/runtime/issues/52218): use a dictionary if count > ~15. + 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(); + + foreach (TypeMetadata metadata in _rootSerializableTypes.Values) + { + usingStatements.UnionWith(GetUsingStatements(metadata)); + } + + sb.Append(@$"{GetUsingStatementsString(usingStatements)} + +namespace {_generationNamespace} +{{ + {JsonContextDeclarationSource} + {{ + public override JsonTypeInfo GetTypeInfo(System.Type type) + {{"); + + // TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64. + foreach (TypeMetadata metadata in _rootSerializableTypes.Values) + { + if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen) + { + sb.Append($@" + if (type == typeof({metadata.Type.GetUniqueCompilableTypeName()})) + {{ + return this.{metadata.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/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs new file mode 100644 index 0000000000000..86a7043b67f18 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -0,0 +1,517 @@ +// 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; +using System.Text.Json.SourceGeneration.Reflection; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace System.Text.Json.SourceGeneration +{ + public sealed partial class JsonSourceGenerator + { + private sealed class Parser + { + 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"; + + private readonly Compilation _compilation; + + private readonly MetadataLoadContextInternal _metadataLoadContext; + + 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(); + + /// + /// Type information for member types in input object graphs. + /// + private readonly Dictionary _typeMetadataCache = new(); + + public Parser(Compilation compilation) + { + _compilation = compilation; + _metadataLoadContext = new MetadataLoadContextInternal(compilation); + + _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(); + } + + public Dictionary? GetRootSerializableTypes(List compilationUnits) + { + TypeExtensions.NullableOfTType = _metadataLoadContext.Resolve(typeof(Nullable<>)); + + const string JsonSerializableAttributeName = "System.Text.Json.Serialization.JsonSerializableAttribute"; + INamedTypeSymbol jsonSerializableAttribute = _compilation.GetTypeByMetadataName(JsonSerializableAttributeName); + if (jsonSerializableAttribute == null) + { + return null; + } + + // Discover serializable types indicated by JsonSerializableAttribute. + Dictionary? rootTypes = null; + + foreach (CompilationUnitSyntax compilationUnit in compilationUnits) + { + SemanticModel compilationSemanticModel = _compilation.GetSemanticModel(compilationUnit.SyntaxTree); + + foreach (AttributeListSyntax attributeListSyntax in compilationUnit.AttributeLists) + { + AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.First(); + 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); + + ITypeSymbol? typeSymbol = null; + string? typeInfoPropertyName = null; + + 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++; + } + + if (typeSymbol == null) + { + 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 the user needs to fix their code. + // The compiler will notify the user so we don't have to. + return null; + } + + rootTypes ??= new Dictionary(); + rootTypes[type.FullName] = GetOrAddTypeMetadata(type, typeInfoPropertyName); + } + } + + return rootTypes; + } + + private TypeMetadata GetOrAddTypeMetadata(Type type, string? typeInfoPropertyName = null) + { + 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) + { + constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor; + } + + for (Type? currentType = type; currentType != null; currentType = currentType.BaseType) + { + const BindingFlags bindingFlags = + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.DeclaredOnly; + + foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) + { + PropertyMetadata metadata = GetPropertyMetadata(propertyInfo); + + // Ignore indexers. + if (propertyInfo.GetIndexParameters().Length > 0) + { + 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 (metadata.HasGetter || metadata.HasSetter) + { + (propertiesMetadata ??= new()).Add(metadata); + } + } + } + } + + typeMetadata.Initialize( + compilableName: type.GetUniqueCompilableTypeName(), + friendlyName: typeInfoPropertyName ?? type.GetFriendlyTypeName(), + 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 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 == SystemTextJsonNamespace) + { + switch (attributeData.AttributeType.FullName) + { + case JsonIgnoreAttributeFullName: + { + IList namedArgs = attributeData.NamedArguments; + + if (namedArgs.Count == 0) + { + ignoreCondition = JsonIgnoreCondition.Always; + } + else if (namedArgs.Count == 1 && + namedArgs[0].MemberInfo.MemberType == MemberTypes.Property && + ((PropertyInfo)namedArgs[0].MemberInfo).PropertyType.FullName == JsonIgnoreConditionFullName) + { + ignoreCondition = (JsonIgnoreCondition)namedArgs[0].TypedValue.Value; + } + } + break; + case JsonIncludeAttributeFullName: + { + hasJsonInclude = true; + } + break; + case JsonNumberHandlingAttributeFullName: + { + IList ctorArgs = attributeData.ConstructorArguments; + numberHandling = (JsonNumberHandling)ctorArgs[0].Value; + } + break; + case JsonPropertyNameAttributeFullName: + { + IList ctorArgs = attributeData.ConstructorArguments; + 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 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() + { + 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() + { + PopulateNumberTypes(); + + 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; + } + } +} diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs index ef2412f6aaf90..eb23225f37f29 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs @@ -2,6 +2,7 @@ // 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; @@ -15,14 +16,13 @@ namespace System.Text.Json.SourceGeneration /// Generates source code to optimize serialization and deserialization with JsonSerializer. /// [Generator] - public sealed class JsonSourceGenerator : ISourceGenerator + public sealed partial class JsonSourceGenerator : ISourceGenerator { - private JsonSourceGeneratorHelper? _helper; - /// /// Helper for unit tests. /// - public Dictionary? SerializableTypes => _helper.GetSerializableTypes(); + public Dictionary? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Key, p => p.Value.Type); + private Dictionary? _rootTypes; /// /// Registers a syntax resolver to receive compilation units. @@ -30,7 +30,7 @@ public sealed class JsonSourceGenerator : ISourceGenerator /// public void Initialize(GeneratorInitializationContext context) { - context.RegisterForSyntaxNotifications(() => new JsonSerializableSyntaxReceiver()); + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); } /// @@ -39,90 +39,34 @@ public void Initialize(GeneratorInitializationContext context) /// 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) + SyntaxReceiver receiver = (SyntaxReceiver)executionContext.SyntaxReceiver; + List compilationUnits = receiver.CompilationUnits; + if (compilationUnits == null) { return; } - JsonSerializableSyntaxReceiver receiver = (JsonSerializableSyntaxReceiver)executionContext.SyntaxReceiver; - MetadataLoadContextInternal metadataLoadContext = new(compilation); + Parser parser = new(executionContext.Compilation); + _rootTypes = parser.GetRootSerializableTypes(receiver.CompilationUnits); - TypeExtensions.NullableOfTType = metadataLoadContext.Resolve(typeof(Nullable<>)); + if (_rootTypes != null) + { + Emitter emitter = new(executionContext, _rootTypes); + emitter.Emit(); + } + } - JsonSourceGeneratorHelper helper = new(executionContext, metadataLoadContext); - _helper = helper; + internal sealed class SyntaxReceiver : ISyntaxReceiver + { + public List? CompilationUnits { get; private set; } - // Discover serializable types indicated by JsonSerializableAttribute. - foreach (CompilationUnitSyntax compilationUnit in receiver.CompilationUnits) + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { - SemanticModel compilationSemanticModel = executionContext.Compilation.GetSemanticModel(compilationUnit.SyntaxTree); - - foreach (AttributeListSyntax attributeListSyntax in compilationUnit.AttributeLists) + if (syntaxNode is CompilationUnitSyntax compilationUnit) { - AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.First(); - 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); - - ITypeSymbol? typeSymbol = null; - string? typeInfoPropertyName = null; - - 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++; - } - - if (typeSymbol == null) - { - 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 the user needs to fix their code. - // The compiler will notify the user so we don't have to. - return; - } - - helper.RegisterRootSerializableType(type, typeInfoPropertyName); + (CompilationUnits ??= new List()).Add(compilationUnit); } } - - helper.GenerateSerializationMetadata(); } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.Generate.cs b/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.Generate.cs deleted file mode 100644 index 040c11cf9acf0..0000000000000 --- a/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.Generate.cs +++ /dev/null @@ -1,651 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text.Json.Serialization; -using System.Text.Json.SourceGeneration.Reflection; -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. - // 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"; - - 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}"); - - 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. - /// - private readonly HashSet _typesWithMetadataGenerated = new(); - - private void GenerateTypeMetadata(TypeMetadata typeMetadata) - { - Debug.Assert(typeMetadata != null); - - if (_typesWithMetadataGenerated.Contains(typeMetadata)) - { - return; - } - - _typesWithMetadataGenerated.Add(typeMetadata); - - string source; - - switch (typeMetadata.ClassType) - { - case ClassType.KnownType: - { - source = GenerateForTypeWithKnownConverter(typeMetadata); - } - break; - case ClassType.TypeWithDesignTimeProvidedCustomConverter: - { - source = GenerateForTypeWithUnknownConverter(typeMetadata); - } - break; - case ClassType.Nullable: - { - source = GenerateForNullable(typeMetadata); - - GenerateTypeMetadata(typeMetadata.NullableUnderlyingTypeMetadata); - } - break; - case ClassType.Enum: - { - source = GenerateForEnum(typeMetadata); - } - break; - case ClassType.Enumerable: - { - source = GenerateForCollection(typeMetadata); - - GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata); - } - break; - case ClassType.Dictionary: - { - source = GenerateForCollection(typeMetadata); - - GenerateTypeMetadata(typeMetadata.CollectionKeyTypeMetadata); - GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata); - } - break; - case ClassType.Object: - { - source = GenerateForObject(typeMetadata); - - 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(); - } - } - - 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) - { - 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}, JsonMetadataServices.GetEnumConverter<{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 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}); - "); - } - } - - sb.Append(@$" - return {PropVarName}; - }}"); - - return sb.ToString(); - } - - 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; - - // TODO: use a dictionary if count > ~15. - 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(); - - foreach (TypeMetadata metadata in _rootSerializableTypes.Values) - { - 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 metadata in _rootSerializableTypes.Values) - { - if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen) - { - sb.Append($@" - if (type == typeof({metadata.Type.GetUniqueCompilableTypeName()})) - {{ - return this.{metadata.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/gen/JsonSourceGeneratorHelper.cs b/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.cs deleted file mode 100644 index 10f1dd2f011a6..0000000000000 --- a/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.cs +++ /dev/null @@ -1,467 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -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 -{ - 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(); - - /// - /// 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 Dictionary _rootSerializableTypes; - - private readonly GeneratorExecutionContext _executionContext; - - 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, - MetadataLoadContextInternal metadataLoadContext) - { - _generationNamespace = $"{executionContext.Compilation.AssemblyName}.JsonSourceGeneration"; - _executionContext = executionContext; - _metadataLoadContext = metadataLoadContext; - - _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 RegisterRootSerializableType(Type type, string? typeInfoPropertyName) - { - _rootSerializableTypes ??= new Dictionary(); - _rootSerializableTypes[type.FullName] = GetOrAddTypeMetadata(type, typeInfoPropertyName); - } - - public void GenerateSerializationMetadata() - { - if (_rootSerializableTypes == null || _rootSerializableTypes.Count == 0) - { - return; - } - - foreach (KeyValuePair pair in _rootSerializableTypes) - { - TypeMetadata typeMetadata = pair.Value; - 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, string? typeInfoPropertyName = null) - { - 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; - } - - for (Type? currentType = type; currentType != null; currentType = currentType.BaseType) - { - const BindingFlags bindingFlags = - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.NonPublic | - BindingFlags.DeclaredOnly; - - foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) - { - PropertyMetadata metadata = GetPropertyMetadata(propertyInfo); - - // Ignore indexers. - if (propertyInfo.GetIndexParameters().Length > 0) - { - 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 (metadata.HasGetter || metadata.HasSetter) - { - (propertiesMetadata ??= new()).Add(metadata); - } - } - } - } - - typeMetadata.Initialize( - compilableName: type.GetUniqueCompilableTypeName(), - friendlyName: typeInfoPropertyName ?? type.GetFriendlyTypeName(), - 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 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 == SystemTextJsonNamespace) - { - switch (attributeData.AttributeType.FullName) - { - case JsonIgnoreAttributeFullName: - { - IList namedArgs = attributeData.NamedArguments; - - if (namedArgs.Count == 0) - { - ignoreCondition = JsonIgnoreCondition.Always; - } - else if (namedArgs.Count == 1 && - namedArgs[0].MemberInfo.MemberType == MemberTypes.Property && - ((PropertyInfo)namedArgs[0].MemberInfo).PropertyType.FullName == JsonIgnoreConditionFullName) - { - ignoreCondition = (JsonIgnoreCondition)namedArgs[0].TypedValue.Value; - } - } - break; - case JsonIncludeAttributeFullName: - { - hasJsonInclude = true; - } - break; - case JsonNumberHandlingAttributeFullName: - { - IList ctorArgs = attributeData.ConstructorArguments; - numberHandling = (JsonNumberHandling)ctorArgs[0].Value; - } - break; - case JsonPropertyNameAttributeFullName: - { - IList ctorArgs = attributeData.ConstructorArguments; - 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 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(MetadataLoadContextInternal 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(MetadataLoadContextInternal 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; - - public Dictionary? GetSerializableTypes() => _rootSerializableTypes?.ToDictionary(p => p.Key, p => p.Value.Type); - } -} diff --git a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs index 07dce68fcbb89..1c0619ddda58e 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.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.Diagnostics; using System.Linq; using System.Reflection; @@ -20,13 +21,12 @@ public static TValue GetConstructorArgument(this CustomAttributeData cus public static bool IsInitOnly(this MethodInfo method) { - MethodInfoWrapper? methodInfoWrapper = method as MethodInfoWrapper; - - if (methodInfoWrapper == null) + if (method == null) { - throw new ArgumentException("Expected a MethodInfoWrapper instance.", nameof(method)); + throw new ArgumentNullException(nameof(method)); } + MethodInfoWrapper methodInfoWrapper = (MethodInfoWrapper)method; return methodInfoWrapper.IsInitOnly; } } diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx new file mode 100644 index 0000000000000..4560355948055 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + Duplicate type name. + + + Did not generate serialization metadata for type '{0}'. + + + Did not generate serialization metadata for type. + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf new file mode 100644 index 0000000000000..85182d5d5047a --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf new file mode 100644 index 0000000000000..1b94670b3e543 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf new file mode 100644 index 0000000000000..55915ce81424e --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf new file mode 100644 index 0000000000000..cb63078ef065e --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf new file mode 100644 index 0000000000000..2ac4e6fc16613 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf new file mode 100644 index 0000000000000..4661920d08a5c --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf new file mode 100644 index 0000000000000..322cc79f11a3b --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf new file mode 100644 index 0000000000000..408e27c293575 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf new file mode 100644 index 0000000000000..e131d2555c888 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf new file mode 100644 index 0000000000000..f02ca1c7c22a7 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf new file mode 100644 index 0000000000000..2a00a92caf8e6 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf new file mode 100644 index 0000000000000..2822193d1924a --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf new file mode 100644 index 0000000000000..c5c1cc188a7f8 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -0,0 +1,27 @@ + + + + + + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision. + + + + Duplicate type name. + Duplicate type name. + + + + Did not generate serialization metadata for type '{0}'. + Did not generate serialization metadata for type '{0}'. + + + + Did not generate serialization metadata for type. + Did not generate serialization metadata for type. + + + + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj index 92e70583da672..74dd28a5b00a3 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj @@ -5,6 +5,8 @@ enable CS1574 + false + true @@ -19,15 +21,14 @@ + - - - - + + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs index 945c79f044cff..f462ca0dd14ef 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs @@ -56,16 +56,18 @@ public void UsePrivates() CheckCompilationDiagnosticsErrors(generatorDiags); CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics()); + Dictionary types = generator.GetSerializableTypes(); + // Check base functionality of found types. - Assert.Equal(1, generator.SerializableTypes.Count); - Type myType= generator.SerializableTypes["HelloWorld.MyType"]; + Assert.Equal(1, types.Count); + Type myType = types["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); + CheckFieldsPropertiesMethods(myType, expectedFieldNames, expectedPropertyNames, expectedMethodNames); } [Fact] @@ -122,10 +124,12 @@ public void UsePrivates() CheckCompilationDiagnosticsErrors(generatorDiags); CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics()); + Dictionary types = generator.GetSerializableTypes(); + // Check base functionality of found types. - Assert.Equal(2, generator.SerializableTypes.Count); - Type myType = generator.SerializableTypes["HelloWorld.MyType"]; - Type notMyType = generator.SerializableTypes["ReferencedAssembly.Location"]; + Assert.Equal(2, types.Count); + Type myType = types["HelloWorld.MyType"]; + Type notMyType = types["ReferencedAssembly.Location"]; // Check for MyType. Assert.Equal("HelloWorld.MyType", myType.FullName); @@ -134,7 +138,7 @@ public void UsePrivates() 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); + CheckFieldsPropertiesMethods(myType, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType); // Check for NotMyType. Assert.Equal("ReferencedAssembly.Location", notMyType.FullName); @@ -144,7 +148,7 @@ public void UsePrivates() 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); + CheckFieldsPropertiesMethods(notMyType, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType); } [Fact] @@ -204,27 +208,31 @@ public void UsePrivates() CheckCompilationDiagnosticsErrors(generatorDiags); CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics()); + Dictionary types = generator.GetSerializableTypes(); + // Check base functionality of found types. - Assert.Equal(2, generator.SerializableTypes.Count); + Assert.Equal(2, types.Count); // Check for MyType. - Assert.Equal("HelloWorld.MyType", generator.SerializableTypes["HelloWorld.MyType"].FullName); + Type myType = types["HelloWorld.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); + CheckFieldsPropertiesMethods(myType, expectedFieldNamesMyType, expectedPropertyNamesMyType, expectedMethodNamesMyType); // Check for NotMyType. - Assert.Equal("ReferencedAssembly.Location", generator.SerializableTypes["ReferencedAssembly.Location"].FullName); + Type notMyType = types["ReferencedAssembly.Location"]; + 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 ); + CheckFieldsPropertiesMethods(notMyType, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType ); } [Theory] @@ -256,7 +264,7 @@ public JsonSerializableAttribute(Type type) { } CompilationHelper.RunGenerators(compilation, out ImmutableArray generatorDiags, generator); - Dictionary types = generator.SerializableTypes; + Dictionary types = generator.GetSerializableTypes(); if (includeSTJ) { Assert.Equal("System.Int32", types["System.Int32"].FullName); @@ -297,7 +305,7 @@ public JsonSerializableAttribute(string typeInfoPropertyName, Type type) { } JsonSourceGenerator generator = new JsonSourceGenerator(); CompilationHelper.RunGenerators(compilation, out ImmutableArray generatorDiags, generator); - Assert.Null(generator.SerializableTypes); + Assert.Null(generator.GetSerializableTypes()); CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); @@ -369,9 +377,8 @@ private void CheckCompilationDiagnosticsErrors(ImmutableArray diagno Assert.Empty(diagnostics.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)); } - private void CheckFieldsPropertiesMethods(string typeName, ref JsonSourceGenerator generator, string[] expectedFields, string[] expectedProperties, string[] expectedMethods) + private void CheckFieldsPropertiesMethods(Type type, 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(); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs index 6f16665ced954..e08ee5ecaa2e0 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.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.IO; using System.Linq; @@ -71,7 +72,7 @@ public void MySecondMethod() { } 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); + Assert.Equal(2, generator.GetSerializableTypes().Count); } [Fact] @@ -128,8 +129,9 @@ public void MySecondMethod() { } 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; + Dictionary types = generator.GetSerializableTypes(); + Assert.Equal(1, types.Count); + Type foundType = types.First().Value; Assert.Equal("HelloWorld.MyType", foundType.FullName); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/IsExternalInit.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/IsExternalInit.cs deleted file mode 100644 index d789ecb6e2a18..0000000000000 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/IsExternalInit.cs +++ /dev/null @@ -1,10 +0,0 @@ -// 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# 'Record' types can compile on NetStandard and NetFx. - /// - public sealed class IsExternalInit { } -} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index fa45a2859cc5c..62488ff19037e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -189,7 +189,7 @@ - +