Skip to content

Commit

Permalink
Merge in 'release/6.0' changes
Browse files Browse the repository at this point in the history
  • Loading branch information
dotnet-bot committed Aug 15, 2022
2 parents 56fabdb + 5a0d081 commit b114055
Show file tree
Hide file tree
Showing 20 changed files with 1,066 additions and 12 deletions.
125 changes: 125 additions & 0 deletions src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs
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;
}
}
}
38 changes: 38 additions & 0 deletions src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions
{
Expand Down Expand Up @@ -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"),
};
}
}
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.
// 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());
}
}
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.
// 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.
}
}
66 changes: 66 additions & 0 deletions src/libraries/Common/src/Roslyn/ISyntaxHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions
{
internal interface ISyntaxHelper
{
bool IsCaseSensitive { get; }

bool IsValidIdentifier(string name);

bool IsAnyNamespaceBlock(SyntaxNode node);

bool IsAttributeList(SyntaxNode node);
SeparatedSyntaxList<SyntaxNode> GetAttributesOfAttributeList(SyntaxNode node);

void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder<SyntaxNode> targets);

bool IsAttribute(SyntaxNode node);
SyntaxNode GetNameOfAttribute(SyntaxNode node);

bool IsLambdaExpression(SyntaxNode node);

SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node);

/// <summary>
/// <paramref name="node"/> must be a compilation unit or namespace block.
/// </summary>
void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global);
void AddAliases(CompilationOptions options, ref ValueListBuilder<(string aliasName, string symbolName)> aliases);

bool ContainsGlobalAliases(SyntaxNode root);
}

internal abstract class AbstractSyntaxHelper : ISyntaxHelper
{
public abstract bool IsCaseSensitive { get; }

public abstract bool IsValidIdentifier(string name);

public abstract SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode name);

public abstract bool IsAnyNamespaceBlock(SyntaxNode node);

public abstract bool IsAttribute(SyntaxNode node);
public abstract SyntaxNode GetNameOfAttribute(SyntaxNode node);

public abstract bool IsAttributeList(SyntaxNode node);
public abstract SeparatedSyntaxList<SyntaxNode> GetAttributesOfAttributeList(SyntaxNode node);
public abstract void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder<SyntaxNode> targets);

public abstract bool IsLambdaExpression(SyntaxNode node);

public abstract void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global);
public abstract void AddAliases(CompilationOptions options, ref ValueListBuilder<(string aliasName, string symbolName)> aliases);

public abstract bool ContainsGlobalAliases(SyntaxNode root);
}
}
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.
// 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;
}
}
Loading

0 comments on commit b114055

Please sign in to comment.