-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Polyfill the incremental generator ForAttributeWithMetadataName from …
…roslyn. (#70911) * Add the initial roslyn files * IN progress * Builds * Use api * ifdef * Move using outside namespace * Move to debug assert * Optimize common cases * Explain if'defed regions * Explain if'defed regions * Update System.Text.RegularExpressions.Generator.csproj * Port latest changes over * Renames * Update src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs * Simplify * Dispose builders * Dispose builders * Simplify by removing support for nested attributes * Simplify
- Loading branch information
1 parent
338db1a
commit d4ac721
Showing
12 changed files
with
921 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// 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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// 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); | ||
} | ||
|
||
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} | ||
} |
Oops, something went wrong.