From 5887663e612863d6773ad5f7658646e96c056666 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 23 May 2023 01:25:13 +0100 Subject: [PATCH 1/2] Remove dependency on IncrementalValuesProvider.Collect() --- ...onSourceGenerator.DiagnosticDescriptors.cs | 77 +++ .../gen/JsonSourceGenerator.Emitter.cs | 61 +- .../gen/JsonSourceGenerator.Parser.cs | 550 ++++++++---------- .../gen/JsonSourceGenerator.Roslyn3.11.cs | 70 ++- .../gen/JsonSourceGenerator.Roslyn4.0.cs | 66 +-- .../gen/Model/SourceGenerationSpec.cs | 29 - .../System.Text.Json.SourceGeneration.targets | 2 +- .../CompilationHelper.cs | 12 +- .../JsonSourceGeneratorIncrementalTests.cs | 96 ++- 9 files changed, 497 insertions(+), 466 deletions(-) create mode 100644 src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs delete mode 100644 src/libraries/System.Text.Json/gen/Model/SourceGenerationSpec.cs diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs new file mode 100644 index 00000000000000..17d7dd58dbe416 --- /dev/null +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; + +namespace System.Text.Json.SourceGeneration +{ + public sealed partial class JsonSourceGenerator + { + internal static class DiagnosticDescriptors + { + public 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: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public 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: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor ContextClassesMustBePartial { get; } = new DiagnosticDescriptor( + id: "SYSLIB1032", + title: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor MultipleJsonConstructorAttribute { get; } = new DiagnosticDescriptor( + id: "SYSLIB1033", + title: new LocalizableResourceString(nameof(SR.MultipleJsonConstructorAttributeTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.MultipleJsonConstructorAttributeFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor MultipleJsonExtensionDataAttribute { get; } = new DiagnosticDescriptor( + id: "SYSLIB1035", + title: new LocalizableResourceString(nameof(SR.MultipleJsonExtensionDataAttributeTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.MultipleJsonExtensionDataAttributeFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor DataExtensionPropertyInvalid { get; } = new DiagnosticDescriptor( + id: "SYSLIB1036", + title: new LocalizableResourceString(nameof(SR.DataExtensionPropertyInvalidTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.DataExtensionPropertyInvalidFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor InaccessibleJsonIncludePropertiesNotSupported { get; } = new DiagnosticDescriptor( + id: "SYSLIB1038", + title: new LocalizableResourceString(nameof(SR.InaccessibleJsonIncludePropertiesNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.InaccessibleJsonIncludePropertiesNotSupportedFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor PolymorphismNotSupported { get; } = new DiagnosticDescriptor( + id: "SYSLIB1039", + title: new LocalizableResourceString(nameof(SR.FastPathPolymorphismNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.FastPathPolymorphismNotSupportedMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + } + } +} diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index e497bcde706d5c..e4e8e5acfc8d9c 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -68,9 +68,6 @@ private sealed partial class Emitter private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo"; private const string JsonTypeInfoResolverTypeRef = "global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver"; - private readonly JsonSourceGenerationContext _sourceGenerationContext; - private readonly SourceGenerationSpec _generationSpec; - /// /// Contains an index from TypeRef to TypeGenerationSpec for the current ContextGenerationSpec. /// @@ -83,53 +80,43 @@ private sealed partial class Emitter /// private readonly Dictionary _propertyNames = new(); - public Emitter(in JsonSourceGenerationContext sourceGenerationContext, SourceGenerationSpec generationSpec) - { - _sourceGenerationContext = sourceGenerationContext; - _generationSpec = generationSpec; - } + /// + /// The SourceText emit implementation filled by the individual Roslyn versions. + /// + private partial void AddSource(string hintName, SourceText sourceText); - public void Emit() + public void Emit(ContextGenerationSpec contextGenerationSpec) { - foreach (DiagnosticInfo diagnostic in _generationSpec.Diagnostics) + Debug.Assert(_typeIndex.Count == 0); + Debug.Assert(_propertyNames.Count == 0); + + foreach (TypeGenerationSpec spec in contextGenerationSpec.GeneratedTypes) { - // Report any diagnostics produced by the parser ahead of formatting source code. - _sourceGenerationContext.ReportDiagnostic(diagnostic.CreateDiagnostic()); + _typeIndex.Add(spec.TypeRef, spec); } - foreach (ContextGenerationSpec contextGenerationSpec in _generationSpec.ContextGenerationSpecs) + foreach (TypeGenerationSpec typeGenerationSpec in contextGenerationSpec.GeneratedTypes) { - Debug.Assert(_typeIndex.Count == 0); - Debug.Assert(_propertyNames.Count == 0); - - foreach (TypeGenerationSpec spec in contextGenerationSpec.GeneratedTypes) + SourceText? sourceText = GenerateTypeInfo(contextGenerationSpec, typeGenerationSpec); + if (sourceText != null) { - _typeIndex.Add(spec.TypeRef, spec); - } - - foreach (TypeGenerationSpec typeGenerationSpec in contextGenerationSpec.GeneratedTypes) - { - SourceText? sourceText = GenerateTypeInfo(contextGenerationSpec, typeGenerationSpec); - if (sourceText != null) - { - _sourceGenerationContext.AddSource($"{contextGenerationSpec.ContextType.Name}.{typeGenerationSpec.TypeInfoPropertyName}.g.cs", sourceText); - } + AddSource($"{contextGenerationSpec.ContextType.Name}.{typeGenerationSpec.TypeInfoPropertyName}.g.cs", sourceText); } + } - string contextName = contextGenerationSpec.ContextType.Name; + string contextName = contextGenerationSpec.ContextType.Name; - // Add root context implementation. - _sourceGenerationContext.AddSource($"{contextName}.g.cs", GetRootJsonContextImplementation(contextGenerationSpec)); + // Add root context implementation. + AddSource($"{contextName}.g.cs", GetRootJsonContextImplementation(contextGenerationSpec)); - // Add GetJsonTypeInfo override implementation. - _sourceGenerationContext.AddSource($"{contextName}.GetJsonTypeInfo.g.cs", GetGetTypeInfoImplementation(contextGenerationSpec)); + // Add GetJsonTypeInfo override implementation. + AddSource($"{contextName}.GetJsonTypeInfo.g.cs", GetGetTypeInfoImplementation(contextGenerationSpec)); - // Add property name initialization. - _sourceGenerationContext.AddSource($"{contextName}.PropertyNames.g.cs", GetPropertyNameInitialization(contextGenerationSpec)); + // Add property name initialization. + AddSource($"{contextName}.PropertyNames.g.cs", GetPropertyNameInitialization(contextGenerationSpec)); - _propertyNames.Clear(); - _typeIndex.Clear(); - } + _propertyNames.Clear(); + _typeIndex.Clear(); } private static SourceWriter CreateSourceWriterWithContextHeader(ContextGenerationSpec contextSpec, bool isPrimaryContextSourceFile = false, string? interfaceImplementation = null) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 248469af5f462e..d9e3bed8a71217 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -34,27 +34,23 @@ private sealed class Parser internal const string JsonSerializableAttributeFullName = "System.Text.Json.Serialization.JsonSerializableAttribute"; - private readonly Compilation _compilation; private readonly KnownTypeSymbols _knownSymbols; + private readonly bool _compilationContainsCoreJsonTypes; // Keeps track of generated context type names private readonly HashSet<(string ContextName, string TypeName)> _generatedContextAndTypeNames = new(); -#pragma warning disable RS1024 // Compare symbols correctly https://github.com/dotnet/roslyn-analyzers/issues/5804 - private readonly HashSet _builtInSupportTypes = new(SymbolEqualityComparer.Default); -#pragma warning restore - - private readonly Queue<(ITypeSymbol type, JsonSourceGenerationMode mode, string? typeInfoPropertyName, Location? attributeLocation)> _typesToGenerate = new(); + private readonly HashSet _builtInSupportTypes; + private readonly Queue _typesToGenerate = new(); #pragma warning disable RS1024 // Compare symbols correctly https://github.com/dotnet/roslyn-analyzers/issues/5804 private readonly Dictionary _generatedTypes = new(SymbolEqualityComparer.Default); #pragma warning restore - private JsonKnownNamingPolicy _currentContextNamingPolicy; - private readonly List _diagnostics = new(); + public List Diagnostics { get; } = new(); public void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location, params object?[]? messageArgs) { - _diagnostics.Add(new DiagnosticInfo + Diagnostics.Add(new DiagnosticInfo { Descriptor = descriptor, Location = location.GetTrimmedLocation(), @@ -62,214 +58,104 @@ public void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location }); } - 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: JsonConstants.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: JsonConstants.SystemTextJsonSourceGenerationName, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - private static DiagnosticDescriptor ContextClassesMustBePartial { get; } = new DiagnosticDescriptor( - id: "SYSLIB1032", - title: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - messageFormat: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - category: JsonConstants.SystemTextJsonSourceGenerationName, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - private static DiagnosticDescriptor MultipleJsonConstructorAttribute { get; } = new DiagnosticDescriptor( - id: "SYSLIB1033", - title: new LocalizableResourceString(nameof(SR.MultipleJsonConstructorAttributeTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - messageFormat: new LocalizableResourceString(nameof(SR.MultipleJsonConstructorAttributeFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - category: JsonConstants.SystemTextJsonSourceGenerationName, - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); - - private static DiagnosticDescriptor MultipleJsonExtensionDataAttribute { get; } = new DiagnosticDescriptor( - id: "SYSLIB1035", - title: new LocalizableResourceString(nameof(SR.MultipleJsonExtensionDataAttributeTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - messageFormat: new LocalizableResourceString(nameof(SR.MultipleJsonExtensionDataAttributeFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - category: JsonConstants.SystemTextJsonSourceGenerationName, - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); - - private static DiagnosticDescriptor DataExtensionPropertyInvalid { get; } = new DiagnosticDescriptor( - id: "SYSLIB1036", - title: new LocalizableResourceString(nameof(SR.DataExtensionPropertyInvalidTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - messageFormat: new LocalizableResourceString(nameof(SR.DataExtensionPropertyInvalidFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - category: JsonConstants.SystemTextJsonSourceGenerationName, - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); - - private static DiagnosticDescriptor InaccessibleJsonIncludePropertiesNotSupported { get; } = new DiagnosticDescriptor( - id: "SYSLIB1038", - title: new LocalizableResourceString(nameof(SR.InaccessibleJsonIncludePropertiesNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - messageFormat: new LocalizableResourceString(nameof(SR.InaccessibleJsonIncludePropertiesNotSupportedFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - category: JsonConstants.SystemTextJsonSourceGenerationName, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - private static DiagnosticDescriptor PolymorphismNotSupported { get; } = new DiagnosticDescriptor( - id: "SYSLIB1039", - title: new LocalizableResourceString(nameof(SR.FastPathPolymorphismNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - messageFormat: new LocalizableResourceString(nameof(SR.FastPathPolymorphismNotSupportedMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - category: JsonConstants.SystemTextJsonSourceGenerationName, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public Parser(Compilation compilation) + public Parser(KnownTypeSymbols knownSymbols) { - _compilation = compilation; - _knownSymbols = new KnownTypeSymbols(compilation); - - PopulateBuiltInSupportTypes(); + _knownSymbols = knownSymbols; + _compilationContainsCoreJsonTypes = + knownSymbols.JsonSerializerContextType != null && + knownSymbols.JsonSerializableAttributeType != null && + knownSymbols.JsonSourceGenerationOptionsAttributeType != null && + knownSymbols.JsonConverterType != null; + + _builtInSupportTypes = CreateBuiltInSupportTypeSet(knownSymbols); } - public SourceGenerationSpec? GetGenerationSpec(IEnumerable classDeclarationSyntaxList, CancellationToken cancellationToken) + public ContextGenerationSpec? ParseContextGenerationSpec(ClassDeclarationSyntax contextClassDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken) { - Compilation compilation = _compilation; - INamedTypeSymbol? jsonSerializerContextSymbol = _knownSymbols.JsonSerializerContextType; - INamedTypeSymbol? jsonSerializableAttributeSymbol = _knownSymbols.JsonSerializableAttributeType; - INamedTypeSymbol? jsonSourceGenerationOptionsAttributeSymbol = _knownSymbols.JsonSourceGenerationOptionsAttributeType; - INamedTypeSymbol? jsonConverterSymbol = _knownSymbols.JsonConverterType; - - if (jsonSerializerContextSymbol == null || - jsonSerializableAttributeSymbol == null || - jsonSourceGenerationOptionsAttributeSymbol == null || - jsonConverterSymbol == null) + if (!_compilationContainsCoreJsonTypes) { return null; } - List? contextGenSpecList = null; - - foreach (IGrouping group in classDeclarationSyntaxList.GroupBy(c => c.SyntaxTree)) - { - SyntaxTree syntaxTree = group.Key; - SemanticModel compilationSemanticModel = compilation.GetSemanticModel(syntaxTree); - CompilationUnitSyntax compilationUnitSyntax = (CompilationUnitSyntax)syntaxTree.GetRoot(cancellationToken); - - foreach (ClassDeclarationSyntax classDeclarationSyntax in group) - { - cancellationToken.ThrowIfCancellationRequested(); - - // Ensure context-scoped metadata caches are empty. - Debug.Assert(_typesToGenerate.Count == 0); - Debug.Assert(_generatedTypes.Count == 0); - - if (!DerivesFromJsonSerializerContext(classDeclarationSyntax, jsonSerializerContextSymbol, compilationSemanticModel, cancellationToken)) - { - continue; - } - - JsonSourceGenerationOptionsAttribute? options = null; - List? serializableAttributeList = null; - - foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists) - { - AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.First(); - if (compilationSemanticModel.GetSymbolInfo(attributeSyntax, cancellationToken).Symbol is not IMethodSymbol attributeSymbol) - { - continue; - } - - INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType; - - if (jsonSerializableAttributeSymbol.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) - { - (serializableAttributeList ??= new List()).Add(attributeSyntax); - } - else if (jsonSourceGenerationOptionsAttributeSymbol.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) - { - options = GetSerializerOptions(attributeSyntax); - } - } - - if (serializableAttributeList == null) - { - // No types were indicated with [JsonSerializable] - continue; - } + Debug.Assert(_knownSymbols.JsonSerializerContextType != null); - INamedTypeSymbol? contextTypeSymbol = compilationSemanticModel.GetDeclaredSymbol(classDeclarationSyntax, cancellationToken); - Debug.Assert(contextTypeSymbol != null); + // Ensure context-scoped metadata caches are empty. + Debug.Assert(_typesToGenerate.Count == 0); + Debug.Assert(_generatedTypes.Count == 0); - Location contextLocation = contextTypeSymbol.Locations.Length > 0 ? contextTypeSymbol.Locations[0] : Location.None; - - if (!TryGetClassDeclarationList(contextTypeSymbol, out List? classDeclarationList)) - { - // Class or one of its containing types is not partial so we can't add to it. - ReportDiagnostic(ContextClassesMustBePartial, contextLocation, new string[] { contextTypeSymbol.Name }); - continue; - } + if (!DerivesFromJsonSerializerContext(contextClassDeclaration, _knownSymbols.JsonSerializerContextType, semanticModel, cancellationToken)) + { + return null; + } - options ??= new JsonSourceGenerationOptionsAttribute(); + if (!TryParseJsonSerializerContextAttributes( + contextClassDeclaration, + semanticModel, + cancellationToken, + out List? rootSerializableTypes, + out JsonSourceGenerationOptionsAttribute? options)) + { + // Context does not specify any source gen attributes. + return null; + } - // Set the naming policy for the current context. - _currentContextNamingPolicy = options.PropertyNamingPolicy; + if (rootSerializableTypes is null) + { + // No types were indicated with [JsonSerializable] + return null; + } - foreach (AttributeSyntax attribute in serializableAttributeList) - { - EnqueueRootType(compilationSemanticModel, attribute, options.GenerationMode, cancellationToken); - } + INamedTypeSymbol? contextTypeSymbol = semanticModel.GetDeclaredSymbol(contextClassDeclaration, cancellationToken); + Debug.Assert(contextTypeSymbol != null); - while (_typesToGenerate.Count > 0) - { - (ITypeSymbol type, JsonSourceGenerationMode mode, string? typeInfoPropertyName, Location? attributeLocation) = _typesToGenerate.Dequeue(); - if (!_generatedTypes.ContainsKey(type)) - { - TypeGenerationSpec spec = CreateTypeGenerationSpec(type, mode, typeInfoPropertyName, attributeLocation, contextLocation, contextName: contextTypeSymbol.Name); - _generatedTypes.Add(type, spec); - } - } + Location contextLocation = contextClassDeclaration.GetLocation(); + if (!TryGetClassDeclarationList(contextTypeSymbol, out List? classDeclarationList)) + { + // Class or one of its containing types is not partial so we can't add to it. + ReportDiagnostic(DiagnosticDescriptors.ContextClassesMustBePartial, contextLocation, new string[] { contextTypeSymbol.Name }); + return null; + } - if (_generatedTypes.Count == 0) - { - continue; - } + options ??= new JsonSourceGenerationOptionsAttribute(); - ContextGenerationSpec contextGenSpec = new() - { - ContextType = new(contextTypeSymbol), - GeneratedTypes = _generatedTypes.Values.OrderBy(t => t.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(), - Namespace = contextTypeSymbol.ContainingNamespace.ToDisplayString(), - ContextClassDeclarations = classDeclarationList.ToImmutableEquatableArray(), - DefaultIgnoreCondition = options.DefaultIgnoreCondition, - IgnoreReadOnlyFields = options.IgnoreReadOnlyFields, - IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties, - IncludeFields = options.IncludeFields, - PropertyNamingPolicy = options.PropertyNamingPolicy, - WriteIndented = options.WriteIndented, - }; - - contextGenSpecList ??= new List(); - contextGenSpecList.Add(contextGenSpec); - - // Clear the caches of generated metadata between the processing of context classes. - _generatedTypes.Clear(); - _typesToGenerate.Clear(); - } + // Enqueue attribute data for spec generation + foreach (TypeToGenerate rootSerializableType in rootSerializableTypes) + { + EnqueueType(rootSerializableType.Type, rootSerializableType.Mode, rootSerializableType.TypeInfoPropertyName, rootSerializableType.AttributeLocation); } - if (contextGenSpecList == null) + // Walk the transitive type graph generating specs for every encountered type. + while (_typesToGenerate.Count > 0) { - return null; + cancellationToken.ThrowIfCancellationRequested(); + TypeToGenerate typeToGenerate = _typesToGenerate.Dequeue(); + if (!_generatedTypes.ContainsKey(typeToGenerate.Type)) + { + TypeGenerationSpec spec = ParseTypeGenerationSpec(typeToGenerate, contextName: contextTypeSymbol.Name, contextLocation, options); + _generatedTypes.Add(typeToGenerate.Type, spec); + } } - return new SourceGenerationSpec + Debug.Assert(_generatedTypes.Count > 0); + + ContextGenerationSpec contextGenSpec = new() { - ContextGenerationSpecs = contextGenSpecList.ToImmutableEquatableArray(), - Diagnostics = _diagnostics.ToImmutableEquatableArray(), + ContextType = new(contextTypeSymbol), + GeneratedTypes = _generatedTypes.Values.OrderBy(t => t.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(), + Namespace = contextTypeSymbol.ContainingNamespace.ToDisplayString(), + ContextClassDeclarations = classDeclarationList.ToImmutableEquatableArray(), + DefaultIgnoreCondition = options.DefaultIgnoreCondition, + IgnoreReadOnlyFields = options.IgnoreReadOnlyFields, + IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties, + IncludeFields = options.IncludeFields, + PropertyNamingPolicy = options.PropertyNamingPolicy, + WriteIndented = options.WriteIndented, }; + + // Clear the caches of generated metadata between the processing of context classes. + _generatedTypes.Clear(); + _typesToGenerate.Clear(); + return contextGenSpec; } // Returns true if a given type derives directly from JsonSerializerContext. @@ -380,75 +266,24 @@ private static string GetClassDeclarationName(INamedTypeSymbol typeSymbol) return sb.ToString(); } - private void EnqueueRootType( - SemanticModel compilationSemanticModel, - AttributeSyntax attributeSyntax, - JsonSourceGenerationMode generationMode, - CancellationToken cancellationToken) - { - IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); - - ITypeSymbol? typeSymbol = null; - string? typeInfoPropertyName = null; - - bool seenFirstArg = false; - foreach (AttributeArgumentSyntax node in attributeArguments) - { - if (!seenFirstArg) - { - TypeOfExpressionSyntax? typeNode = node.ChildNodes().Single() as TypeOfExpressionSyntax; - if (typeNode != null) - { - ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single(); - typeSymbol = compilationSemanticModel.GetTypeInfo(typeNameSyntax, cancellationToken).ConvertedType; - } - - seenFirstArg = true; - } - else - { - IEnumerable childNodes = node.ChildNodes(); - - NameEqualsSyntax? propertyNameNode = childNodes.First() as NameEqualsSyntax; - Debug.Assert(propertyNameNode != null); - - SyntaxNode propertyValueNode = childNodes.ElementAt(1); - string optionName = propertyNameNode.Name.Identifier.ValueText; - - if (optionName == nameof(JsonSerializableAttribute.TypeInfoPropertyName)) - { - typeInfoPropertyName = propertyValueNode.GetFirstToken().ValueText; - } - else if (optionName == nameof(JsonSerializableAttribute.GenerationMode)) - { - JsonSourceGenerationMode? mode = GetJsonSourceGenerationModeEnumVal(propertyValueNode); - if (mode.HasValue) - { - generationMode = mode.Value; - } - } - } - } - - if (typeSymbol == null) - { - return; - } - - EnqueueType(typeSymbol, generationMode, typeInfoPropertyName, attributeSyntax.GetLocation()); - } - - private TypeRef EnqueueType(ITypeSymbol type, JsonSourceGenerationMode generationMode, string? typeInfoPropertyName = null, Location? attributeLocation = null) + private TypeRef EnqueueType(ITypeSymbol type, JsonSourceGenerationMode? generationMode, string? typeInfoPropertyName = null, Location? attributeLocation = null) { // Trim compile-time erased metadata such as tuple labels and NRT annotations. - type = _compilation.EraseCompileTimeMetadata(type); + type = _knownSymbols.Compilation.EraseCompileTimeMetadata(type); if (_generatedTypes.TryGetValue(type, out TypeGenerationSpec? spec)) { return spec.TypeRef; } - _typesToGenerate.Enqueue((type, generationMode, typeInfoPropertyName, attributeLocation)); + _typesToGenerate.Enqueue(new TypeToGenerate + { + Type = type, + Mode = generationMode, + TypeInfoPropertyName = typeInfoPropertyName, + AttributeLocation = attributeLocation, + }); + return new TypeRef(type); } @@ -470,13 +305,53 @@ private TypeRef EnqueueType(ITypeSymbol type, JsonSourceGenerationMode generatio static bool IsValidEnumIdentifier(string token) => token != nameof(JsonSourceGenerationMode) && token != "." && token != "|"; } - private static JsonSourceGenerationOptionsAttribute? GetSerializerOptions(AttributeSyntax? attributeSyntax) + private bool TryParseJsonSerializerContextAttributes( + ClassDeclarationSyntax classDeclarationSyntax, + SemanticModel semanticModel, + CancellationToken cancellationToken, + out List? rootSerializableTypes, + out JsonSourceGenerationOptionsAttribute? options) { - if (attributeSyntax == null) + Debug.Assert(_knownSymbols.JsonSerializableAttributeType != null); + Debug.Assert(_knownSymbols.JsonSourceGenerationOptionsAttributeType != null); + + bool foundSourceGenAttributes = false; + rootSerializableTypes = null; + options = null; + + foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists) { - return null; + AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.First(); + if (semanticModel.GetSymbolInfo(attributeSyntax, cancellationToken).Symbol is not IMethodSymbol attributeSymbol) + { + continue; + } + + INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType; + + if (_knownSymbols.JsonSerializableAttributeType.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) + { + foundSourceGenAttributes = true; + TypeToGenerate? typeToGenerate = ParseJsonSerializableAttribute(semanticModel, attributeSyntax, cancellationToken); + if (typeToGenerate is null) + { + continue; + } + + (rootSerializableTypes ??= new()).Add(typeToGenerate.Value); + } + else if (_knownSymbols.JsonSourceGenerationOptionsAttributeType.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) + { + foundSourceGenAttributes = true; + options = ParseJsonSourceGenerationOptionsAttribute(attributeSyntax); + } } + return foundSourceGenAttributes; + } + + private static JsonSourceGenerationOptionsAttribute ParseJsonSourceGenerationOptionsAttribute(AttributeSyntax attributeSyntax) + { IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); JsonSourceGenerationOptionsAttribute options = new(); @@ -527,7 +402,7 @@ private TypeRef EnqueueType(ITypeSymbol type, JsonSourceGenerationMode generatio break; case nameof(JsonSourceGenerationOptionsAttribute.PropertyNamingPolicy): { - if (Enum.TryParse(propertyValueStr, out JsonKnownNamingPolicy value)) + if (Enum.TryParse(propertyValueStr, out JsonKnownNamingPolicy value)) { options.PropertyNamingPolicy = value; } @@ -558,9 +433,71 @@ private TypeRef EnqueueType(ITypeSymbol type, JsonSourceGenerationMode generatio return options; } - private TypeGenerationSpec CreateTypeGenerationSpec(ITypeSymbol type, JsonSourceGenerationMode generationMode, string? typeInfoPropertyName, Location? attributeLocation, Location contextLocation, string contextName) + private static TypeToGenerate? ParseJsonSerializableAttribute(SemanticModel semanticModel, AttributeSyntax attributeSyntax, CancellationToken cancellationToken) { - Location typeLocation = type.GetDiagnosticLocation() ?? attributeLocation ?? contextLocation; + IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); + + ITypeSymbol? typeSymbol = null; + string? typeInfoPropertyName = null; + JsonSourceGenerationMode? generationMode = null; + + bool seenFirstArg = false; + foreach (AttributeArgumentSyntax node in attributeArguments) + { + if (!seenFirstArg) + { + TypeOfExpressionSyntax? typeNode = node.ChildNodes().Single() as TypeOfExpressionSyntax; + if (typeNode != null) + { + ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single(); + typeSymbol = semanticModel.GetTypeInfo(typeNameSyntax, cancellationToken).ConvertedType; + } + + seenFirstArg = true; + } + else + { + IEnumerable childNodes = node.ChildNodes(); + + NameEqualsSyntax? propertyNameNode = childNodes.First() as NameEqualsSyntax; + Debug.Assert(propertyNameNode != null); + + SyntaxNode propertyValueNode = childNodes.ElementAt(1); + string optionName = propertyNameNode.Name.Identifier.ValueText; + + if (optionName == nameof(JsonSerializableAttribute.TypeInfoPropertyName)) + { + typeInfoPropertyName = propertyValueNode.GetFirstToken().ValueText; + } + else if (optionName == nameof(JsonSerializableAttribute.GenerationMode)) + { + JsonSourceGenerationMode? mode = GetJsonSourceGenerationModeEnumVal(propertyValueNode); + if (mode.HasValue) + { + generationMode = mode.Value; + } + } + } + } + + if (typeSymbol is null) + { + return null; + } + + return new TypeToGenerate + { + Type = typeSymbol, + Mode = generationMode, + TypeInfoPropertyName = typeInfoPropertyName, + AttributeLocation = attributeSyntax.GetLocation(), + }; + } + + private TypeGenerationSpec ParseTypeGenerationSpec(TypeToGenerate typeToGenerate, string contextName, Location contextLocation, JsonSourceGenerationOptionsAttribute options) + { + ITypeSymbol type = typeToGenerate.Type; + Location typeLocation = type.GetDiagnosticLocation() ?? typeToGenerate.AttributeLocation ?? contextLocation; ClassType classType; JsonPrimitiveTypeKind? primitiveTypeKind = GetPrimitiveTypeKind(type); @@ -618,11 +555,11 @@ private TypeGenerationSpec CreateTypeGenerationSpec(ITypeSymbol type, JsonSource { Debug.Assert(attributeData.ConstructorArguments.Length > 0); var derivedType = (ITypeSymbol)attributeData.ConstructorArguments[0].Value!; - EnqueueType(derivedType, generationMode); + EnqueueType(derivedType, typeToGenerate.Mode); - if (!isPolymorphic && generationMode == JsonSourceGenerationMode.Serialization) + if (!isPolymorphic && typeToGenerate.Mode == JsonSourceGenerationMode.Serialization) { - ReportDiagnostic(PolymorphismNotSupported, typeLocation, new string[] { type.ToDisplayString() }); + ReportDiagnostic(DiagnosticDescriptors.PolymorphismNotSupported, typeLocation, new string[] { type.ToDisplayString() }); } isPolymorphic = true; @@ -646,7 +583,7 @@ private TypeGenerationSpec CreateTypeGenerationSpec(ITypeSymbol type, JsonSource else if (type.IsNullableValueType(out ITypeSymbol? underlyingType)) { classType = ClassType.Nullable; - nullableUnderlyingType = EnqueueType(underlyingType, generationMode); + nullableUnderlyingType = EnqueueType(underlyingType, typeToGenerate.Mode); } else if (type.TypeKind is TypeKind.Enum) { @@ -661,7 +598,7 @@ private TypeGenerationSpec CreateTypeGenerationSpec(ITypeSymbol type, JsonSource } ITypeSymbol elementType = iasyncEnumerableType.TypeArguments[0]; - collectionValueType = EnqueueType(elementType, generationMode); + collectionValueType = EnqueueType(elementType, typeToGenerate.Mode); collectionType = CollectionType.IAsyncEnumerableOfT; classType = ClassType.Enumerable; } @@ -816,11 +753,11 @@ private TypeGenerationSpec CreateTypeGenerationSpec(ITypeSymbol type, JsonSource valueType = _knownSymbols.ObjectType; } - collectionValueType = EnqueueType(valueType, generationMode); + collectionValueType = EnqueueType(valueType, typeToGenerate.Mode); if (keyType != null) { - collectionKeyType = EnqueueType(keyType, generationMode); + collectionKeyType = EnqueueType(keyType, typeToGenerate.Mode); if (needsRuntimeType) { @@ -835,7 +772,7 @@ private TypeGenerationSpec CreateTypeGenerationSpec(ITypeSymbol type, JsonSource if (!TryGetDeserializationConstructor(type, useDefaultCtorInAnnotatedStructs, out IMethodSymbol? constructor)) { classType = ClassType.TypeUnsupportedBySourceGen; - ReportDiagnostic(MultipleJsonConstructorAttribute, typeLocation, new string[] { type.ToDisplayString() }); + ReportDiagnostic(DiagnosticDescriptors.MultipleJsonConstructorAttribute, typeLocation, new string[] { type.ToDisplayString() }); } else { @@ -859,7 +796,7 @@ private TypeGenerationSpec CreateTypeGenerationSpec(ITypeSymbol type, JsonSource for (int i = 0; i < paramCount; i++) { IParameterSymbol parameterInfo = parameters![i]; - TypeRef parameterTypeRef = EnqueueType(parameterInfo.Type, generationMode); + TypeRef parameterTypeRef = EnqueueType(parameterInfo.Type, typeToGenerate.Mode); paramGenSpecs[i] = new ParameterGenerationSpec { @@ -903,7 +840,7 @@ private TypeGenerationSpec CreateTypeGenerationSpec(ITypeSymbol type, JsonSource continue; } - PropertyGenerationSpec? spec = GetPropertyGenerationSpec(declaringTypeRef, propertyInfo.Type, propertyInfo, isVirtual, generationMode); + PropertyGenerationSpec? spec = ParsePropertyGenerationSpec(declaringTypeRef, propertyInfo.Type, propertyInfo, isVirtual, typeToGenerate.Mode, options); if (spec is null) { continue; @@ -928,7 +865,7 @@ private TypeGenerationSpec CreateTypeGenerationSpec(ITypeSymbol type, JsonSource continue; } - PropertyGenerationSpec? spec = GetPropertyGenerationSpec(declaringTypeRef, fieldInfo.Type, fieldInfo, isVirtual: false, generationMode); + PropertyGenerationSpec? spec = ParsePropertyGenerationSpec(declaringTypeRef, fieldInfo.Type, fieldInfo, isVirtual: false, typeToGenerate.Mode, options); if (spec is null) { continue; @@ -947,12 +884,12 @@ void CacheMemberHelper(ITypeSymbol memberType, ISymbol memberInfo, PropertyGener { if (extensionDataPropertyType != null) { - ReportDiagnostic(MultipleJsonExtensionDataAttribute, typeLocation, new string[] { type.Name }); + ReportDiagnostic(DiagnosticDescriptors.MultipleJsonExtensionDataAttribute, typeLocation, new string[] { type.Name }); } if (!IsValidDataExtensionPropertyType(memberType)) { - ReportDiagnostic(DataExtensionPropertyInvalid, memberInfo.GetDiagnosticLocation(), new string[] { type.Name, spec.MemberName }); + ReportDiagnostic(DiagnosticDescriptors.DataExtensionPropertyInvalid, memberInfo.GetDiagnosticLocation(), new string[] { type.Name, spec.MemberName }); } extensionDataPropertyType = spec.PropertyType; @@ -981,7 +918,7 @@ void CacheMemberHelper(ITypeSymbol memberType, ISymbol memberInfo, PropertyGener if (spec.HasJsonInclude && (!spec.CanUseGetter || !spec.CanUseSetter || !spec.IsPublic)) { - ReportDiagnostic(InaccessibleJsonIncludePropertiesNotSupported, memberInfo.GetDiagnosticLocation(), new string[] { type.Name, spec.MemberName }); + ReportDiagnostic(DiagnosticDescriptors.InaccessibleJsonIncludePropertiesNotSupported, memberInfo.GetDiagnosticLocation(), new string[] { type.Name, spec.MemberName }); } } } @@ -994,18 +931,18 @@ void CacheMemberHelper(ITypeSymbol memberType, ISymbol memberInfo, PropertyGener } var typeRef = new TypeRef(type); - typeInfoPropertyName ??= GetTypeInfoPropertyName(type); + string typeInfoPropertyName = typeToGenerate.TypeInfoPropertyName ?? GetTypeInfoPropertyName(type); if (classType is ClassType.TypeUnsupportedBySourceGen) { - ReportDiagnostic(TypeNotSupported, typeLocation, new string[] { typeRef.FullyQualifiedName }); + ReportDiagnostic(DiagnosticDescriptors.TypeNotSupported, typeLocation, new string[] { typeRef.FullyQualifiedName }); } if (!_generatedContextAndTypeNames.Add((contextName, typeInfoPropertyName))) { // The context name/property name combination will result in a conflict in generated types. // Workaround for https://github.com/dotnet/roslyn/issues/54185 by keeping track of the file names we've used. - ReportDiagnostic(DuplicateTypeName, attributeLocation ?? contextLocation, new string[] { typeInfoPropertyName }); + ReportDiagnostic(DiagnosticDescriptors.DuplicateTypeName, typeToGenerate.AttributeLocation ?? contextLocation, new string[] { typeInfoPropertyName }); classType = ClassType.TypeUnsupportedBySourceGen; } @@ -1013,7 +950,7 @@ void CacheMemberHelper(ITypeSymbol memberType, ISymbol memberInfo, PropertyGener { TypeRef = typeRef, TypeInfoPropertyName = typeInfoPropertyName, - GenerationMode = generationMode, + GenerationMode = typeToGenerate.Mode ?? options.GenerationMode, ClassType = classType, PrimitiveTypeKind = primitiveTypeKind, IsPolymorphic = isPolymorphic, @@ -1102,12 +1039,13 @@ private static bool PropertyIsOverriddenAndIgnored( ignoredMember.IsVirtual(); } - private PropertyGenerationSpec? GetPropertyGenerationSpec( + private PropertyGenerationSpec? ParsePropertyGenerationSpec( TypeRef declaringType, ITypeSymbol memberType, ISymbol memberInfo, bool isVirtual, - JsonSourceGenerationMode generationMode) + JsonSourceGenerationMode? generationMode, + JsonSourceGenerationOptionsAttribute options) { Debug.Assert(memberInfo is IFieldSymbol or IPropertySymbol); @@ -1141,7 +1079,7 @@ private static bool PropertyIsOverriddenAndIgnored( bool needsAtSign = memberInfo.MemberNameNeedsAtSign(); string clrName = memberInfo.Name; - string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, _currentContextNamingPolicy); + string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, options.PropertyNamingPolicy); string propertyNameVarName = DeterminePropNameIdentifier(runtimePropertyName); return new PropertyGenerationSpec @@ -1588,25 +1526,29 @@ SpecialType.System_String or _builtInSupportTypes.Contains(type); } - private void PopulateBuiltInSupportTypes() + private static HashSet CreateBuiltInSupportTypeSet(KnownTypeSymbols knownSymbols) { - HashSet builtInSupportTypes = _builtInSupportTypes; - - AddTypeIfNotNull(_knownSymbols.ByteArrayType); - AddTypeIfNotNull(_knownSymbols.TimeSpanType); - AddTypeIfNotNull(_knownSymbols.DateTimeOffsetType); - AddTypeIfNotNull(_knownSymbols.DateOnlyType); - AddTypeIfNotNull(_knownSymbols.TimeOnlyType); - AddTypeIfNotNull(_knownSymbols.GuidType); - AddTypeIfNotNull(_knownSymbols.UriType); - AddTypeIfNotNull(_knownSymbols.VersionType); - - AddTypeIfNotNull(_knownSymbols.JsonArrayType); - AddTypeIfNotNull(_knownSymbols.JsonElementType); - AddTypeIfNotNull(_knownSymbols.JsonNodeType); - AddTypeIfNotNull(_knownSymbols.JsonObjectType); - AddTypeIfNotNull(_knownSymbols.JsonValueType); - AddTypeIfNotNull(_knownSymbols.JsonDocumentType); +#pragma warning disable RS1024 // Compare symbols correctly https://github.com/dotnet/roslyn-analyzers/issues/5804 + HashSet builtInSupportTypes = new(SymbolEqualityComparer.Default); +#pragma warning restore + + AddTypeIfNotNull(knownSymbols.ByteArrayType); + AddTypeIfNotNull(knownSymbols.TimeSpanType); + AddTypeIfNotNull(knownSymbols.DateTimeOffsetType); + AddTypeIfNotNull(knownSymbols.DateOnlyType); + AddTypeIfNotNull(knownSymbols.TimeOnlyType); + AddTypeIfNotNull(knownSymbols.GuidType); + AddTypeIfNotNull(knownSymbols.UriType); + AddTypeIfNotNull(knownSymbols.VersionType); + + AddTypeIfNotNull(knownSymbols.JsonArrayType); + AddTypeIfNotNull(knownSymbols.JsonElementType); + AddTypeIfNotNull(knownSymbols.JsonNodeType); + AddTypeIfNotNull(knownSymbols.JsonObjectType); + AddTypeIfNotNull(knownSymbols.JsonValueType); + AddTypeIfNotNull(knownSymbols.JsonDocumentType); + + return builtInSupportTypes; void AddTypeIfNotNull(ITypeSymbol? type) { @@ -1616,6 +1558,14 @@ void AddTypeIfNotNull(ITypeSymbol? type) } } } + + private readonly struct TypeToGenerate + { + public required ITypeSymbol Type { get; init; } + public JsonSourceGenerationMode? Mode { get; init; } + public string? TypeInfoPropertyName { get; init; } + public Location? AttributeLocation { get; init; } + } } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs index 870883e8298e6f..dbcecd8e02ab80 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -23,7 +24,7 @@ public sealed partial class JsonSourceGenerator : ISourceGenerator public void Initialize(GeneratorInitializationContext context) { #if LAUNCH_DEBUGGER - Diagnostics.Debugger.Launch(); + System.Diagnostics.Debugger.Launch(); #endif // Unfortunately, there is no cancellation token that can be passed here @@ -39,22 +40,45 @@ public void Initialize(GeneratorInitializationContext context) /// public void Execute(GeneratorExecutionContext executionContext) { - if (executionContext.SyntaxContextReceiver is not SyntaxContextReceiver receiver || receiver.ClassDeclarationSyntaxList == null) + if (executionContext.SyntaxContextReceiver is not SyntaxContextReceiver receiver || receiver.ContextClassDeclarations == null) { // nothing to do yet return; } - JsonSourceGenerationContext context = new JsonSourceGenerationContext(executionContext); - Parser parser = new(executionContext.Compilation); - SourceGenerationSpec? spec = parser.GetGenerationSpec(receiver.ClassDeclarationSyntaxList, executionContext.CancellationToken); + // Stage 1. Parse the identified JsonSerializerContext classes and store the model types. + KnownTypeSymbols knownSymbols = new(executionContext.Compilation); + Parser parser = new(knownSymbols); - OnSourceEmitting?.Invoke(spec); + List? contextGenerationSpecs = null; + foreach ((ClassDeclarationSyntax? contextClassDeclaration, SemanticModel semanticModel) in receiver.ContextClassDeclarations) + { + ContextGenerationSpec? contextGenerationSpec = parser.ParseContextGenerationSpec(contextClassDeclaration, semanticModel, executionContext.CancellationToken); + if (contextGenerationSpec is null) + { + continue; + } + + (contextGenerationSpecs ??= new()).Add(contextGenerationSpec); + } + + // Stage 2. Report any diagnostics gathered by the parser. + foreach (DiagnosticInfo diagnosticInfo in parser.Diagnostics) + { + executionContext.ReportDiagnostic(diagnosticInfo.CreateDiagnostic()); + } + + if (contextGenerationSpecs is null) + { + return; + } - if (spec != null) + // Stage 3. Emit source code from the spec models. + OnSourceEmitting?.Invoke(contextGenerationSpecs.ToImmutableArray()); + Emitter emitter = new(in executionContext); + foreach (ContextGenerationSpec contextGenerationSpec in contextGenerationSpecs) { - Emitter emitter = new(context, spec); - emitter.Emit(); + emitter.Emit(contextGenerationSpec); } } @@ -67,7 +91,7 @@ public SyntaxContextReceiver(CancellationToken cancellationToken) _cancellationToken = cancellationToken; } - public List? ClassDeclarationSyntaxList { get; private set; } + public List<(ClassDeclarationSyntax, SemanticModel)>? ContextClassDeclarations { get; private set; } public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { @@ -76,7 +100,7 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) ClassDeclarationSyntax? classSyntax = GetSemanticTargetForGeneration(context, _cancellationToken); if (classSyntax != null) { - (ClassDeclarationSyntaxList ??= new List()).Add(classSyntax); + (ContextClassDeclarations ??= new()).Add((classSyntax, context.SemanticModel)); } } } @@ -107,7 +131,6 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) return classDeclarationSyntax; } } - } return null; @@ -117,26 +140,17 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) /// /// Instrumentation helper for unit tests. /// - public Action? OnSourceEmitting { get; init; } - } - - internal readonly struct JsonSourceGenerationContext - { - private readonly GeneratorExecutionContext _context; + public Action>? OnSourceEmitting { get; init; } - public JsonSourceGenerationContext(GeneratorExecutionContext context) + private partial class Emitter { - _context = context; - } + private readonly GeneratorExecutionContext _context; - public void ReportDiagnostic(Diagnostic diagnostic) - { - _context.ReportDiagnostic(diagnostic); - } + public Emitter(in GeneratorExecutionContext context) + => _context = context; - public void AddSource(string hintName, SourceText sourceText) - { - _context.AddSource(hintName, sourceText); + private partial void AddSource(string hintName, SourceText sourceText) + => _context.AddSource(hintName, sourceText); } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs index 222404812b6e09..6c2a75e4f78497 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.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.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -24,70 +25,67 @@ public sealed partial class JsonSourceGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { #if LAUNCH_DEBUGGER - Diagnostics.Debugger.Launch(); + System.Diagnostics.Debugger.Launch(); #endif - IncrementalValuesProvider classDeclarations = context.SyntaxProvider + IncrementalValueProvider knownTypeSymbols = context.CompilationProvider + .Select((compilation, _) => new KnownTypeSymbols(compilation)); + + IncrementalValuesProvider<(ContextGenerationSpec?, ImmutableEquatableArray)> contextGenerationSpecs = context.SyntaxProvider .ForAttributeWithMetadataName( #if !ROSLYN4_4_OR_GREATER context, #endif Parser.JsonSerializableAttributeFullName, (node, _) => node is ClassDeclarationSyntax, - (context, _) => (ClassDeclarationSyntax)context.TargetNode); - - IncrementalValueProvider sourceGenSpec = context.CompilationProvider - .Combine(classDeclarations.Collect()) + (context, _) => (ContextClass: (ClassDeclarationSyntax)context.TargetNode, context.SemanticModel)) + .Combine(knownTypeSymbols) .Select(static (tuple, cancellationToken) => { - Parser parser = new(tuple.Left); - return parser.GetGenerationSpec(tuple.Right, cancellationToken); + Parser parser = new(tuple.Right); + ContextGenerationSpec? contextGenerationSpec = parser.ParseContextGenerationSpec(tuple.Left.ContextClass, tuple.Left.SemanticModel, cancellationToken); + ImmutableEquatableArray diagnostics = parser.Diagnostics.ToImmutableEquatableArray(); + return (contextGenerationSpec, diagnostics); }) #if ROSLYN4_4_OR_GREATER - .WithTrackingName(SourceGenerationSpecTrackingName); -#else - ; + .WithTrackingName(SourceGenerationSpecTrackingName) #endif + ; - context.RegisterSourceOutput(sourceGenSpec, EmitSource); + context.RegisterSourceOutput(contextGenerationSpecs, ReportDiagnosticsAndEmitSource); } - private void EmitSource(SourceProductionContext sourceProductionContext, SourceGenerationSpec? sourceGenSpec) + private void ReportDiagnosticsAndEmitSource(SourceProductionContext sourceProductionContext, (ContextGenerationSpec? ContextGenerationSpec, ImmutableEquatableArray Diagnostics) input) { - OnSourceEmitting?.Invoke(sourceGenSpec); + // Report any diagnostics ahead of emitting. + foreach (DiagnosticInfo diagnostic in input.Diagnostics) + { + sourceProductionContext.ReportDiagnostic(diagnostic.CreateDiagnostic()); + } - if (sourceGenSpec is null) + if (input.ContextGenerationSpec is null) { return; } - JsonSourceGenerationContext context = new JsonSourceGenerationContext(sourceProductionContext); - Emitter emitter = new(context, sourceGenSpec); - emitter.Emit(); + OnSourceEmitting?.Invoke(ImmutableArray.Create(input.ContextGenerationSpec)); + Emitter emitter = new(in sourceProductionContext); + emitter.Emit(input.ContextGenerationSpec); } /// /// Instrumentation helper for unit tests. /// - public Action? OnSourceEmitting { get; init; } - } + public Action>? OnSourceEmitting { get; init; } - internal readonly struct JsonSourceGenerationContext - { - private readonly SourceProductionContext _context; - - public JsonSourceGenerationContext(SourceProductionContext context) + private partial class Emitter { - _context = context; - } + private readonly SourceProductionContext _context; - public void ReportDiagnostic(Diagnostic diagnostic) - { - _context.ReportDiagnostic(diagnostic); - } + public Emitter(in SourceProductionContext context) + => _context = context; - public void AddSource(string hintName, SourceText sourceText) - { - _context.AddSource(hintName, sourceText); + private partial void AddSource(string hintName, SourceText sourceText) + => _context.AddSource(hintName, sourceText); } } } diff --git a/src/libraries/System.Text.Json/gen/Model/SourceGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/SourceGenerationSpec.cs deleted file mode 100644 index b93e1e574a0623..00000000000000 --- a/src/libraries/System.Text.Json/gen/Model/SourceGenerationSpec.cs +++ /dev/null @@ -1,29 +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.Text.Json.SourceGeneration -{ - /// - /// Models all output produced by the source generator - /// - /// - /// Type needs to be cacheable as a Roslyn incremental value so it must be - /// - /// 1) immutable and - /// 2) implement structural (pointwise) equality comparison. - /// - /// We can get these properties for free provided that we - /// - /// a) define the type as an immutable C# record and - /// b) ensure all nested members are also immutable and implement structural equality. - /// - /// When adding new members to the type, please ensure that these properties - /// are satisfied otherwise we risk breaking incremental caching in the source generator! - /// - public sealed record SourceGenerationSpec - { - public required ImmutableEquatableArray ContextGenerationSpecs { get; init; } - - public required ImmutableEquatableArray Diagnostics { get; init; } - } -} diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets index affe3276d6d3c4..b7aa0a9f813a94 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets @@ -54,6 +54,7 @@ + @@ -65,7 +66,6 @@ - diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs index 37a5e4c0bd237d..66a55a42ea3a25 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs @@ -20,13 +20,11 @@ namespace System.Text.Json.SourceGeneration.UnitTests public record JsonSourceGeneratorResult { public Compilation NewCompilation { get; set; } - public SourceGenerationSpec? SourceGenModel { get; set; } + public ImmutableArray ContextGenerationSpecs { get; set; } public ImmutableArray Diagnostics { get; set; } public IEnumerable AllGeneratedTypes - => SourceGenModel is { } model - ? model.ContextGenerationSpecs.SelectMany(ctx => ctx.GeneratedTypes) - : Array.Empty(); + => ContextGenerationSpecs.SelectMany(ctx => ctx.GeneratedTypes); public void AssertContainsType(string fullyQualifiedName) => Assert.Contains( @@ -118,10 +116,10 @@ public static CSharpGeneratorDriver CreateJsonSourceGeneratorDriver(JsonSourceGe public static JsonSourceGeneratorResult RunJsonSourceGenerator(Compilation compilation) { - SourceGenerationSpec? generatedSpec = null; + var generatedSpecs = ImmutableArray.Empty; var generator = new JsonSourceGenerator { - OnSourceEmitting = spec => generatedSpec = spec + OnSourceEmitting = specs => generatedSpecs = specs }; CSharpGeneratorDriver driver = CreateJsonSourceGeneratorDriver(generator); @@ -130,7 +128,7 @@ public static JsonSourceGeneratorResult RunJsonSourceGenerator(Compilation compi { NewCompilation = outCompilation, Diagnostics = diagnostics, - SourceGenModel = generatedSpec, + ContextGenerationSpecs = generatedSpecs, }; } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorIncrementalTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorIncrementalTests.cs index 35081ca9a97eaa..60a3ed97d52646 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorIncrementalTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorIncrementalTests.cs @@ -21,17 +21,18 @@ public static void CompilingTheSameSourceResultsInEqualModels(Func JsonSourceGeneratorResult result1 = CompilationHelper.RunJsonSourceGenerator(factory()); JsonSourceGeneratorResult result2 = CompilationHelper.RunJsonSourceGenerator(factory()); - if (result1.SourceGenModel is null) - { - Assert.Null(result2.SourceGenModel); - } - else + Assert.Equal(result1.ContextGenerationSpecs.Length, result2.ContextGenerationSpecs.Length); + + for (int i = 0; i < result1.ContextGenerationSpecs.Length; i++) { - Assert.NotSame(result1.SourceGenModel, result2.SourceGenModel); - AssertStructurallyEqual(result1.SourceGenModel, result2.SourceGenModel); + ContextGenerationSpec ctx1 = result1.ContextGenerationSpecs[i]; + ContextGenerationSpec ctx2 = result2.ContextGenerationSpecs[i]; + + Assert.NotSame(ctx1, ctx2); + AssertStructurallyEqual(ctx1, ctx2); - Assert.Equal(result1.SourceGenModel, result2.SourceGenModel); - Assert.Equal(result1.SourceGenModel.GetHashCode(), result2.SourceGenModel.GetHashCode()); + Assert.Equal(ctx1, ctx2); + Assert.Equal(ctx1.GetHashCode(), ctx2.GetHashCode()); } } @@ -81,11 +82,17 @@ public partial class JsonContext : JsonSerializerContext { } Assert.Empty(result1.Diagnostics); Assert.Empty(result2.Diagnostics); - Assert.NotSame(result1.SourceGenModel, result2.SourceGenModel); - AssertStructurallyEqual(result1.SourceGenModel, result2.SourceGenModel); + Assert.Equal(1, result1.ContextGenerationSpecs.Length); + Assert.Equal(1, result2.ContextGenerationSpecs.Length); - Assert.Equal(result1.SourceGenModel, result2.SourceGenModel); - Assert.Equal(result1.SourceGenModel.GetHashCode(), result2.SourceGenModel.GetHashCode()); + ContextGenerationSpec ctx1 = result1.ContextGenerationSpecs[0]; + ContextGenerationSpec ctx2 = result2.ContextGenerationSpecs[0]; + + Assert.NotSame(ctx1, ctx2); + AssertStructurallyEqual(ctx1, ctx2); + + Assert.Equal(ctx1, ctx2); + Assert.Equal(ctx1.GetHashCode(), ctx2.GetHashCode()); } [Fact] @@ -128,7 +135,12 @@ public class MyPoco Assert.Empty(result1.Diagnostics); Assert.Empty(result2.Diagnostics); - Assert.NotEqual(result1.SourceGenModel, result2.SourceGenModel); + Assert.Equal(1, result1.ContextGenerationSpecs.Length); + Assert.Equal(1, result2.ContextGenerationSpecs.Length); + + ContextGenerationSpec ctx1 = result1.ContextGenerationSpecs[0]; + ContextGenerationSpec ctx2 = result2.ContextGenerationSpecs[0]; + Assert.NotEqual(ctx1, ctx2); } [Theory] @@ -136,7 +148,7 @@ public class MyPoco public static void SourceGenModelDoesNotEncapsulateSymbolsOrCompilationData(Func factory) { JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(factory()); - WalkObjectGraph(result.SourceGenModel); + WalkObjectGraph(result.ContextGenerationSpecs); static void WalkObjectGraph(object obj) { @@ -187,26 +199,50 @@ public static void IncrementalGenerator_SameInput_DoesNotRegenerate(Func - { - Assert.Collection(step.Inputs, - source => Assert.Equal(IncrementalStepRunReason.New, source.Source.Outputs[source.OutputIndex].Reason)); - Assert.Collection(step.Outputs, - output => Assert.Equal(IncrementalStepRunReason.New, output.Reason)); - }); + + IncrementalGeneratorRunStep[] runSteps = GetSourceGenRunStep(runResult); + if (runSteps != null) + { + Assert.Collection(runSteps, + step => + { + Assert.Collection(step.Inputs, + source => Assert.Equal(IncrementalStepRunReason.New, source.Source.Outputs[source.OutputIndex].Reason)); + Assert.Collection(step.Outputs, + output => Assert.Equal(IncrementalStepRunReason.New, output.Reason)); + }); + } // run the same compilation through again, and confirm the output wasn't called driver = driver.RunGenerators(compilation); runResult = driver.GetRunResult().Results[0]; - Assert.Collection(runResult.TrackedSteps[JsonSourceGenerator.SourceGenerationSpecTrackingName], - step => + IncrementalGeneratorRunStep[] runSteps2 = GetSourceGenRunStep(runResult); + + if (runSteps != null) + { + Assert.Collection(runSteps2, + step => + { + Assert.Collection(step.Inputs, + source => Assert.Equal(IncrementalStepRunReason.Cached, source.Source.Outputs[source.OutputIndex].Reason)); + Assert.Collection(step.Outputs, + output => Assert.Equal(IncrementalStepRunReason.Cached, output.Reason)); + }); + } + else + { + Assert.Null(runSteps2); + } + + static IncrementalGeneratorRunStep[]? GetSourceGenRunStep(GeneratorRunResult runResult) + { + if (!runResult.TrackedSteps.TryGetValue(JsonSourceGenerator.SourceGenerationSpecTrackingName, out var runSteps)) { - Assert.Collection(step.Inputs, - source => Assert.Equal(IncrementalStepRunReason.Cached, source.Source.Outputs[source.OutputIndex].Reason)); - Assert.Collection(step.Outputs, - output => Assert.Equal(IncrementalStepRunReason.Cached, output.Reason)); - }); + return null; + } + + return runSteps.ToArray(); + } } [Fact] From e1b196a5eb35042176926b5380174235001decbe Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 23 May 2023 15:28:03 +0100 Subject: [PATCH 2/2] Address feedback. --- .../gen/Helpers/KnownTypeSymbols.cs | 291 +++++++++++++----- .../gen/JsonSourceGenerator.Parser.cs | 12 +- .../gen/JsonSourceGenerator.Roslyn3.11.cs | 4 +- .../gen/JsonSourceGenerator.Roslyn4.0.cs | 4 +- 4 files changed, 230 insertions(+), 81 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs b/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs index 412a33bd692394..2c32d002381afa 100644 --- a/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs +++ b/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs @@ -11,86 +11,208 @@ namespace System.Text.Json.SourceGeneration { - internal sealed class KnownTypeSymbols(Compilation compilation) + internal sealed class KnownTypeSymbols { -#pragma warning disable CA1822 // Mark members as static false positive with primary constructors. - public Compilation Compilation => compilation!; - - public readonly INamedTypeSymbol? IListOfTType = compilation!.GetBestTypeByMetadataName(typeof(IList<>)); - public readonly INamedTypeSymbol? ICollectionOfTType = compilation!.GetBestTypeByMetadataName(typeof(ICollection<>)); - public readonly INamedTypeSymbol? IEnumerableType = compilation!.GetBestTypeByMetadataName(typeof(IEnumerable)); - public readonly INamedTypeSymbol? IEnumerableOfTType = compilation!.GetBestTypeByMetadataName(typeof(IEnumerable<>)); - - public readonly INamedTypeSymbol? ListOfTType = compilation!.GetBestTypeByMetadataName(typeof(List<>)); - public readonly INamedTypeSymbol? DictionaryOfTKeyTValueType = compilation!.GetBestTypeByMetadataName(typeof(Dictionary<,>)); - public readonly INamedTypeSymbol? IAsyncEnumerableOfTType = compilation!.GetBestTypeByMetadataName("System.Collections.Generic.IAsyncEnumerable`1"); - public readonly INamedTypeSymbol? IDictionaryOfTKeyTValueType = compilation!.GetBestTypeByMetadataName(typeof(IDictionary<,>)); - public readonly INamedTypeSymbol? IReadonlyDictionaryOfTKeyTValueType = compilation!.GetBestTypeByMetadataName(typeof(IReadOnlyDictionary<,>)); - public readonly INamedTypeSymbol? ISetOfTType = compilation!.GetBestTypeByMetadataName(typeof(ISet<>)); - public readonly INamedTypeSymbol? StackOfTType = compilation!.GetBestTypeByMetadataName(typeof(Stack<>)); - public readonly INamedTypeSymbol? QueueOfTType = compilation!.GetBestTypeByMetadataName(typeof(Queue<>)); - public readonly INamedTypeSymbol? ConcurrentStackType = compilation!.GetBestTypeByMetadataName(typeof(ConcurrentStack<>)); - public readonly INamedTypeSymbol? ConcurrentQueueType = compilation!.GetBestTypeByMetadataName(typeof(ConcurrentQueue<>)); - public readonly INamedTypeSymbol? IDictionaryType = compilation!.GetBestTypeByMetadataName(typeof(IDictionary)); - public readonly INamedTypeSymbol? IListType = compilation!.GetBestTypeByMetadataName(typeof(IList)); - public readonly INamedTypeSymbol? StackType = compilation!.GetBestTypeByMetadataName(typeof(Stack)); - public readonly INamedTypeSymbol? QueueType = compilation!.GetBestTypeByMetadataName(typeof(Queue)); - public readonly INamedTypeSymbol? KeyValuePair = compilation!.GetBestTypeByMetadataName(typeof(KeyValuePair<,>)); - - public readonly INamedTypeSymbol? ImmutableArrayType = compilation!.GetBestTypeByMetadataName(typeof(ImmutableArray<>)); - public readonly INamedTypeSymbol? ImmutableListType = compilation!.GetBestTypeByMetadataName(typeof(ImmutableList<>)); - public readonly INamedTypeSymbol? IImmutableListType = compilation!.GetBestTypeByMetadataName(typeof(IImmutableList<>)); - public readonly INamedTypeSymbol? ImmutableStackType = compilation!.GetBestTypeByMetadataName(typeof(ImmutableStack<>)); - public readonly INamedTypeSymbol? IImmutableStackType = compilation!.GetBestTypeByMetadataName(typeof(IImmutableStack<>)); - public readonly INamedTypeSymbol? ImmutableQueueType = compilation!.GetBestTypeByMetadataName(typeof(ImmutableQueue<>)); - public readonly INamedTypeSymbol? IImmutableQueueType = compilation!.GetBestTypeByMetadataName(typeof(IImmutableQueue<>)); - public readonly INamedTypeSymbol? ImmutableSortedType = compilation!.GetBestTypeByMetadataName(typeof(ImmutableSortedSet<>)); - public readonly INamedTypeSymbol? ImmutableHashSetType = compilation!.GetBestTypeByMetadataName(typeof(ImmutableHashSet<>)); - public readonly INamedTypeSymbol? IImmutableSetType = compilation!.GetBestTypeByMetadataName(typeof(IImmutableSet<>)); - public readonly INamedTypeSymbol? ImmutableDictionaryType = compilation!.GetBestTypeByMetadataName(typeof(ImmutableDictionary<,>)); - public readonly INamedTypeSymbol? ImmutableSortedDictionaryType = compilation!.GetBestTypeByMetadataName(typeof(ImmutableSortedDictionary<,>)); - public readonly INamedTypeSymbol? IImmutableDictionaryType = compilation!.GetBestTypeByMetadataName(typeof(IImmutableDictionary<,>)); - - public readonly INamedTypeSymbol ObjectType = compilation!.GetSpecialType(SpecialType.System_Object); - public readonly INamedTypeSymbol StringType = compilation!.GetSpecialType(SpecialType.System_String); - - public readonly INamedTypeSymbol? DateTimeOffsetType = compilation!.GetBestTypeByMetadataName(typeof(DateTimeOffset)); - public readonly INamedTypeSymbol? TimeSpanType = compilation!.GetBestTypeByMetadataName(typeof(TimeSpan)); - public readonly INamedTypeSymbol? DateOnlyType = compilation!.GetBestTypeByMetadataName("System.DateOnly"); - public readonly INamedTypeSymbol? TimeOnlyType = compilation!.GetBestTypeByMetadataName("System.TimeOnly"); - public readonly IArrayTypeSymbol? ByteArrayType = compilation!.CreateArrayTypeSymbol(compilation.GetSpecialType(SpecialType.System_Byte), rank: 1); - public readonly INamedTypeSymbol? GuidType = compilation!.GetBestTypeByMetadataName(typeof(Guid)); - public readonly INamedTypeSymbol? UriType = compilation!.GetBestTypeByMetadataName(typeof(Uri)); - public readonly INamedTypeSymbol? VersionType = compilation!.GetBestTypeByMetadataName(typeof(Version)); + public KnownTypeSymbols(Compilation compilation) + => Compilation = compilation; + + public Compilation Compilation { get; } + + // Caches a set of types with built-in converter support. Populated by the Parser class. + public HashSet? BuiltInSupportTypes { get; set; } + + public INamedTypeSymbol? IListOfTType => GetOrResolveType(typeof(IList<>), ref _IListOfTType); + private Option _IListOfTType; + + public INamedTypeSymbol? ICollectionOfTType => GetOrResolveType(typeof(ICollection<>), ref _ICollectionOfTType); + private Option _ICollectionOfTType; + + public INamedTypeSymbol? IEnumerableType => GetOrResolveType(typeof(IEnumerable), ref _IEnumerableType); + private Option _IEnumerableType; + + public INamedTypeSymbol? IEnumerableOfTType => GetOrResolveType(typeof(IEnumerable<>), ref _IEnumerableOfTType); + private Option _IEnumerableOfTType; + + public INamedTypeSymbol? ListOfTType => GetOrResolveType(typeof(List<>), ref _ListOfTType); + private Option _ListOfTType; + + public INamedTypeSymbol? DictionaryOfTKeyTValueType => GetOrResolveType(typeof(Dictionary<,>), ref _DictionaryOfTKeyTValueType); + private Option _DictionaryOfTKeyTValueType; + + public INamedTypeSymbol? IAsyncEnumerableOfTType => GetOrResolveType("System.Collections.Generic.IAsyncEnumerable`1", ref _AsyncEnumerableOfTType); + private Option _AsyncEnumerableOfTType; + + public INamedTypeSymbol? IDictionaryOfTKeyTValueType => GetOrResolveType(typeof(IDictionary<,>), ref _IDictionaryOfTKeyTValueType); + private Option _IDictionaryOfTKeyTValueType; + + public INamedTypeSymbol? IReadonlyDictionaryOfTKeyTValueType => GetOrResolveType(typeof(IReadOnlyDictionary<,>), ref _IReadonlyDictionaryOfTKeyTValueType); + private Option _IReadonlyDictionaryOfTKeyTValueType; + + public INamedTypeSymbol? ISetOfTType => GetOrResolveType(typeof(ISet<>), ref _ISetOfTType); + private Option _ISetOfTType; + + public INamedTypeSymbol? StackOfTType => GetOrResolveType(typeof(Stack<>), ref _StackOfTType); + private Option _StackOfTType; + + public INamedTypeSymbol? QueueOfTType => GetOrResolveType(typeof(Queue<>), ref _QueueOfTType); + private Option _QueueOfTType; + + public INamedTypeSymbol? ConcurrentStackType => GetOrResolveType(typeof(ConcurrentStack<>), ref _ConcurrentStackType); + private Option _ConcurrentStackType; + + public INamedTypeSymbol? ConcurrentQueueType => GetOrResolveType(typeof(ConcurrentQueue<>), ref _ConcurrentQueueType); + private Option _ConcurrentQueueType; + + public INamedTypeSymbol? IDictionaryType => GetOrResolveType(typeof(IDictionary), ref _IDictionaryType); + private Option _IDictionaryType; + + public INamedTypeSymbol? IListType => GetOrResolveType(typeof(IList), ref _IListType); + private Option _IListType; + + public INamedTypeSymbol? StackType => GetOrResolveType(typeof(Stack), ref _StackType); + private Option _StackType; + + public INamedTypeSymbol? QueueType => GetOrResolveType(typeof(Queue), ref _QueueType); + private Option _QueueType; + + public INamedTypeSymbol? KeyValuePair => GetOrResolveType(typeof(KeyValuePair<,>), ref _KeyValuePair); + private Option _KeyValuePair; + + public INamedTypeSymbol? ImmutableArrayType => GetOrResolveType(typeof(ImmutableArray<>), ref _ImmutableArrayType); + private Option _ImmutableArrayType; + + public INamedTypeSymbol? ImmutableListType => GetOrResolveType(typeof(ImmutableList<>), ref _ImmutableListType); + private Option _ImmutableListType; + + public INamedTypeSymbol? IImmutableListType => GetOrResolveType(typeof(IImmutableList<>), ref _IImmutableListType); + private Option _IImmutableListType; + + public INamedTypeSymbol? ImmutableStackType => GetOrResolveType(typeof(ImmutableStack<>), ref _ImmutableStackType); + private Option _ImmutableStackType; + + public INamedTypeSymbol? IImmutableStackType => GetOrResolveType(typeof(IImmutableStack<>), ref _IImmutableStackType); + private Option _IImmutableStackType; + + public INamedTypeSymbol? ImmutableQueueType => GetOrResolveType(typeof(ImmutableQueue<>), ref _ImmutableQueueType); + private Option _ImmutableQueueType; + + public INamedTypeSymbol? IImmutableQueueType => GetOrResolveType(typeof(IImmutableQueue<>), ref _IImmutableQueueType); + private Option _IImmutableQueueType; + + public INamedTypeSymbol? ImmutableSortedType => GetOrResolveType(typeof(ImmutableSortedSet<>), ref _ImmutableSortedType); + private Option _ImmutableSortedType; + + public INamedTypeSymbol? ImmutableHashSetType => GetOrResolveType(typeof(ImmutableHashSet<>), ref _ImmutableHashSetType); + private Option _ImmutableHashSetType; + + public INamedTypeSymbol? IImmutableSetType => GetOrResolveType(typeof(IImmutableSet<>), ref _IImmutableSetType); + private Option _IImmutableSetType; + + public INamedTypeSymbol? ImmutableDictionaryType => GetOrResolveType(typeof(ImmutableDictionary<,>), ref _ImmutableDictionaryType); + private Option _ImmutableDictionaryType; + + public INamedTypeSymbol? ImmutableSortedDictionaryType => GetOrResolveType(typeof(ImmutableSortedDictionary<,>), ref _ImmutableSortedDictionaryType); + private Option _ImmutableSortedDictionaryType; + + public INamedTypeSymbol? IImmutableDictionaryType => GetOrResolveType(typeof(IImmutableDictionary<,>), ref _IImmutableDictionaryType); + private Option _IImmutableDictionaryType; + + public INamedTypeSymbol ObjectType => _ObjectType ??= Compilation.GetSpecialType(SpecialType.System_Object); + private INamedTypeSymbol? _ObjectType; + + public INamedTypeSymbol StringType => _StringType ??= Compilation.GetSpecialType(SpecialType.System_String); + private INamedTypeSymbol? _StringType; + + public INamedTypeSymbol? DateTimeOffsetType => GetOrResolveType(typeof(DateTimeOffset), ref _DateTimeOffsetType); + private Option _DateTimeOffsetType; + + public INamedTypeSymbol? TimeSpanType => GetOrResolveType(typeof(TimeSpan), ref _TimeSpanType); + private Option _TimeSpanType; + + public INamedTypeSymbol? DateOnlyType => GetOrResolveType("System.DateOnly", ref _DateOnlyType); + private Option _DateOnlyType; + + public INamedTypeSymbol? TimeOnlyType => GetOrResolveType("System.TimeOnly", ref _TimeOnlyType); + private Option _TimeOnlyType; + + public IArrayTypeSymbol? ByteArrayType => _ByteArrayType.HasValue + ? _ByteArrayType.Value + : (_ByteArrayType = new(Compilation.CreateArrayTypeSymbol(Compilation.GetSpecialType(SpecialType.System_Byte), rank: 1))).Value; + + private Option _ByteArrayType; + + public INamedTypeSymbol? GuidType => GetOrResolveType(typeof(Guid), ref _GuidType); + private Option _GuidType; + + public INamedTypeSymbol? UriType => GetOrResolveType(typeof(Uri), ref _UriType); + private Option _UriType; + + public INamedTypeSymbol? VersionType => GetOrResolveType(typeof(Version), ref _VersionType); + private Option _VersionType; // System.Text.Json types - public readonly INamedTypeSymbol? JsonConverterType = compilation!.GetBestTypeByMetadataName("System.Text.Json.Serialization.JsonConverter"); - public readonly INamedTypeSymbol? JsonSerializerContextType = compilation.GetBestTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerContext"); - public readonly INamedTypeSymbol? JsonSerializableAttributeType = compilation.GetBestTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute"); + public INamedTypeSymbol? JsonConverterType => GetOrResolveType("System.Text.Json.Serialization.JsonConverter", ref _JsonConverterType); + private Option _JsonConverterType; + + public INamedTypeSymbol? JsonSerializerContextType => GetOrResolveType("System.Text.Json.Serialization.JsonSerializerContext", ref _JsonSerializerContextType); + private Option _JsonSerializerContextType; + + public INamedTypeSymbol? JsonSerializableAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonSerializableAttribute", ref _JsonSerializableAttributeType); + private Option _JsonSerializableAttributeType; - public readonly INamedTypeSymbol? JsonDocumentType = compilation!.GetBestTypeByMetadataName("System.Text.Json.JsonDocument"); - public readonly INamedTypeSymbol? JsonElementType = compilation!.GetBestTypeByMetadataName("System.Text.Json.JsonElement"); + public INamedTypeSymbol? JsonDocumentType => GetOrResolveType("System.Text.Json.JsonDocument", ref _JsonDocumentType); + private Option _JsonDocumentType; - public readonly INamedTypeSymbol? JsonNodeType = compilation!.GetBestTypeByMetadataName("System.Text.Json.Nodes.JsonNode"); - public readonly INamedTypeSymbol? JsonValueType = compilation!.GetBestTypeByMetadataName("System.Text.Json.Nodes.JsonValue"); - public readonly INamedTypeSymbol? JsonObjectType = compilation!.GetBestTypeByMetadataName("System.Text.Json.Nodes.JsonObject"); - public readonly INamedTypeSymbol? JsonArrayType = compilation!.GetBestTypeByMetadataName("System.Text.Json.Nodes.JsonArray"); + public INamedTypeSymbol? JsonElementType => GetOrResolveType("System.Text.Json.JsonElement", ref _JsonElementType); + private Option _JsonElementType; + + public INamedTypeSymbol? JsonNodeType => GetOrResolveType("System.Text.Json.Nodes.JsonNode", ref _JsonNodeType); + private Option _JsonNodeType; + + public INamedTypeSymbol? JsonValueType => GetOrResolveType("System.Text.Json.Nodes.JsonValue", ref _JsonValueType); + private Option _JsonValueType; + + public INamedTypeSymbol? JsonObjectType => GetOrResolveType("System.Text.Json.Nodes.JsonObject", ref _JsonObjectType); + private Option _JsonObjectType; + + public INamedTypeSymbol? JsonArrayType => GetOrResolveType("System.Text.Json.Nodes.JsonArray", ref _JsonArrayType); + private Option _JsonArrayType; // System.Text.Json attributes - public readonly INamedTypeSymbol? JsonConverterAttributeType = compilation!.GetBestTypeByMetadataName("System.Text.Json.Serialization.JsonConverterAttribute"); - public readonly INamedTypeSymbol? JsonDerivedTypeAttributeType = compilation!.GetBestTypeByMetadataName("System.Text.Json.Serialization.JsonDerivedTypeAttribute"); - public readonly INamedTypeSymbol? JsonNumberHandlingAttributeType = compilation!.GetBestTypeByMetadataName("System.Text.Json.Serialization.JsonNumberHandlingAttribute"); - public readonly INamedTypeSymbol? JsonObjectCreationHandlingAttributeType = compilation!.GetBestTypeByMetadataName("System.Text.Json.Serialization.JsonObjectCreationHandlingAttribute"); - public readonly INamedTypeSymbol? JsonSourceGenerationOptionsAttributeType = compilation.GetBestTypeByMetadataName("System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute"); - public readonly INamedTypeSymbol? JsonUnmappedMemberHandlingAttributeType = compilation!.GetBestTypeByMetadataName("System.Text.Json.Serialization.JsonUnmappedMemberHandlingAttribute"); + public INamedTypeSymbol? JsonConverterAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonConverterAttribute", ref _JsonConverterAttributeType); + private Option _JsonConverterAttributeType; + + public INamedTypeSymbol? JsonDerivedTypeAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonDerivedTypeAttribute", ref _JsonDerivedTypeAttributeType); + private Option _JsonDerivedTypeAttributeType; + + public INamedTypeSymbol? JsonNumberHandlingAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonNumberHandlingAttribute", ref _JsonNumberHandlingAttributeType); + private Option _JsonNumberHandlingAttributeType; + + public INamedTypeSymbol? JsonObjectCreationHandlingAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonObjectCreationHandlingAttribute", ref _JsonObjectCreationHandlingAttributeType); + private Option _JsonObjectCreationHandlingAttributeType; + + public INamedTypeSymbol? JsonSourceGenerationOptionsAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute", ref _JsonSourceGenerationOptionsAttributeType); + private Option _JsonSourceGenerationOptionsAttributeType; + + public INamedTypeSymbol? JsonUnmappedMemberHandlingAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonUnmappedMemberHandlingAttribute", ref _JsonUnmappedMemberHandlingAttributeType); + private Option _JsonUnmappedMemberHandlingAttributeType; // Unsupported types - public readonly INamedTypeSymbol? DelegateType = compilation!.GetSpecialType(SpecialType.System_Delegate); - public readonly INamedTypeSymbol? MemberInfoType = compilation!.GetBestTypeByMetadataName(typeof(MemberInfo)); - public readonly INamedTypeSymbol? SerializationInfoType = compilation!.GetBestTypeByMetadataName(typeof(Runtime.Serialization.SerializationInfo)); - public readonly INamedTypeSymbol? IntPtrType = compilation!.GetBestTypeByMetadataName(typeof(IntPtr)); - public readonly INamedTypeSymbol? UIntPtrType = compilation!.GetBestTypeByMetadataName(typeof(UIntPtr)); -#pragma warning restore CA1822 // Mark members as static false positive with primary constructors. + public INamedTypeSymbol? DelegateType => _DelegateType ??= Compilation.GetSpecialType(SpecialType.System_Delegate); + private INamedTypeSymbol? _DelegateType; + + public INamedTypeSymbol? MemberInfoType => GetOrResolveType(typeof(MemberInfo), ref _MemberInfoType); + private Option _MemberInfoType; + + public INamedTypeSymbol? SerializationInfoType => GetOrResolveType(typeof(Runtime.Serialization.SerializationInfo), ref _SerializationInfoType); + private Option _SerializationInfoType; + + public INamedTypeSymbol? IntPtrType => GetOrResolveType(typeof(IntPtr), ref _IntPtrType); + private Option _IntPtrType; + + public INamedTypeSymbol? UIntPtrType => GetOrResolveType(typeof(UIntPtr), ref _UIntPtrType); + private Option _UIntPtrType; + public bool IsImmutableEnumerableType(ITypeSymbol type, out string? factoryTypeFullName) { @@ -171,5 +293,32 @@ public bool IsImmutableDictionaryType(ITypeSymbol type, out string? factoryTypeF factoryTypeFullName = null; return false; } + + private INamedTypeSymbol? GetOrResolveType(Type type, ref Option field) + => GetOrResolveType(type.FullName!, ref field); + + private INamedTypeSymbol? GetOrResolveType(string fullyQualifiedName, ref Option field) + { + if (field.HasValue) + { + return field.Value; + } + + INamedTypeSymbol? type = Compilation.GetBestTypeByMetadataName(fullyQualifiedName); + field = new(type); + return type; + } + + private readonly struct Option + { + public readonly bool HasValue; + public readonly T Value; + + public Option(T value) + { + HasValue = true; + Value = value; + } + } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index d9e3bed8a71217..6fe2320dbe5ade 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -67,7 +67,7 @@ public Parser(KnownTypeSymbols knownSymbols) knownSymbols.JsonSourceGenerationOptionsAttributeType != null && knownSymbols.JsonConverterType != null; - _builtInSupportTypes = CreateBuiltInSupportTypeSet(knownSymbols); + _builtInSupportTypes = (knownSymbols.BuiltInSupportTypes ??= CreateBuiltInSupportTypeSet(knownSymbols)); } public ContextGenerationSpec? ParseContextGenerationSpec(ClassDeclarationSyntax contextClassDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken) @@ -89,11 +89,11 @@ public Parser(KnownTypeSymbols knownSymbols) } if (!TryParseJsonSerializerContextAttributes( - contextClassDeclaration, - semanticModel, - cancellationToken, - out List? rootSerializableTypes, - out JsonSourceGenerationOptionsAttribute? options)) + contextClassDeclaration, + semanticModel, + cancellationToken, + out List? rootSerializableTypes, + out JsonSourceGenerationOptionsAttribute? options)) { // Context does not specify any source gen attributes. return null; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs index dbcecd8e02ab80..4c58a3d968ac54 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs @@ -75,7 +75,7 @@ public void Execute(GeneratorExecutionContext executionContext) // Stage 3. Emit source code from the spec models. OnSourceEmitting?.Invoke(contextGenerationSpecs.ToImmutableArray()); - Emitter emitter = new(in executionContext); + Emitter emitter = new(executionContext); foreach (ContextGenerationSpec contextGenerationSpec in contextGenerationSpecs) { emitter.Emit(contextGenerationSpec); @@ -146,7 +146,7 @@ private partial class Emitter { private readonly GeneratorExecutionContext _context; - public Emitter(in GeneratorExecutionContext context) + public Emitter(GeneratorExecutionContext context) => _context = context; private partial void AddSource(string hintName, SourceText sourceText) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs index 6c2a75e4f78497..e3f8b4aacf6c5b 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs @@ -68,7 +68,7 @@ private void ReportDiagnosticsAndEmitSource(SourceProductionContext sourceProduc } OnSourceEmitting?.Invoke(ImmutableArray.Create(input.ContextGenerationSpec)); - Emitter emitter = new(in sourceProductionContext); + Emitter emitter = new(sourceProductionContext); emitter.Emit(input.ContextGenerationSpec); } @@ -81,7 +81,7 @@ private partial class Emitter { private readonly SourceProductionContext _context; - public Emitter(in SourceProductionContext context) + public Emitter(SourceProductionContext context) => _context = context; private partial void AddSource(string hintName, SourceText sourceText)