From 6e03dca757ad09def512b18a4285bdee0e9e7ea6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 17 Jun 2022 11:10:56 -0700 Subject: [PATCH 01/19] Add the initial roslyn files --- .../src/Roslyn/GetBestTypeByMetadataName.cs | 2 + .../Common/src/Roslyn/SyntaxNodeGrouping.cs | 43 +++ ...lueProvider.ImmutableArrayValueComparer.cs | 35 +++ ...ueProvider_ForAttributeWithMetadataName.cs | 188 ++++++++++++ ...alueProvider_ForAttributeWithSimpleName.cs | 284 ++++++++++++++++++ ...m.Text.RegularExpressions.Generator.csproj | 4 + 6 files changed, 556 insertions(+) create mode 100644 src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs create mode 100644 src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs create mode 100644 src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs create mode 100644 src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs diff --git a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs index c86d7f2e00ebc4..094547525d9ecd 100644 --- a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs @@ -134,5 +134,7 @@ private enum SymbolVisibility Private = 2, Friend = Internal, } + + } } diff --git a/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs b/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs new file mode 100644 index 00000000000000..b4310296989abf --- /dev/null +++ b/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + +internal static partial class SyntaxValueProviderExtensions +{ + /// + /// 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 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. + /// + private class SyntaxNodeGrouping : IEquatable> + where TSyntaxNode : SyntaxNode + { + public readonly SyntaxTree SyntaxTree; + public readonly ImmutableArray SyntaxNodes; + + public SyntaxNodeGrouping(IGrouping 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); + + public bool Equals(SyntaxNodeGrouping? obj) + => this.SyntaxTree == obj?.SyntaxTree; + } +} diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs new file mode 100644 index 00000000000000..e93162452b5cc7 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + +internal static partial class SyntaxValueProviderExtensions +{ + private class ImmutableArrayValueComparer : IEqualityComparer> + { + public static readonly IEqualityComparer> Instance = new ImmutableArrayValueComparer(); + + public bool Equals(ImmutableArray x, ImmutableArray y) + { + if (x == y) + return true; + + return x.SequenceEqual(y, 0, static (a, b, _) => EqualityComparer.Default.Equals(a, b)); + } + + public int GetHashCode(ImmutableArray obj) + { + var hashCode = 0; + foreach (var value in obj) + hashCode = Hash.Combine(hashCode, EqualityComparer.Default.GetHashCode(value!)); + + return hashCode; + } + } +} diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs new file mode 100644 index 00000000000000..c00f01f9e60843 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Xml.Serialization; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.SourceGeneration; +using System.Threading; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + +using Aliases = ArrayBuilder<(string aliasName, string symbolName)>; + +internal readonly struct GeneratorAttributeSyntaxContext +{ + /// + /// The syntax node the attribute is attached to. For example, with [CLSCompliant] class C { } this would + /// the class declaration node. + /// + public SyntaxNode TargetNode { get; } + + /// + /// The symbol that the attribute is attached to. For example, with [CLSCompliant] class C { } this would be + /// the for "C". + /// + public ISymbol TargetSymbol { get; } + + /// + /// Semantic model for the file that is contained within. + /// + public SemanticModel SemanticModel { get; } + + /// + /// s for any matching attributes on . Always non-empty. All + /// these attributes will have an whose fully qualified name metadata + /// name matches the name requested in . + /// + /// To get the entire list of attributes, use on . + /// + /// + public ImmutableArray Attributes { get; } + + internal GeneratorAttributeSyntaxContext( + SyntaxNode targetNode, + ISymbol targetSymbol, + SemanticModel semanticModel, + ImmutableArray attributes) + { + TargetNode = targetNode; + TargetSymbol = targetSymbol; + SemanticModel = semanticModel; + Attributes = attributes; + } +} + +internal static partial class SyntaxValueProviderExtensions +{ + private static readonly char[] s_nestedTypeNameSeparators = new char[] { '+' }; + private static readonly SymbolDisplayFormat s_metadataDisplayFormat = + SymbolDisplayFormat.QualifiedNameArityFormat.AddCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UsePlusForNestedTypes); + + /// + /// Creates an that can provide a transform over all s if that node has an attribute on it that binds to a with the + /// same fully-qualified metadata as the provided . should be the fully-qualified, metadata name of the attribute, including the + /// Attribute suffix. For example "System.CLSCompliantAttribute for . + /// + /// A function that determines if the given attribute target () should be transformed. Nodes that do not pass this + /// predicate will not have their attributes looked at at all. + /// A function that performs the transform. This will only be passed nodes that return for and which have a matching whose + /// has the same fully qualified, metadata name as . + public IncrementalValuesProvider ForAttributeWithMetadataName( + string fullyQualifiedMetadataName, + Func predicate, + Func transform) + { + var metadataName = fullyQualifiedMetadataName.Contains('+') + ? MetadataTypeName.FromFullName(fullyQualifiedMetadataName.Split(s_nestedTypeNameSeparators).Last()) + : MetadataTypeName.FromFullName(fullyQualifiedMetadataName); + + var nodesWithAttributesMatchingSimpleName = this.ForAttributeWithSimpleName(metadataName.UnmangledTypeName, predicate); + + var collectedNodes = nodesWithAttributesMatchingSimpleName + .Collect() + .WithComparer(ImmutableArrayValueComparer.Instance) + .WithTrackingName("collectedNodes_ForAttributeWithMetadataName"); + + // Group all the nodes by syntax tree, so we can process a whole syntax tree at a time. This will let us make + // the required semantic model for it once, instead of potentially many times (in the rare, but possible case of + // a single file with a ton of matching nodes in it). + var groupedNodes = collectedNodes.SelectMany( + static (array, cancellationToken) => + array.GroupBy(static n => n.SyntaxTree) + .Select(static g => new SyntaxNodeGrouping(g))).WithTrackingName("groupedNodes_ForAttributeWithMetadataName"); + + var compilationAndGroupedNodesProvider = groupedNodes + .Combine(_context.CompilationProvider) + .WithTrackingName("compilationAndGroupedNodes_ForAttributeWithMetadataName"); + + var syntaxHelper = _context.SyntaxHelper; + var finalProvider = compilationAndGroupedNodesProvider.SelectMany((tuple, cancellationToken) => + { + var (grouping, compilation) = tuple; + + var result = ArrayBuilder.GetInstance(); + try + { + var syntaxTree = grouping.SyntaxTree; + var semanticModel = compilation.GetSemanticModel(syntaxTree); + + foreach (var targetNode in grouping.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.Add(transform( + new GeneratorAttributeSyntaxContext(targetNode, targetSymbol, semanticModel, attributes), + cancellationToken)); + } + } + + return result.ToImmutable(); + } + finally + { + result.Free(); + } + }).WithTrackingName("result_ForAttributeWithMetadataName"); + + return finalProvider; + + static ImmutableArray getMatchingAttributes( + SyntaxNode attributeTarget, + ISymbol symbol, + string fullyQualifiedMetadataName) + { + var targetSyntaxTree = attributeTarget.SyntaxTree; + var result = ArrayBuilder.GetInstance(); + + addMatchingAttributes(symbol.GetAttributes()); + addMatchingAttributes((symbol as IMethodSymbol)?.GetReturnTypeAttributes()); + + if (symbol is IAssemblySymbol assemblySymbol) + { + foreach (var module in assemblySymbol.Modules) + addMatchingAttributes(module.GetAttributes()); + } + + return result.ToImmutableAndFree(); + + void addMatchingAttributes(ImmutableArray? attributes) + { + if (!attributes.HasValue) + return; + + foreach (var attribute in attributes.Value) + { + if (attribute.ApplicationSyntaxReference?.SyntaxTree == targetSyntaxTree && + attribute.AttributeClass?.ToDisplayString(s_metadataDisplayFormat) == fullyQualifiedMetadataName) + { + result.Add(attribute); + } + } + } + } + } +} diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs new file mode 100644 index 00000000000000..3ec954a6ca13a5 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -0,0 +1,284 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Transactions; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.SourceGeneration; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + +using Aliases = ArrayBuilder<(string aliasName, string symbolName)>; + +internal static partial class SyntaxValueProviderExtensions +{ + private static readonly ObjectPool> s_stackPool = new(static () => new()); + + /// + /// Returns all syntax nodes of that match if that node has an attribute on it that + /// could possibly bind to the provided . should be the + /// simple, non-qualified, name of the attribute, including the Attribute suffix, and not containing any + /// generics, containing types, or namespaces. For example CLSCompliantAttribute for . + /// This provider understands (Import in Visual Basic) aliases and will find + /// matches even when the attribute references an alias name. For example, given: + /// + /// using XAttribute = System.CLSCompliantAttribute; + /// [X] + /// class C { } + /// + /// Then + /// context.SyntaxProvider.CreateSyntaxProviderForAttribute(nameof(CLSCompliantAttribute), (node, c) => node is ClassDeclarationSyntax) + /// will find the C class. + /// + internal IncrementalValuesProvider ForAttributeWithSimpleName( + string simpleName, + Func predicate) + { + var syntaxHelper = _context.SyntaxHelper; + + // Create a provider that provides (and updates) the global aliases for any particular file when it is edited. + var individualFileGlobalAliasesProvider = this.CreateSyntaxProvider( + static (n, _) => n is ICompilationUnitSyntax, + static (context, _) => getGlobalAliasesInCompilationUnit(context.SyntaxHelper, context.Node)).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.Instance) + .WithTrackingName("collectedGlobalAliases_ForAttribute"); + + var allUpGlobalAliasesProvider = collectedGlobalAliasesProvider + .Select(static (arrays, _) => GlobalAliases.Create(arrays.SelectMany(a => a.AliasAndSymbolNames).ToImmutableArray())) + .WithTrackingName("allUpGlobalAliases_ForAttribute"); + + // 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"); + + // Create a syntax provider for every compilation unit. + var compilationUnitProvider = this.CreateSyntaxProvider( + static (n, _) => n is ICompilationUnitSyntax, + static (context, _) => context.Node).WithTrackingName("compilationUnit_ForAttribute"); + + // 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 compilationUnitAndGlobalAliasesProvider = compilationUnitProvider + .Combine(allUpGlobalAliasesProvider) + .WithTrackingName("compilationUnitAndGlobalAliases_ForAttribute"); + + // For each pair of compilation unit + global aliases, walk the compilation unit + var result = compilationUnitAndGlobalAliasesProvider + .SelectMany((globalAliasesAndCompilationUnit, cancellationToken) => GetMatchingNodes( + syntaxHelper, globalAliasesAndCompilationUnit.Right, globalAliasesAndCompilationUnit.Left, simpleName, predicate, cancellationToken)) + .WithTrackingName("result_ForAttribute"); + + return result; + + static GlobalAliases getGlobalAliasesInCompilationUnit( + ISyntaxHelper syntaxHelper, + SyntaxNode compilationUnit) + { + Debug.Assert(compilationUnit is ICompilationUnitSyntax); + var globalAliases = Aliases.GetInstance(); + + syntaxHelper.AddAliases(compilationUnit, globalAliases, global: true); + + return GlobalAliases.Create(globalAliases.ToImmutableAndFree()); + } + } + + private static ImmutableArray GetMatchingNodes( + ISyntaxHelper syntaxHelper, + GlobalAliases globalAliases, + SyntaxNode compilationUnit, + string name, + Func predicate, + CancellationToken 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 = Aliases.GetInstance(); + 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 = s_stackPool.Allocate(); + var results = ArrayBuilder.GetInstance(); + var attributeTargets = ArrayBuilder.GetInstance(); + + try + { + recurse(compilationUnit); + } + finally + { + localAliases.Free(); + seenNames.Clear(); + s_stackPool.Free(seenNames); + attributeTargets.Free(); + } + + results.RemoveDuplicates(); + return results.ToImmutableAndFree(); + + void recurse(SyntaxNode node) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (node is ICompilationUnitSyntax) + { + syntaxHelper.AddAliases(node, localAliases, global: false); + + recurseChildren(node); + } + else if (syntaxHelper.IsAnyNamespaceBlock(node)) + { + var localAliasCount = localAliases.Count; + syntaxHelper.AddAliases(node, localAliases, global: false); + + recurseChildren(node); + + // after recursing into this namespace, dump any local aliases we added from this namespace decl itself. + localAliases.Count = 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(simpleAttributeName, withAttributeSuffix: false) || + matchesAttributeName(simpleAttributeName, withAttributeSuffix: true)) + { + attributeTargets.Clear(); + syntaxHelper.AddAttributeTargets(node, attributeTargets); + + foreach (var target in attributeTargets) + { + if (predicate(target, cancellationToken)) + results.Add(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); + } + + return; + + void recurseChildren(SyntaxNode node) + { + foreach (var child in node.ChildNodesAndTokens()) + { + if (child.IsNode) + recurse(child.AsNode()!); + } + } + } + + // 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(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. + if (seenNames.Contains(currentAttributeName)) + return false; + + seenNames.Push(currentAttributeName); + + foreach (var (aliasName, symbolName) in localAliases) + { + // 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(symbolName, withAttributeSuffix: false)) + { + return true; + } + } + + foreach (var (aliasName, symbolName) in globalAliases.AliasAndSymbolNames) + { + if (matchesName(currentAttributeName, aliasName, withAttributeSuffix) && + matchesAttributeName(symbolName, withAttributeSuffix: false)) + { + return true; + } + } + + seenNames.Pop(); + return false; + } + } +} diff --git a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj index 082bc8f33c0fe9..e783706b56c13a 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj +++ b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj @@ -20,6 +20,10 @@ + + + + From e8f0a8e8a189360d8616345dbf7606209f2fd7f1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 17 Jun 2022 12:26:43 -0700 Subject: [PATCH 02/19] IN progress --- .../Common/src/Roslyn/CSharpSyntaxHelper.cs | 103 ++ .../src/Roslyn/GetBestTypeByMetadataName.cs | 8 + .../Common/src/Roslyn/GlobalAliases.cs | 74 ++ .../Common/src/Roslyn/ISyntaxHelper.cs | 62 + .../Common/src/Roslyn/MetadataHelpers.cs | 1015 +++++++++++++++++ .../Common/src/Roslyn/MetadataTypeName.cs | 307 +++++ .../Common/src/Roslyn/SyntaxNodeGrouping.cs | 1 - ...lueProvider.ImmutableArrayValueComparer.cs | 1 - ...ueProvider_ForAttributeWithMetadataName.cs | 75 +- ...alueProvider_ForAttributeWithSimpleName.cs | 31 +- ...m.Text.RegularExpressions.Generator.csproj | 5 + 11 files changed, 1629 insertions(+), 53 deletions(-) create mode 100644 src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs create mode 100644 src/libraries/Common/src/Roslyn/GlobalAliases.cs create mode 100644 src/libraries/Common/src/Roslyn/ISyntaxHelper.cs create mode 100644 src/libraries/Common/src/Roslyn/MetadataHelpers.cs create mode 100644 src/libraries/Common/src/Roslyn/MetadataTypeName.cs diff --git a/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs new file mode 100644 index 00000000000000..c525b8c0943cb1 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs @@ -0,0 +1,103 @@ +// 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 Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +using Roslyn.Utilities; + +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, ArrayBuilder targets) + { + var attributeList = (AttributeListSyntax)node; + var container = attributeList.Parent; + RoslynDebug.AssertNotNull(container); + + // For fields/events, the attribute applies to all the variables declared. + if (container is FieldDeclarationSyntax field) + targets.AddRange(field.Declaration.Variables); + else if (container is EventFieldDeclarationSyntax ev) + targets.AddRange(ev.Declaration.Variables); + else + targets.Add(container); + } + + public override SeparatedSyntaxList 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, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global) + { + if (node is CompilationUnitSyntax compilationUnit) + { + AddAliases(compilationUnit.Usings, aliases, global); + } + else if (node is BaseNamespaceDeclarationSyntax namespaceDeclaration) + { + AddAliases(namespaceDeclaration.Usings, aliases, global); + } + else + { + throw ExceptionUtilities.UnexpectedValue(node.Kind()); + } + } + + private static void AddAliases(SyntaxList usings, ArrayBuilder<(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.Add((aliasName, symbolName)); + } + } + + public override void AddAliases(CompilationOptions compilation, ArrayBuilder<(string aliasName, string symbolName)> aliases) + { + // C# doesn't have global aliases at the compilation level. + return; + } + } +} diff --git a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs index 094547525d9ecd..d2a873afe8e2f7 100644 --- a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs @@ -1,6 +1,8 @@ // 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 Microsoft.CodeAnalysis; namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions @@ -135,6 +137,12 @@ private enum SymbolVisibility 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); + } } } diff --git a/src/libraries/Common/src/Roslyn/GlobalAliases.cs b/src/libraries/Common/src/Roslyn/GlobalAliases.cs new file mode 100644 index 00000000000000..2108923c76c271 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/GlobalAliases.cs @@ -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; + +/// +/// 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. +/// +internal sealed class GlobalAliases : IEquatable +{ + 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()); + } +} diff --git a/src/libraries/Common/src/Roslyn/ISyntaxHelper.cs b/src/libraries/Common/src/Roslyn/ISyntaxHelper.cs new file mode 100644 index 00000000000000..95bf4e72ecfa84 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/ISyntaxHelper.cs @@ -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 GetAttributesOfAttributeList(SyntaxNode node); + + void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder targets); + + bool IsAttribute(SyntaxNode node); + SyntaxNode GetNameOfAttribute(SyntaxNode node); + + bool IsLambdaExpression(SyntaxNode node); + + SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node); + + /// + /// must be a compilation unit or namespace block. + /// + 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 GetAttributesOfAttributeList(SyntaxNode node); + public abstract void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder 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); + } +} diff --git a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs new file mode 100644 index 00000000000000..440db10838871a --- /dev/null +++ b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs @@ -0,0 +1,1015 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + internal static class MetadataHelpers + { + public const char DotDelimiter = '.'; + public const string DotDelimiterString = "."; + public const char GenericTypeNameManglingChar = '`'; + private const string GenericTypeNameManglingString = "`"; + public const int MaxStringLengthForParamSize = 22; + public const int MaxStringLengthForIntToStringConversion = 22; + public const string SystemString = "System"; + + // These can appear in the interface name that precedes an explicit interface implementation member. + public const char MangledNameRegionStartChar = '<'; + public const char MangledNameRegionEndChar = '>'; + + internal struct AssemblyQualifiedTypeName + { + internal readonly string TopLevelType; + internal readonly string[] NestedTypes; + internal readonly AssemblyQualifiedTypeName[] TypeArguments; + internal readonly int PointerCount; + + /// + /// Rank equal 0 is used to denote an SzArray, rank equal 1 denotes multi-dimensional array of rank 1. + /// + internal readonly int[] ArrayRanks; + internal readonly string AssemblyName; + + internal AssemblyQualifiedTypeName( + string topLevelType, + string[] nestedTypes, + AssemblyQualifiedTypeName[] typeArguments, + int pointerCount, + int[] arrayRanks, + string assemblyName) + { + this.TopLevelType = topLevelType; + this.NestedTypes = nestedTypes; + this.TypeArguments = typeArguments; + this.PointerCount = pointerCount; + this.ArrayRanks = arrayRanks; + this.AssemblyName = assemblyName; + } + } + + internal static AssemblyQualifiedTypeName DecodeTypeName(string s) + { + var decoder = new SerializedTypeDecoder(s); + return decoder.DecodeTypeName(); + } + + /// + /// Decodes a serialized type name in its canonical form. The canonical name is its full type name, followed + /// optionally by the assembly where it is defined, its version, culture and public key token. If the assembly + /// name is omitted, the type name is in the current assembly otherwise it is in the referenced assembly. The + /// full type name is the fully qualified metadata type name. + /// + private struct SerializedTypeDecoder + { + private static readonly char[] s_typeNameDelimiters = { '+', ',', '[', ']', '*' }; + private readonly string _input; + private int _offset; + + internal SerializedTypeDecoder(string s) + { + _input = s; + _offset = 0; + } + + private void Advance() + { + if (!EndOfInput) + { + _offset++; + } + } + + private void AdvanceTo(int i) + { + if (i <= _input.Length) + { + _offset = i; + } + } + + private bool EndOfInput + { + get + { + return _offset >= _input.Length; + } + } + + private char Current + { + get + { + return _input[_offset]; + } + } + + /// + /// Decodes a type name. A type name is a string which is terminated by the end of the string or one of the + /// delimiters '+', ',', '[', ']'. '+' separates nested classes. '[' and ']' + /// enclosed generic type arguments. ',' separates types. + /// + internal AssemblyQualifiedTypeName DecodeTypeName(bool isTypeArgument = false, bool isTypeArgumentWithAssemblyName = false) + { + Debug.Assert(!isTypeArgumentWithAssemblyName || isTypeArgument); + + string topLevelType = null; + ArrayBuilder nestedTypesBuilder = null; + AssemblyQualifiedTypeName[] typeArguments = null; + int pointerCount = 0; + ArrayBuilder arrayRanksBuilder = null; + string assemblyName = null; + bool decodingTopLevelType = true; + bool isGenericTypeName = false; + + var pooledStrBuilder = PooledStringBuilder.GetInstance(); + StringBuilder typeNameBuilder = pooledStrBuilder.Builder; + + while (!EndOfInput) + { + int i = _input.IndexOfAny(s_typeNameDelimiters, _offset); + if (i >= 0) + { + char c = _input[i]; + + // Found name, which could be a generic name with arity. + // Generic type parameter count, if any, are handled in DecodeGenericName. + string decodedString = DecodeGenericName(i); + Debug.Assert(decodedString != null); + + // Type name is generic if the decoded name of the top level type OR any of the outer types of a nested type had the '`' character. + isGenericTypeName = isGenericTypeName || decodedString.IndexOf(GenericTypeNameManglingChar) >= 0; + typeNameBuilder.Append(decodedString); + + switch (c) + { + case '*': + if (arrayRanksBuilder != null) + { + // Error case, array shape must be specified at the end of the type name. + // Process as a regular character and continue. + typeNameBuilder.Append(c); + } + else + { + pointerCount++; + } + + Advance(); + break; + + case '+': + if (arrayRanksBuilder != null || pointerCount > 0) + { + // Error case, array shape must be specified at the end of the type name. + // Process as a regular character and continue. + typeNameBuilder.Append(c); + } + else + { + // Type followed by nested type. Handle nested class separator and collect the nested types. + HandleDecodedTypeName(typeNameBuilder.ToString(), decodingTopLevelType, ref topLevelType, ref nestedTypesBuilder); + typeNameBuilder.Clear(); + decodingTopLevelType = false; + } + + Advance(); + break; + + case '[': + // Is type followed by generic type arguments? + if (isGenericTypeName && typeArguments == null) + { + Advance(); + if (arrayRanksBuilder != null || pointerCount > 0) + { + // Error case, array shape must be specified at the end of the type name. + // Process as a regular character and continue. + typeNameBuilder.Append(c); + } + else + { + // Decode type arguments. + typeArguments = DecodeTypeArguments(); + } + } + else + { + // Decode array shape. + DecodeArrayShape(typeNameBuilder, ref arrayRanksBuilder); + } + + break; + + case ']': + if (isTypeArgument) + { + // End of type arguments. This occurs when the last type argument is a type in the + // current assembly. + goto ExitDecodeTypeName; + } + else + { + // Error case, process as a regular character and continue. + typeNameBuilder.Append(c); + Advance(); + break; + } + + case ',': + // A comma may separate a type name from its assembly name or a type argument from + // another type argument. + // If processing non-type argument or a type argument with assembly name, + // process the characters after the comma as an assembly name. + if (!isTypeArgument || isTypeArgumentWithAssemblyName) + { + Advance(); + if (!EndOfInput && Char.IsWhiteSpace(Current)) + { + Advance(); + } + + assemblyName = DecodeAssemblyName(isTypeArgumentWithAssemblyName); + } + goto ExitDecodeTypeName; + + default: + throw ExceptionUtilities.UnexpectedValue(c); + } + } + else + { + typeNameBuilder.Append(DecodeGenericName(_input.Length)); + goto ExitDecodeTypeName; + } + } + +ExitDecodeTypeName: + HandleDecodedTypeName(typeNameBuilder.ToString(), decodingTopLevelType, ref topLevelType, ref nestedTypesBuilder); + pooledStrBuilder.Free(); + + return new AssemblyQualifiedTypeName( + topLevelType, + nestedTypesBuilder?.ToArrayAndFree(), + typeArguments, + pointerCount, + arrayRanksBuilder?.ToArrayAndFree(), + assemblyName); + } + + private static void HandleDecodedTypeName(string decodedTypeName, bool decodingTopLevelType, ref string topLevelType, ref ArrayBuilder nestedTypesBuilder) + { + if (decodedTypeName.Length != 0) + { + if (decodingTopLevelType) + { + Debug.Assert(topLevelType == null); + topLevelType = decodedTypeName; + } + else + { + if (nestedTypesBuilder == null) + { + nestedTypesBuilder = ArrayBuilder.GetInstance(); + } + + nestedTypesBuilder.Add(decodedTypeName); + } + } + } + + /// + /// Decodes a generic name. This is a type name followed optionally by a type parameter count + /// + private string DecodeGenericName(int i) + { + Debug.Assert(i == _input.Length || s_typeNameDelimiters.Contains(_input[i])); + + var length = i - _offset; + if (length == 0) + { + return String.Empty; + } + + // Save start of name. The name should be the emitted name including the '`' and arity. + int start = _offset; + AdvanceTo(i); + + // Get the emitted name. + return _input.Substring(start, _offset - start); + } + + private AssemblyQualifiedTypeName[] DecodeTypeArguments() + { + if (EndOfInput) + { + return null; + } + + var typeBuilder = ArrayBuilder.GetInstance(); + + while (!EndOfInput) + { + typeBuilder.Add(DecodeTypeArgument()); + + if (!EndOfInput) + { + switch (Current) + { + case ',': + // More type arguments follow + Advance(); + if (!EndOfInput && Char.IsWhiteSpace(Current)) + { + Advance(); + } + break; + + case ']': + // End of type arguments + Advance(); + return typeBuilder.ToArrayAndFree(); + + default: + throw ExceptionUtilities.UnexpectedValue(EndOfInput); + } + } + } + + return typeBuilder.ToArrayAndFree(); + } + + private AssemblyQualifiedTypeName DecodeTypeArgument() + { + bool isTypeArgumentWithAssemblyName = false; + if (Current == '[') + { + isTypeArgumentWithAssemblyName = true; + Advance(); + } + + AssemblyQualifiedTypeName result = DecodeTypeName(isTypeArgument: true, isTypeArgumentWithAssemblyName: isTypeArgumentWithAssemblyName); + + if (isTypeArgumentWithAssemblyName) + { + if (!EndOfInput && Current == ']') + { + Advance(); + } + } + + return result; + } + + private string DecodeAssemblyName(bool isTypeArgumentWithAssemblyName) + { + if (EndOfInput) + { + return null; + } + + int i; + if (isTypeArgumentWithAssemblyName) + { + i = _input.IndexOf(']', _offset); + if (i < 0) + { + i = _input.Length; + } + } + else + { + i = _input.Length; + } + + string name = _input.Substring(_offset, i - _offset); + AdvanceTo(i); + return name; + } + + /// + /// Rank equal 0 is used to denote an SzArray, rank equal 1 denotes multi-dimensional array of rank 1. + /// + private void DecodeArrayShape(StringBuilder typeNameBuilder, ref ArrayBuilder arrayRanksBuilder) + { + Debug.Assert(Current == '['); + + int start = _offset; + int rank = 1; + bool isMultiDimensionalIfRankOne = false; + Advance(); + + while (!EndOfInput) + { + switch (Current) + { + case ',': + rank++; + Advance(); + break; + + case ']': + if (arrayRanksBuilder == null) + { + arrayRanksBuilder = ArrayBuilder.GetInstance(); + } + + arrayRanksBuilder.Add(rank == 1 && !isMultiDimensionalIfRankOne ? 0 : rank); + Advance(); + return; + + case '*': + if (rank != 1) + { + goto default; + } + + Advance(); + if (Current != ']') + { + // Error case, process as regular characters + typeNameBuilder.Append(_input.Substring(start, _offset - start)); + return; + } + + isMultiDimensionalIfRankOne = true; + break; + + default: + // Error case, process as regular characters + Advance(); + typeNameBuilder.Append(_input.Substring(start, _offset - start)); + return; + } + } + + // Error case, process as regular characters + typeNameBuilder.Append(_input.Substring(start, _offset - start)); + } + } + + private static readonly string[] s_aritySuffixesOneToNine = { "`1", "`2", "`3", "`4", "`5", "`6", "`7", "`8", "`9" }; + + internal static string GetAritySuffix(int arity) + { + Debug.Assert(arity > 0); + return (arity <= 9) ? s_aritySuffixesOneToNine[arity - 1] : string.Concat(GenericTypeNameManglingString, arity.ToString(CultureInfo.InvariantCulture)); + } + + internal static string ComposeAritySuffixedMetadataName(string name, int arity) + { + return arity == 0 ? name : name + GetAritySuffix(arity); + } + + internal static int InferTypeArityFromMetadataName(string emittedTypeName) + { + int suffixStartsAt; + return InferTypeArityFromMetadataName(emittedTypeName, out suffixStartsAt); + } + + private static short InferTypeArityFromMetadataName(string emittedTypeName, out int suffixStartsAt) + { + Debug.Assert(emittedTypeName != null, "NULL actual name unexpected!!!"); + int emittedTypeNameLength = emittedTypeName.Length; + + int indexOfManglingChar; + for (indexOfManglingChar = emittedTypeNameLength; indexOfManglingChar >= 1; indexOfManglingChar--) + { + if (emittedTypeName[indexOfManglingChar - 1] == GenericTypeNameManglingChar) + { + break; + } + } + + if (indexOfManglingChar < 2 || + (emittedTypeNameLength - indexOfManglingChar) == 0 || + emittedTypeNameLength - indexOfManglingChar > MaxStringLengthForParamSize) + { + suffixStartsAt = -1; + return 0; + } + + // Given a name corresponding to `, + // extract the arity. + string stringRepresentingArity = emittedTypeName.Substring(indexOfManglingChar); + + int arity; + bool nonNumericCharFound = !int.TryParse(stringRepresentingArity, NumberStyles.None, CultureInfo.InvariantCulture, out arity); + + if (nonNumericCharFound || arity < 0 || arity > short.MaxValue || + stringRepresentingArity != arity.ToString()) + { + suffixStartsAt = -1; + return 0; + } + + suffixStartsAt = indexOfManglingChar - 1; + return (short)arity; + } + + internal static string InferTypeArityAndUnmangleMetadataName(string emittedTypeName, out short arity) + { + int suffixStartsAt; + arity = InferTypeArityFromMetadataName(emittedTypeName, out suffixStartsAt); + + if (arity == 0) + { + Debug.Assert(suffixStartsAt == -1); + return emittedTypeName; + } + + Debug.Assert(suffixStartsAt > 0 && suffixStartsAt < emittedTypeName.Length - 1); + return emittedTypeName.Substring(0, suffixStartsAt); + } + + internal static string UnmangleMetadataNameForArity(string emittedTypeName, int arity) + { + Debug.Assert(arity > 0); + + int suffixStartsAt; + if (arity == InferTypeArityFromMetadataName(emittedTypeName, out suffixStartsAt)) + { + Debug.Assert(suffixStartsAt > 0 && suffixStartsAt < emittedTypeName.Length - 1); + return emittedTypeName.Substring(0, suffixStartsAt); + } + + return emittedTypeName; + } + + /// + /// An ImmutableArray representing the single string "System" + /// + private static readonly ImmutableArray s_splitQualifiedNameSystem = ImmutableArray.Create(SystemString); + + internal static ImmutableArray SplitQualifiedName( + string name) + { + Debug.Assert(name != null); + + if (name.Length == 0) + { + return ImmutableArray.Empty; + } + + // PERF: Avoid String.Split because of the allocations. Also, we can special-case + // for "System" if it is the first or only part. + + int dots = 0; + foreach (char ch in name) + { + if (ch == DotDelimiter) + { + dots++; + } + } + + if (dots == 0) + { + return name == SystemString ? s_splitQualifiedNameSystem : ImmutableArray.Create(name); + } + + var result = ArrayBuilder.GetInstance(dots + 1); + + int start = 0; + for (int i = 0; dots > 0; i++) + { + if (name[i] == DotDelimiter) + { + int len = i - start; + if (len == 6 && start == 0 && name.StartsWith(SystemString, StringComparison.Ordinal)) + { + result.Add(SystemString); + } + else + { + result.Add(name.Substring(start, len)); + } + + dots--; + start = i + 1; + } + } + + result.Add(name.Substring(start)); + + return result.ToImmutableAndFree(); + } + + internal static string SplitQualifiedName( + string pstrName, + out string qualifier) + { + Debug.Assert(pstrName != null); + + // In mangled names, the original unmangled name is frequently included, + // surrounded by angle brackets. The unmangled name may contain dots + // (e.g. if it is an explicit interface implementation) or paired angle + // brackets (e.g. if the explicitly implemented interface is generic). + var angleBracketDepth = 0; + var delimiter = -1; + for (int i = 0; i < pstrName.Length; i++) + { + switch (pstrName[i]) + { + case MangledNameRegionStartChar: + angleBracketDepth++; + break; + case MangledNameRegionEndChar: + angleBracketDepth--; + break; + case DotDelimiter: + // If we see consecutive dots, the second is part of the method name + // (i.e. ".ctor" or ".cctor"). + if (angleBracketDepth == 0 && (i == 0 || delimiter < i - 1)) + { + delimiter = i; + } + break; + } + } + Debug.Assert(angleBracketDepth == 0); + + if (delimiter < 0) + { + qualifier = string.Empty; + return pstrName; + } + + if (delimiter == 6 && pstrName.StartsWith(SystemString, StringComparison.Ordinal)) + { + qualifier = SystemString; + } + else + { + qualifier = pstrName.Substring(0, delimiter); + } + + return pstrName.Substring(delimiter + 1); + } + + internal static string BuildQualifiedName( + string qualifier, + string name) + { + Debug.Assert(name != null); + + if (!string.IsNullOrEmpty(qualifier)) + { + return String.Concat(qualifier, DotDelimiterString, name); + } + + return name; + } + + /// + /// Calculates information about types and namespaces immediately contained within a namespace. + /// + /// + /// Is current namespace a global namespace? + /// + /// + /// Length of the fully-qualified name of this namespace. + /// + /// + /// The sequence of groups of TypeDef row ids for types contained within the namespace, + /// recursively including those from nested namespaces. The row ids must be grouped by the + /// fully-qualified namespace name in case-sensitive manner. + /// Key of each IGrouping is a fully-qualified namespace name, which starts with the name of + /// this namespace. There could be multiple groups for each fully-qualified namespace name. + /// + /// The groups must be sorted by the keys in a manner consistent with comparer passed in as + /// nameComparer. Therefore, all types immediately contained within THIS namespace, if any, + /// must be in several IGrouping at the very beginning of the sequence. + /// + /// + /// Equality comparer to compare namespace names. + /// + /// + /// Output parameter, never null: + /// A sequence of groups of TypeDef row ids for types immediately contained within this namespace. + /// + /// + /// Output parameter, never null: + /// A sequence with information about namespaces immediately contained within this namespace. + /// For each pair: + /// Key - contains simple name of a child namespace. + /// Value - contains a sequence similar to the one passed to this function, but + /// calculated for the child namespace. + /// + /// + public static void GetInfoForImmediateNamespaceMembers( + bool isGlobalNamespace, + int namespaceNameLength, + IEnumerable> typesByNS, + StringComparer nameComparer, + out IEnumerable> types, + out IEnumerable>>> namespaces) + { + Debug.Assert(typesByNS != null); + Debug.Assert(namespaceNameLength >= 0); + Debug.Assert(!isGlobalNamespace || namespaceNameLength == 0); + + // A list of groups of TypeDef row ids for types immediately contained within this namespace. + var nestedTypes = new List>(); + + // A list accumulating information about namespaces immediately contained within this namespace. + // For each pair: + // Key - contains simple name of a child namespace. + // Value – contains a sequence similar to the one passed to this function, but + // calculated for the child namespace. + var nestedNamespaces = new List>>>(); + bool possiblyHavePairsWithDuplicateKey = false; + + var enumerator = typesByNS.GetEnumerator(); + + using (enumerator) + { + if (enumerator.MoveNext()) + { + var pair = enumerator.Current; + + // Simple name of the last encountered child namespace. + string lastChildNamespaceName = null; + + // A list accumulating information about types within the last encountered child namespace. + // The list is similar to the sequence passed to this function. + List> typesInLastChildNamespace = null; + + // if there are any types in this namespace, + // they will be in the first several groups if their key length + // is equal to namespaceNameLength. + while (pair.Key.Length == namespaceNameLength) + { + nestedTypes.Add(pair); + + if (!enumerator.MoveNext()) + { + goto DoneWithSequence; + } + + pair = enumerator.Current; + } + + // Account for the dot following THIS namespace name. + if (!isGlobalNamespace) + { + namespaceNameLength++; + } + + do + { + pair = enumerator.Current; + + string childNamespaceName = ExtractSimpleNameOfChildNamespace(namespaceNameLength, pair.Key); + + int cmp = nameComparer.Compare(lastChildNamespaceName, childNamespaceName); + if (cmp == 0) + { + // We are still processing the same child namespace + typesInLastChildNamespace.Add(pair); + } + else + { + // This is a new child namespace + if (cmp > 0) + { + // The sort order is violated for child namespace names. Obfuscation is the likely reason for this. + Debug.Assert((object)lastChildNamespaceName != null); + possiblyHavePairsWithDuplicateKey = true; + } + + // Preserve information about previous child namespace. + if (typesInLastChildNamespace != null) + { + Debug.Assert(typesInLastChildNamespace.Count != 0); + nestedNamespaces.Add( + new KeyValuePair>>( + lastChildNamespaceName, typesInLastChildNamespace)); + } + + typesInLastChildNamespace = new List>(); + lastChildNamespaceName = childNamespaceName; + Debug.Assert((object)lastChildNamespaceName != null); + + typesInLastChildNamespace.Add(pair); + } + } + while (enumerator.MoveNext()); + + // Preserve information about last child namespace. + if (typesInLastChildNamespace != null) + { + Debug.Assert(typesInLastChildNamespace.Count != 0); + nestedNamespaces.Add( + new KeyValuePair>>( + lastChildNamespaceName, typesInLastChildNamespace)); + } + +DoneWithSequence: +/*empty statement*/ + ; + } + } // using + + types = nestedTypes; + + // Merge pairs with the same key + if (possiblyHavePairsWithDuplicateKey) + { + var names = new Dictionary(nestedNamespaces.Count, nameComparer); + + for (int i = nestedNamespaces.Count - 1; i >= 0; i--) + { + names[nestedNamespaces[i].Key] = i; + } + + if (names.Count != nestedNamespaces.Count) // nothing to merge otherwise + { + for (int i = 1; i < nestedNamespaces.Count; i++) + { + var pair = nestedNamespaces[i]; + int keyIndex = names[pair.Key]; + if (keyIndex != i) + { + Debug.Assert(keyIndex < i); + var primaryPair = nestedNamespaces[keyIndex]; + nestedNamespaces[keyIndex] = KeyValuePairUtil.Create(primaryPair.Key, primaryPair.Value.Concat(pair.Value)); + nestedNamespaces[i] = default(KeyValuePair>>); + } + } + + int removed = nestedNamespaces.RemoveAll(pair => (object)pair.Key == null); + Debug.Assert(removed > 0); + } + } + + namespaces = nestedNamespaces; + + Debug.Assert(types != null); + Debug.Assert(namespaces != null); + } + + /// + /// Extract a simple name of a top level child namespace from potentially qualified namespace name. + /// + /// + /// Parent namespace name length plus the dot. + /// + /// + /// Fully qualified namespace name. + /// + /// + /// Simple name of a top level child namespace, the left-most name following parent namespace name + /// in the fully qualified name. + /// + private static string ExtractSimpleNameOfChildNamespace( + int parentNamespaceNameLength, + string fullName) + { + int index = fullName.IndexOf('.', parentNamespaceNameLength); + + if (index < 0) + { + return fullName.Substring(parentNamespaceNameLength); + } + else + { + return fullName.Substring(parentNamespaceNameLength, index - parentNamespaceNameLength); + } + } + + /// + /// Determines whether given string can be used as a non-empty metadata identifier (a NUL-terminated UTF8 string). + /// + internal static bool IsValidMetadataIdentifier(string str) + { + return !string.IsNullOrEmpty(str) && str.IsValidUnicodeString() && str.IndexOf('\0') == -1; + } + + /// + /// True if the string doesn't contain incomplete surrogates. + /// + internal static bool IsValidUnicodeString(string str) + { + return str == null || str.IsValidUnicodeString(); + } + + internal static bool IsValidAssemblyOrModuleName(string name) + { + return GetAssemblyOrModuleNameErrorArgumentResourceName(name) == null; + } + + internal static void CheckAssemblyOrModuleName(string name, CommonMessageProvider messageProvider, int code, DiagnosticBag diagnostics) + { + string errorArgumentResourceId = GetAssemblyOrModuleNameErrorArgumentResourceName(name); + if (errorArgumentResourceId != null) + { + diagnostics.Add( + messageProvider.CreateDiagnostic(code, Location.None, + new CodeAnalysisResourcesLocalizableErrorArgument(errorArgumentResourceId))); + } + } + + internal static void CheckAssemblyOrModuleName(string name, CommonMessageProvider messageProvider, int code, ArrayBuilder builder) + { + string errorArgumentResourceId = GetAssemblyOrModuleNameErrorArgumentResourceName(name); + if (errorArgumentResourceId != null) + { + builder.Add( + messageProvider.CreateDiagnostic(code, Location.None, + new CodeAnalysisResourcesLocalizableErrorArgument(errorArgumentResourceId))); + } + } + + private static string GetAssemblyOrModuleNameErrorArgumentResourceName(string name) + { + if (name == null) + { + return nameof(CodeAnalysisResources.NameCannotBeNull); + } + + // Dev11 VB can produce assembly with no name (vbc /out:".dll" /target:library). + // We disallow it. PEVerify reports an error: Assembly has no name. + if (name.Length == 0) + { + return nameof(CodeAnalysisResources.NameCannotBeEmpty); + } + + // Dev11 VB can produce assembly that starts with whitespace (vbc /out:" a.dll" /target:library). + // We disallow it. PEVerify reports an error: Assembly name contains leading spaces. + if (char.IsWhiteSpace(name[0])) + { + return nameof(CodeAnalysisResources.NameCannotStartWithWhitespace); + } + + if (!IsValidMetadataFileName(name)) + { + return nameof(CodeAnalysisResources.NameContainsInvalidCharacter); + } + + return null; + } + + /// + /// Checks that the specified name is a valid metadata String and a file name. + /// The specification isn't entirely consistent and complete but it mentions: + /// + /// 22.19.2: "Name shall index a non-empty string in the String heap. It shall be in the format {filename}.{extension} (e.g., 'goo.dll', but not 'c:\utils\goo.dll')." + /// 22.30.2: "The format of Name is {file name}.{file extension} with no path or drive letter; on POSIX-compliant systems Name contains no colon, no forward-slash, no backslash." + /// As Microsoft specific constraint. + /// + /// A reasonable restriction seems to be a valid UTF8 non-empty string that doesn't contain '\0', '\', '/', ':' characters. + /// + internal static bool IsValidMetadataFileName(string name) + { + return FileNameUtilities.IsFileName(name) && IsValidMetadataIdentifier(name); + } + + /// + /// Determine if the given namespace and type names combine to produce the given fully qualified name. + /// + /// The namespace part of the split name. + /// The type name part of the split name. + /// The fully qualified name to compare with. + /// true if the combination of and equals the fully-qualified name given by + internal static bool SplitNameEqualsFullyQualifiedName(string namespaceName, string typeName, string fullyQualified) + { + // Look for "[namespaceName].[typeName]" exactly + return fullyQualified.Length == namespaceName.Length + typeName.Length + 1 && + fullyQualified[namespaceName.Length] == MetadataHelpers.DotDelimiter && + fullyQualified.StartsWith(namespaceName, StringComparison.Ordinal) && + fullyQualified.EndsWith(typeName, StringComparison.Ordinal); + } + + internal static bool IsValidPublicKey(ImmutableArray bytes) => CryptoBlobParser.IsValidPublicKey(bytes); + + /// + /// Given an input string changes it to be acceptable as a part of a type name. + /// + internal static string MangleForTypeNameIfNeeded(string moduleName) + { + var pooledStrBuilder = PooledStringBuilder.GetInstance(); + var s = pooledStrBuilder.Builder; + s.Append(moduleName); + s.Replace("Q", "QQ"); + s.Replace("_", "Q_"); + s.Replace('.', '_'); + + return pooledStrBuilder.ToStringAndFree(); + } + } +} diff --git a/src/libraries/Common/src/Roslyn/MetadataTypeName.cs b/src/libraries/Common/src/Roslyn/MetadataTypeName.cs new file mode 100644 index 00000000000000..1d0b170866fbe0 --- /dev/null +++ b/src/libraries/Common/src/Roslyn/MetadataTypeName.cs @@ -0,0 +1,307 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + /// + /// Helper structure to encapsulate/cache various information about metadata name of a type and + /// name resolution options. + /// Also, allows us to stop using strings in the APIs that accept only metadata names, + /// making usage of them less bug prone. + /// + internal partial struct MetadataTypeName + { + /// + /// Full metadata name of a type, includes namespace name for top level types. + /// + private string _fullName; + + /// + /// Namespace name for top level types. + /// + private string _namespaceName; + + /// + /// Name of the type without namespace prefix, but possibly with generic arity mangling present. + /// + private string _typeName; + + /// + /// Name of the type without namespace prefix and without generic arity mangling. + /// + private string _unmangledTypeName; + + /// + /// Arity of the type inferred based on the name mangling. It doesn't have to match the actual + /// arity of the type. + /// + private short _inferredArity; + + /// + /// While resolving the name, consider only types with this arity. + /// (-1) means allow any arity. + /// If forcedArity >= 0 and useCLSCompliantNameArityEncoding, lookup may + /// fail because forcedArity doesn't match the one encoded in the name. + /// + private short _forcedArity; + + /// + /// While resolving the name, consider only types following + /// CLS-compliant generic type names and arity encoding (ECMA-335, section 10.7.2). + /// I.e. arity is inferred from the name and matching type must have the same + /// emitted name and arity. + /// TODO: PERF: Encode this field elsewhere to save 4 bytes + /// + private bool _useCLSCompliantNameArityEncoding; + + /// + /// Individual parts of qualified namespace name. + /// + private ImmutableArray _namespaceSegments; + + public static MetadataTypeName FromFullName(string fullName, bool useCLSCompliantNameArityEncoding = false, int forcedArity = -1) + { + Debug.Assert(fullName != null); + Debug.Assert(forcedArity >= -1 && forcedArity < short.MaxValue); + Debug.Assert(forcedArity == -1 || + !useCLSCompliantNameArityEncoding || + forcedArity == MetadataHelpers.InferTypeArityFromMetadataName(fullName), + "Conflicting metadata type name resolution options!"); + + MetadataTypeName name; + + name._fullName = fullName; + name._namespaceName = null; + name._typeName = null; + name._unmangledTypeName = null; + name._inferredArity = -1; + name._useCLSCompliantNameArityEncoding = useCLSCompliantNameArityEncoding; + name._forcedArity = (short)forcedArity; + name._namespaceSegments = default(ImmutableArray); + + return name; + } + + public static MetadataTypeName FromNamespaceAndTypeName( + string namespaceName, string typeName, + bool useCLSCompliantNameArityEncoding = false, int forcedArity = -1 + ) + { + Debug.Assert(namespaceName != null); + Debug.Assert(typeName != null); + Debug.Assert(forcedArity >= -1 && forcedArity < short.MaxValue); + Debug.Assert(!typeName.Contains(MetadataHelpers.DotDelimiterString)); + Debug.Assert(forcedArity == -1 || + !useCLSCompliantNameArityEncoding || + forcedArity == MetadataHelpers.InferTypeArityFromMetadataName(typeName), + "Conflicting metadata type name resolution options!"); + + MetadataTypeName name; + + name._fullName = null; + name._namespaceName = namespaceName; + name._typeName = typeName; + name._unmangledTypeName = null; + name._inferredArity = -1; + name._useCLSCompliantNameArityEncoding = useCLSCompliantNameArityEncoding; + name._forcedArity = (short)forcedArity; + name._namespaceSegments = default(ImmutableArray); + + return name; + } + + public static MetadataTypeName FromTypeName(string typeName, bool useCLSCompliantNameArityEncoding = false, int forcedArity = -1) + { + Debug.Assert(typeName != null); + Debug.Assert(!typeName.Contains(MetadataHelpers.DotDelimiterString) || typeName.IndexOf(MetadataHelpers.MangledNameRegionStartChar) >= 0); + Debug.Assert(forcedArity >= -1 && forcedArity < short.MaxValue); + Debug.Assert(forcedArity == -1 || + !useCLSCompliantNameArityEncoding || + forcedArity == MetadataHelpers.InferTypeArityFromMetadataName(typeName), + "Conflicting metadata type name resolution options!"); + + MetadataTypeName name; + + name._fullName = typeName; + name._namespaceName = string.Empty; + name._typeName = typeName; + name._unmangledTypeName = null; + name._inferredArity = -1; + name._useCLSCompliantNameArityEncoding = useCLSCompliantNameArityEncoding; + name._forcedArity = (short)forcedArity; + name._namespaceSegments = ImmutableArray.Empty; + + return name; + } + + /// + /// Full metadata name of a type, includes namespace name for top level types. + /// + public string FullName + { + get + { + if (_fullName == null) + { + Debug.Assert(_namespaceName != null); + Debug.Assert(_typeName != null); + _fullName = MetadataHelpers.BuildQualifiedName(_namespaceName, _typeName); + } + + return _fullName; + } + } + + /// + /// Namespace name for top level types, empty string for nested types. + /// + public string NamespaceName + { + get + { + if (_namespaceName == null) + { + Debug.Assert(_fullName != null); + _typeName = MetadataHelpers.SplitQualifiedName(_fullName, out _namespaceName); + } + + return _namespaceName; + } + } + + /// + /// Name of the type without namespace prefix, but possibly with generic arity mangling present. + /// + public string TypeName + { + get + { + if (_typeName == null) + { + Debug.Assert(_fullName != null); + _typeName = MetadataHelpers.SplitQualifiedName(_fullName, out _namespaceName); + } + + return _typeName; + } + } + + /// + /// Name of the type without namespace prefix and without generic arity mangling. + /// + public string UnmangledTypeName + { + get + { + if (_unmangledTypeName == null) + { + Debug.Assert(_inferredArity == -1); + _unmangledTypeName = MetadataHelpers.InferTypeArityAndUnmangleMetadataName(TypeName, out _inferredArity); + } + + return _unmangledTypeName; + } + } + + /// + /// Arity of the type inferred based on the name mangling. It doesn't have to match the actual + /// arity of the type. + /// + public int InferredArity + { + get + { + if (_inferredArity == -1) + { + Debug.Assert(_unmangledTypeName == null); + _unmangledTypeName = MetadataHelpers.InferTypeArityAndUnmangleMetadataName(TypeName, out _inferredArity); + } + + return _inferredArity; + } + } + + /// + /// Does name include arity mangling suffix. + /// + public bool IsMangled + { + get + { + return InferredArity > 0; + } + } + + /// + /// While resolving the name, consider only types following + /// CLS-compliant generic type names and arity encoding (ECMA-335, section 10.7.2). + /// I.e. arity is inferred from the name and matching type must have the same + /// emitted name and arity. + /// + public readonly bool UseCLSCompliantNameArityEncoding + { + get + { + return _useCLSCompliantNameArityEncoding; + } + } + + /// + /// While resolving the name, consider only types with this arity. + /// (-1) means allow any arity. + /// If ForcedArity >= 0 and UseCLSCompliantNameArityEncoding, lookup may + /// fail because ForcedArity doesn't match the one encoded in the name. + /// + public readonly int ForcedArity + { + get + { + return _forcedArity; + } + } + + /// + /// Individual parts of qualified namespace name. + /// + public ImmutableArray NamespaceSegments + { + get + { + if (_namespaceSegments.IsDefault) + { + _namespaceSegments = MetadataHelpers.SplitQualifiedName(NamespaceName); + } + + return _namespaceSegments; + } + } + + public readonly bool IsNull + { + get + { + return _typeName == null && _fullName == null; + } + } + + public override string ToString() + { + if (IsNull) + { + return "{Null}"; + } + else + { + return string.Format("{{{0},{1},{2},{3}}}", NamespaceName, TypeName, UseCLSCompliantNameArityEncoding.ToString(), _forcedArity.ToString()); + } + } + } +} diff --git a/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs b/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs index b4310296989abf..d3d9d948655dee 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Immutable; diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs index e93162452b5cc7..e4feeac4402dd1 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Collections.Immutable; diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs index c00f01f9e60843..526a9bc81c6fce 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Immutable; @@ -11,12 +10,13 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.SourceGeneration; using System.Threading; namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; -using Aliases = ArrayBuilder<(string aliasName, string symbolName)>; +using Aliases = ValueListBuilder<(string aliasName, string symbolName)>; internal readonly struct GeneratorAttributeSyntaxContext { @@ -81,7 +81,9 @@ internal static partial class SyntaxValueProviderExtensions /// langword="true"/> for and which have a matching whose /// has the same fully qualified, metadata name as . - public IncrementalValuesProvider ForAttributeWithMetadataName( + public static IncrementalValuesProvider ForAttributeWithMetadataName( + this SyntaxValueProvider @this, + IncrementalGeneratorInitializationContext context, string fullyQualifiedMetadataName, Func predicate, Func transform) @@ -90,7 +92,7 @@ public IncrementalValuesProvider ForAttributeWithMetadataName( ? MetadataTypeName.FromFullName(fullyQualifiedMetadataName.Split(s_nestedTypeNameSeparators).Last()) : MetadataTypeName.FromFullName(fullyQualifiedMetadataName); - var nodesWithAttributesMatchingSimpleName = this.ForAttributeWithSimpleName(metadataName.UnmangledTypeName, predicate); + var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(metadataName.UnmangledTypeName, predicate); var collectedNodes = nodesWithAttributesMatchingSimpleName .Collect() @@ -106,46 +108,39 @@ public IncrementalValuesProvider ForAttributeWithMetadataName( .Select(static g => new SyntaxNodeGrouping(g))).WithTrackingName("groupedNodes_ForAttributeWithMetadataName"); var compilationAndGroupedNodesProvider = groupedNodes - .Combine(_context.CompilationProvider) + .Combine(context.CompilationProvider) .WithTrackingName("compilationAndGroupedNodes_ForAttributeWithMetadataName"); - var syntaxHelper = _context.SyntaxHelper; + var syntaxHelper = CSharpSyntaxHelper.Instance; var finalProvider = compilationAndGroupedNodesProvider.SelectMany((tuple, cancellationToken) => { var (grouping, compilation) = tuple; - var result = ArrayBuilder.GetInstance(); - try - { - var syntaxTree = grouping.SyntaxTree; - var semanticModel = compilation.GetSemanticModel(syntaxTree); + var result = new ValueListBuilder(Span.Empty); + var syntaxTree = grouping.SyntaxTree; + var semanticModel = compilation.GetSemanticModel(syntaxTree); - foreach (var targetNode in grouping.SyntaxNodes) - { - cancellationToken.ThrowIfCancellationRequested(); + foreach (var targetNode in grouping.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 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.Add(transform( - new GeneratorAttributeSyntaxContext(targetNode, targetSymbol, semanticModel, attributes), - cancellationToken)); - } + var attributes = getMatchingAttributes(targetNode, targetSymbol, fullyQualifiedMetadataName); + if (attributes.Length > 0) + { + result.Append(transform( + new GeneratorAttributeSyntaxContext(targetNode, targetSymbol, semanticModel, attributes), + cancellationToken)); } - - return result.ToImmutable(); - } - finally - { - result.Free(); } + + return result.ToImmutable(); }).WithTrackingName("result_ForAttributeWithMetadataName"); return finalProvider; @@ -156,20 +151,22 @@ static ImmutableArray getMatchingAttributes( string fullyQualifiedMetadataName) { var targetSyntaxTree = attributeTarget.SyntaxTree; - var result = ArrayBuilder.GetInstance(); + var result = new ValueListBuilder(Span.Empty); - addMatchingAttributes(symbol.GetAttributes()); - addMatchingAttributes((symbol as IMethodSymbol)?.GetReturnTypeAttributes()); + addMatchingAttributes(ref result, symbol.GetAttributes()); + addMatchingAttributes(ref result, (symbol as IMethodSymbol)?.GetReturnTypeAttributes()); if (symbol is IAssemblySymbol assemblySymbol) { foreach (var module in assemblySymbol.Modules) - addMatchingAttributes(module.GetAttributes()); + addMatchingAttributes(ref result, module.GetAttributes()); } return result.ToImmutableAndFree(); - void addMatchingAttributes(ImmutableArray? attributes) + void addMatchingAttributes( + ref ValueListBuilder result, + ImmutableArray? attributes) { if (!attributes.HasValue) return; @@ -179,7 +176,7 @@ void addMatchingAttributes(ImmutableArray? attributes) if (attribute.ApplicationSyntaxReference?.SyntaxTree == targetSyntaxTree && attribute.AttributeClass?.ToDisplayString(s_metadataDisplayFormat) == fullyQualifiedMetadataName) { - result.Add(attribute); + result.Append(attribute); } } } diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs index 3ec954a6ca13a5..5e4223f8732300 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -10,13 +10,14 @@ using System.Text; using System.Threading; using System.Transactions; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.SourceGeneration; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; -using Aliases = ArrayBuilder<(string aliasName, string symbolName)>; +using Aliases = ValueListBuilder<(string aliasName, string symbolName)>; internal static partial class SyntaxValueProviderExtensions { @@ -39,16 +40,17 @@ internal static partial class SyntaxValueProviderExtensions /// context.SyntaxProvider.CreateSyntaxProviderForAttribute(nameof(CLSCompliantAttribute), (node, c) => node is ClassDeclarationSyntax) /// will find the C class. /// - internal IncrementalValuesProvider ForAttributeWithSimpleName( + public static IncrementalValuesProvider ForAttributeWithSimpleName( + this SyntaxValueProvider @this, string simpleName, Func predicate) { - var syntaxHelper = _context.SyntaxHelper; + var syntaxHelper = CSharpSyntaxHelper.Instance; // Create a provider that provides (and updates) the global aliases for any particular file when it is edited. - var individualFileGlobalAliasesProvider = this.CreateSyntaxProvider( + var individualFileGlobalAliasesProvider = @this.CreateSyntaxProvider( static (n, _) => n is ICompilationUnitSyntax, - static (context, _) => getGlobalAliasesInCompilationUnit(context.SyntaxHelper, context.Node)).WithTrackingName("individualFileGlobalAliases_ForAttribute"); + static (context, _) => getGlobalAliasesInCompilationUnit(context.Node)).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 @@ -61,6 +63,10 @@ internal IncrementalValuesProvider ForAttributeWithSimpleName( .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( @@ -76,8 +82,10 @@ internal IncrementalValuesProvider ForAttributeWithSimpleName( .Select((tuple, _) => GlobalAliases.Concat(tuple.Left, tuple.Right)) .WithTrackingName("allUpIncludingCompilationGlobalAliases_ForAttribute"); +#endif + // Create a syntax provider for every compilation unit. - var compilationUnitProvider = this.CreateSyntaxProvider( + var compilationUnitProvider = @this.CreateSyntaxProvider( static (n, _) => n is ICompilationUnitSyntax, static (context, _) => context.Node).WithTrackingName("compilationUnit_ForAttribute"); @@ -87,7 +95,7 @@ internal IncrementalValuesProvider ForAttributeWithSimpleName( .Combine(allUpGlobalAliasesProvider) .WithTrackingName("compilationUnitAndGlobalAliases_ForAttribute"); - // For each pair of compilation unit + global aliases, walk the compilation unit + // For each pair of compilation unit + global aliases, walk the compilation unit var result = compilationUnitAndGlobalAliasesProvider .SelectMany((globalAliasesAndCompilationUnit, cancellationToken) => GetMatchingNodes( syntaxHelper, globalAliasesAndCompilationUnit.Right, globalAliasesAndCompilationUnit.Left, simpleName, predicate, cancellationToken)) @@ -96,15 +104,14 @@ internal IncrementalValuesProvider ForAttributeWithSimpleName( return result; static GlobalAliases getGlobalAliasesInCompilationUnit( - ISyntaxHelper syntaxHelper, SyntaxNode compilationUnit) { Debug.Assert(compilationUnit is ICompilationUnitSyntax); - var globalAliases = Aliases.GetInstance(); + var globalAliases = new Aliases(Span<(string aliasName, string symbolName)>.Empty); - syntaxHelper.AddAliases(compilationUnit, globalAliases, global: true); + CSharpSyntaxHelper.Instance.AddAliases(compilationUnit, ref globalAliases, global: true); - return GlobalAliases.Create(globalAliases.ToImmutableAndFree()); + return GlobalAliases.Create(ImmutableArray.CreateRange( globalAliases.AsSpan()); } } diff --git a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj index e783706b56c13a..f03936689bbdd3 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj +++ b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj @@ -20,6 +20,11 @@ + + + + + From 1c0fbf233095ba3c1544c9673ae0550e0bebaf6f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 17 Jun 2022 13:14:42 -0700 Subject: [PATCH 03/19] Builds --- .../Common/src/Roslyn/CSharpSyntaxHelper.cs | 39 +- .../src/Roslyn/GetBestTypeByMetadataName.cs | 23 ++ src/libraries/Common/src/Roslyn/Hash.cs | 377 ++++++++++++++++++ .../Common/src/Roslyn/MetadataHelpers.cs | 29 +- .../Common/src/Roslyn/MetadataTypeName.cs | 16 +- .../Common/src/Roslyn/SyntaxNodeGrouping.cs | 2 +- ...lueProvider.ImmutableArrayValueComparer.cs | 13 +- ...ueProvider_ForAttributeWithMetadataName.cs | 36 +- ...alueProvider_ForAttributeWithSimpleName.cs | 112 +++--- ...m.Text.RegularExpressions.Generator.csproj | 4 +- 10 files changed, 550 insertions(+), 101 deletions(-) create mode 100644 src/libraries/Common/src/Roslyn/Hash.cs diff --git a/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs index c525b8c0943cb1..0a587a732f5195 100644 --- a/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs +++ b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs @@ -1,15 +1,12 @@ // 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 System.Collections.Generic; +using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Roslyn.Utilities; - namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions { internal sealed class CSharpSyntaxHelper : AbstractSyntaxHelper @@ -38,19 +35,27 @@ public override SyntaxNode GetNameOfAttribute(SyntaxNode node) public override bool IsAttributeList(SyntaxNode node) => node is AttributeListSyntax; - public override void AddAttributeTargets(SyntaxNode node, ArrayBuilder targets) + public override void AddAttributeTargets(SyntaxNode node, ref ValueListBuilder targets) { var attributeList = (AttributeListSyntax)node; var container = attributeList.Parent; - RoslynDebug.AssertNotNull(container); + Debug.Assert(container != null); // For fields/events, the attribute applies to all the variables declared. if (container is FieldDeclarationSyntax field) - targets.AddRange(field.Declaration.Variables); + { + foreach (var variable in field.Declaration.Variables) + targets.Append(variable); + } else if (container is EventFieldDeclarationSyntax ev) - targets.AddRange(ev.Declaration.Variables); + { + foreach (var variable in ev.Declaration.Variables) + targets.Append(variable); + } else - targets.Add(container); + { + targets.Append(container); + } } public override SeparatedSyntaxList GetAttributesOfAttributeList(SyntaxNode node) @@ -62,23 +67,23 @@ public override bool IsLambdaExpression(SyntaxNode node) public override SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node) => ((NameSyntax)node).GetUnqualifiedName().Identifier; - public override void AddAliases(SyntaxNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global) + public override void AddAliases(SyntaxNode node, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global) { if (node is CompilationUnitSyntax compilationUnit) { - AddAliases(compilationUnit.Usings, aliases, global); + AddAliases(compilationUnit.Usings, ref aliases, global); } else if (node is BaseNamespaceDeclarationSyntax namespaceDeclaration) { - AddAliases(namespaceDeclaration.Usings, aliases, global); + AddAliases(namespaceDeclaration.Usings, ref aliases, global); } else { - throw ExceptionUtilities.UnexpectedValue(node.Kind()); + throw new System.InvalidOperationException("Unreachable"); } } - private static void AddAliases(SyntaxList usings, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global) + private static void AddAliases(SyntaxList usings, ref ValueListBuilder<(string aliasName, string symbolName)> aliases, bool global) { foreach (var usingDirective in usings) { @@ -90,11 +95,11 @@ private static void AddAliases(SyntaxList usings, ArrayBui var aliasName = usingDirective.Alias.Name.Identifier.ValueText; var symbolName = usingDirective.Name.GetUnqualifiedName().Identifier.ValueText; - aliases.Add((aliasName, symbolName)); + aliases.Append((aliasName, symbolName)); } } - public override void AddAliases(CompilationOptions compilation, ArrayBuilder<(string aliasName, string symbolName)> aliases) + public override void AddAliases(CompilationOptions compilation, ref ValueListBuilder<(string aliasName, string symbolName)> aliases) { // C# doesn't have global aliases at the compilation level. return; diff --git a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs index d2a873afe8e2f7..9d6d235d4b4a92 100644 --- a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs @@ -2,8 +2,10 @@ // 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 { @@ -144,5 +146,26 @@ internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive) var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; return name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison); } + + public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) + { + if (span.Length == 0) + return ImmutableArray.Empty; + + var builder = ImmutableArray.CreateBuilder(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"), + }; } } diff --git a/src/libraries/Common/src/Roslyn/Hash.cs b/src/libraries/Common/src/Roslyn/Hash.cs new file mode 100644 index 00000000000000..32a49abeb3379d --- /dev/null +++ b/src/libraries/Common/src/Roslyn/Hash.cs @@ -0,0 +1,377 @@ +// 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 + { + /// + /// This is how VB Anonymous Types combine hash values for fields. + /// + internal static int Combine(int newKey, int currentKey) + { + return unchecked((currentKey * (int)0xA5555529) + newKey); + } + + internal static int Combine(bool newKeyPart, int currentKey) + { + return Combine(currentKey, newKeyPart ? 1 : 0); + } + + /// + /// This is how VB Anonymous Types combine hash values for fields. + /// PERF: Do not use with enum types because that involves multiple + /// unnecessary boxing operations. Unfortunately, we can't constrain + /// T to "non-enum", so we'll use a more restrictive constraint. + /// + internal static int Combine(T newKeyPart, int currentKey) where T : class? + { + int hash = unchecked(currentKey * (int)0xA5555529); + + if (newKeyPart != null) + { + return unchecked(hash + newKeyPart.GetHashCode()); + } + + return hash; + } + + internal static int CombineValues(IEnumerable? values, int maxItemsToHash = int.MaxValue) + { + if (values == null) + { + return 0; + } + + var hashCode = 0; + var count = 0; + foreach (var value in values) + { + if (count++ >= maxItemsToHash) + { + break; + } + + // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). + if (value != null) + { + hashCode = Hash.Combine(value.GetHashCode(), hashCode); + } + } + + return hashCode; + } + + internal static int CombineValues(T[]? values, int maxItemsToHash = int.MaxValue) + { + if (values == null) + { + return 0; + } + + var maxSize = Math.Min(maxItemsToHash, values.Length); + var hashCode = 0; + + for (int i = 0; i < maxSize; i++) + { + T value = values[i]; + + // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). + if (value != null) + { + hashCode = Hash.Combine(value.GetHashCode(), hashCode); + } + } + + return hashCode; + } + + internal static int CombineValues(ImmutableArray values, int maxItemsToHash = int.MaxValue) + { + if (values.IsDefaultOrEmpty) + { + return 0; + } + + var hashCode = 0; + var count = 0; + foreach (var value in values) + { + if (count++ >= maxItemsToHash) + { + break; + } + + // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). + if (value != null) + { + hashCode = Hash.Combine(value.GetHashCode(), hashCode); + } + } + + return hashCode; + } + + internal static int CombineValues(IEnumerable? values, StringComparer stringComparer, int maxItemsToHash = int.MaxValue) + { + if (values == null) + { + return 0; + } + + var hashCode = 0; + var count = 0; + foreach (var value in values) + { + if (count++ >= maxItemsToHash) + { + break; + } + + if (value != null) + { + hashCode = Hash.Combine(stringComparer.GetHashCode(value), hashCode); + } + } + + return hashCode; + } + + /// + /// The offset bias value used in the FNV-1a algorithm + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + internal const int FnvOffsetBias = unchecked((int)2166136261); + + /// + /// The generative factor used in the FNV-1a algorithm + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + internal const int FnvPrime = 16777619; + + /// + /// Compute the FNV-1a hash of a sequence of bytes + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The sequence of bytes + /// The FNV-1a hash of + internal static int GetFNVHashCode(byte[] data) + { + int hashCode = Hash.FnvOffsetBias; + + for (int i = 0; i < data.Length; i++) + { + hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); + } + + return hashCode; + } + + /// + /// Compute the FNV-1a hash of a sequence of bytes and determines if the byte + /// sequence is valid ASCII and hence the hash code matches a char sequence + /// encoding the same text. + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The sequence of bytes that are likely to be ASCII text. + /// True if the sequence contains only characters in the ASCII range. + /// The FNV-1a hash of + internal static int GetFNVHashCode(ReadOnlySpan data, out bool isAscii) + { + int hashCode = Hash.FnvOffsetBias; + + byte asciiMask = 0; + + for (int i = 0; i < data.Length; i++) + { + byte b = data[i]; + asciiMask |= b; + hashCode = unchecked((hashCode ^ b) * Hash.FnvPrime); + } + + isAscii = (asciiMask & 0x80) == 0; + return hashCode; + } + + /// + /// Compute the FNV-1a hash of a sequence of bytes + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The sequence of bytes + /// The FNV-1a hash of + internal static int GetFNVHashCode(ImmutableArray data) + { + int hashCode = Hash.FnvOffsetBias; + + for (int i = 0; i < data.Length; i++) + { + hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); + } + + return hashCode; + } + + /// + /// Compute the hashcode of a sub-string using FNV-1a + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// Note: FNV-1a was developed and tuned for 8-bit sequences. We're using it here + /// for 16-bit Unicode chars on the understanding that the majority of chars will + /// fit into 8-bits and, therefore, the algorithm will retain its desirable traits + /// for generating hash codes. + /// + internal static int GetFNVHashCode(ReadOnlySpan data) + { + int hashCode = Hash.FnvOffsetBias; + + for (int i = 0; i < data.Length; i++) + { + hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); + } + + return hashCode; + } + + /// + /// Compute the hashcode of a sub-string using FNV-1a + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// Note: FNV-1a was developed and tuned for 8-bit sequences. We're using it here + /// for 16-bit Unicode chars on the understanding that the majority of chars will + /// fit into 8-bits and, therefore, the algorithm will retain its desirable traits + /// for generating hash codes. + /// + /// The input string + /// The start index of the first character to hash + /// The number of characters, beginning with to hash + /// The FNV-1a hash code of the substring beginning at and ending after characters. + internal static int GetFNVHashCode(string text, int start, int length) + => GetFNVHashCode(text.AsSpan(start, length)); + + internal static int GetCaseInsensitiveFNVHashCode(string text) + { + return GetCaseInsensitiveFNVHashCode(text.AsSpan(0, text.Length)); + } + + internal static int GetCaseInsensitiveFNVHashCode(ReadOnlySpan data) + { + int hashCode = Hash.FnvOffsetBias; + + for (int i = 0; i < data.Length; i++) + { + hashCode = unchecked((hashCode ^ CaseInsensitiveComparison.ToLower(data[i])) * Hash.FnvPrime); + } + + return hashCode; + } + + /// + /// Compute the hashcode of a sub-string using FNV-1a + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The input string + /// The start index of the first character to hash + /// The FNV-1a hash code of the substring beginning at and ending at the end of the string. + internal static int GetFNVHashCode(string text, int start) + { + return GetFNVHashCode(text, start, length: text.Length - start); + } + + /// + /// Compute the hashcode of a string using FNV-1a + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The input string + /// The FNV-1a hash code of + internal static int GetFNVHashCode(string text) + { + return CombineFNVHash(Hash.FnvOffsetBias, text); + } + + /// + /// Compute the hashcode of a string using FNV-1a + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The input string + /// The FNV-1a hash code of + internal static int GetFNVHashCode(System.Text.StringBuilder text) + { + int hashCode = Hash.FnvOffsetBias; + int end = text.Length; + + for (int i = 0; i < end; i++) + { + hashCode = unchecked((hashCode ^ text[i]) * Hash.FnvPrime); + } + + return hashCode; + } + + /// + /// Compute the hashcode of a sub string using FNV-1a + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The input string as a char array + /// The start index of the first character to hash + /// The number of characters, beginning with to hash + /// The FNV-1a hash code of the substring beginning at and ending after characters. + internal static int GetFNVHashCode(char[] text, int start, int length) + { + int hashCode = Hash.FnvOffsetBias; + int end = start + length; + + for (int i = start; i < end; i++) + { + hashCode = unchecked((hashCode ^ text[i]) * Hash.FnvPrime); + } + + return hashCode; + } + + /// + /// Compute the hashcode of a single character using the FNV-1a algorithm + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// Note: In general, this isn't any more useful than "char.GetHashCode". However, + /// it may be needed if you need to generate the same hash code as a string or + /// substring with just a single character. + /// + /// The character to hash + /// The FNV-1a hash code of the character. + internal static int GetFNVHashCode(char ch) + { + return Hash.CombineFNVHash(Hash.FnvOffsetBias, ch); + } + + /// + /// Combine a string with an existing FNV-1a hash code + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The accumulated hash code + /// The string to combine + /// The result of combining with using the FNV-1a algorithm + internal static int CombineFNVHash(int hashCode, string text) + { + foreach (char ch in text) + { + hashCode = unchecked((hashCode ^ ch) * Hash.FnvPrime); + } + + return hashCode; + } + + /// + /// Combine a char with an existing FNV-1a hash code + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The accumulated hash code + /// The new character to combine + /// The result of combining with using the FNV-1a algorithm + internal static int CombineFNVHash(int hashCode, char ch) + { + return unchecked((hashCode ^ ch) * Hash.FnvPrime); + } + } +} diff --git a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs index 440db10838871a..1b7834c69a9935 100644 --- a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs +++ b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. #nullable disable @@ -13,6 +12,7 @@ using System.Reflection.Metadata; using System.Text; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -23,7 +23,9 @@ internal static class MetadataHelpers public const char DotDelimiter = '.'; public const string DotDelimiterString = "."; public const char GenericTypeNameManglingChar = '`'; +#if false private const string GenericTypeNameManglingString = "`"; +#endif public const int MaxStringLengthForParamSize = 22; public const int MaxStringLengthForIntToStringConversion = 22; public const string SystemString = "System"; @@ -32,6 +34,8 @@ internal static class MetadataHelpers public const char MangledNameRegionStartChar = '<'; public const char MangledNameRegionEndChar = '>'; +#if false + internal struct AssemblyQualifiedTypeName { internal readonly string TopLevelType; @@ -475,10 +479,11 @@ internal static string ComposeAritySuffixedMetadataName(string name, int arity) return arity == 0 ? name : name + GetAritySuffix(arity); } +#endif + internal static int InferTypeArityFromMetadataName(string emittedTypeName) { - int suffixStartsAt; - return InferTypeArityFromMetadataName(emittedTypeName, out suffixStartsAt); + return InferTypeArityFromMetadataName(emittedTypeName, out _); } private static short InferTypeArityFromMetadataName(string emittedTypeName, out int suffixStartsAt) @@ -536,6 +541,8 @@ internal static string InferTypeArityAndUnmangleMetadataName(string emittedTypeN return emittedTypeName.Substring(0, suffixStartsAt); } +#if false + internal static string UnmangleMetadataNameForArity(string emittedTypeName, int arity) { Debug.Assert(arity > 0); @@ -550,6 +557,8 @@ internal static string UnmangleMetadataNameForArity(string emittedTypeName, int return emittedTypeName; } +#endif + /// /// An ImmutableArray representing the single string "System" /// @@ -582,7 +591,7 @@ internal static ImmutableArray SplitQualifiedName( return name == SystemString ? s_splitQualifiedNameSystem : ImmutableArray.Create(name); } - var result = ArrayBuilder.GetInstance(dots + 1); + var result = new ValueListBuilder(Span.Empty); int start = 0; for (int i = 0; dots > 0; i++) @@ -592,11 +601,11 @@ internal static ImmutableArray SplitQualifiedName( int len = i - start; if (len == 6 && start == 0 && name.StartsWith(SystemString, StringComparison.Ordinal)) { - result.Add(SystemString); + result.Append(SystemString); } else { - result.Add(name.Substring(start, len)); + result.Append(name.Substring(start, len)); } dots--; @@ -604,9 +613,9 @@ internal static ImmutableArray SplitQualifiedName( } } - result.Add(name.Substring(start)); + result.Append(name.Substring(start)); - return result.ToImmutableAndFree(); + return result.AsSpan().ToImmutableArray(); } internal static string SplitQualifiedName( @@ -661,6 +670,8 @@ internal static string SplitQualifiedName( return pstrName.Substring(delimiter + 1); } +#if false + internal static string BuildQualifiedName( string qualifier, string name) @@ -1011,5 +1022,7 @@ internal static string MangleForTypeNameIfNeeded(string moduleName) return pooledStrBuilder.ToStringAndFree(); } + +#endif } } diff --git a/src/libraries/Common/src/Roslyn/MetadataTypeName.cs b/src/libraries/Common/src/Roslyn/MetadataTypeName.cs index 1d0b170866fbe0..93a736e39135aa 100644 --- a/src/libraries/Common/src/Roslyn/MetadataTypeName.cs +++ b/src/libraries/Common/src/Roslyn/MetadataTypeName.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. #nullable disable @@ -62,11 +61,15 @@ internal partial struct MetadataTypeName /// private bool _useCLSCompliantNameArityEncoding; +#if false + /// /// Individual parts of qualified namespace name. /// private ImmutableArray _namespaceSegments; +#endif + public static MetadataTypeName FromFullName(string fullName, bool useCLSCompliantNameArityEncoding = false, int forcedArity = -1) { Debug.Assert(fullName != null); @@ -85,11 +88,14 @@ public static MetadataTypeName FromFullName(string fullName, bool useCLSComplian name._inferredArity = -1; name._useCLSCompliantNameArityEncoding = useCLSCompliantNameArityEncoding; name._forcedArity = (short)forcedArity; +#if false name._namespaceSegments = default(ImmutableArray); - +#endif return name; } +#if false + public static MetadataTypeName FromNamespaceAndTypeName( string namespaceName, string typeName, bool useCLSCompliantNameArityEncoding = false, int forcedArity = -1 @@ -177,6 +183,8 @@ public string NamespaceName } } +#endif + /// /// Name of the type without namespace prefix, but possibly with generic arity mangling present. /// @@ -211,6 +219,8 @@ public string UnmangledTypeName } } +#if false + /// /// Arity of the type inferred based on the name mangling. It doesn't have to match the actual /// arity of the type. @@ -303,5 +313,7 @@ public override string ToString() return string.Format("{{{0},{1},{2},{3}}}", NamespaceName, TypeName, UseCLSCompliantNameArityEncoding.ToString(), _forcedArity.ToString()); } } + +#endif } } diff --git a/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs b/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs index d3d9d948655dee..94dd5ae5f9d13c 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxNodeGrouping.cs @@ -18,7 +18,7 @@ internal static partial class SyntaxValueProviderExtensions /// 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. /// - private class SyntaxNodeGrouping : IEquatable> + private sealed class SyntaxNodeGrouping : IEquatable> where TSyntaxNode : SyntaxNode { public readonly SyntaxTree SyntaxTree; diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs index e4feeac4402dd1..a798de0d7801a7 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; internal static partial class SyntaxValueProviderExtensions { - private class ImmutableArrayValueComparer : IEqualityComparer> + private sealed class ImmutableArrayValueComparer : IEqualityComparer> { public static readonly IEqualityComparer> Instance = new ImmutableArrayValueComparer(); @@ -19,7 +19,16 @@ public bool Equals(ImmutableArray x, ImmutableArray y) if (x == y) return true; - return x.SequenceEqual(y, 0, static (a, b, _) => EqualityComparer.Default.Equals(a, b)); + if (x.Length != y.Length) + return false; + + for (int i = 0, n = x.Length; i < n; i++) + { + if (!EqualityComparer.Default.Equals(x[i], y[i])) + return false; + } + + return true; } public int GetHashCode(ImmutableArray obj) diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs index 526a9bc81c6fce..f0badededfe457 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs @@ -2,22 +2,16 @@ // 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 System.Xml.Serialization; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.SourceGeneration; using System.Threading; -namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; +using Microsoft.CodeAnalysis; -using Aliases = ValueListBuilder<(string aliasName, string symbolName)>; +using Roslyn.Utilities; +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; internal readonly struct GeneratorAttributeSyntaxContext { /// @@ -63,9 +57,17 @@ internal GeneratorAttributeSyntaxContext( internal static partial class SyntaxValueProviderExtensions { private static readonly char[] s_nestedTypeNameSeparators = new char[] { '+' }; + +#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 SymbolDisplayFormat s_metadataDisplayFormat = SymbolDisplayFormat.QualifiedNameArityFormat.AddCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UsePlusForNestedTypes); +#endif + /// /// Creates an that can provide a transform over all s if that node has an attribute on it that binds to a with the @@ -97,7 +99,7 @@ public static IncrementalValuesProvider ForAttributeWithMetadataName( var collectedNodes = nodesWithAttributesMatchingSimpleName .Collect() .WithComparer(ImmutableArrayValueComparer.Instance) - .WithTrackingName("collectedNodes_ForAttributeWithMetadataName"); + /*.WithTrackingName("collectedNodes_ForAttributeWithMetadataName")*/; // Group all the nodes by syntax tree, so we can process a whole syntax tree at a time. This will let us make // the required semantic model for it once, instead of potentially many times (in the rare, but possible case of @@ -105,11 +107,11 @@ public static IncrementalValuesProvider ForAttributeWithMetadataName( var groupedNodes = collectedNodes.SelectMany( static (array, cancellationToken) => array.GroupBy(static n => n.SyntaxTree) - .Select(static g => new SyntaxNodeGrouping(g))).WithTrackingName("groupedNodes_ForAttributeWithMetadataName"); + .Select(static g => new SyntaxNodeGrouping(g)))/*.WithTrackingName("groupedNodes_ForAttributeWithMetadataName")*/; var compilationAndGroupedNodesProvider = groupedNodes .Combine(context.CompilationProvider) - .WithTrackingName("compilationAndGroupedNodes_ForAttributeWithMetadataName"); + /*.WithTrackingName("compilationAndGroupedNodes_ForAttributeWithMetadataName")*/; var syntaxHelper = CSharpSyntaxHelper.Instance; var finalProvider = compilationAndGroupedNodesProvider.SelectMany((tuple, cancellationToken) => @@ -140,8 +142,8 @@ public static IncrementalValuesProvider ForAttributeWithMetadataName( } } - return result.ToImmutable(); - }).WithTrackingName("result_ForAttributeWithMetadataName"); + return result.AsSpan().ToImmutableArray(); + })/*.WithTrackingName("result_ForAttributeWithMetadataName")*/; return finalProvider; @@ -162,7 +164,7 @@ static ImmutableArray getMatchingAttributes( addMatchingAttributes(ref result, module.GetAttributes()); } - return result.ToImmutableAndFree(); + return result.AsSpan().ToImmutableArray(); void addMatchingAttributes( ref ValueListBuilder result, @@ -174,7 +176,7 @@ void addMatchingAttributes( foreach (var attribute in attributes.Value) { if (attribute.ApplicationSyntaxReference?.SyntaxTree == targetSyntaxTree && - attribute.AttributeClass?.ToDisplayString(s_metadataDisplayFormat) == fullyQualifiedMetadataName) + attribute.AttributeClass?.ToDisplayString(/*s_metadataDisplayFormat*/) == fullyQualifiedMetadataName) { result.Append(attribute); } diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs index 5e4223f8732300..390e2ef270dfa8 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -2,17 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Buffers; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using System.Text; using System.Threading; -using System.Transactions; + using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.SourceGeneration; + using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; @@ -21,7 +18,7 @@ namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; internal static partial class SyntaxValueProviderExtensions { - private static readonly ObjectPool> s_stackPool = new(static () => new()); + // private static readonly ObjectPool> s_stackPool = new(static () => new()); /// /// Returns all syntax nodes of that match if that node has an attribute on it that @@ -50,18 +47,18 @@ public static IncrementalValuesProvider ForAttributeWithSimpleName( // Create a provider that provides (and updates) the global aliases for any particular file when it is edited. var individualFileGlobalAliasesProvider = @this.CreateSyntaxProvider( static (n, _) => n is ICompilationUnitSyntax, - static (context, _) => getGlobalAliasesInCompilationUnit(context.Node)).WithTrackingName("individualFileGlobalAliases_ForAttribute"); + static (context, _) => getGlobalAliasesInCompilationUnit(context.Node))/*.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.Instance) - .WithTrackingName("collectedGlobalAliases_ForAttribute"); + /*.WithTrackingName("collectedGlobalAliases_ForAttribute")*/; var allUpGlobalAliasesProvider = collectedGlobalAliasesProvider .Select(static (arrays, _) => GlobalAliases.Create(arrays.SelectMany(a => a.AliasAndSymbolNames).ToImmutableArray())) - .WithTrackingName("allUpGlobalAliases_ForAttribute"); + /*.WithTrackingName("allUpGlobalAliases_ForAttribute")*/; #if false @@ -87,19 +84,19 @@ public static IncrementalValuesProvider ForAttributeWithSimpleName( // Create a syntax provider for every compilation unit. var compilationUnitProvider = @this.CreateSyntaxProvider( static (n, _) => n is ICompilationUnitSyntax, - static (context, _) => context.Node).WithTrackingName("compilationUnit_ForAttribute"); + static (context, _) => context.Node)/*.WithTrackingName("compilationUnit_ForAttribute")*/; // 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 compilationUnitAndGlobalAliasesProvider = compilationUnitProvider .Combine(allUpGlobalAliasesProvider) - .WithTrackingName("compilationUnitAndGlobalAliases_ForAttribute"); + /*.WithTrackingName("compilationUnitAndGlobalAliases_ForAttribute")*/; // For each pair of compilation unit + global aliases, walk the compilation unit var result = compilationUnitAndGlobalAliasesProvider .SelectMany((globalAliasesAndCompilationUnit, cancellationToken) => GetMatchingNodes( syntaxHelper, globalAliasesAndCompilationUnit.Right, globalAliasesAndCompilationUnit.Left, simpleName, predicate, cancellationToken)) - .WithTrackingName("result_ForAttribute"); + /*.WithTrackingName("result_ForAttribute")*/; return result; @@ -111,7 +108,7 @@ static GlobalAliases getGlobalAliasesInCompilationUnit( CSharpSyntaxHelper.Instance.AddAliases(compilationUnit, ref globalAliases, global: true); - return GlobalAliases.Create(ImmutableArray.CreateRange( globalAliases.AsSpan()); + return GlobalAliases.Create(globalAliases.AsSpan().ToImmutableArray()); } } @@ -131,49 +128,46 @@ private static ImmutableArray GetMatchingNodes( // 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 = Aliases.GetInstance(); + 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 = s_stackPool.Allocate(); - var results = ArrayBuilder.GetInstance(); - var attributeTargets = ArrayBuilder.GetInstance(); + var seenNames = new ValueListBuilder(Span.Empty); + var results = new ValueListBuilder(Span.Empty); + var attributeTargets = new ValueListBuilder(Span.Empty); - try - { - recurse(compilationUnit); - } - finally - { - localAliases.Free(); - seenNames.Clear(); - s_stackPool.Free(seenNames); - attributeTargets.Free(); - } + recurse(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); + + if (results.Length == 0) + return ImmutableArray.Empty; - results.RemoveDuplicates(); - return results.ToImmutableAndFree(); + return results.AsSpan().ToArray().Distinct().ToImmutableArray(); - void recurse(SyntaxNode node) + void recurse( + SyntaxNode node, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) { cancellationToken.ThrowIfCancellationRequested(); if (node is ICompilationUnitSyntax) { - syntaxHelper.AddAliases(node, localAliases, global: false); + syntaxHelper.AddAliases(node, ref localAliases, global: false); - recurseChildren(node); + recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); } else if (syntaxHelper.IsAnyNamespaceBlock(node)) { - var localAliasCount = localAliases.Count; - syntaxHelper.AddAliases(node, localAliases, global: false); + var localAliasCount = localAliases.Length; + syntaxHelper.AddAliases(node, ref localAliases, global: false); - recurseChildren(node); + 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.Count = localAliasCount; + localAliases.Length = localAliasCount; } else if (syntaxHelper.IsAttributeList(node)) { @@ -183,16 +177,16 @@ void recurse(SyntaxNode node) // 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(simpleAttributeName, withAttributeSuffix: false) || - matchesAttributeName(simpleAttributeName, withAttributeSuffix: true)) + if (matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: false) || + matchesAttributeName(ref localAliases, ref seenNames, simpleAttributeName, withAttributeSuffix: true)) { - attributeTargets.Clear(); - syntaxHelper.AddAttributeTargets(node, attributeTargets); + attributeTargets.Length = 0; + syntaxHelper.AddAttributeTargets(node, ref attributeTargets); - foreach (var target in attributeTargets) + foreach (var target in attributeTargets.AsSpan()) { if (predicate(target, cancellationToken)) - results.Add(target); + results.Append(target); } return; @@ -206,17 +200,22 @@ void recurse(SyntaxNode node) // 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); + recurseChildren(node, ref localAliases, ref seenNames, ref results, ref attributeTargets); } return; - void recurseChildren(SyntaxNode node) + void recurseChildren( + SyntaxNode node, + ref Aliases localAliases, + ref ValueListBuilder seenNames, + ref ValueListBuilder results, + ref ValueListBuilder attributeTargets) { foreach (var child in node.ChildNodesAndTokens()) { if (child.IsNode) - recurse(child.AsNode()!); + recurse(child.AsNode()!, ref localAliases, ref seenNames, ref results, ref attributeTargets); } } } @@ -237,7 +236,11 @@ bool matchesName(string name, string matchAgainst, bool withAttributeSuffix) } } - bool matchesAttributeName(string currentAttributeName, bool withAttributeSuffix) + bool matchesAttributeName( + ref Aliases localAliases, + ref ValueListBuilder seenNames, + string currentAttributeName, + bool withAttributeSuffix) { // If the names match, we're done. if (withAttributeSuffix) @@ -259,17 +262,20 @@ bool matchesAttributeName(string currentAttributeName, bool withAttributeSuffix) // // 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. - if (seenNames.Contains(currentAttributeName)) - return false; + foreach (var seenName in seenNames.AsSpan()) + { + if (seenName == currentAttributeName) + return false; + } - seenNames.Push(currentAttributeName); + seenNames.Append(currentAttributeName); - foreach (var (aliasName, symbolName) in localAliases) + 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(symbolName, withAttributeSuffix: false)) + matchesAttributeName(ref localAliases, ref seenNames, symbolName, withAttributeSuffix: false)) { return true; } @@ -278,7 +284,7 @@ bool matchesAttributeName(string currentAttributeName, bool withAttributeSuffix) foreach (var (aliasName, symbolName) in globalAliases.AliasAndSymbolNames) { if (matchesName(currentAttributeName, aliasName, withAttributeSuffix) && - matchesAttributeName(symbolName, withAttributeSuffix: false)) + matchesAttributeName(ref localAliases, ref seenNames, symbolName, withAttributeSuffix: false)) { return true; } diff --git a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj index f03936689bbdd3..589c869ccf59d2 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj +++ b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -20,10 +20,12 @@ + + From b533f261f271a96b07e16560087bc74487b54f95 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 17 Jun 2022 13:19:18 -0700 Subject: [PATCH 04/19] Use api --- .../gen/RegexGenerator.Parser.cs | 46 ++++--------------- 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs index 0829ccf42ff174..33dd18fc67f8fd 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs @@ -1,19 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; -using System; -using System.Collections; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Globalization; using System.Linq; using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + namespace System.Text.RegularExpressions.Generator { public partial class RegexGenerator @@ -21,38 +18,13 @@ public partial class RegexGenerator private const string RegexName = "System.Text.RegularExpressions.Regex"; private const string RegexGeneratorAttributeName = "System.Text.RegularExpressions.RegexGeneratorAttribute"; - private static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken cancellationToken) => - // We don't have a semantic model here, so the best we can do is say whether there are any attributes. - node is MethodDeclarationSyntax { AttributeLists: { Count: > 0 } }; - - private static bool IsSemanticTargetForGeneration(SemanticModel semanticModel, MethodDeclarationSyntax methodDeclarationSyntax, CancellationToken cancellationToken) - { - foreach (AttributeListSyntax attributeListSyntax in methodDeclarationSyntax.AttributeLists) - { - foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) - { - if (semanticModel.GetSymbolInfo(attributeSyntax, cancellationToken).Symbol is IMethodSymbol attributeSymbol && - attributeSymbol.ContainingType.ToDisplayString() == RegexGeneratorAttributeName) - { - return true; - } - } - } - - return false; - } - // Returns null if nothing to do, Diagnostic if there's an error to report, or RegexType if the type was analyzed successfully. - private static object? GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken cancellationToken) + private static object? GetSemanticTargetForGeneration( + GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { - var methodSyntax = (MethodDeclarationSyntax)context.Node; + var methodSyntax = (MethodDeclarationSyntax)context.TargetNode; SemanticModel sm = context.SemanticModel; - if (!IsSemanticTargetForGeneration(sm, methodSyntax, cancellationToken)) - { - return null; - } - Compilation compilation = sm.Compilation; INamedTypeSymbol? regexSymbol = compilation.GetBestTypeByMetadataName(RegexName); INamedTypeSymbol? regexGeneratorAttributeSymbol = compilation.GetBestTypeByMetadataName(RegexGeneratorAttributeName); @@ -69,7 +41,7 @@ private static bool IsSemanticTargetForGeneration(SemanticModel semanticModel, M return null; } - IMethodSymbol? regexMethodSymbol = sm.GetDeclaredSymbol(methodSyntax, cancellationToken) as IMethodSymbol; + IMethodSymbol regexMethodSymbol = context.TargetSymbol as IMethodSymbol; if (regexMethodSymbol is null) { return null; From c1a76b131752bb05f5769ff895d77d8118ea984b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 17 Jun 2022 13:24:01 -0700 Subject: [PATCH 05/19] ifdef --- src/libraries/Common/src/Roslyn/Hash.cs | 4 ++++ .../SyntaxValueProvider_ForAttributeWithMetadataName.cs | 1 + .../System.Text.RegularExpressions/gen/RegexGenerator.cs | 7 ++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Roslyn/Hash.cs b/src/libraries/Common/src/Roslyn/Hash.cs index 32a49abeb3379d..7cfa0b2e341eb7 100644 --- a/src/libraries/Common/src/Roslyn/Hash.cs +++ b/src/libraries/Common/src/Roslyn/Hash.cs @@ -18,6 +18,8 @@ internal static int Combine(int newKey, int currentKey) return unchecked((currentKey * (int)0xA5555529) + newKey); } +#if false + internal static int Combine(bool newKeyPart, int currentKey) { return Combine(currentKey, newKeyPart ? 1 : 0); @@ -373,5 +375,7 @@ internal static int CombineFNVHash(int hashCode, char ch) { return unchecked((hashCode ^ ch) * Hash.FnvPrime); } + +#endif } } diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs index f0badededfe457..597f44034fc4a2 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs @@ -12,6 +12,7 @@ using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; + internal readonly struct GeneratorAttributeSyntaxContext { /// diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs index 8aac6281fa0022..ef2c27bc94faaa 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; [assembly: System.Resources.NeutralResourcesLanguage("en-us")] @@ -51,7 +52,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.SyntaxProvider // Find all MethodDeclarationSyntax nodes attributed with RegexGenerator and gather the required information. - .CreateSyntaxProvider(IsSyntaxTargetForGeneration, GetSemanticTargetForGeneration) + .ForAttributeWithMetadataName( + context, + RegexGeneratorAttributeName, + (n, c) => n is MethodDeclarationSyntax, + GetSemanticTargetForGeneration) .Where(static m => m is not null) // Generate the RunnerFactory for each regex, if possible. This is where the bulk of the implementation occurs. From 1f35a7e5a5d2033fdcf0b1c641879890b8118885 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 20 Jun 2022 10:37:51 -0700 Subject: [PATCH 06/19] Move using outside namespace --- .../Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs index 390e2ef270dfa8..74c8a0612fc6b6 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -10,12 +10,10 @@ using Microsoft.CodeAnalysis; -using Roslyn.Utilities; +using Aliases = System.Collections.Generic.ValueListBuilder<(string aliasName, string symbolName)>; namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; -using Aliases = ValueListBuilder<(string aliasName, string symbolName)>; - internal static partial class SyntaxValueProviderExtensions { // private static readonly ObjectPool> s_stackPool = new(static () => new()); From f5d813766ae142da490aae864434257f340756b2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 20 Jun 2022 10:40:26 -0700 Subject: [PATCH 07/19] Move to debug assert --- src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs index 0a587a732f5195..12bcf4b8409bef 100644 --- a/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs +++ b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs @@ -79,7 +79,7 @@ public override void AddAliases(SyntaxNode node, ref ValueListBuilder<(string al } else { - throw new System.InvalidOperationException("Unreachable"); + Debug.Assert(false, "This should not be reachable. Caller already checked we had a compilation unit or namespace."); } } From be4ef0ff3c1e9cf2fe3816322c5c22426b99457c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 20 Jun 2022 10:42:10 -0700 Subject: [PATCH 08/19] Optimize common cases --- .../src/Roslyn/GetBestTypeByMetadataName.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs index 9d6d235d4b4a92..92728794020a2e 100644 --- a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs @@ -149,14 +149,20 @@ internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive) public static ImmutableArray ToImmutableArray(this ReadOnlySpan span) { - if (span.Length == 0) - return ImmutableArray.Empty; - - var builder = ImmutableArray.CreateBuilder(span.Length); - foreach (var item in span) - builder.Add(item); - - return builder.MoveToImmutable(); + switch (span.Length) + { + case 0: return ImmutableArray.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(span.Length); + foreach (var item in span) + builder.Add(item); + + return builder.MoveToImmutable(); + } } public static SimpleNameSyntax GetUnqualifiedName(this NameSyntax name) From 1cdc42866d610db2e1a71acff90fe0511a5e5fba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 20 Jun 2022 16:10:08 -0700 Subject: [PATCH 09/19] Explain if'defed regions --- src/libraries/Common/src/Roslyn/Hash.cs | 2 ++ src/libraries/Common/src/Roslyn/MetadataHelpers.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/libraries/Common/src/Roslyn/Hash.cs b/src/libraries/Common/src/Roslyn/Hash.cs index 7cfa0b2e341eb7..dc22ca62d28df5 100644 --- a/src/libraries/Common/src/Roslyn/Hash.cs +++ b/src/libraries/Common/src/Roslyn/Hash.cs @@ -20,6 +20,8 @@ internal static int Combine(int newKey, int currentKey) #if false + // Disabled as these helpers are not currently needed in the polyfill of + internal static int Combine(bool newKeyPart, int currentKey) { return Combine(currentKey, newKeyPart ? 1 : 0); diff --git a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs index 1b7834c69a9935..2f7c6634ef30f6 100644 --- a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs +++ b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs @@ -36,6 +36,8 @@ internal static class MetadataHelpers #if false + // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName + internal struct AssemblyQualifiedTypeName { internal readonly string TopLevelType; From cfce55373fd682b0672fd87f910c8682d669dd5a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 20 Jun 2022 16:15:16 -0700 Subject: [PATCH 10/19] Explain if'defed regions --- src/libraries/Common/src/Roslyn/Hash.cs | 2 +- src/libraries/Common/src/Roslyn/MetadataHelpers.cs | 4 ++++ src/libraries/Common/src/Roslyn/MetadataTypeName.cs | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Roslyn/Hash.cs b/src/libraries/Common/src/Roslyn/Hash.cs index dc22ca62d28df5..1b0dd45df91a59 100644 --- a/src/libraries/Common/src/Roslyn/Hash.cs +++ b/src/libraries/Common/src/Roslyn/Hash.cs @@ -20,7 +20,7 @@ internal static int Combine(int newKey, int currentKey) #if false - // Disabled as these helpers are not currently needed in the polyfill of + // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName internal static int Combine(bool newKeyPart, int currentKey) { diff --git a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs index 2f7c6634ef30f6..ae261f5ae2bd1b 100644 --- a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs +++ b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs @@ -545,6 +545,8 @@ internal static string InferTypeArityAndUnmangleMetadataName(string emittedTypeN #if false + // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName + internal static string UnmangleMetadataNameForArity(string emittedTypeName, int arity) { Debug.Assert(arity > 0); @@ -674,6 +676,8 @@ internal static string SplitQualifiedName( #if false + // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName + internal static string BuildQualifiedName( string qualifier, string name) diff --git a/src/libraries/Common/src/Roslyn/MetadataTypeName.cs b/src/libraries/Common/src/Roslyn/MetadataTypeName.cs index 93a736e39135aa..cebe41748cb808 100644 --- a/src/libraries/Common/src/Roslyn/MetadataTypeName.cs +++ b/src/libraries/Common/src/Roslyn/MetadataTypeName.cs @@ -63,6 +63,8 @@ internal partial struct MetadataTypeName #if false + // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName + /// /// Individual parts of qualified namespace name. /// @@ -96,6 +98,8 @@ public static MetadataTypeName FromFullName(string fullName, bool useCLSComplian #if false + // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName + public static MetadataTypeName FromNamespaceAndTypeName( string namespaceName, string typeName, bool useCLSCompliantNameArityEncoding = false, int forcedArity = -1 @@ -221,6 +225,8 @@ public string UnmangledTypeName #if false + // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName + /// /// Arity of the type inferred based on the name mangling. It doesn't have to match the actual /// arity of the type. From fd9a27eadabca2b0aaccdd04497b11b4f409e820 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 21 Jun 2022 09:48:21 -0700 Subject: [PATCH 11/19] Update System.Text.RegularExpressions.Generator.csproj --- .../gen/System.Text.RegularExpressions.Generator.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj index 589c869ccf59d2..3d8d8b553b7ac1 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj +++ b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj @@ -24,7 +24,6 @@ - From e2613b6c38e4d360b38eeeb2a0fbd0e4a4f47634 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 22 Jun 2022 11:28:27 -0700 Subject: [PATCH 12/19] Port latest changes over --- ...ueProvider_ForAttributeWithMetadataName.cs | 2 +- ...alueProvider_ForAttributeWithSimpleName.cs | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs index 597f44034fc4a2..e7b1696ac90e5a 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs @@ -95,7 +95,7 @@ public static IncrementalValuesProvider ForAttributeWithMetadataName( ? MetadataTypeName.FromFullName(fullyQualifiedMetadataName.Split(s_nestedTypeNameSeparators).Last()) : MetadataTypeName.FromFullName(fullyQualifiedMetadataName); - var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(metadataName.UnmangledTypeName, predicate); + var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(context, metadataName.UnmangledTypeName, predicate); var collectedNodes = nodesWithAttributesMatchingSimpleName .Collect() diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs index 74c8a0612fc6b6..8bab554f3bf2fe 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -37,15 +37,23 @@ internal static partial class SyntaxValueProviderExtensions /// public static IncrementalValuesProvider ForAttributeWithSimpleName( this SyntaxValueProvider @this, + IncrementalGeneratorInitializationContext context, string simpleName, Func 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(static (c, _) => c.SyntaxTrees) + /*.WithTrackingName("compilationUnit_ForAttribute")*/; + // Create a provider that provides (and updates) the global aliases for any particular file when it is edited. - var individualFileGlobalAliasesProvider = @this.CreateSyntaxProvider( - static (n, _) => n is ICompilationUnitSyntax, - static (context, _) => getGlobalAliasesInCompilationUnit(context.Node))/*.WithTrackingName("individualFileGlobalAliases_ForAttribute")*/; + var individualFileGlobalAliasesProvider = syntaxTreesProvider.Select( + (s, c) => getGlobalAliasesInCompilationUnit(s.GetRoot(c)))/*.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 @@ -79,19 +87,14 @@ public static IncrementalValuesProvider ForAttributeWithSimpleName( #endif - // Create a syntax provider for every compilation unit. - var compilationUnitProvider = @this.CreateSyntaxProvider( - static (n, _) => n is ICompilationUnitSyntax, - static (context, _) => context.Node)/*.WithTrackingName("compilationUnit_ForAttribute")*/; - // 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 compilationUnitAndGlobalAliasesProvider = compilationUnitProvider + var syntaxTreeAndGlobalAliasesProvider = syntaxTreesProvider .Combine(allUpGlobalAliasesProvider) /*.WithTrackingName("compilationUnitAndGlobalAliases_ForAttribute")*/; // For each pair of compilation unit + global aliases, walk the compilation unit - var result = compilationUnitAndGlobalAliasesProvider + var result = syntaxTreeAndGlobalAliasesProvider .SelectMany((globalAliasesAndCompilationUnit, cancellationToken) => GetMatchingNodes( syntaxHelper, globalAliasesAndCompilationUnit.Right, globalAliasesAndCompilationUnit.Left, simpleName, predicate, cancellationToken)) /*.WithTrackingName("result_ForAttribute")*/; @@ -113,11 +116,12 @@ static GlobalAliases getGlobalAliasesInCompilationUnit( private static ImmutableArray GetMatchingNodes( ISyntaxHelper syntaxHelper, GlobalAliases globalAliases, - SyntaxNode compilationUnit, + SyntaxTree syntaxTree, string name, Func predicate, CancellationToken cancellationToken) { + var compilationUnit = syntaxTree.GetRoot(cancellationToken); Debug.Assert(compilationUnit is ICompilationUnitSyntax); var isCaseSensitive = syntaxHelper.IsCaseSensitive; From 91672fede0b3919ff46b5bbaf8dc6ea469462f68 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 28 Jun 2022 09:43:55 -0700 Subject: [PATCH 13/19] Renames --- .../System.Text.RegularExpressions/gen/RegexGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs index ef2c27bc94faaa..388984725f6a3a 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs @@ -55,7 +55,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .ForAttributeWithMetadataName( context, RegexGeneratorAttributeName, - (n, c) => n is MethodDeclarationSyntax, + (node, _) => node is MethodDeclarationSyntax, GetSemanticTargetForGeneration) .Where(static m => m is not null) From cc933a2912b2db3faaf91ad5677a617553a883d8 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 28 Jun 2022 13:12:01 -0700 Subject: [PATCH 14/19] Update src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs --- src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs index 12bcf4b8409bef..349d5f973c9149 100644 --- a/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs +++ b/src/libraries/Common/src/Roslyn/CSharpSyntaxHelper.cs @@ -79,7 +79,7 @@ public override void AddAliases(SyntaxNode node, ref ValueListBuilder<(string al } else { - Debug.Assert(false, "This should not be reachable. Caller already checked we had a compilation unit or namespace."); + Debug.Fail("This should not be reachable. Caller already checked we had a compilation unit or namespace."); } } From 8a5a8d06941a33c694b85b367df2fa5b49d43018 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 28 Jun 2022 13:17:22 -0700 Subject: [PATCH 15/19] Simplify --- ...alueProvider.ImmutableArrayValueComparer.cs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs index a798de0d7801a7..5323a100883a73 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider.ImmutableArrayValueComparer.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; +using System.Linq; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions; @@ -15,21 +15,7 @@ private sealed class ImmutableArrayValueComparer : IEqualityComparer> Instance = new ImmutableArrayValueComparer(); public bool Equals(ImmutableArray x, ImmutableArray y) - { - if (x == y) - return true; - - if (x.Length != y.Length) - return false; - - for (int i = 0, n = x.Length; i < n; i++) - { - if (!EqualityComparer.Default.Equals(x[i], y[i])) - return false; - } - - return true; - } + => x.SequenceEqual(y, EqualityComparer.Default); public int GetHashCode(ImmutableArray obj) { From 4eb00d1219f6f7e4dd88af3c7b36708b5ff4df89 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 28 Jun 2022 13:20:33 -0700 Subject: [PATCH 16/19] Dispose builders --- .../Common/src/Roslyn/MetadataHelpers.cs | 2 +- ...lueProvider_ForAttributeWithMetadataName.cs | 4 ++-- ...ValueProvider_ForAttributeWithSimpleName.cs | 18 ++++++++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs index ae261f5ae2bd1b..a88b1d4026ef54 100644 --- a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs +++ b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs @@ -595,7 +595,7 @@ internal static ImmutableArray SplitQualifiedName( return name == SystemString ? s_splitQualifiedNameSystem : ImmutableArray.Create(name); } - var result = new ValueListBuilder(Span.Empty); + using var result = new ValueListBuilder(Span.Empty); int start = 0; for (int i = 0; dots > 0; i++) diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs index e7b1696ac90e5a..9940a8405dc0a1 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs @@ -119,7 +119,7 @@ public static IncrementalValuesProvider ForAttributeWithMetadataName( { var (grouping, compilation) = tuple; - var result = new ValueListBuilder(Span.Empty); + using var result = new ValueListBuilder(Span.Empty); var syntaxTree = grouping.SyntaxTree; var semanticModel = compilation.GetSemanticModel(syntaxTree); @@ -154,7 +154,7 @@ static ImmutableArray getMatchingAttributes( string fullyQualifiedMetadataName) { var targetSyntaxTree = attributeTarget.SyntaxTree; - var result = new ValueListBuilder(Span.Empty); + using var result = new ValueListBuilder(Span.Empty); addMatchingAttributes(ref result, symbol.GetAttributes()); addMatchingAttributes(ref result, (symbol as IMethodSymbol)?.GetReturnTypeAttributes()); diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs index 8bab554f3bf2fe..e040b4968be598 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -135,16 +135,26 @@ private static ImmutableArray GetMatchingNodes( // 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(Span.Empty); var results = new ValueListBuilder(Span.Empty); var attributeTargets = new ValueListBuilder(Span.Empty); - recurse(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); + try + { + recurse(compilationUnit, ref localAliases, ref seenNames, ref results, ref attributeTargets); - if (results.Length == 0) - return ImmutableArray.Empty; + if (results.Length == 0) + return ImmutableArray.Empty; - return results.AsSpan().ToArray().Distinct().ToImmutableArray(); + return results.AsSpan().ToArray().Distinct().ToImmutableArray(); + } + finally + { + attributeTargets.Dispose(); + results.Dispose(); + seenNames.Dispose(); + } void recurse( SyntaxNode node, From 59cdea7a03fa1c54c8f79b890cfcb6fbe31a4a58 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 28 Jun 2022 13:23:02 -0700 Subject: [PATCH 17/19] Dispose builders --- ...ueProvider_ForAttributeWithMetadataName.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs index 9940a8405dc0a1..9492e86c501f16 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs @@ -154,19 +154,26 @@ static ImmutableArray getMatchingAttributes( string fullyQualifiedMetadataName) { var targetSyntaxTree = attributeTarget.SyntaxTree; - using var result = new ValueListBuilder(Span.Empty); + var result = new ValueListBuilder(Span.Empty); - addMatchingAttributes(ref result, symbol.GetAttributes()); - addMatchingAttributes(ref result, (symbol as IMethodSymbol)?.GetReturnTypeAttributes()); + 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()); + } - if (symbol is IAssemblySymbol assemblySymbol) + return result.AsSpan().ToImmutableArray(); + } + finally { - foreach (var module in assemblySymbol.Modules) - addMatchingAttributes(ref result, module.GetAttributes()); + result.Dispose(); } - return result.AsSpan().ToImmutableArray(); - void addMatchingAttributes( ref ValueListBuilder result, ImmutableArray? attributes) From e7a1f3e1e3533e9659e8abf43061f9ef94cf3eec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 28 Jun 2022 13:27:33 -0700 Subject: [PATCH 18/19] Simplify by removing support for nested attributes --- .../Common/src/Roslyn/MetadataHelpers.cs | 1034 ----------------- .../Common/src/Roslyn/MetadataTypeName.cs | 325 ------ ...ueProvider_ForAttributeWithMetadataName.cs | 20 +- ...m.Text.RegularExpressions.Generator.csproj | 4 +- 4 files changed, 19 insertions(+), 1364 deletions(-) delete mode 100644 src/libraries/Common/src/Roslyn/MetadataHelpers.cs delete mode 100644 src/libraries/Common/src/Roslyn/MetadataTypeName.cs diff --git a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs b/src/libraries/Common/src/Roslyn/MetadataHelpers.cs deleted file mode 100644 index a88b1d4026ef54..00000000000000 --- a/src/libraries/Common/src/Roslyn/MetadataHelpers.cs +++ /dev/null @@ -1,1034 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Reflection.Metadata; -using System.Text; -using Microsoft.CodeAnalysis.Collections; -using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis -{ - internal static class MetadataHelpers - { - public const char DotDelimiter = '.'; - public const string DotDelimiterString = "."; - public const char GenericTypeNameManglingChar = '`'; -#if false - private const string GenericTypeNameManglingString = "`"; -#endif - public const int MaxStringLengthForParamSize = 22; - public const int MaxStringLengthForIntToStringConversion = 22; - public const string SystemString = "System"; - - // These can appear in the interface name that precedes an explicit interface implementation member. - public const char MangledNameRegionStartChar = '<'; - public const char MangledNameRegionEndChar = '>'; - -#if false - - // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName - - internal struct AssemblyQualifiedTypeName - { - internal readonly string TopLevelType; - internal readonly string[] NestedTypes; - internal readonly AssemblyQualifiedTypeName[] TypeArguments; - internal readonly int PointerCount; - - /// - /// Rank equal 0 is used to denote an SzArray, rank equal 1 denotes multi-dimensional array of rank 1. - /// - internal readonly int[] ArrayRanks; - internal readonly string AssemblyName; - - internal AssemblyQualifiedTypeName( - string topLevelType, - string[] nestedTypes, - AssemblyQualifiedTypeName[] typeArguments, - int pointerCount, - int[] arrayRanks, - string assemblyName) - { - this.TopLevelType = topLevelType; - this.NestedTypes = nestedTypes; - this.TypeArguments = typeArguments; - this.PointerCount = pointerCount; - this.ArrayRanks = arrayRanks; - this.AssemblyName = assemblyName; - } - } - - internal static AssemblyQualifiedTypeName DecodeTypeName(string s) - { - var decoder = new SerializedTypeDecoder(s); - return decoder.DecodeTypeName(); - } - - /// - /// Decodes a serialized type name in its canonical form. The canonical name is its full type name, followed - /// optionally by the assembly where it is defined, its version, culture and public key token. If the assembly - /// name is omitted, the type name is in the current assembly otherwise it is in the referenced assembly. The - /// full type name is the fully qualified metadata type name. - /// - private struct SerializedTypeDecoder - { - private static readonly char[] s_typeNameDelimiters = { '+', ',', '[', ']', '*' }; - private readonly string _input; - private int _offset; - - internal SerializedTypeDecoder(string s) - { - _input = s; - _offset = 0; - } - - private void Advance() - { - if (!EndOfInput) - { - _offset++; - } - } - - private void AdvanceTo(int i) - { - if (i <= _input.Length) - { - _offset = i; - } - } - - private bool EndOfInput - { - get - { - return _offset >= _input.Length; - } - } - - private char Current - { - get - { - return _input[_offset]; - } - } - - /// - /// Decodes a type name. A type name is a string which is terminated by the end of the string or one of the - /// delimiters '+', ',', '[', ']'. '+' separates nested classes. '[' and ']' - /// enclosed generic type arguments. ',' separates types. - /// - internal AssemblyQualifiedTypeName DecodeTypeName(bool isTypeArgument = false, bool isTypeArgumentWithAssemblyName = false) - { - Debug.Assert(!isTypeArgumentWithAssemblyName || isTypeArgument); - - string topLevelType = null; - ArrayBuilder nestedTypesBuilder = null; - AssemblyQualifiedTypeName[] typeArguments = null; - int pointerCount = 0; - ArrayBuilder arrayRanksBuilder = null; - string assemblyName = null; - bool decodingTopLevelType = true; - bool isGenericTypeName = false; - - var pooledStrBuilder = PooledStringBuilder.GetInstance(); - StringBuilder typeNameBuilder = pooledStrBuilder.Builder; - - while (!EndOfInput) - { - int i = _input.IndexOfAny(s_typeNameDelimiters, _offset); - if (i >= 0) - { - char c = _input[i]; - - // Found name, which could be a generic name with arity. - // Generic type parameter count, if any, are handled in DecodeGenericName. - string decodedString = DecodeGenericName(i); - Debug.Assert(decodedString != null); - - // Type name is generic if the decoded name of the top level type OR any of the outer types of a nested type had the '`' character. - isGenericTypeName = isGenericTypeName || decodedString.IndexOf(GenericTypeNameManglingChar) >= 0; - typeNameBuilder.Append(decodedString); - - switch (c) - { - case '*': - if (arrayRanksBuilder != null) - { - // Error case, array shape must be specified at the end of the type name. - // Process as a regular character and continue. - typeNameBuilder.Append(c); - } - else - { - pointerCount++; - } - - Advance(); - break; - - case '+': - if (arrayRanksBuilder != null || pointerCount > 0) - { - // Error case, array shape must be specified at the end of the type name. - // Process as a regular character and continue. - typeNameBuilder.Append(c); - } - else - { - // Type followed by nested type. Handle nested class separator and collect the nested types. - HandleDecodedTypeName(typeNameBuilder.ToString(), decodingTopLevelType, ref topLevelType, ref nestedTypesBuilder); - typeNameBuilder.Clear(); - decodingTopLevelType = false; - } - - Advance(); - break; - - case '[': - // Is type followed by generic type arguments? - if (isGenericTypeName && typeArguments == null) - { - Advance(); - if (arrayRanksBuilder != null || pointerCount > 0) - { - // Error case, array shape must be specified at the end of the type name. - // Process as a regular character and continue. - typeNameBuilder.Append(c); - } - else - { - // Decode type arguments. - typeArguments = DecodeTypeArguments(); - } - } - else - { - // Decode array shape. - DecodeArrayShape(typeNameBuilder, ref arrayRanksBuilder); - } - - break; - - case ']': - if (isTypeArgument) - { - // End of type arguments. This occurs when the last type argument is a type in the - // current assembly. - goto ExitDecodeTypeName; - } - else - { - // Error case, process as a regular character and continue. - typeNameBuilder.Append(c); - Advance(); - break; - } - - case ',': - // A comma may separate a type name from its assembly name or a type argument from - // another type argument. - // If processing non-type argument or a type argument with assembly name, - // process the characters after the comma as an assembly name. - if (!isTypeArgument || isTypeArgumentWithAssemblyName) - { - Advance(); - if (!EndOfInput && Char.IsWhiteSpace(Current)) - { - Advance(); - } - - assemblyName = DecodeAssemblyName(isTypeArgumentWithAssemblyName); - } - goto ExitDecodeTypeName; - - default: - throw ExceptionUtilities.UnexpectedValue(c); - } - } - else - { - typeNameBuilder.Append(DecodeGenericName(_input.Length)); - goto ExitDecodeTypeName; - } - } - -ExitDecodeTypeName: - HandleDecodedTypeName(typeNameBuilder.ToString(), decodingTopLevelType, ref topLevelType, ref nestedTypesBuilder); - pooledStrBuilder.Free(); - - return new AssemblyQualifiedTypeName( - topLevelType, - nestedTypesBuilder?.ToArrayAndFree(), - typeArguments, - pointerCount, - arrayRanksBuilder?.ToArrayAndFree(), - assemblyName); - } - - private static void HandleDecodedTypeName(string decodedTypeName, bool decodingTopLevelType, ref string topLevelType, ref ArrayBuilder nestedTypesBuilder) - { - if (decodedTypeName.Length != 0) - { - if (decodingTopLevelType) - { - Debug.Assert(topLevelType == null); - topLevelType = decodedTypeName; - } - else - { - if (nestedTypesBuilder == null) - { - nestedTypesBuilder = ArrayBuilder.GetInstance(); - } - - nestedTypesBuilder.Add(decodedTypeName); - } - } - } - - /// - /// Decodes a generic name. This is a type name followed optionally by a type parameter count - /// - private string DecodeGenericName(int i) - { - Debug.Assert(i == _input.Length || s_typeNameDelimiters.Contains(_input[i])); - - var length = i - _offset; - if (length == 0) - { - return String.Empty; - } - - // Save start of name. The name should be the emitted name including the '`' and arity. - int start = _offset; - AdvanceTo(i); - - // Get the emitted name. - return _input.Substring(start, _offset - start); - } - - private AssemblyQualifiedTypeName[] DecodeTypeArguments() - { - if (EndOfInput) - { - return null; - } - - var typeBuilder = ArrayBuilder.GetInstance(); - - while (!EndOfInput) - { - typeBuilder.Add(DecodeTypeArgument()); - - if (!EndOfInput) - { - switch (Current) - { - case ',': - // More type arguments follow - Advance(); - if (!EndOfInput && Char.IsWhiteSpace(Current)) - { - Advance(); - } - break; - - case ']': - // End of type arguments - Advance(); - return typeBuilder.ToArrayAndFree(); - - default: - throw ExceptionUtilities.UnexpectedValue(EndOfInput); - } - } - } - - return typeBuilder.ToArrayAndFree(); - } - - private AssemblyQualifiedTypeName DecodeTypeArgument() - { - bool isTypeArgumentWithAssemblyName = false; - if (Current == '[') - { - isTypeArgumentWithAssemblyName = true; - Advance(); - } - - AssemblyQualifiedTypeName result = DecodeTypeName(isTypeArgument: true, isTypeArgumentWithAssemblyName: isTypeArgumentWithAssemblyName); - - if (isTypeArgumentWithAssemblyName) - { - if (!EndOfInput && Current == ']') - { - Advance(); - } - } - - return result; - } - - private string DecodeAssemblyName(bool isTypeArgumentWithAssemblyName) - { - if (EndOfInput) - { - return null; - } - - int i; - if (isTypeArgumentWithAssemblyName) - { - i = _input.IndexOf(']', _offset); - if (i < 0) - { - i = _input.Length; - } - } - else - { - i = _input.Length; - } - - string name = _input.Substring(_offset, i - _offset); - AdvanceTo(i); - return name; - } - - /// - /// Rank equal 0 is used to denote an SzArray, rank equal 1 denotes multi-dimensional array of rank 1. - /// - private void DecodeArrayShape(StringBuilder typeNameBuilder, ref ArrayBuilder arrayRanksBuilder) - { - Debug.Assert(Current == '['); - - int start = _offset; - int rank = 1; - bool isMultiDimensionalIfRankOne = false; - Advance(); - - while (!EndOfInput) - { - switch (Current) - { - case ',': - rank++; - Advance(); - break; - - case ']': - if (arrayRanksBuilder == null) - { - arrayRanksBuilder = ArrayBuilder.GetInstance(); - } - - arrayRanksBuilder.Add(rank == 1 && !isMultiDimensionalIfRankOne ? 0 : rank); - Advance(); - return; - - case '*': - if (rank != 1) - { - goto default; - } - - Advance(); - if (Current != ']') - { - // Error case, process as regular characters - typeNameBuilder.Append(_input.Substring(start, _offset - start)); - return; - } - - isMultiDimensionalIfRankOne = true; - break; - - default: - // Error case, process as regular characters - Advance(); - typeNameBuilder.Append(_input.Substring(start, _offset - start)); - return; - } - } - - // Error case, process as regular characters - typeNameBuilder.Append(_input.Substring(start, _offset - start)); - } - } - - private static readonly string[] s_aritySuffixesOneToNine = { "`1", "`2", "`3", "`4", "`5", "`6", "`7", "`8", "`9" }; - - internal static string GetAritySuffix(int arity) - { - Debug.Assert(arity > 0); - return (arity <= 9) ? s_aritySuffixesOneToNine[arity - 1] : string.Concat(GenericTypeNameManglingString, arity.ToString(CultureInfo.InvariantCulture)); - } - - internal static string ComposeAritySuffixedMetadataName(string name, int arity) - { - return arity == 0 ? name : name + GetAritySuffix(arity); - } - -#endif - - internal static int InferTypeArityFromMetadataName(string emittedTypeName) - { - return InferTypeArityFromMetadataName(emittedTypeName, out _); - } - - private static short InferTypeArityFromMetadataName(string emittedTypeName, out int suffixStartsAt) - { - Debug.Assert(emittedTypeName != null, "NULL actual name unexpected!!!"); - int emittedTypeNameLength = emittedTypeName.Length; - - int indexOfManglingChar; - for (indexOfManglingChar = emittedTypeNameLength; indexOfManglingChar >= 1; indexOfManglingChar--) - { - if (emittedTypeName[indexOfManglingChar - 1] == GenericTypeNameManglingChar) - { - break; - } - } - - if (indexOfManglingChar < 2 || - (emittedTypeNameLength - indexOfManglingChar) == 0 || - emittedTypeNameLength - indexOfManglingChar > MaxStringLengthForParamSize) - { - suffixStartsAt = -1; - return 0; - } - - // Given a name corresponding to `, - // extract the arity. - string stringRepresentingArity = emittedTypeName.Substring(indexOfManglingChar); - - int arity; - bool nonNumericCharFound = !int.TryParse(stringRepresentingArity, NumberStyles.None, CultureInfo.InvariantCulture, out arity); - - if (nonNumericCharFound || arity < 0 || arity > short.MaxValue || - stringRepresentingArity != arity.ToString()) - { - suffixStartsAt = -1; - return 0; - } - - suffixStartsAt = indexOfManglingChar - 1; - return (short)arity; - } - - internal static string InferTypeArityAndUnmangleMetadataName(string emittedTypeName, out short arity) - { - int suffixStartsAt; - arity = InferTypeArityFromMetadataName(emittedTypeName, out suffixStartsAt); - - if (arity == 0) - { - Debug.Assert(suffixStartsAt == -1); - return emittedTypeName; - } - - Debug.Assert(suffixStartsAt > 0 && suffixStartsAt < emittedTypeName.Length - 1); - return emittedTypeName.Substring(0, suffixStartsAt); - } - -#if false - - // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName - - internal static string UnmangleMetadataNameForArity(string emittedTypeName, int arity) - { - Debug.Assert(arity > 0); - - int suffixStartsAt; - if (arity == InferTypeArityFromMetadataName(emittedTypeName, out suffixStartsAt)) - { - Debug.Assert(suffixStartsAt > 0 && suffixStartsAt < emittedTypeName.Length - 1); - return emittedTypeName.Substring(0, suffixStartsAt); - } - - return emittedTypeName; - } - -#endif - - /// - /// An ImmutableArray representing the single string "System" - /// - private static readonly ImmutableArray s_splitQualifiedNameSystem = ImmutableArray.Create(SystemString); - - internal static ImmutableArray SplitQualifiedName( - string name) - { - Debug.Assert(name != null); - - if (name.Length == 0) - { - return ImmutableArray.Empty; - } - - // PERF: Avoid String.Split because of the allocations. Also, we can special-case - // for "System" if it is the first or only part. - - int dots = 0; - foreach (char ch in name) - { - if (ch == DotDelimiter) - { - dots++; - } - } - - if (dots == 0) - { - return name == SystemString ? s_splitQualifiedNameSystem : ImmutableArray.Create(name); - } - - using var result = new ValueListBuilder(Span.Empty); - - int start = 0; - for (int i = 0; dots > 0; i++) - { - if (name[i] == DotDelimiter) - { - int len = i - start; - if (len == 6 && start == 0 && name.StartsWith(SystemString, StringComparison.Ordinal)) - { - result.Append(SystemString); - } - else - { - result.Append(name.Substring(start, len)); - } - - dots--; - start = i + 1; - } - } - - result.Append(name.Substring(start)); - - return result.AsSpan().ToImmutableArray(); - } - - internal static string SplitQualifiedName( - string pstrName, - out string qualifier) - { - Debug.Assert(pstrName != null); - - // In mangled names, the original unmangled name is frequently included, - // surrounded by angle brackets. The unmangled name may contain dots - // (e.g. if it is an explicit interface implementation) or paired angle - // brackets (e.g. if the explicitly implemented interface is generic). - var angleBracketDepth = 0; - var delimiter = -1; - for (int i = 0; i < pstrName.Length; i++) - { - switch (pstrName[i]) - { - case MangledNameRegionStartChar: - angleBracketDepth++; - break; - case MangledNameRegionEndChar: - angleBracketDepth--; - break; - case DotDelimiter: - // If we see consecutive dots, the second is part of the method name - // (i.e. ".ctor" or ".cctor"). - if (angleBracketDepth == 0 && (i == 0 || delimiter < i - 1)) - { - delimiter = i; - } - break; - } - } - Debug.Assert(angleBracketDepth == 0); - - if (delimiter < 0) - { - qualifier = string.Empty; - return pstrName; - } - - if (delimiter == 6 && pstrName.StartsWith(SystemString, StringComparison.Ordinal)) - { - qualifier = SystemString; - } - else - { - qualifier = pstrName.Substring(0, delimiter); - } - - return pstrName.Substring(delimiter + 1); - } - -#if false - - // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName - - internal static string BuildQualifiedName( - string qualifier, - string name) - { - Debug.Assert(name != null); - - if (!string.IsNullOrEmpty(qualifier)) - { - return String.Concat(qualifier, DotDelimiterString, name); - } - - return name; - } - - /// - /// Calculates information about types and namespaces immediately contained within a namespace. - /// - /// - /// Is current namespace a global namespace? - /// - /// - /// Length of the fully-qualified name of this namespace. - /// - /// - /// The sequence of groups of TypeDef row ids for types contained within the namespace, - /// recursively including those from nested namespaces. The row ids must be grouped by the - /// fully-qualified namespace name in case-sensitive manner. - /// Key of each IGrouping is a fully-qualified namespace name, which starts with the name of - /// this namespace. There could be multiple groups for each fully-qualified namespace name. - /// - /// The groups must be sorted by the keys in a manner consistent with comparer passed in as - /// nameComparer. Therefore, all types immediately contained within THIS namespace, if any, - /// must be in several IGrouping at the very beginning of the sequence. - /// - /// - /// Equality comparer to compare namespace names. - /// - /// - /// Output parameter, never null: - /// A sequence of groups of TypeDef row ids for types immediately contained within this namespace. - /// - /// - /// Output parameter, never null: - /// A sequence with information about namespaces immediately contained within this namespace. - /// For each pair: - /// Key - contains simple name of a child namespace. - /// Value - contains a sequence similar to the one passed to this function, but - /// calculated for the child namespace. - /// - /// - public static void GetInfoForImmediateNamespaceMembers( - bool isGlobalNamespace, - int namespaceNameLength, - IEnumerable> typesByNS, - StringComparer nameComparer, - out IEnumerable> types, - out IEnumerable>>> namespaces) - { - Debug.Assert(typesByNS != null); - Debug.Assert(namespaceNameLength >= 0); - Debug.Assert(!isGlobalNamespace || namespaceNameLength == 0); - - // A list of groups of TypeDef row ids for types immediately contained within this namespace. - var nestedTypes = new List>(); - - // A list accumulating information about namespaces immediately contained within this namespace. - // For each pair: - // Key - contains simple name of a child namespace. - // Value – contains a sequence similar to the one passed to this function, but - // calculated for the child namespace. - var nestedNamespaces = new List>>>(); - bool possiblyHavePairsWithDuplicateKey = false; - - var enumerator = typesByNS.GetEnumerator(); - - using (enumerator) - { - if (enumerator.MoveNext()) - { - var pair = enumerator.Current; - - // Simple name of the last encountered child namespace. - string lastChildNamespaceName = null; - - // A list accumulating information about types within the last encountered child namespace. - // The list is similar to the sequence passed to this function. - List> typesInLastChildNamespace = null; - - // if there are any types in this namespace, - // they will be in the first several groups if their key length - // is equal to namespaceNameLength. - while (pair.Key.Length == namespaceNameLength) - { - nestedTypes.Add(pair); - - if (!enumerator.MoveNext()) - { - goto DoneWithSequence; - } - - pair = enumerator.Current; - } - - // Account for the dot following THIS namespace name. - if (!isGlobalNamespace) - { - namespaceNameLength++; - } - - do - { - pair = enumerator.Current; - - string childNamespaceName = ExtractSimpleNameOfChildNamespace(namespaceNameLength, pair.Key); - - int cmp = nameComparer.Compare(lastChildNamespaceName, childNamespaceName); - if (cmp == 0) - { - // We are still processing the same child namespace - typesInLastChildNamespace.Add(pair); - } - else - { - // This is a new child namespace - if (cmp > 0) - { - // The sort order is violated for child namespace names. Obfuscation is the likely reason for this. - Debug.Assert((object)lastChildNamespaceName != null); - possiblyHavePairsWithDuplicateKey = true; - } - - // Preserve information about previous child namespace. - if (typesInLastChildNamespace != null) - { - Debug.Assert(typesInLastChildNamespace.Count != 0); - nestedNamespaces.Add( - new KeyValuePair>>( - lastChildNamespaceName, typesInLastChildNamespace)); - } - - typesInLastChildNamespace = new List>(); - lastChildNamespaceName = childNamespaceName; - Debug.Assert((object)lastChildNamespaceName != null); - - typesInLastChildNamespace.Add(pair); - } - } - while (enumerator.MoveNext()); - - // Preserve information about last child namespace. - if (typesInLastChildNamespace != null) - { - Debug.Assert(typesInLastChildNamespace.Count != 0); - nestedNamespaces.Add( - new KeyValuePair>>( - lastChildNamespaceName, typesInLastChildNamespace)); - } - -DoneWithSequence: -/*empty statement*/ - ; - } - } // using - - types = nestedTypes; - - // Merge pairs with the same key - if (possiblyHavePairsWithDuplicateKey) - { - var names = new Dictionary(nestedNamespaces.Count, nameComparer); - - for (int i = nestedNamespaces.Count - 1; i >= 0; i--) - { - names[nestedNamespaces[i].Key] = i; - } - - if (names.Count != nestedNamespaces.Count) // nothing to merge otherwise - { - for (int i = 1; i < nestedNamespaces.Count; i++) - { - var pair = nestedNamespaces[i]; - int keyIndex = names[pair.Key]; - if (keyIndex != i) - { - Debug.Assert(keyIndex < i); - var primaryPair = nestedNamespaces[keyIndex]; - nestedNamespaces[keyIndex] = KeyValuePairUtil.Create(primaryPair.Key, primaryPair.Value.Concat(pair.Value)); - nestedNamespaces[i] = default(KeyValuePair>>); - } - } - - int removed = nestedNamespaces.RemoveAll(pair => (object)pair.Key == null); - Debug.Assert(removed > 0); - } - } - - namespaces = nestedNamespaces; - - Debug.Assert(types != null); - Debug.Assert(namespaces != null); - } - - /// - /// Extract a simple name of a top level child namespace from potentially qualified namespace name. - /// - /// - /// Parent namespace name length plus the dot. - /// - /// - /// Fully qualified namespace name. - /// - /// - /// Simple name of a top level child namespace, the left-most name following parent namespace name - /// in the fully qualified name. - /// - private static string ExtractSimpleNameOfChildNamespace( - int parentNamespaceNameLength, - string fullName) - { - int index = fullName.IndexOf('.', parentNamespaceNameLength); - - if (index < 0) - { - return fullName.Substring(parentNamespaceNameLength); - } - else - { - return fullName.Substring(parentNamespaceNameLength, index - parentNamespaceNameLength); - } - } - - /// - /// Determines whether given string can be used as a non-empty metadata identifier (a NUL-terminated UTF8 string). - /// - internal static bool IsValidMetadataIdentifier(string str) - { - return !string.IsNullOrEmpty(str) && str.IsValidUnicodeString() && str.IndexOf('\0') == -1; - } - - /// - /// True if the string doesn't contain incomplete surrogates. - /// - internal static bool IsValidUnicodeString(string str) - { - return str == null || str.IsValidUnicodeString(); - } - - internal static bool IsValidAssemblyOrModuleName(string name) - { - return GetAssemblyOrModuleNameErrorArgumentResourceName(name) == null; - } - - internal static void CheckAssemblyOrModuleName(string name, CommonMessageProvider messageProvider, int code, DiagnosticBag diagnostics) - { - string errorArgumentResourceId = GetAssemblyOrModuleNameErrorArgumentResourceName(name); - if (errorArgumentResourceId != null) - { - diagnostics.Add( - messageProvider.CreateDiagnostic(code, Location.None, - new CodeAnalysisResourcesLocalizableErrorArgument(errorArgumentResourceId))); - } - } - - internal static void CheckAssemblyOrModuleName(string name, CommonMessageProvider messageProvider, int code, ArrayBuilder builder) - { - string errorArgumentResourceId = GetAssemblyOrModuleNameErrorArgumentResourceName(name); - if (errorArgumentResourceId != null) - { - builder.Add( - messageProvider.CreateDiagnostic(code, Location.None, - new CodeAnalysisResourcesLocalizableErrorArgument(errorArgumentResourceId))); - } - } - - private static string GetAssemblyOrModuleNameErrorArgumentResourceName(string name) - { - if (name == null) - { - return nameof(CodeAnalysisResources.NameCannotBeNull); - } - - // Dev11 VB can produce assembly with no name (vbc /out:".dll" /target:library). - // We disallow it. PEVerify reports an error: Assembly has no name. - if (name.Length == 0) - { - return nameof(CodeAnalysisResources.NameCannotBeEmpty); - } - - // Dev11 VB can produce assembly that starts with whitespace (vbc /out:" a.dll" /target:library). - // We disallow it. PEVerify reports an error: Assembly name contains leading spaces. - if (char.IsWhiteSpace(name[0])) - { - return nameof(CodeAnalysisResources.NameCannotStartWithWhitespace); - } - - if (!IsValidMetadataFileName(name)) - { - return nameof(CodeAnalysisResources.NameContainsInvalidCharacter); - } - - return null; - } - - /// - /// Checks that the specified name is a valid metadata String and a file name. - /// The specification isn't entirely consistent and complete but it mentions: - /// - /// 22.19.2: "Name shall index a non-empty string in the String heap. It shall be in the format {filename}.{extension} (e.g., 'goo.dll', but not 'c:\utils\goo.dll')." - /// 22.30.2: "The format of Name is {file name}.{file extension} with no path or drive letter; on POSIX-compliant systems Name contains no colon, no forward-slash, no backslash." - /// As Microsoft specific constraint. - /// - /// A reasonable restriction seems to be a valid UTF8 non-empty string that doesn't contain '\0', '\', '/', ':' characters. - /// - internal static bool IsValidMetadataFileName(string name) - { - return FileNameUtilities.IsFileName(name) && IsValidMetadataIdentifier(name); - } - - /// - /// Determine if the given namespace and type names combine to produce the given fully qualified name. - /// - /// The namespace part of the split name. - /// The type name part of the split name. - /// The fully qualified name to compare with. - /// true if the combination of and equals the fully-qualified name given by - internal static bool SplitNameEqualsFullyQualifiedName(string namespaceName, string typeName, string fullyQualified) - { - // Look for "[namespaceName].[typeName]" exactly - return fullyQualified.Length == namespaceName.Length + typeName.Length + 1 && - fullyQualified[namespaceName.Length] == MetadataHelpers.DotDelimiter && - fullyQualified.StartsWith(namespaceName, StringComparison.Ordinal) && - fullyQualified.EndsWith(typeName, StringComparison.Ordinal); - } - - internal static bool IsValidPublicKey(ImmutableArray bytes) => CryptoBlobParser.IsValidPublicKey(bytes); - - /// - /// Given an input string changes it to be acceptable as a part of a type name. - /// - internal static string MangleForTypeNameIfNeeded(string moduleName) - { - var pooledStrBuilder = PooledStringBuilder.GetInstance(); - var s = pooledStrBuilder.Builder; - s.Append(moduleName); - s.Replace("Q", "QQ"); - s.Replace("_", "Q_"); - s.Replace('.', '_'); - - return pooledStrBuilder.ToStringAndFree(); - } - -#endif - } -} diff --git a/src/libraries/Common/src/Roslyn/MetadataTypeName.cs b/src/libraries/Common/src/Roslyn/MetadataTypeName.cs deleted file mode 100644 index cebe41748cb808..00000000000000 --- a/src/libraries/Common/src/Roslyn/MetadataTypeName.cs +++ /dev/null @@ -1,325 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable disable - -using System; -using System.Collections.Immutable; -using System.Diagnostics; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis -{ - /// - /// Helper structure to encapsulate/cache various information about metadata name of a type and - /// name resolution options. - /// Also, allows us to stop using strings in the APIs that accept only metadata names, - /// making usage of them less bug prone. - /// - internal partial struct MetadataTypeName - { - /// - /// Full metadata name of a type, includes namespace name for top level types. - /// - private string _fullName; - - /// - /// Namespace name for top level types. - /// - private string _namespaceName; - - /// - /// Name of the type without namespace prefix, but possibly with generic arity mangling present. - /// - private string _typeName; - - /// - /// Name of the type without namespace prefix and without generic arity mangling. - /// - private string _unmangledTypeName; - - /// - /// Arity of the type inferred based on the name mangling. It doesn't have to match the actual - /// arity of the type. - /// - private short _inferredArity; - - /// - /// While resolving the name, consider only types with this arity. - /// (-1) means allow any arity. - /// If forcedArity >= 0 and useCLSCompliantNameArityEncoding, lookup may - /// fail because forcedArity doesn't match the one encoded in the name. - /// - private short _forcedArity; - - /// - /// While resolving the name, consider only types following - /// CLS-compliant generic type names and arity encoding (ECMA-335, section 10.7.2). - /// I.e. arity is inferred from the name and matching type must have the same - /// emitted name and arity. - /// TODO: PERF: Encode this field elsewhere to save 4 bytes - /// - private bool _useCLSCompliantNameArityEncoding; - -#if false - - // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName - - /// - /// Individual parts of qualified namespace name. - /// - private ImmutableArray _namespaceSegments; - -#endif - - public static MetadataTypeName FromFullName(string fullName, bool useCLSCompliantNameArityEncoding = false, int forcedArity = -1) - { - Debug.Assert(fullName != null); - Debug.Assert(forcedArity >= -1 && forcedArity < short.MaxValue); - Debug.Assert(forcedArity == -1 || - !useCLSCompliantNameArityEncoding || - forcedArity == MetadataHelpers.InferTypeArityFromMetadataName(fullName), - "Conflicting metadata type name resolution options!"); - - MetadataTypeName name; - - name._fullName = fullName; - name._namespaceName = null; - name._typeName = null; - name._unmangledTypeName = null; - name._inferredArity = -1; - name._useCLSCompliantNameArityEncoding = useCLSCompliantNameArityEncoding; - name._forcedArity = (short)forcedArity; -#if false - name._namespaceSegments = default(ImmutableArray); -#endif - return name; - } - -#if false - - // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName - - public static MetadataTypeName FromNamespaceAndTypeName( - string namespaceName, string typeName, - bool useCLSCompliantNameArityEncoding = false, int forcedArity = -1 - ) - { - Debug.Assert(namespaceName != null); - Debug.Assert(typeName != null); - Debug.Assert(forcedArity >= -1 && forcedArity < short.MaxValue); - Debug.Assert(!typeName.Contains(MetadataHelpers.DotDelimiterString)); - Debug.Assert(forcedArity == -1 || - !useCLSCompliantNameArityEncoding || - forcedArity == MetadataHelpers.InferTypeArityFromMetadataName(typeName), - "Conflicting metadata type name resolution options!"); - - MetadataTypeName name; - - name._fullName = null; - name._namespaceName = namespaceName; - name._typeName = typeName; - name._unmangledTypeName = null; - name._inferredArity = -1; - name._useCLSCompliantNameArityEncoding = useCLSCompliantNameArityEncoding; - name._forcedArity = (short)forcedArity; - name._namespaceSegments = default(ImmutableArray); - - return name; - } - - public static MetadataTypeName FromTypeName(string typeName, bool useCLSCompliantNameArityEncoding = false, int forcedArity = -1) - { - Debug.Assert(typeName != null); - Debug.Assert(!typeName.Contains(MetadataHelpers.DotDelimiterString) || typeName.IndexOf(MetadataHelpers.MangledNameRegionStartChar) >= 0); - Debug.Assert(forcedArity >= -1 && forcedArity < short.MaxValue); - Debug.Assert(forcedArity == -1 || - !useCLSCompliantNameArityEncoding || - forcedArity == MetadataHelpers.InferTypeArityFromMetadataName(typeName), - "Conflicting metadata type name resolution options!"); - - MetadataTypeName name; - - name._fullName = typeName; - name._namespaceName = string.Empty; - name._typeName = typeName; - name._unmangledTypeName = null; - name._inferredArity = -1; - name._useCLSCompliantNameArityEncoding = useCLSCompliantNameArityEncoding; - name._forcedArity = (short)forcedArity; - name._namespaceSegments = ImmutableArray.Empty; - - return name; - } - - /// - /// Full metadata name of a type, includes namespace name for top level types. - /// - public string FullName - { - get - { - if (_fullName == null) - { - Debug.Assert(_namespaceName != null); - Debug.Assert(_typeName != null); - _fullName = MetadataHelpers.BuildQualifiedName(_namespaceName, _typeName); - } - - return _fullName; - } - } - - /// - /// Namespace name for top level types, empty string for nested types. - /// - public string NamespaceName - { - get - { - if (_namespaceName == null) - { - Debug.Assert(_fullName != null); - _typeName = MetadataHelpers.SplitQualifiedName(_fullName, out _namespaceName); - } - - return _namespaceName; - } - } - -#endif - - /// - /// Name of the type without namespace prefix, but possibly with generic arity mangling present. - /// - public string TypeName - { - get - { - if (_typeName == null) - { - Debug.Assert(_fullName != null); - _typeName = MetadataHelpers.SplitQualifiedName(_fullName, out _namespaceName); - } - - return _typeName; - } - } - - /// - /// Name of the type without namespace prefix and without generic arity mangling. - /// - public string UnmangledTypeName - { - get - { - if (_unmangledTypeName == null) - { - Debug.Assert(_inferredArity == -1); - _unmangledTypeName = MetadataHelpers.InferTypeArityAndUnmangleMetadataName(TypeName, out _inferredArity); - } - - return _unmangledTypeName; - } - } - -#if false - - // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName - - /// - /// Arity of the type inferred based on the name mangling. It doesn't have to match the actual - /// arity of the type. - /// - public int InferredArity - { - get - { - if (_inferredArity == -1) - { - Debug.Assert(_unmangledTypeName == null); - _unmangledTypeName = MetadataHelpers.InferTypeArityAndUnmangleMetadataName(TypeName, out _inferredArity); - } - - return _inferredArity; - } - } - - /// - /// Does name include arity mangling suffix. - /// - public bool IsMangled - { - get - { - return InferredArity > 0; - } - } - - /// - /// While resolving the name, consider only types following - /// CLS-compliant generic type names and arity encoding (ECMA-335, section 10.7.2). - /// I.e. arity is inferred from the name and matching type must have the same - /// emitted name and arity. - /// - public readonly bool UseCLSCompliantNameArityEncoding - { - get - { - return _useCLSCompliantNameArityEncoding; - } - } - - /// - /// While resolving the name, consider only types with this arity. - /// (-1) means allow any arity. - /// If ForcedArity >= 0 and UseCLSCompliantNameArityEncoding, lookup may - /// fail because ForcedArity doesn't match the one encoded in the name. - /// - public readonly int ForcedArity - { - get - { - return _forcedArity; - } - } - - /// - /// Individual parts of qualified namespace name. - /// - public ImmutableArray NamespaceSegments - { - get - { - if (_namespaceSegments.IsDefault) - { - _namespaceSegments = MetadataHelpers.SplitQualifiedName(NamespaceName); - } - - return _namespaceSegments; - } - } - - public readonly bool IsNull - { - get - { - return _typeName == null && _fullName == null; - } - } - - public override string ToString() - { - if (IsNull) - { - return "{Null}"; - } - else - { - return string.Format("{{{0},{1},{2},{3}}}", NamespaceName, TypeName, UseCLSCompliantNameArityEncoding.ToString(), _forcedArity.ToString()); - } - } - -#endif - } -} diff --git a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs index 9492e86c501f16..86c75926842cd0 100644 --- a/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/SyntaxValueProvider_ForAttributeWithMetadataName.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; @@ -57,13 +58,13 @@ internal GeneratorAttributeSyntaxContext( internal static partial class SyntaxValueProviderExtensions { - private static readonly char[] s_nestedTypeNameSeparators = new char[] { '+' }; - #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); @@ -91,12 +92,27 @@ public static IncrementalValuesProvider ForAttributeWithMetadataName( Func predicate, Func 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 collectedNodes = nodesWithAttributesMatchingSimpleName .Collect() .WithComparer(ImmutableArrayValueComparer.Instance) diff --git a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj index 3d8d8b553b7ac1..ff5a56cee85696 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj +++ b/src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -24,8 +24,6 @@ - - From 20d62a2c82fc6c62c5220b92de9387a8c0efd3fa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 28 Jun 2022 13:31:00 -0700 Subject: [PATCH 19/19] Simplify --- src/libraries/Common/src/Roslyn/Hash.cs | 363 +----------------------- 1 file changed, 2 insertions(+), 361 deletions(-) diff --git a/src/libraries/Common/src/Roslyn/Hash.cs b/src/libraries/Common/src/Roslyn/Hash.cs index 1b0dd45df91a59..028ac50017e0b9 100644 --- a/src/libraries/Common/src/Roslyn/Hash.cs +++ b/src/libraries/Common/src/Roslyn/Hash.cs @@ -18,366 +18,7 @@ internal static int Combine(int newKey, int currentKey) return unchecked((currentKey * (int)0xA5555529) + newKey); } -#if false - - // Disabled as these helpers are not currently needed in the polyfill of SyntaxValueProvider.ForAttributeWithMetadataName - - internal static int Combine(bool newKeyPart, int currentKey) - { - return Combine(currentKey, newKeyPart ? 1 : 0); - } - - /// - /// This is how VB Anonymous Types combine hash values for fields. - /// PERF: Do not use with enum types because that involves multiple - /// unnecessary boxing operations. Unfortunately, we can't constrain - /// T to "non-enum", so we'll use a more restrictive constraint. - /// - internal static int Combine(T newKeyPart, int currentKey) where T : class? - { - int hash = unchecked(currentKey * (int)0xA5555529); - - if (newKeyPart != null) - { - return unchecked(hash + newKeyPart.GetHashCode()); - } - - return hash; - } - - internal static int CombineValues(IEnumerable? values, int maxItemsToHash = int.MaxValue) - { - if (values == null) - { - return 0; - } - - var hashCode = 0; - var count = 0; - foreach (var value in values) - { - if (count++ >= maxItemsToHash) - { - break; - } - - // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). - if (value != null) - { - hashCode = Hash.Combine(value.GetHashCode(), hashCode); - } - } - - return hashCode; - } - - internal static int CombineValues(T[]? values, int maxItemsToHash = int.MaxValue) - { - if (values == null) - { - return 0; - } - - var maxSize = Math.Min(maxItemsToHash, values.Length); - var hashCode = 0; - - for (int i = 0; i < maxSize; i++) - { - T value = values[i]; - - // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). - if (value != null) - { - hashCode = Hash.Combine(value.GetHashCode(), hashCode); - } - } - - return hashCode; - } - - internal static int CombineValues(ImmutableArray values, int maxItemsToHash = int.MaxValue) - { - if (values.IsDefaultOrEmpty) - { - return 0; - } - - var hashCode = 0; - var count = 0; - foreach (var value in values) - { - if (count++ >= maxItemsToHash) - { - break; - } - - // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). - if (value != null) - { - hashCode = Hash.Combine(value.GetHashCode(), hashCode); - } - } - - return hashCode; - } - - internal static int CombineValues(IEnumerable? values, StringComparer stringComparer, int maxItemsToHash = int.MaxValue) - { - if (values == null) - { - return 0; - } - - var hashCode = 0; - var count = 0; - foreach (var value in values) - { - if (count++ >= maxItemsToHash) - { - break; - } - - if (value != null) - { - hashCode = Hash.Combine(stringComparer.GetHashCode(value), hashCode); - } - } - - return hashCode; - } - - /// - /// The offset bias value used in the FNV-1a algorithm - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - internal const int FnvOffsetBias = unchecked((int)2166136261); - - /// - /// The generative factor used in the FNV-1a algorithm - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - internal const int FnvPrime = 16777619; - - /// - /// Compute the FNV-1a hash of a sequence of bytes - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The sequence of bytes - /// The FNV-1a hash of - internal static int GetFNVHashCode(byte[] data) - { - int hashCode = Hash.FnvOffsetBias; - - for (int i = 0; i < data.Length; i++) - { - hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Compute the FNV-1a hash of a sequence of bytes and determines if the byte - /// sequence is valid ASCII and hence the hash code matches a char sequence - /// encoding the same text. - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The sequence of bytes that are likely to be ASCII text. - /// True if the sequence contains only characters in the ASCII range. - /// The FNV-1a hash of - internal static int GetFNVHashCode(ReadOnlySpan data, out bool isAscii) - { - int hashCode = Hash.FnvOffsetBias; - - byte asciiMask = 0; - - for (int i = 0; i < data.Length; i++) - { - byte b = data[i]; - asciiMask |= b; - hashCode = unchecked((hashCode ^ b) * Hash.FnvPrime); - } - - isAscii = (asciiMask & 0x80) == 0; - return hashCode; - } - - /// - /// Compute the FNV-1a hash of a sequence of bytes - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The sequence of bytes - /// The FNV-1a hash of - internal static int GetFNVHashCode(ImmutableArray data) - { - int hashCode = Hash.FnvOffsetBias; - - for (int i = 0; i < data.Length; i++) - { - hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Compute the hashcode of a sub-string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// Note: FNV-1a was developed and tuned for 8-bit sequences. We're using it here - /// for 16-bit Unicode chars on the understanding that the majority of chars will - /// fit into 8-bits and, therefore, the algorithm will retain its desirable traits - /// for generating hash codes. - /// - internal static int GetFNVHashCode(ReadOnlySpan data) - { - int hashCode = Hash.FnvOffsetBias; - - for (int i = 0; i < data.Length; i++) - { - hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Compute the hashcode of a sub-string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// Note: FNV-1a was developed and tuned for 8-bit sequences. We're using it here - /// for 16-bit Unicode chars on the understanding that the majority of chars will - /// fit into 8-bits and, therefore, the algorithm will retain its desirable traits - /// for generating hash codes. - /// - /// The input string - /// The start index of the first character to hash - /// The number of characters, beginning with to hash - /// The FNV-1a hash code of the substring beginning at and ending after characters. - internal static int GetFNVHashCode(string text, int start, int length) - => GetFNVHashCode(text.AsSpan(start, length)); - - internal static int GetCaseInsensitiveFNVHashCode(string text) - { - return GetCaseInsensitiveFNVHashCode(text.AsSpan(0, text.Length)); - } - - internal static int GetCaseInsensitiveFNVHashCode(ReadOnlySpan data) - { - int hashCode = Hash.FnvOffsetBias; - - for (int i = 0; i < data.Length; i++) - { - hashCode = unchecked((hashCode ^ CaseInsensitiveComparison.ToLower(data[i])) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Compute the hashcode of a sub-string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The input string - /// The start index of the first character to hash - /// The FNV-1a hash code of the substring beginning at and ending at the end of the string. - internal static int GetFNVHashCode(string text, int start) - { - return GetFNVHashCode(text, start, length: text.Length - start); - } - - /// - /// Compute the hashcode of a string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The input string - /// The FNV-1a hash code of - internal static int GetFNVHashCode(string text) - { - return CombineFNVHash(Hash.FnvOffsetBias, text); - } - - /// - /// Compute the hashcode of a string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The input string - /// The FNV-1a hash code of - internal static int GetFNVHashCode(System.Text.StringBuilder text) - { - int hashCode = Hash.FnvOffsetBias; - int end = text.Length; - - for (int i = 0; i < end; i++) - { - hashCode = unchecked((hashCode ^ text[i]) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Compute the hashcode of a sub string using FNV-1a - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The input string as a char array - /// The start index of the first character to hash - /// The number of characters, beginning with to hash - /// The FNV-1a hash code of the substring beginning at and ending after characters. - internal static int GetFNVHashCode(char[] text, int start, int length) - { - int hashCode = Hash.FnvOffsetBias; - int end = start + length; - - for (int i = start; i < end; i++) - { - hashCode = unchecked((hashCode ^ text[i]) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Compute the hashcode of a single character using the FNV-1a algorithm - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// Note: In general, this isn't any more useful than "char.GetHashCode". However, - /// it may be needed if you need to generate the same hash code as a string or - /// substring with just a single character. - /// - /// The character to hash - /// The FNV-1a hash code of the character. - internal static int GetFNVHashCode(char ch) - { - return Hash.CombineFNVHash(Hash.FnvOffsetBias, ch); - } - - /// - /// Combine a string with an existing FNV-1a hash code - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The accumulated hash code - /// The string to combine - /// The result of combining with using the FNV-1a algorithm - internal static int CombineFNVHash(int hashCode, string text) - { - foreach (char ch in text) - { - hashCode = unchecked((hashCode ^ ch) * Hash.FnvPrime); - } - - return hashCode; - } - - /// - /// Combine a char with an existing FNV-1a hash code - /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - /// - /// The accumulated hash code - /// The new character to combine - /// The result of combining with using the FNV-1a algorithm - internal static int CombineFNVHash(int hashCode, char ch) - { - return unchecked((hashCode ^ ch) * Hash.FnvPrime); - } - -#endif + // 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. } }