Port the incremental source generator polyfill code to the 6.0 servicing branch....
authorCyrusNajmabadi <cyrusn@microsoft.com>
Mon, 15 Aug 2022 23:37:02 +0000 (19:37 -0400)
committerGitHub <noreply@github.com>
Mon, 15 Aug 2022 23:37:02 +0000 (16:37 -0700)
* 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 <joperezr@microsoft.com>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
21 files changed:
src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs [new file with mode: 0644]
src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs
src/libraries/Common/src/Roslyn/GlobalAliases.cs [new file with mode: 0644]
src/libraries/Common/src/Roslyn/Hash.cs [new file with mode: 0644]
src/libraries/Common/src/Roslyn/ISyntaxHelper.cs [new file with mode: 0644]
src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs [new file with mode: 0644]
src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs [new file with mode: 0644]
src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs [new file with mode: 0644]
src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Roslyn4.0.cs
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.Roslyn4.0.csproj
src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.Pop.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.cs
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs
src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.Roslyn4.0.csproj
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj
src/libraries/System.Text.RegularExpressions/src/System/Collections/Generic/ValueListBuilder.Pop.cs [deleted file]

diff --git a/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs
new file mode 100644 (file)
index 0000000..f7ab0d4
--- /dev/null
@@ -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<SyntaxNode> 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<SyntaxNode> 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<UsingDirectiveSyntax> 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;
+        }
+    }
+}
index c86d7f2e00ebc45ec747ec84f1f5b6e6493c3c48..8dbf92f48a7fe1724c96a08c26a614e2d540b295 100644 (file)
@@ -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 @@ namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions
             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<T> ToImmutableArray<T>(this ReadOnlySpan<T> span)
+        {
+            switch (span.Length)
+            {
+                case 0: return ImmutableArray<T>.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<T>(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 (file)
index 0000000..2108923
--- /dev/null
@@ -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;
+
+/// <summary>
+/// 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.
+/// </summary>
+internal sealed class GlobalAliases : IEquatable<GlobalAliases>
+{
+    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 (file)
index 0000000..028ac50
--- /dev/null
@@ -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
+    {
+        /// <summary>
+        /// This is how VB Anonymous Types combine hash values for fields.
+        /// </summary>
+        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 (file)
index 0000000..846ef9b
--- /dev/null
@@ -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<SyntaxNode> GetAttributesOfAttributeList(SyntaxNode node);
+
+        void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder<SyntaxNode> targets);
+
+        bool IsAttribute(SyntaxNode node);
+        SyntaxNode GetNameOfAttribute(SyntaxNode node);
+
+        bool IsLambdaExpression(SyntaxNode node);
+
+        SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node);
+
+        /// <summary>
+        /// <paramref name="node"/> must be a compilation unit or namespace block.
+        /// </summary>
+        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<SyntaxNode> GetAttributesOfAttributeList(SyntaxNode node);
+        public abstract void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder<SyntaxNode> 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 (file)
index 0000000..94dd5ae
--- /dev/null
@@ -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
+{
+    /// <summary>
+    /// 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 <see
+    /// cref="SyntaxTree"/> 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.
+    /// </summary>
+    private sealed class SyntaxNodeGrouping<TSyntaxNode> : IEquatable<SyntaxNodeGrouping<TSyntaxNode>>
+        where TSyntaxNode : SyntaxNode
+    {
+        public readonly SyntaxTree SyntaxTree;
+        public readonly ImmutableArray<TSyntaxNode> SyntaxNodes;
+
+        public SyntaxNodeGrouping(IGrouping<SyntaxTree, TSyntaxNode> 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<TSyntaxNode>);
+
+        public bool Equals(SyntaxNodeGrouping<TSyntaxNode>? 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 (file)
index 0000000..5323a10
--- /dev/null
@@ -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<T> : IEqualityComparer<ImmutableArray<T>>
+    {
+        public static readonly IEqualityComparer<ImmutableArray<T>> Instance = new ImmutableArrayValueComparer<T>();
+
+        public bool Equals(ImmutableArray<T> x, ImmutableArray<T> y)
+            => x.SequenceEqual(y, EqualityComparer<T>.Default);
+
+        public int GetHashCode(ImmutableArray<T> obj)
+        {
+            var hashCode = 0;
+            foreach (var value in obj)
+                hashCode = Hash.Combine(hashCode, EqualityComparer<T>.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 (file)
index 0000000..f6be8fb
--- /dev/null
@@ -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
+{
+    /// <summary>
+    /// The syntax node the attribute is attached to.  For example, with <c>[CLSCompliant] class C { }</c> this would
+    /// the class declaration node.
+    /// </summary>
+    public SyntaxNode TargetNode { get; }
+
+    /// <summary>
+    /// The symbol that the attribute is attached to.  For example, with <c>[CLSCompliant] class C { }</c> this would be
+    /// the <see cref="INamedTypeSymbol"/> for <c>"C"</c>.
+    /// </summary>
+    public ISymbol TargetSymbol { get; }
+
+    /// <summary>
+    /// Semantic model for the file that <see cref="TargetNode"/> is contained within.
+    /// </summary>
+    public SemanticModel SemanticModel { get; }
+
+    /// <summary>
+    /// <see cref="AttributeData"/>s for any matching attributes on <see cref="TargetSymbol"/>.  Always non-empty.  All
+    /// these attributes will have an <see cref="AttributeData.AttributeClass"/> whose fully qualified name metadata
+    /// name matches the name requested in <see cref="SyntaxValueProvider.ForAttributeWithMetadataName{T}"/>.
+    /// <para>
+    /// To get the entire list of attributes, use <see cref="ISymbol.GetAttributes"/> on <see cref="TargetSymbol"/>.
+    /// </para>
+    /// </summary>
+    public ImmutableArray<AttributeData> Attributes { get; }
+
+    internal GeneratorAttributeSyntaxContext(
+        SyntaxNode targetNode,
+        ISymbol targetSymbol,
+        SemanticModel semanticModel,
+        ImmutableArray<AttributeData> 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
+
+    /// <summary>
+    /// Creates an <see cref="IncrementalValuesProvider{T}"/> that can provide a transform over all <see
+    /// cref="SyntaxNode"/>s if that node has an attribute on it that binds to a <see cref="INamedTypeSymbol"/> with the
+    /// same fully-qualified metadata as the provided <paramref name="fullyQualifiedMetadataName"/>. <paramref
+    /// name="fullyQualifiedMetadataName"/> should be the fully-qualified, metadata name of the attribute, including the
+    /// <c>Attribute</c> suffix.  For example <c>"System.CLSCompliantAttribute</c> for <see
+    /// cref="System.CLSCompliantAttribute"/>.
+    /// </summary>
+    /// <param name="predicate">A function that determines if the given <see cref="SyntaxNode"/> attribute target (<see
+    /// cref="GeneratorAttributeSyntaxContext.TargetNode"/>) should be transformed.  Nodes that do not pass this
+    /// predicate will not have their attributes looked at at all.</param>
+    /// <param name="transform">A function that performs the transform. This will only be passed nodes that return <see
+    /// langword="true"/> for <paramref name="predicate"/> and which have a matching <see cref="AttributeData"/> whose
+    /// <see cref="AttributeData.AttributeClass"/> has the same fully qualified, metadata name as <paramref
+    /// name="fullyQualifiedMetadataName"/>.</param>
+    public static IncrementalValuesProvider<T> ForAttributeWithMetadataName<T>(
+        this SyntaxValueProvider @this,
+        IncrementalGeneratorInitializationContext context,
+        string fullyQualifiedMetadataName,
+        Func<SyntaxNode, CancellationToken, bool> predicate,
+        Func<GeneratorAttributeSyntaxContext, CancellationToken, T> 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<T>(Span<T>.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<AttributeData> getMatchingAttributes(
+            SyntaxNode attributeTarget,
+            ISymbol symbol,
+            string fullyQualifiedMetadataName)
+        {
+            var targetSyntaxTree = attributeTarget.SyntaxTree;
+            var result = new ValueListBuilder<AttributeData>(Span<AttributeData>.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<AttributeData> result,
+                ImmutableArray<AttributeData>? 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 (file)
index 0000000..03ab737
--- /dev/null
@@ -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.
+
+    /// <summary>
+    /// Information computed about a particular tree.  Cached so we don't repeatedly recompute this important
+    /// information each time the incremental pipeline is rerun.
+    /// </summary>
+    private sealed class SyntaxTreeInfo : IEquatable<SyntaxTreeInfo>
+    {
+        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();
+    }
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    private static readonly ConditionalWeakTable<SyntaxTree, SyntaxTreeInfo> s_treeToInfo = new ConditionalWeakTable<SyntaxTree, SyntaxTreeInfo>();
+
+#if false
+    // Not used in runtime.  Pooling is not a pattern here, and we use ValueListBuilder instead.
+
+    private static readonly ObjectPool<Stack<string>> s_stackPool = new(static () => new());
+#endif
+
+    /// <summary>
+    /// Returns all syntax nodes of that match <paramref name="predicate"/> if that node has an attribute on it that
+    /// could possibly bind to the provided <paramref name="simpleName"/>. <paramref name="simpleName"/> should be the
+    /// simple, non-qualified, name of the attribute, including the <c>Attribute</c> suffix, and not containing any
+    /// generics, containing types, or namespaces.  For example <c>CLSCompliantAttribute</c> for <see
+    /// cref="System.CLSCompliantAttribute"/>.
+    /// <para/> This provider understands <see langword="using"/> (<c>Import</c> in Visual Basic) aliases and will find
+    /// matches even when the attribute references an alias name.  For example, given:
+    /// <code>
+    /// using XAttribute = System.CLSCompliantAttribute;
+    /// [X]
+    /// class C { }
+    /// </code>
+    /// Then
+    /// <c>context.SyntaxProvider.CreateSyntaxProviderForAttribute(nameof(CLSCompliantAttribute), (node, c) => node is ClassDeclarationSyntax)</c>
+    /// will find the <c>C</c> class.
+    /// </summary>
+    /// <remarks>
+    /// Note: a 'Values'-provider of arrays are returned.  Each array provides all the matching nodes from a single <see
+    /// cref="SyntaxTree"/>.
+    /// </remarks>
+    public static IncrementalValuesProvider<(SyntaxTree tree, ImmutableArray<SyntaxNode> matches)> ForAttributeWithSimpleName(
+        this SyntaxValueProvider @this,
+        IncrementalGeneratorInitializationContext context,
+        string simpleName,
+        Func<SyntaxNode, CancellationToken, bool> 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<GlobalAliases>.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<SyntaxNode> GetMatchingNodes(
+        ISyntaxHelper syntaxHelper,
+        GlobalAliases globalAliases,
+        SyntaxTree syntaxTree,
+        string name,
+        Func<SyntaxNode, CancellationToken, bool> 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<string>(Span<string>.Empty);
+        var results = new ValueListBuilder<SyntaxNode>(Span<SyntaxNode>.Empty);
+        var attributeTargets = new ValueListBuilder<SyntaxNode>(Span<SyntaxNode>.Empty);
+
+        try
+        {
+            recurse(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets);
+
+            if (results.Length == 0)
+                return ImmutableArray<SyntaxNode>.Empty;
+
+            return results.AsSpan().ToArray().Distinct().ToImmutableArray();
+        }
+        finally
+        {
+            attributeTargets.Dispose();
+            results.Dispose();
+            seenNames.Dispose();
+        }
+
+        void recurse(
+            SyntaxNode node,
+            ref Aliases localAliases,
+            ref ValueListBuilder<string> seenNames,
+            ref ValueListBuilder<SyntaxNode> results,
+            ref ValueListBuilder<SyntaxNode> 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<string> seenNames,
+                ref ValueListBuilder<SyntaxNode> results,
+                ref ValueListBuilder<SyntaxNode> 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<string> 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;
+    }
+}
index 592ee84655139edc225a5061483a322f159a5159..c84d195e22227e148018b8f891471f00b71cdd52 100644 (file)
@@ -18,7 +18,7 @@ namespace Microsoft.Extensions.Logging.Generators
     {
         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;
index 3b7817cebeeba4af8eba6b74e1c785a43f403de9..ab34238427080779610d37bb5e3d15e1a32ae9e8 100644 (file)
@@ -7,6 +7,7 @@ using System.Linq;
 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 @@ namespace Microsoft.Extensions.Logging.Generators
         public void Initialize(IncrementalGeneratorInitializationContext context)
         {
             IncrementalValuesProvider<ClassDeclarationSyntax> 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<ClassDeclarationSyntax>)> compilationAndClasses =
index 50e939c50b1985f7cb0af91599c0a06e7ed2360c..03d839d0869cdae10fd563c39d66bdb9d53eb63d 100644 (file)
@@ -8,6 +8,20 @@
 
   <Import Project="Microsoft.Extensions.Logging.Generators.targets" />
 
+  <ItemGroup>
+    <Compile Include="$(CommonPath)Roslyn\Hash.cs" Link="Common\Roslyn\Hash.cs" />
+    <Compile Include="$(CommonPath)Roslyn\ISyntaxHelper.cs" Link="Common\Roslyn\ISyntaxHelper.cs" />
+    <Compile Include="$(CommonPath)Roslyn\CSharpSyntaxHelper.cs" Link="Common\Roslyn\CSharpSyntaxHelper.cs" />
+    <Compile Include="$(CommonPath)Roslyn\GlobalAliases.cs" Link="Common\Roslyn\GlobalAliases.cs" />
+    <Compile Include="$(CommonPath)Roslyn\SyntaxNodeGrouping.cs" Link="Common\Roslyn\SyntaxNodeGrouping.cs" />
+    <Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider.ImmutableArrayValueComparer.cs" Link="Common\Roslyn\SyntaxValueProvider.ImmutableArrayValueComparer.cs" />
+    <Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider_ForAttributeWithMetadataName.cs" Link="Common\Roslyn\SyntaxValueProvider_ForAttributeWithMetadataName.cs" />
+    <Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider_ForAttributeWithSimpleName.cs" Link="Common\Roslyn\SyntaxValueProvider_ForAttributeWithSimpleName.cs" />
+
+    <Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.cs" Link="Production\ValueListBuilder.cs" />
+    <Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.Pop.cs" Link="Production\ValueListBuilder.Pop.cs" />
+  </ItemGroup>
+
   <ItemGroup>
     <Compile Remove="LoggerMessageGenerator.Roslyn3.11.cs" />
   </ItemGroup>
index 2714a3077ddc47a23205e2d9857e1df5c5f5442f..d22d3e32289c4ed0de3a435048fca4bde65911c9 100644 (file)
@@ -17,7 +17,8 @@ Microsoft.Extensions.Logging.LogLevel
 Microsoft.Extensions.Logging.Logger&lt;T&gt;
 Microsoft.Extensions.Logging.LoggerMessage
 Microsoft.Extensions.Logging.Abstractions.NullLogger</PackageDescription>
-    <ServicingVersion>1</ServicingVersion>
+    <ServicingVersion>2</ServicingVersion>
+    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
   </PropertyGroup>
 
   <ItemGroup>
diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.Pop.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ValueListBuilder.Pop.cs
new file mode 100644 (file)
index 0000000..c5aa6bb
--- /dev/null
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+    /// <summary>
+    /// These public methods are required by RegexWriter.
+    /// </summary>
+    internal ref partial struct ValueListBuilder<T>
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public T Pop()
+        {
+            _pos--;
+            return _span[_pos];
+        }
+    }
+}
index a82372b92b34db08906770b1da380d9c45fdd526..d29b59bd4a8aace295707947f068a4e71ca1bf5e 100644 (file)
@@ -69,10 +69,25 @@ namespace System.Collections.Generic
 
         private void Grow()
         {
-            T[] array = ArrayPool<T>.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<T>.Shared.Rent(nextCapacity);
+            _span.CopyTo(array);
 
             T[]? toReturn = _arrayFromPool;
             _span = _arrayFromPool = array;
index 92c60d4ace019cf44d9c84c1b05b24f22fad0373..93675141412cef75d9d10a463e1db005db225ad1 100644 (file)
@@ -40,9 +40,10 @@ namespace System.Text.Json.SourceGeneration
             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 @@ namespace System.Text.Json.SourceGeneration
             {
                 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);
 
index 2f4fba241f097aec7212b8dff13f6903ed234079..00ceb2af04c36d0fec18b68bf6483648cef16990 100644 (file)
@@ -11,6 +11,7 @@ using System.Text.Json.Reflection;
 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 @@ namespace System.Text.Json.SourceGeneration
         public void Initialize(IncrementalGeneratorInitializationContext context)
         {
             IncrementalValuesProvider<ClassDeclarationSyntax> 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<ClassDeclarationSyntax>)> compilationAndClasses =
                 context.CompilationProvider.Combine(classDeclarations.Collect());
index b088862764623db53201e87ba04c5d9a51d16182..5dd33e6ab2af20f934bcdb04f807ae8d6faa2d93 100644 (file)
     <Compile Include="JsonSourceGenerator.Roslyn4.0.cs" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Compile Include="$(CommonPath)Roslyn\Hash.cs" Link="Common\Roslyn\Hash.cs" />
+    <Compile Include="$(CommonPath)Roslyn\ISyntaxHelper.cs" Link="Common\Roslyn\ISyntaxHelper.cs" />
+    <Compile Include="$(CommonPath)Roslyn\CSharpSyntaxHelper.cs" Link="Common\Roslyn\CSharpSyntaxHelper.cs" />
+    <Compile Include="$(CommonPath)Roslyn\GlobalAliases.cs" Link="Common\Roslyn\GlobalAliases.cs" />
+    <Compile Include="$(CommonPath)Roslyn\SyntaxNodeGrouping.cs" Link="Common\Roslyn\SyntaxNodeGrouping.cs" />
+    <Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider.ImmutableArrayValueComparer.cs" Link="Common\Roslyn\SyntaxValueProvider.ImmutableArrayValueComparer.cs" />
+    <Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider_ForAttributeWithMetadataName.cs" Link="Common\Roslyn\SyntaxValueProvider_ForAttributeWithMetadataName.cs" />
+    <Compile Include="$(CommonPath)Roslyn\SyntaxValueProvider_ForAttributeWithSimpleName.cs" Link="Common\Roslyn\SyntaxValueProvider_ForAttributeWithSimpleName.cs" />
+    
+    <Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.cs" Link="Production\ValueListBuilder.cs" />
+    <Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.Pop.cs" Link="Production\ValueListBuilder.Pop.cs" />
+  </ItemGroup>
+
 </Project>
index 22b91af46cadb99416ceba9514e71ec059151941..6a9e3176e7a05474ed3138fa9a2c0314f5107572 100644 (file)
@@ -9,7 +9,8 @@
     <Nullable>enable</Nullable>
     <IncludeInternalObsoleteAttribute>true</IncludeInternalObsoleteAttribute>
     <IsPackable>true</IsPackable>
-    <ServicingVersion>5</ServicingVersion>
+    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+    <ServicingVersion>6</ServicingVersion>
     <PackageDescription>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:
index 77351ca146f7ae02974f78ac06bbf2864f9cecfc..ce562d56914ff08681d8799a6e42da8f16b2dd52 100644 (file)
@@ -6,7 +6,6 @@
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="System\Collections\HashtableExtensions.cs" />
-    <Compile Include="System\Collections\Generic\ValueListBuilder.Pop.cs" />
     <Compile Include="System\Text\RegularExpressions\RegexCharClass.MappingTable.cs" />
     <Compile Include="System\Text\SegmentStringBuilder.cs" />
     <Compile Include="System\Text\RegularExpressions\Capture.cs" />
@@ -51,6 +50,7 @@
     <Compile Include="$(CommonPath)System\HexConverter.cs" Link="Common\System\HexConverter.cs" />
     <Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.cs" Link="Common\System\Collections\Generic\ValueListBuilder.cs" />
     <Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs" Link="Common\System\Text\ValueStringBuilder.cs" />
+    <Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ValueListBuilder.Pop.cs" Link="Common\System\Collections\Generic\ValueListBuilder.Pop.cs" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="System.Collections" />
diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Collections/Generic/ValueListBuilder.Pop.cs b/src/libraries/System.Text.RegularExpressions/src/System/Collections/Generic/ValueListBuilder.Pop.cs
deleted file mode 100644 (file)
index c5aa6bb..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Runtime.CompilerServices;
-
-namespace System.Collections.Generic
-{
-    /// <summary>
-    /// These public methods are required by RegexWriter.
-    /// </summary>
-    internal ref partial struct ValueListBuilder<T>
-    {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public T Pop()
-        {
-            _pos--;
-            return _span[_pos];
-        }
-    }
-}