-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Port the incremental source generator polyfill code to the 6.0 servicing branch. #72219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
ecc4ede
671c6c7
3124240
6385d08
9ee1f8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,10 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exact copy of 7.0 line. |
||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Collections.Immutable; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions | ||
| { | ||
|
|
@@ -134,5 +137,40 @@ private enum SymbolVisibility | |
| Private = 2, | ||
| Friend = Internal, | ||
| } | ||
|
|
||
| internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive) | ||
| { | ||
| const string AttributeSuffix = "Attribute"; | ||
|
|
||
| var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; | ||
| return name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison); | ||
| } | ||
|
|
||
| public static ImmutableArray<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"), | ||
| }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exact copy of 7.0 line. |
||
| // 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()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exact copy of 7.0 line. |
||
| // 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. | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exact copy of 7.0 line. |
||
| // 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); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exact copy of 7.0 line. |
||
| // 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; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exact copy of 7.0 line.