* 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>
--- /dev/null
+// 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;
+ }
+ }
+}
// 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
{
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"),
+ };
}
}
--- /dev/null
+// 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());
+ }
+}
--- /dev/null
+// 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.
+ }
+}
--- /dev/null
+// 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);
+ }
+}
--- /dev/null
+// 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;
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// 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;
+ }
+}
{
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;
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")]
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 =
<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>
Microsoft.Extensions.Logging.Logger<T>
Microsoft.Extensions.Logging.LoggerMessage
Microsoft.Extensions.Logging.Abstractions.NullLogger</PackageDescription>
- <ServicingVersion>1</ServicingVersion>
+ <ServicingVersion>2</ServicingVersion>
+ <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
--- /dev/null
+// 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];
+ }
+ }
+}
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;
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";
{
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);
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
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());
<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>
<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:
</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" />
<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" />
+++ /dev/null
-// 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];
- }
- }
-}