From 5a0d0813d32bcf29aff5d6b06c0eb8b00f183aeb Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Mon, 15 Aug 2022 19:37:02 -0400 Subject: [PATCH] Port the incremental source generator polyfill code to the 6.0 servicing branch. (#72219) * Port the incremental source generator polyfill code to the 6.0 servicing branch. * Use common code * Add logging extensions * Fix typo on project Compile item and enable producing packages for both packages that need to be serviced. * Allow ValueListBuilder to work with empty scratch spans (#70917) Co-authored-by: Jose Perez Rodriguez Co-authored-by: Stephen Toub --- .../Common/src/Roslyn/CSharpSyntaxHelper.cs | 125 ++++++ .../src/Roslyn/GetBestTypeByMetadataName.cs | 38 ++ .../Common/src/Roslyn/GlobalAliases.cs | 74 ++++ src/libraries/Common/src/Roslyn/Hash.cs | 24 ++ .../Common/src/Roslyn/ISyntaxHelper.cs | 66 +++ .../Common/src/Roslyn/SyntaxNodeGrouping.cs | 42 ++ ...lueProvider.ImmutableArrayValueComparer.cs | 29 ++ ...ueProvider_ForAttributeWithMetadataName.cs | 201 +++++++++ ...alueProvider_ForAttributeWithSimpleName.cs | 400 ++++++++++++++++++ .../gen/LoggerMessageGenerator.Parser.cs | 2 +- .../gen/LoggerMessageGenerator.Roslyn4.0.cs | 7 +- ...nsions.Logging.Generators.Roslyn4.0.csproj | 14 + ...oft.Extensions.Logging.Abstractions.csproj | 3 +- .../Generic/ValueListBuilder.Pop.cs | 0 .../Collections/Generic/ValueListBuilder.cs | 21 +- .../gen/JsonSourceGenerator.Parser.cs | 5 +- .../gen/JsonSourceGenerator.Roslyn4.0.cs | 8 +- ...ext.Json.SourceGeneration.Roslyn4.0.csproj | 14 + .../src/System.Text.Json.csproj | 3 +- .../src/System.Text.RegularExpressions.csproj | 2 +- 20 files changed, 1066 insertions(+), 12 deletions(-) create mode 100644 src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs create mode 100644 src/libraries/Common/src/Roslyn/GlobalAliases.cs create mode 100644 src/libraries/Common/src/Roslyn/Hash.cs create mode 100644 src/libraries/Common/src/Roslyn/ISyntaxHelper.cs create mode 100644 src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs create mode 100644 src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs create mode 100644 src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs create mode 100644 src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs rename src/libraries/{System.Text.RegularExpressions => System.Private.CoreLib}/src/System/Collections/Generic/ValueListBuilder.Pop.cs (100%) diff --git a/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs new file mode 100644 index 0000000000000..f7ab0d494a581 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs @@ -0,0 +1,125 @@ +// 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 Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions +{ + internal sealed class CSharpSyntaxHelper : AbstractSyntaxHelper + { + public static readonly ISyntaxHelper Instance = new CSharpSyntaxHelper(); + + private CSharpSyntaxHelper() + { + } + + public override bool IsCaseSensitive + => true; + + public override bool IsValidIdentifier(string name) + => SyntaxFacts.IsValidIdentifier(name); + + public override bool IsAnyNamespaceBlock(SyntaxNode node) + => node is BaseNamespaceDeclarationSyntax; + + public override bool IsAttribute(SyntaxNode node) + => node is AttributeSyntax; + + public override SyntaxNode GetNameOfAttribute(SyntaxNode node) + => ((AttributeSyntax)node).Name; + + public override bool IsAttributeList(SyntaxNode node) + => node is AttributeListSyntax; + + public override void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder targets) + { + var attributeList = (AttributeListSyntax)node; + var container = attributeList.Parent; + Debug.Assert(container != null); + + // For fields/events, the attribute applies to all the variables declared. + if (container is FieldDeclarationSyntax field) + { + foreach (var variable in field.Declaration.Variables) + targets.Append(variable); + } + else if (container is EventFieldDeclarationSyntax ev) + { + foreach (var variable in ev.Declaration.Variables) + targets.Append(variable); + } + else + { + targets.Append(container); + } + } + + public override SeparatedSyntaxList GetAttributesOfAttributeList(SyntaxNode node) + => ((AttributeListSyntax)node).Attributes; + + public override bool IsLambdaExpression(SyntaxNode node) + => node is LambdaExpressionSyntax; + + public override SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node) + => ((NameSyntax)node).GetUnqualifiedName().Identifier; + + public override void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global) + { + if (node is CompilationUnitSyntax compilationUnit) + { + AddAliases(compilationUnit.Usings, ref aliases, global); + } + else if (node is BaseNamespaceDeclarationSyntax namespaceDeclaration) + { + AddAliases(namespaceDeclaration.Usings, ref aliases, global); + } + else + { + Debug.Fail("This should not be reachable. Caller already checked we had a compilation unit or namespace."); + } + } + + private static void AddAliases(SyntaxList usings, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global) + { + foreach (var usingDirective in usings) + { + if (usingDirective.Alias is null) + continue; + + if (global != usingDirective.GlobalKeyword.Kind() is SyntaxKind.GlobalKeyword) + continue; + + var aliasName = usingDirective.Alias.Name.Identifier.ValueText; + var symbolName = usingDirective.Name.GetUnqualifiedName().Identifier.ValueText; + aliases.Append((aliasName, symbolName)); + } + } + + public override void AddAliases(CompilationOptions compilation, ref ValueListBuilder<(string aliasName, string symbolName)> aliases) + { + // C# doesn't have global aliases at the compilation level. + return; + } + + public override bool ContainsGlobalAliases(SyntaxNode root) + { + // Global usings can only exist at the compilation-unit level, so no need to dive any deeper than that. + var compilationUnit = (CompilationUnitSyntax)root; + + foreach (var directive in compilationUnit.Usings) + { + if (directive.GlobalKeyword.IsKind(SyntaxKind.GlobalKeyword) && + directive.Alias != null) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs index c86d7f2e00ebc..8dbf92f48a7fe 100644 --- a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions { @@ -134,5 +137,40 @@ private enum SymbolVisibility Private = 2, Friend = Internal, } + + internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive) + { + const string AttributeSuffix = "Attribute"; + + var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + return name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison); + } + + public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) + { + switch (span.Length) + { + case 0: return ImmutableArray.Empty; + case 1: return ImmutableArray.Create(span[0]); + case 2: return ImmutableArray.Create(span[0], span[1]); + case 3: return ImmutableArray.Create(span[0], span[1], span[2]); + case 4: return ImmutableArray.Create(span[0], span[1], span[2], span[3]); + default: + var builder = ImmutableArray.CreateBuilder(span.Length); + foreach (var item in span) + builder.Add(item); + + return builder.MoveToImmutable(); + } + } + + public static SimpleNameSyntax GetUnqualifiedName(this NameSyntax name) + => name switch + { + AliasQualifiedNameSyntax alias => alias.Name, + QualifiedNameSyntax qualified => qualified.Right, + SimpleNameSyntax simple => simple, + _ => throw new InvalidOperationException("Unreachable"), + }; } } diff --git a/src/libraries/Common/src/Roslyn/GlobalAliases.cs b/src/libraries/Common/src/Roslyn/GlobalAliases.cs new file mode 100644 index 0000000000000..2108923c76c27 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/GlobalAliases.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + +/// +/// Simple wrapper class around an immutable array so we can have the value-semantics needed for the incremental +/// generator to know when a change actually happened and it should run later transform stages. +/// +internal sealed class GlobalAliases : IEquatable +{ + public static readonly GlobalAliases Empty = new(ImmutableArray<(string aliasName, string symbolName)>.Empty); + + public readonly ImmutableArray<(string aliasName, string symbolName)> AliasAndSymbolNames; + + private int _hashCode; + + private GlobalAliases(ImmutableArray<(string aliasName, string symbolName)> aliasAndSymbolNames) + { + AliasAndSymbolNames = aliasAndSymbolNames; + } + + public static GlobalAliases Create(ImmutableArray<(string aliasName, string symbolName)> aliasAndSymbolNames) + { + return aliasAndSymbolNames.IsEmpty ? Empty : new GlobalAliases(aliasAndSymbolNames); + } + + public static GlobalAliases Concat(GlobalAliases ga1, GlobalAliases ga2) + { + if (ga1.AliasAndSymbolNames.Length == 0) + return ga2; + + if (ga2.AliasAndSymbolNames.Length == 0) + return ga1; + + return new(ga1.AliasAndSymbolNames.AddRange(ga2.AliasAndSymbolNames)); + } + + public override int GetHashCode() + { + if (_hashCode == 0) + { + var hashCode = 0; + foreach (var tuple in this.AliasAndSymbolNames) + hashCode = Hash.Combine(tuple.GetHashCode(), hashCode); + + _hashCode = hashCode == 0 ? 1 : hashCode; + } + + return _hashCode; + } + + public override bool Equals(object? obj) + => this.Equals(obj as GlobalAliases); + + public bool Equals(GlobalAliases? aliases) + { + if (aliases is null) + return false; + + if (ReferenceEquals(this, aliases)) + return true; + + if (this.AliasAndSymbolNames == aliases.AliasAndSymbolNames) + return true; + + return this.AliasAndSymbolNames.AsSpan().SequenceEqual(aliases.AliasAndSymbolNames.AsSpan()); + } +} diff --git a/src/libraries/Common/src/Roslyn/Hash.cs b/src/libraries/Common/src/Roslyn/Hash.cs new file mode 100644 index 0000000000000..028ac50017e0b --- /dev/null +++ b/src/libraries/Common/src/Roslyn/Hash.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace Roslyn.Utilities +{ + internal static class Hash + { + /// + /// This is how VB Anonymous Types combine hash values for fields. + /// + internal static int Combine(int newKey, int currentKey) + { + return unchecked((currentKey * (int)0xA5555529) + newKey); + } + + // The rest of this file was removed as they were not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName. + // If that changes, they should be added back as necessary. + } +} diff --git a/src/libraries/Common/src/Roslyn/ISyntaxHelper.cs b/src/libraries/Common/src/Roslyn/ISyntaxHelper.cs new file mode 100644 index 0000000000000..846ef9b16d13f --- /dev/null +++ b/src/libraries/Common/src/Roslyn/ISyntaxHelper.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions +{ + internal interface ISyntaxHelper + { + bool IsCaseSensitive { get; } + + bool IsValidIdentifier(string name); + + bool IsAnyNamespaceBlock(SyntaxNode node); + + bool IsAttributeList(SyntaxNode node); + SeparatedSyntaxList GetAttributesOfAttributeList(SyntaxNode node); + + void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder targets); + + bool IsAttribute(SyntaxNode node); + SyntaxNode GetNameOfAttribute(SyntaxNode node); + + bool IsLambdaExpression(SyntaxNode node); + + SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node); + + /// + /// must be a compilation unit or namespace block. + /// + void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global); + void AddAliases(CompilationOptions options, ref ValueListBuilder<(string aliasName, string symbolName)> aliases); + + bool ContainsGlobalAliases(SyntaxNode root); + } + + internal abstract class AbstractSyntaxHelper : ISyntaxHelper + { + public abstract bool IsCaseSensitive { get; } + + public abstract bool IsValidIdentifier(string name); + + public abstract SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode name); + + public abstract bool IsAnyNamespaceBlock(SyntaxNode node); + + public abstract bool IsAttribute(SyntaxNode node); + public abstract SyntaxNode GetNameOfAttribute(SyntaxNode node); + + public abstract bool IsAttributeList(SyntaxNode node); + public abstract SeparatedSyntaxList GetAttributesOfAttributeList(SyntaxNode node); + public abstract void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder targets); + + public abstract bool IsLambdaExpression(SyntaxNode node); + + public abstract void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global); + public abstract void AddAliases(CompilationOptions options, ref ValueListBuilder<(string aliasName, string symbolName)> aliases); + + public abstract bool ContainsGlobalAliases(SyntaxNode root); + } +} diff --git a/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs b/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs new file mode 100644 index 0000000000000..94dd5ae5f9d13 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Immutable; +using System.Linq; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + +internal static partial class SyntaxValueProviderExtensions +{ + /// + /// Wraps a grouping of nodes within a syntax tree so we can have value-semantics around them usable by the + /// incremental driver. Note: we do something very sneaky here. Specifically, as long as we have the same from before, then we know we must have the same nodes as before (since the nodes are + /// entirely determined from the text+options which is exactly what the syntax tree represents). Similarly, if the + /// syntax tree changes, we will always get different nodes (since they point back at the syntax tree). So we can + /// just use the syntax tree itself to determine value semantics here. + /// + private sealed class SyntaxNodeGrouping : IEquatable> + where TSyntaxNode : SyntaxNode + { + public readonly SyntaxTree SyntaxTree; + public readonly ImmutableArray SyntaxNodes; + + public SyntaxNodeGrouping(IGrouping grouping) + { + SyntaxTree = grouping.Key; + SyntaxNodes = grouping.OrderBy(static n => n.FullSpan.Start).ToImmutableArray(); + } + + public override int GetHashCode() + => SyntaxTree.GetHashCode(); + + public override bool Equals(object? obj) + => Equals(obj as SyntaxNodeGrouping); + + public bool Equals(SyntaxNodeGrouping? obj) + => this.SyntaxTree == obj?.SyntaxTree; + } +} diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs new file mode 100644 index 0000000000000..5323a100883a7 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + +internal static partial class SyntaxValueProviderExtensions +{ + private sealed class ImmutableArrayValueComparer : IEqualityComparer> + { + public static readonly IEqualityComparer> Instance = new ImmutableArrayValueComparer(); + + public bool Equals(ImmutableArray x, ImmutableArray y) + => x.SequenceEqual(y, EqualityComparer.Default); + + public int GetHashCode(ImmutableArray obj) + { + var hashCode = 0; + foreach (var value in obj) + hashCode = Hash.Combine(hashCode, EqualityComparer.Default.GetHashCode(value!)); + + return hashCode; + } + } +} diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs new file mode 100644 index 0000000000000..f6be8fbcb6858 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs @@ -0,0 +1,201 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +using Microsoft.CodeAnalysis; + +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + +internal readonly struct GeneratorAttributeSyntaxContext +{ + /// + /// The syntax node the attribute is attached to. For example, with [CLSCompliant] class C { } this would + /// the class declaration node. + /// + public SyntaxNode TargetNode { get; } + + /// + /// The symbol that the attribute is attached to. For example, with [CLSCompliant] class C { } this would be + /// the for "C". + /// + public ISymbol TargetSymbol { get; } + + /// + /// Semantic model for the file that is contained within. + /// + public SemanticModel SemanticModel { get; } + + /// + /// s for any matching attributes on . Always non-empty. All + /// these attributes will have an whose fully qualified name metadata + /// name matches the name requested in . + /// + /// To get the entire list of attributes, use on . + /// + /// + public ImmutableArray Attributes { get; } + + internal GeneratorAttributeSyntaxContext( + SyntaxNode targetNode, + ISymbol targetSymbol, + SemanticModel semanticModel, + ImmutableArray attributes) + { + TargetNode = targetNode; + TargetSymbol = targetSymbol; + SemanticModel = semanticModel; + Attributes = attributes; + } +} + +internal static partial class SyntaxValueProviderExtensions +{ +#if false + + // Deviation from roslyn. We do not support attributes that are nested or generic. That's ok as that's not a + // scenario that ever arises in our generators. + + private static readonly char[] s_nestedTypeNameSeparators = new char[] { '+' }; + + private static readonly SymbolDisplayFormat s_metadataDisplayFormat = + SymbolDisplayFormat.QualifiedNameArityFormat.AddCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UsePlusForNestedTypes); + +#endif + + /// + /// Creates an that can provide a transform over all s if that node has an attribute on it that binds to a with the + /// same fully-qualified metadata as the provided . should be the fully-qualified, metadata name of the attribute, including the + /// Attribute suffix. For example "System.CLSCompliantAttribute for . + /// + /// A function that determines if the given attribute target () should be transformed. Nodes that do not pass this + /// predicate will not have their attributes looked at at all. + /// A function that performs the transform. This will only be passed nodes that return for and which have a matching whose + /// has the same fully qualified, metadata name as . + public static IncrementalValuesProvider ForAttributeWithMetadataName( + this SyntaxValueProvider @this, + IncrementalGeneratorInitializationContext context, + string fullyQualifiedMetadataName, + Func predicate, + Func transform) + { +#if false + + // Deviation from roslyn. We do not support attributes that are nested or generic. That's ok as that's not a + // scenario that ever arises in our generators. + + var metadataName = fullyQualifiedMetadataName.Contains('+') + ? MetadataTypeName.FromFullName(fullyQualifiedMetadataName.Split(s_nestedTypeNameSeparators).Last()) + : MetadataTypeName.FromFullName(fullyQualifiedMetadataName); + + var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(context, metadataName.UnmangledTypeName, predicate); + +#else + + var lastDotIndex = fullyQualifiedMetadataName.LastIndexOf('.'); + Debug.Assert(lastDotIndex > 0); + var unmangledTypeName = fullyQualifiedMetadataName.Substring(lastDotIndex + 1); + + var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(context, unmangledTypeName, predicate); + +#endif + + var compilationAndGroupedNodesProvider = nodesWithAttributesMatchingSimpleName + .Combine(context.CompilationProvider) + /*.WithTrackingName("compilationAndGroupedNodes_ForAttributeWithMetadataName")*/; + + var syntaxHelper = CSharpSyntaxHelper.Instance; + var finalProvider = compilationAndGroupedNodesProvider.SelectMany((tuple, cancellationToken) => + { + var ((syntaxTree, syntaxNodes), compilation) = tuple; + Debug.Assert(syntaxNodes.All(n => n.SyntaxTree == syntaxTree)); + + using var result = new ValueListBuilder(Span.Empty); + if (!syntaxNodes.IsEmpty) + { + var semanticModel = compilation.GetSemanticModel(syntaxTree); + + foreach (var targetNode in syntaxNodes) + { + cancellationToken.ThrowIfCancellationRequested(); + + var targetSymbol = + targetNode is ICompilationUnitSyntax compilationUnit ? semanticModel.Compilation.Assembly : + syntaxHelper.IsLambdaExpression(targetNode) ? semanticModel.GetSymbolInfo(targetNode, cancellationToken).Symbol : + semanticModel.GetDeclaredSymbol(targetNode, cancellationToken); + if (targetSymbol is null) + continue; + + var attributes = getMatchingAttributes(targetNode, targetSymbol, fullyQualifiedMetadataName); + if (attributes.Length > 0) + { + result.Append(transform( + new GeneratorAttributeSyntaxContext(targetNode, targetSymbol, semanticModel, attributes), + cancellationToken)); + } + } + } + + return result.AsSpan().ToImmutableArray(); + })/*.WithTrackingName("result_ForAttributeWithMetadataName")*/; + + return finalProvider; + + static ImmutableArray getMatchingAttributes( + SyntaxNode attributeTarget, + ISymbol symbol, + string fullyQualifiedMetadataName) + { + var targetSyntaxTree = attributeTarget.SyntaxTree; + var result = new ValueListBuilder(Span.Empty); + + try + { + addMatchingAttributes(ref result, symbol.GetAttributes()); + addMatchingAttributes(ref result, (symbol as IMethodSymbol)?.GetReturnTypeAttributes()); + + if (symbol is IAssemblySymbol assemblySymbol) + { + foreach (var module in assemblySymbol.Modules) + addMatchingAttributes(ref result, module.GetAttributes()); + } + + return result.AsSpan().ToImmutableArray(); + } + finally + { + result.Dispose(); + } + + void addMatchingAttributes( + ref ValueListBuilder result, + ImmutableArray? attributes) + { + if (!attributes.HasValue) + return; + + foreach (var attribute in attributes.Value) + { + if (attribute.ApplicationSyntaxReference?.SyntaxTree == targetSyntaxTree && + attribute.AttributeClass?.ToDisplayString(/*s_metadataDisplayFormat*/) == fullyQualifiedMetadataName) + { + result.Append(attribute); + } + } + } + } + } +} diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs new file mode 100644 index 0000000000000..03ab7371863e3 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -0,0 +1,400 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Aliases = System.Collections.Generic.ValueListBuilder<(string aliasName, string symbolName)>; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + +internal static partial class SyntaxValueProviderExtensions +{ + // Normal class as Runtime does not seem to support records currently. + + /// + /// Information computed about a particular tree. Cached so we don't repeatedly recompute this important + /// information each time the incremental pipeline is rerun. + /// + private sealed class SyntaxTreeInfo : IEquatable + { + public readonly SyntaxTree Tree; + public readonly bool ContainsGlobalAliases; + public readonly bool ContainsAttributeList; + + public SyntaxTreeInfo(SyntaxTree tree, bool containsGlobalAliases, bool containsAttributeList) + { + Tree = tree; + ContainsGlobalAliases = containsGlobalAliases; + ContainsAttributeList = containsAttributeList; + } + + public bool Equals(SyntaxTreeInfo other) + => Tree == other?.Tree; + + public override bool Equals(object obj) + => this.Equals(obj as SyntaxTreeInfo); + + public override int GetHashCode() + => Tree.GetHashCode(); + } + + /// + /// Caching of syntax-tree to the info we've computed about it. Used because compilations will have thousands of + /// trees, and the incremental pipeline will get called back for *all* of them each time a compilation changes. We + /// do not want to continually recompute this data over and over again each time that happens given that normally + /// only one tree will be different. We also do not want to create an IncrementalValuesProvider that yield this + /// information as that will mean we have a node in the tree that scales with the number of *all syntax trees*, not + /// the number of *relevant syntax trees*. This can lead to huge memory churn keeping track of a high number of + /// trees, most of which are not going to be relevant. + /// + private static readonly ConditionalWeakTable s_treeToInfo = new ConditionalWeakTable(); + +#if false + // Not used in runtime. Pooling is not a pattern here, and we use ValueListBuilder instead. + + private static readonly ObjectPool> s_stackPool = new(static () => new()); +#endif + + /// + /// Returns all syntax nodes of that match if that node has an attribute on it that + /// could possibly bind to the provided . should be the + /// simple, non-qualified, name of the attribute, including the Attribute suffix, and not containing any + /// generics, containing types, or namespaces. For example CLSCompliantAttribute for . + /// This provider understands (Import in Visual Basic) aliases and will find + /// matches even when the attribute references an alias name. For example, given: + /// + /// using XAttribute = System.CLSCompliantAttribute; + /// [X] + /// class C { } + /// + /// Then + /// context.SyntaxProvider.CreateSyntaxProviderForAttribute(nameof(CLSCompliantAttribute), (node, c) => node is ClassDeclarationSyntax) + /// will find the C class. + /// + /// + /// Note: a 'Values'-provider of arrays are returned. Each array provides all the matching nodes from a single . + /// + public static IncrementalValuesProvider<(SyntaxTree tree, ImmutableArray matches)> ForAttributeWithSimpleName( + this SyntaxValueProvider @this, + IncrementalGeneratorInitializationContext context, + string simpleName, + Func predicate) + { + var syntaxHelper = CSharpSyntaxHelper.Instance; + + // Create a provider over all the syntax trees in the compilation. This is better than CreateSyntaxProvider as + // using SyntaxTrees is purely syntax and will not update the incremental node for a tree when another tree is + // changed. CreateSyntaxProvider will have to rerun all incremental nodes since it passes along the + // SemanticModel, and that model is updated whenever any tree changes (since it is tied to the compilation). + var syntaxTreesProvider = context.CompilationProvider + .SelectMany((compilation, cancellationToken) => compilation.SyntaxTrees + .Select(tree => GetTreeInfo(tree, syntaxHelper, cancellationToken)) + .Where(info => info.ContainsGlobalAliases || info.ContainsAttributeList)) + /*.WithTrackingName("compilationUnit_ForAttribute")*/; + + // Create a provider that provides (and updates) the global aliases for any particular file when it is edited. + var individualFileGlobalAliasesProvider = syntaxTreesProvider + .Where(info => info.ContainsGlobalAliases) + .Select((info, cancellationToken) => getGlobalAliasesInCompilationUnit(info.Tree.GetRoot(cancellationToken))) + /*.WithTrackingName("individualFileGlobalAliases_ForAttribute")*/; + + // Create an aggregated view of all global aliases across all files. This should only update when an individual + // file changes its global aliases or a file is added / removed from the compilation + var collectedGlobalAliasesProvider = individualFileGlobalAliasesProvider + .Collect() + .WithComparer(ImmutableArrayValueComparer.Instance) + /*.WithTrackingName("collectedGlobalAliases_ForAttribute")*/; + + var allUpGlobalAliasesProvider = collectedGlobalAliasesProvider + .Select(static (arrays, _) => GlobalAliases.Create(arrays.SelectMany(a => a.AliasAndSymbolNames).ToImmutableArray())) + /*.WithTrackingName("allUpGlobalAliases_ForAttribute")*/; + +#if false + + // C# does not support global aliases from compilation options. So we can just ignore this part. + + // Regenerate our data if the compilation options changed. VB can supply global aliases with compilation options, + // so we have to reanalyze everything if those changed. + var compilationGlobalAliases = _context.CompilationOptionsProvider.Select( + (o, _) => + { + var aliases = Aliases.GetInstance(); + syntaxHelper.AddAliases(o, aliases); + return GlobalAliases.Create(aliases.ToImmutableAndFree()); + }).WithTrackingName("compilationGlobalAliases_ForAttribute"); + + allUpGlobalAliasesProvider = allUpGlobalAliasesProvider + .Combine(compilationGlobalAliases) + .Select((tuple, _) => GlobalAliases.Concat(tuple.Left, tuple.Right)) + .WithTrackingName("allUpIncludingCompilationGlobalAliases_ForAttribute"); + +#endif + + // Combine the two providers so that we reanalyze every file if the global aliases change, or we reanalyze a + // particular file when it's compilation unit changes. + var syntaxTreeAndGlobalAliasesProvider = syntaxTreesProvider + .Where(info => info.ContainsAttributeList) + .Combine(allUpGlobalAliasesProvider) + /*.WithTrackingName("compilationUnitAndGlobalAliases_ForAttribute")*/; + + // For each pair of compilation unit + global aliases, walk the compilation unit + var result = syntaxTreeAndGlobalAliasesProvider + .Select((tuple, c) => (tuple.Left.Tree, GetMatchingNodes(syntaxHelper, tuple.Right, tuple.Left.Tree, simpleName, predicate, c))) + .Where(tuple => tuple.Item2.Length > 0) + /*.WithTrackingName("result_ForAttribute")*/; + + return result; + + static GlobalAliases getGlobalAliasesInCompilationUnit( + SyntaxNode compilationUnit) + { + Debug.Assert(compilationUnit is ICompilationUnitSyntax); + var globalAliases = new Aliases(Span<(string aliasName, string symbolName)>.Empty); + + CSharpSyntaxHelper.Instance.AddAliases(compilationUnit, ref globalAliases, global: true); + + return GlobalAliases.Create(globalAliases.AsSpan().ToImmutableArray()); + } + } + + private static SyntaxTreeInfo GetTreeInfo( + SyntaxTree tree, ISyntaxHelper syntaxHelper, CancellationToken cancellationToken) + { + // prevent captures for the case where the item is in the tree. + return s_treeToInfo.TryGetValue(tree, out var info) + ? info + : computeTreeInfo(); + + SyntaxTreeInfo computeTreeInfo() + { + var root = tree.GetRoot(cancellationToken); + var containsGlobalAliases = syntaxHelper.ContainsGlobalAliases(root); + var containsAttributeList = ContainsAttributeList(root); + + var info = new SyntaxTreeInfo(tree, containsGlobalAliases, containsAttributeList); + return s_treeToInfo.GetValue(tree, _ => info); + } + } + + private static ImmutableArray GetMatchingNodes( + ISyntaxHelper syntaxHelper, + GlobalAliases globalAliases, + SyntaxTree syntaxTree, + string name, + Func predicate, + CancellationToken cancellationToken) + { + var compilationUnit = syntaxTree.GetRoot(cancellationToken); + Debug.Assert(compilationUnit is ICompilationUnitSyntax); + + var isCaseSensitive = syntaxHelper.IsCaseSensitive; + var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + + // As we walk down the compilation unit and nested namespaces, we may encounter additional using aliases local + // to this file. Keep track of them so we can determine if they would allow an attribute in code to bind to the + // attribute being searched for. + var localAliases = new Aliases(Span<(string, string)>.Empty); + var nameHasAttributeSuffix = name.HasAttributeSuffix(isCaseSensitive); + + // Used to ensure that as we recurse through alias names to see if they could bind to attributeName that we + // don't get into cycles. + + var seenNames = new ValueListBuilder(Span.Empty); + var results = new ValueListBuilder(Span.Empty); + var attributeTargets = new ValueListBuilder(Span.Empty); + + try + { + recurse(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); + + if (results.Length == 0) + return ImmutableArray.Empty; + + return results.AsSpan().ToArray().Distinct().ToImmutableArray(); + } + finally + { + attributeTargets.Dispose(); + results.Dispose(); + seenNames.Dispose(); + } + + void recurse( + SyntaxNode node, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (node is ICompilationUnitSyntax) + { + syntaxHelper.AddAliases(node, ref localAliases, global: false); + + recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } + else if (syntaxHelper.IsAnyNamespaceBlock(node)) + { + var localAliasCount = localAliases.Length; + syntaxHelper.AddAliases(node, ref localAliases, global: false); + + recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); + + // after recursing into this namespace, dump any local aliases we added from this namespace decl itself. + localAliases.Length = localAliasCount; + } + else if (syntaxHelper.IsAttributeList(node)) + { + foreach (var attribute in syntaxHelper.GetAttributesOfAttributeList(node)) + { + // Have to lookup both with the name in the attribute, as well as adding the 'Attribute' suffix. + // e.g. if there is [X] then we have to lookup with X and with XAttribute. + var simpleAttributeName = syntaxHelper.GetUnqualifiedIdentifierOfName( + syntaxHelper.GetNameOfAttribute(attribute)).ValueText; + if (matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: false) || + matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: true)) + { + attributeTargets.Length = 0; + syntaxHelper.AddAttributeTargets(node, ref attributeTargets); + + foreach (var target in attributeTargets.AsSpan()) + { + if (predicate(target, cancellationToken)) + results.Append(target); + } + + return; + } + } + + // attributes can't have attributes inside of them. so no need to recurse when we're done. + } + else + { + // For any other node, just keep recursing deeper to see if we can find an attribute. Note: we cannot + // terminate the search anywhere as attributes may be found on things like local functions, and that + // means having to dive deep into statements and expressions. + recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } + + return; + + void recurseChildren( + SyntaxNode node, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) + { + foreach (var child in node.ChildNodesAndTokens()) + { + if (child.IsNode) + recurse(child.AsNode()!, ref localAliases, ref seenNames, ref results, ref attributeTargets); + } + } + } + + // Checks if `name` is equal to `matchAgainst`. if `withAttributeSuffix` is true, then + // will check if `name` + "Attribute" is equal to `matchAgainst` + bool matchesName(string name, string matchAgainst, bool withAttributeSuffix) + { + if (withAttributeSuffix) + { + return name.Length + "Attribute".Length == matchAgainst.Length && + matchAgainst.HasAttributeSuffix(isCaseSensitive) && + matchAgainst.StartsWith(name, comparison); + } + else + { + return name.Equals(matchAgainst, comparison); + } + } + + bool matchesAttributeName( + ref Aliases localAliases, + ref ValueListBuilder seenNames, + string currentAttributeName, + bool withAttributeSuffix) + { + // If the names match, we're done. + if (withAttributeSuffix) + { + if (nameHasAttributeSuffix && + matchesName(currentAttributeName, name, withAttributeSuffix)) + { + return true; + } + } + else + { + if (matchesName(currentAttributeName, name, withAttributeSuffix: false)) + return true; + } + + // Otherwise, keep searching through aliases. Check that this is the first time seeing this name so we + // don't infinite recurse in error code where aliases reference each other. + // + // note: as we recurse up the aliases, we do not want to add the attribute suffix anymore. aliases must + // reference the actual real name of the symbol they are aliasing. + foreach (var seenName in seenNames.AsSpan()) + { + if (seenName == currentAttributeName) + return false; + } + + seenNames.Append(currentAttributeName); + + foreach (var (aliasName, symbolName) in localAliases.AsSpan()) + { + // see if user wrote `[SomeAlias]`. If so, if we find a `using SomeAlias = ...` recurse using the + // ... name portion to see if it might bind to the attr name the caller is searching for. + if (matchesName(currentAttributeName, aliasName, withAttributeSuffix) && + matchesAttributeName(ref localAliases, ref seenNames, symbolName, withAttributeSuffix: false)) + { + return true; + } + } + + foreach (var (aliasName, symbolName) in globalAliases.AliasAndSymbolNames) + { + if (matchesName(currentAttributeName, aliasName, withAttributeSuffix) && + matchesAttributeName(ref localAliases, ref seenNames, symbolName, withAttributeSuffix: false)) + { + return true; + } + } + + seenNames.Pop(); + return false; + } + } + + private static bool ContainsAttributeList(SyntaxNode node) + { + if (node.IsKind(SyntaxKind.AttributeList)) + return true; + + foreach (SyntaxNodeOrToken child in node.ChildNodesAndTokens()) + { + if (child.IsToken) + continue; + + SyntaxNode? childNode = child.AsNode()!; + if (ContainsAttributeList(childNode)) + return true; + } + + return false; + } +} diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs index 592ee84655139..c84d195e22227 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs @@ -18,7 +18,7 @@ public partial class LoggerMessageGenerator { internal class Parser { - private const string LoggerMessageAttribute = "Microsoft.Extensions.Logging.LoggerMessageAttribute"; + internal const string LoggerMessageAttribute = "Microsoft.Extensions.Logging.LoggerMessageAttribute"; private readonly CancellationToken _cancellationToken; private readonly Compilation _compilation; diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs index 3b7817cebeeba..ab34238427080 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs @@ -7,6 +7,7 @@ using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; using Microsoft.CodeAnalysis.Text; [assembly: System.Resources.NeutralResourcesLanguage("en-us")] @@ -19,7 +20,11 @@ public partial class LoggerMessageGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { IncrementalValuesProvider classDeclarations = context.SyntaxProvider - .CreateSyntaxProvider(static (s, _) => Parser.IsSyntaxTargetForGeneration(s), static (ctx, _) => Parser.GetSemanticTargetForGeneration(ctx)) + .ForAttributeWithMetadataName( + context, + Parser.LoggerMessageAttribute, + (node, _) => node is MethodDeclarationSyntax, + (context, _) => context.TargetNode.Parent as ClassDeclarationSyntax) .Where(static m => m is not null); IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndClasses = diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.Roslyn4.0.csproj b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.Roslyn4.0.csproj index 50e939c50b198..03d839d0869cd 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.Roslyn4.0.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.Roslyn4.0.csproj @@ -8,6 +8,20 @@ + + + + + + + + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj index 2714a3077ddc4..d22d3e32289c4 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj @@ -17,7 +17,8 @@ Microsoft.Extensions.Logging.LogLevel Microsoft.Extensions.Logging.Logger<T> Microsoft.Extensions.Logging.LoggerMessage Microsoft.Extensions.Logging.Abstractions.NullLogger - 1 + 2 + true diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Collections/Generic/ValueListBuilder.Pop.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.Pop.cs similarity index 100% rename from src/libraries/System.Text.RegularExpressions/src/System/Collections/Generic/ValueListBuilder.Pop.cs rename to src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.Pop.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs index a82372b92b34d..d29b59bd4a8aa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs @@ -69,10 +69,25 @@ public void Dispose() private void Grow() { - T[] array = ArrayPool.Shared.Rent(_span.Length * 2); + const int ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength - bool success = _span.TryCopyTo(array); - Debug.Assert(success); + // Double the size of the span. If it's currently empty, default to size 4, + // although it'll be increased in Rent to the pool's minimum bucket size. + int nextCapacity = _span.Length != 0 ? _span.Length * 2 : 4; + + // If the computed doubled capacity exceeds the possible length of an array, then we + // want to downgrade to either the maximum array length if that's large enough to hold + // an additional item, or the current length + 1 if it's larger than the max length, in + // which case it'll result in an OOM when calling Rent below. In the exceedingly rare + // case where _span.Length is already int.MaxValue (in which case it couldn't be a managed + // array), just use that same value again and let it OOM in Rent as well. + if ((uint)nextCapacity > ArrayMaxLength) + { + nextCapacity = Math.Max(Math.Max(_span.Length + 1, ArrayMaxLength), _span.Length); + } + + T[] array = ArrayPool.Shared.Rent(nextCapacity); + _span.CopyTo(array); T[]? toReturn = _arrayFromPool; _span = _arrayFromPool = array; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 92c60d4ace019..93675141412ce 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -40,9 +40,10 @@ private sealed class Parser private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute"; private const string JsonPropertyOrderAttributeFullName = "System.Text.Json.Serialization.JsonPropertyOrderAttribute"; private const string JsonSerializerContextFullName = "System.Text.Json.Serialization.JsonSerializerContext"; - private const string JsonSerializerAttributeFullName = "System.Text.Json.Serialization.JsonSerializableAttribute"; private const string JsonSourceGenerationOptionsAttributeFullName = "System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute"; + internal const string JsonSerializableAttributeFullName = "System.Text.Json.Serialization.JsonSerializableAttribute"; + private const string DateOnlyFullName = "System.DateOnly"; private const string TimeOnlyFullName = "System.TimeOnly"; private const string IAsyncEnumerableFullName = "System.Collections.Generic.IAsyncEnumerable`1"; @@ -240,7 +241,7 @@ public Parser(Compilation compilation, in JsonSourceGenerationContext sourceGene { Compilation compilation = _compilation; INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetBestTypeByMetadataName(JsonSerializerContextFullName); - INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonSerializerAttributeFullName); + INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonSerializableAttributeFullName); INamedTypeSymbol jsonSourceGenerationOptionsAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonSourceGenerationOptionsAttributeFullName); INamedTypeSymbol jsonConverterOfTAttributeSymbol = compilation.GetBestTypeByMetadataName(JsonConverterOfTFullName); 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 2f4fba241f097..00ceb2af04c36 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; using Microsoft.CodeAnalysis.Text; namespace System.Text.Json.SourceGeneration @@ -24,8 +25,11 @@ public sealed partial class JsonSourceGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { IncrementalValuesProvider classDeclarations = context.SyntaxProvider - .CreateSyntaxProvider(static (s, _) => Parser.IsSyntaxTargetForGeneration(s), static (s, _) => Parser.GetSemanticTargetForGeneration(s)) - .Where(static c => c is not null); + .ForAttributeWithMetadataName( + context, + Parser.JsonSerializableAttributeFullName, + (node, _) => node is ClassDeclarationSyntax, + (context, _) => (ClassDeclarationSyntax)context.TargetNode); IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.Roslyn4.0.csproj b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.Roslyn4.0.csproj index b088862764623..5dd33e6ab2af2 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.Roslyn4.0.csproj +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.Roslyn4.0.csproj @@ -13,4 +13,18 @@ + + + + + + + + + + + + + + diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 22b91af46cadb..6a9e3176e7a05 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -9,7 +9,8 @@ enable true true - 5 + true + 6 Provides high-performance and low-allocating types that serialize objects to JavaScript Object Notation (JSON) text and deserialize JSON text to objects, with UTF-8 support built-in. Also provides types to read and write JSON text encoded as UTF-8, and to create an in-memory document object model (DOM), that is read-only, for random access of the JSON elements within a structured view of the data. Commonly Used Types: diff --git a/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj b/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj index 77351ca146f7a..ce562d56914ff 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj +++ b/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj @@ -6,7 +6,6 @@ - @@ -51,6 +50,7 @@ +