Skip to content
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

Polyfill the incremental generator ForAttributeWithMetadataName from roslyn. #70911

Merged
merged 24 commits into from
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6e03dca
Add the initial roslyn files
CyrusNajmabadi Jun 17, 2022
539b248
Merge remote-tracking branch 'upstream/main' into polyfill
CyrusNajmabadi Jun 17, 2022
e8f0a8e
IN progress
CyrusNajmabadi Jun 17, 2022
1c0fbf2
Builds
CyrusNajmabadi Jun 17, 2022
b533f26
Use api
CyrusNajmabadi Jun 17, 2022
c1a76b1
ifdef
CyrusNajmabadi Jun 17, 2022
2ef0717
Merge remote-tracking branch 'upstream/main' into polyfill
CyrusNajmabadi Jun 20, 2022
1f35a7e
Move using outside namespace
CyrusNajmabadi Jun 20, 2022
f5d8137
Move to debug assert
CyrusNajmabadi Jun 20, 2022
be4ef0f
Optimize common cases
CyrusNajmabadi Jun 20, 2022
2babd86
Merge remote-tracking branch 'upstream/main' into polyfill
CyrusNajmabadi Jun 20, 2022
1cdc428
Explain if'defed regions
CyrusNajmabadi Jun 20, 2022
cfce553
Explain if'defed regions
CyrusNajmabadi Jun 20, 2022
fd9a27e
Update System.Text.RegularExpressions.Generator.csproj
CyrusNajmabadi Jun 21, 2022
e2613b6
Port latest changes over
CyrusNajmabadi Jun 22, 2022
a0e9d88
Merge branch 'polyfill' of https://github.com/CyrusNajmabadi/runtime …
CyrusNajmabadi Jun 22, 2022
91672fe
Renames
CyrusNajmabadi Jun 28, 2022
86de769
Merge branch 'main' into polyfill
CyrusNajmabadi Jun 28, 2022
cc933a2
Update src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs
CyrusNajmabadi Jun 28, 2022
8a5a8d0
Simplify
CyrusNajmabadi Jun 28, 2022
4eb00d1
Dispose builders
CyrusNajmabadi Jun 28, 2022
59cdea7
Dispose builders
CyrusNajmabadi Jun 28, 2022
e7a1f3e
Simplify by removing support for nested attributes
CyrusNajmabadi Jun 28, 2022
20d62a2
Simplify
CyrusNajmabadi Jun 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Licensed to the .NET Foundation under one or more agreements.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the general approach i took was to copy the roslyn code whole-sale and make as few changes to it as possible. The only changes i made were:

  1. update the license.
  2. change the namespace.
  3. use ValueListBuilder instead of ArrayBuilder.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These files are all then source-linked into the project that needs them. So subsequent updates to other generators will be much easier as they can just leverage this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have someone from the Roslyn team review all files added under Common/src/Roslyn? I understand that this is a direct port with just minimal changes, but it may be worth it to have that extra pair of eyes over those minimal changes from the domain experts.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. @chsienki and @jaredpar both reviewed the roslyn side. They should likely review this port as well.

// 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;
}
}
}
39 changes: 39 additions & 0 deletions src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// 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
{
Expand Down Expand Up @@ -134,5 +138,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);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

port of roslyn helper.


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);
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
foreach (var item in span)
builder.Add(item);

return builder.MoveToImmutable();
}
}
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved

public static SimpleNameSyntax GetUnqualifiedName(this NameSyntax name)
=> name switch
{
AliasQualifiedNameSyntax alias => alias.Name,
QualifiedNameSyntax qualified => qualified.Right,
SimpleNameSyntax simple => simple,
_ => throw new InvalidOperationException("Unreachable"),
};
}
}
74 changes: 74 additions & 0 deletions src/libraries/Common/src/Roslyn/GlobalAliases.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Licensed to the .NET Foundation under one or more agreements.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

direct port with only necessary cleanup to fit into runtime.

// 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;
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved

/// <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.
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
/// </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());
}
}
24 changes: 24 additions & 0 deletions src/libraries/Common/src/Roslyn/Hash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

direct port with only necessary cleanup to fit into runtime.

// 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.
}
}
62 changes: 62 additions & 0 deletions src/libraries/Common/src/Roslyn/ISyntaxHelper.cs
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
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
{
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);
}
}
42 changes: 42 additions & 0 deletions src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copied wholesale with only the changes necessary for runtime.

// 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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copied wholesale with only the changes necessary for runtime.

// 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;
}
}
}
Loading