diff --git a/src/Compilers/Core/Portable/CaseInsensitiveComparison.cs b/src/Compilers/Core/Portable/CaseInsensitiveComparison.cs index 8c9e1aad107cc..937429ff7b6b1 100644 --- a/src/Compilers/Core/Portable/CaseInsensitiveComparison.cs +++ b/src/Compilers/Core/Portable/CaseInsensitiveComparison.cs @@ -117,6 +117,24 @@ public override int Compare(string? str1, string? str2) return str1.Length - str2.Length; } +#if !NET20 && !NETSTANDARD1_3 + public int Compare(ReadOnlySpan str1, ReadOnlySpan str2) + { + int len = Math.Min(str1.Length, str2.Length); + for (int i = 0; i < len; i++) + { + int ordDiff = CompareLowerUnicode(str1[i], str2[i]); + if (ordDiff != 0) + { + return ordDiff; + } + } + + // return the smaller string, or 0 if they are equal in length + return str1.Length - str2.Length; + } +#endif + private static bool AreEqualLowerUnicode(char c1, char c2) { return c1 == c2 || ToLower(c1) == ToLower(c2); @@ -150,6 +168,26 @@ public override bool Equals(string? str1, string? str2) return true; } +#if !NET20 && !NETSTANDARD1_3 + public bool Equals(ReadOnlySpan str1, ReadOnlySpan str2) + { + if (str1.Length != str2.Length) + { + return false; + } + + for (int i = 0; i < str1.Length; i++) + { + if (!AreEqualLowerUnicode(str1[i], str2[i])) + { + return false; + } + } + + return true; + } +#endif + public static bool EndsWith(string value, string possibleEnd) { if ((object)value == possibleEnd) @@ -255,6 +293,20 @@ public override int GetHashCode(string str) /// public static bool Equals(string left, string right) => s_comparer.Equals(left, right); +#if !NET20 && !NETSTANDARD1_3 + /// + /// Determines if two strings are equal according to Unicode rules for case-insensitive + /// identifier comparison (lower-case mapping). + /// + /// First identifier to compare + /// Second identifier to compare + /// true if the identifiers should be considered the same. + /// + /// These are also the rules used for VB identifier comparison. + /// + public static bool Equals(ReadOnlySpan left, ReadOnlySpan right) => s_comparer.Equals(left, right); +#endif + /// /// Determines if the string 'value' end with string 'possibleEnd'. /// @@ -283,6 +335,20 @@ public override int GetHashCode(string str) /// public static int Compare(string left, string right) => s_comparer.Compare(left, right); +#if !NET20 && !NETSTANDARD1_3 + /// + /// Compares two strings according to the Unicode rules for case-insensitive + /// identifier comparison (lower-case mapping). + /// + /// First identifier to compare + /// Second identifier to compare + /// -1 if < , 1 if > , 0 if they are equal. + /// + /// These are also the rules used for VB identifier comparison. + /// + public static int Compare(ReadOnlySpan left, ReadOnlySpan right) => s_comparer.Compare(left, right); +#endif + /// /// Gets a case-insensitive hash code for Unicode identifiers. /// diff --git a/src/Compilers/Core/Portable/InternalUtilities/Hash.cs b/src/Compilers/Core/Portable/InternalUtilities/Hash.cs index 6aac504068e7d..a1ea12a025329 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/Hash.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/Hash.cs @@ -254,17 +254,16 @@ internal static int GetFNVHashCode(string text, int start, int length) internal static int GetCaseInsensitiveFNVHashCode(string text) { - return GetCaseInsensitiveFNVHashCode(text, 0, text.Length); + return GetCaseInsensitiveFNVHashCode(text.AsSpan(0, text.Length)); } - internal static int GetCaseInsensitiveFNVHashCode(string text, int start, int length) + internal static int GetCaseInsensitiveFNVHashCode(ReadOnlySpan data) { int hashCode = Hash.FnvOffsetBias; - int end = start + length; - for (int i = start; i < end; i++) + for (int i = 0; i < data.Length; i++) { - hashCode = unchecked((hashCode ^ CaseInsensitiveComparison.ToLower(text[i])) * Hash.FnvPrime); + hashCode = unchecked((hashCode ^ CaseInsensitiveComparison.ToLower(data[i])) * Hash.FnvPrime); } return hashCode; diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 6deb4d5a8706e..5e2cd74d0c0ea 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,3 +1,5 @@ Microsoft.CodeAnalysis.ITypeSymbol.IsRecord.get -> bool Microsoft.CodeAnalysis.SymbolDisplayPartKind.RecordName = 31 -> Microsoft.CodeAnalysis.SymbolDisplayPartKind const Microsoft.CodeAnalysis.WellKnownDiagnosticTags.CompilationEnd = "CompilationEnd" -> string +static Microsoft.CodeAnalysis.CaseInsensitiveComparison.Compare(System.ReadOnlySpan left, System.ReadOnlySpan right) -> int +static Microsoft.CodeAnalysis.CaseInsensitiveComparison.Equals(System.ReadOnlySpan left, System.ReadOnlySpan right) -> bool diff --git a/src/Dependencies/PooledObjects/ArrayBuilder.cs b/src/Dependencies/PooledObjects/ArrayBuilder.cs index 1a9c48bd7760e..55e23aa07f005 100644 --- a/src/Dependencies/PooledObjects/ArrayBuilder.cs +++ b/src/Dependencies/PooledObjects/ArrayBuilder.cs @@ -71,6 +71,29 @@ public ImmutableArray ToImmutable() return _builder.ToImmutable(); } + /// + /// Realizes the array and clears the collection. + /// + public ImmutableArray ToImmutableAndClear() + { + ImmutableArray result; + if (Count == 0) + { + result = ImmutableArray.Empty; + } + else if (_builder.Capacity == Count) + { + result = _builder.MoveToImmutable(); + } + else + { + result = ToImmutable(); + Clear(); + } + + return result; + } + public int Count { get @@ -281,6 +304,8 @@ public ImmutableArray ToDowncastedImmutable() /// public ImmutableArray ToImmutableAndFree() { + // This is mostly the same as 'MoveToImmutable', but avoids delegating to that method since 'Free' contains + // fast paths to avoid caling 'Clear' in some cases. ImmutableArray result; if (Count == 0) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 5b3398aa882c4..5ac5b885504da 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - using ProjectToDocumentMap = Dictionary>; + using ProjectToDocumentMap = Dictionary>>; internal partial class FindReferencesSearchEngine { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs index a7a3c93f8ebfc..2c3ce2468a163 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs @@ -2,30 +2,27 @@ // 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.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.Internal.Log; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.FindSymbols { - using DocumentMap = MultiDictionary; - internal partial class FindReferencesSearchEngine { private async Task ProcessDocumentQueueAsync( Document document, - DocumentMap.ValueSet documentQueue) + HashSet<(ISymbol symbol, IReferenceFinder finder)> documentQueue) { await _progress.OnFindInDocumentStartedAsync(document).ConfigureAwait(false); - SemanticModel model = null; + SemanticModel? model = null; try { - model = await document.GetSemanticModelAsync(_cancellationToken).ConfigureAwait(false); + model = await document.GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false); // start cache for this semantic model FindReferenceCache.Start(model); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs index 39351976b08d5..a59c78e7eb398 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs @@ -2,8 +2,6 @@ // 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; @@ -18,9 +16,9 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - using DocumentMap = MultiDictionary; - using ProjectMap = MultiDictionary; - using ProjectToDocumentMap = Dictionary>; + using DocumentMap = Dictionary>; + using ProjectMap = Dictionary>; + using ProjectToDocumentMap = Dictionary>>; internal partial class FindReferencesSearchEngine { @@ -49,7 +47,7 @@ private async Task CreateProjectToDocumentMapAsync(Project foreach (var document in documents) { finalMap.GetOrAdd(document.Project, s_createDocumentMap) - .Add(document, (symbol, finder)); + .MultiAdd(document, (symbol, finder)); } } @@ -92,7 +90,7 @@ private async Task CreateProjectMapAsync(ConcurrentSet symb { if (scope == null || scope.Contains(project)) { - projectMap.Add(project, (symbol, finder)); + projectMap.MultiAdd(project, (symbol, finder)); } } } @@ -151,8 +149,7 @@ private async Task DetermineAllSymbolsCoreAsync( // Defer to the language to see if it wants to cascade here in some special way. var symbolProject = _solution.GetProject(searchSymbol.ContainingAssembly); - var service = symbolProject?.LanguageServices.GetService(); - if (service != null) + if (symbolProject?.LanguageServices.GetService() is { } service) { symbols = await service.DetermineCascadedSymbolsAsync( searchSymbol, symbolProject, _cancellationToken).ConfigureAwait(false); @@ -185,7 +182,7 @@ private void AddSymbolTasks( } } - private ImmutableHashSet GetProjectScope() + private ImmutableHashSet? GetProjectScope() { if (_documents == null) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs index adffc5c8d1d69..dfd6d543a6411 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs @@ -2,18 +2,15 @@ // 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.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.Internal.Log; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { - using DocumentMap = MultiDictionary; + using DocumentMap = Dictionary>; internal partial class FindReferencesSearchEngine { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 3bebad027df09..982397e804992 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -23,7 +23,7 @@ protected sealed override bool CanFind(TSymbol symbol) protected override Task> DetermineDocumentsToSearchAsync( TSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs index 9f5186f1d5423..c220523a2be38 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs @@ -21,7 +21,7 @@ protected AbstractMethodOrPropertyOrEventSymbolReferenceFinder() protected override async Task> DetermineCascadedSymbolsAsync( TSymbol symbol, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 90396f8fff5ec..60d72165b1ac1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -26,13 +26,13 @@ internal abstract partial class AbstractReferenceFinder : IReferenceFinder public const string ContainingMemberInfoPropertyName = "ContainingMemberInfo"; public abstract Task> DetermineCascadedSymbolsAsync( - ISymbol symbol, Solution solution, IImmutableSet projects, + ISymbol symbol, Solution solution, IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken); - public abstract Task> DetermineProjectsToSearchAsync(ISymbol symbol, Solution solution, IImmutableSet projects, CancellationToken cancellationToken); + public abstract Task> DetermineProjectsToSearchAsync(ISymbol symbol, Solution solution, IImmutableSet? projects, CancellationToken cancellationToken); public abstract Task> DetermineDocumentsToSearchAsync( - ISymbol symbol, Project project, IImmutableSet documents, FindReferencesSearchOptions options, CancellationToken cancellationToken); + ISymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken); public abstract ValueTask> FindReferencesInDocumentAsync( ISymbol symbol, Document document, SemanticModel semanticModel, FindReferencesSearchOptions options, CancellationToken cancellationToken); @@ -45,7 +45,7 @@ protected static bool TryGetNameWithoutAttributeSuffix( return name.TryGetWithoutAttributeSuffix(syntaxFacts.IsCaseSensitive, out result); } - protected static async Task> FindDocumentsAsync(Project project, IImmutableSet scope, Func> predicateAsync, CancellationToken cancellationToken) + protected static async Task> FindDocumentsAsync(Project project, IImmutableSet? scope, Func> predicateAsync, CancellationToken cancellationToken) { // special case for HR if (scope != null && scope.Count == 1) @@ -82,7 +82,7 @@ protected static async Task> FindDocumentsAsync(Project /// protected static Task> FindDocumentsAsync( Project project, - IImmutableSet documents, + IImmutableSet? documents, bool findInGlobalSuppressions, CancellationToken cancellationToken, params string[] values) @@ -110,7 +110,7 @@ protected static Task> FindDocumentsAsync( protected static Task> FindDocumentsAsync( Project project, - IImmutableSet documents, + IImmutableSet? documents, PredefinedType predefinedType, CancellationToken cancellationToken) { @@ -128,7 +128,7 @@ protected static Task> FindDocumentsAsync( protected static Task> FindDocumentsAsync( Project project, - IImmutableSet documents, + IImmutableSet? documents, PredefinedOperator op, CancellationToken cancellationToken) { @@ -471,7 +471,7 @@ private static async Task> FindReferencesThroughA return allAliasReferences.ToImmutableAndFree(); } - protected static Task> FindDocumentsWithPredicateAsync(Project project, IImmutableSet documents, Func predicate, CancellationToken cancellationToken) + protected static Task> FindDocumentsWithPredicateAsync(Project project, IImmutableSet? documents, Func predicate, CancellationToken cancellationToken) { return FindDocumentsAsync(project, documents, async (d, c) => { @@ -480,16 +480,16 @@ protected static Task> FindDocumentsWithPredicateAsync( }, cancellationToken); } - protected static Task> FindDocumentsWithForEachStatementsAsync(Project project, IImmutableSet documents, CancellationToken cancellationToken) + protected static Task> FindDocumentsWithForEachStatementsAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) => FindDocumentsWithPredicateAsync(project, documents, predicate: sti => sti.ContainsForEachStatement, cancellationToken); - protected static Task> FindDocumentsWithDeconstructionAsync(Project project, IImmutableSet documents, CancellationToken cancellationToken) + protected static Task> FindDocumentsWithDeconstructionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) => FindDocumentsWithPredicateAsync(project, documents, predicate: sti => sti.ContainsDeconstruction, cancellationToken); - protected static Task> FindDocumentsWithAwaitExpressionAsync(Project project, IImmutableSet documents, CancellationToken cancellationToken) + protected static Task> FindDocumentsWithAwaitExpressionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) => FindDocumentsWithPredicateAsync(project, documents, predicate: sti => sti.ContainsAwait, cancellationToken); - protected static Task> FindDocumentsWithImplicitObjectCreationExpressionAsync(Project project, IImmutableSet documents, CancellationToken cancellationToken) + protected static Task> FindDocumentsWithImplicitObjectCreationExpressionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) => FindDocumentsWithPredicateAsync(project, documents, predicate: sti => sti.ContainsImplicitObjectCreation, cancellationToken); /// @@ -904,14 +904,14 @@ internal abstract partial class AbstractReferenceFinder : AbstractRefer protected abstract bool CanFind(TSymbol symbol); protected abstract Task> DetermineDocumentsToSearchAsync( - TSymbol symbol, Project project, IImmutableSet documents, + TSymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken); protected abstract ValueTask> FindReferencesInDocumentAsync( TSymbol symbol, Document document, SemanticModel semanticModel, FindReferencesSearchOptions options, CancellationToken cancellationToken); - public override Task> DetermineProjectsToSearchAsync(ISymbol symbol, Solution solution, IImmutableSet projects, CancellationToken cancellationToken) + public override Task> DetermineProjectsToSearchAsync(ISymbol symbol, Solution solution, IImmutableSet? projects, CancellationToken cancellationToken) { return symbol is TSymbol typedSymbol && CanFind(typedSymbol) ? DetermineProjectsToSearchAsync(typedSymbol, solution, projects, cancellationToken) @@ -919,7 +919,7 @@ public override Task> DetermineProjectsToSearchAsync(ISy } public override Task> DetermineDocumentsToSearchAsync( - ISymbol symbol, Project project, IImmutableSet documents, + ISymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { return symbol is TSymbol typedSymbol && CanFind(typedSymbol) @@ -937,7 +937,7 @@ public override ValueTask> FindReferencesInDocume } public override Task> DetermineCascadedSymbolsAsync( - ISymbol symbol, Solution solution, IImmutableSet projects, + ISymbol symbol, Solution solution, IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { if (options.Cascade && @@ -953,14 +953,14 @@ symbol is TSymbol typedSymbol && } protected virtual Task> DetermineProjectsToSearchAsync( - TSymbol symbol, Solution solution, IImmutableSet projects, CancellationToken cancellationToken) + TSymbol symbol, Solution solution, IImmutableSet? projects, CancellationToken cancellationToken) { return DependentProjectsFinder.GetDependentProjectsAsync( solution, symbol, projects, cancellationToken); } protected virtual Task> DetermineCascadedSymbolsAsync( - TSymbol symbol, Solution solution, IImmutableSet projects, + TSymbol symbol, Solution solution, IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { return SpecializedTasks.EmptyImmutableArray(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index 230a6287561d9..e420bde4daa9c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -20,7 +20,7 @@ protected override bool CanFind(IMethodSymbol symbol) protected override Task> DetermineDocumentsToSearchAsync( IMethodSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 7081689c59b12..07e8bafc0afb6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -33,7 +33,7 @@ protected override bool CanFind(IMethodSymbol symbol) protected override async Task> DetermineDocumentsToSearchAsync( IMethodSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs index 74afe3d0ac627..2c7b642f205fc 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs @@ -17,7 +17,7 @@ protected override bool CanFind(IMethodSymbol symbol) protected override Task> DetermineCascadedSymbolsAsync( IMethodSymbol symbol, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -27,7 +27,7 @@ protected override Task> DetermineCascadedSymbolsAsync( protected override Task> DetermineDocumentsToSearchAsync( IMethodSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 9c753414dd96b..efb31e040f2e4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -17,7 +17,7 @@ protected override bool CanFind(IEventSymbol symbol) protected override async Task> DetermineCascadedSymbolsAsync( IEventSymbol symbol, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -39,7 +39,7 @@ protected override async Task> DetermineCascadedSymbolsA protected override Task> DetermineDocumentsToSearchAsync( IEventSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs index b801fa56ebd57..b4c1382e95970 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs @@ -17,7 +17,7 @@ protected override bool CanFind(IMethodSymbol symbol) protected override Task> DetermineCascadedSymbolsAsync( IMethodSymbol symbol, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -28,7 +28,7 @@ protected override Task> DetermineCascadedSymbolsAsync( protected override Task> DetermineDocumentsToSearchAsync( IMethodSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index 82e2c70d70a45..47076afcf406e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -17,7 +17,7 @@ protected override bool CanFind(IFieldSymbol symbol) protected override Task> DetermineCascadedSymbolsAsync( IFieldSymbol symbol, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -34,7 +34,7 @@ protected override Task> DetermineCascadedSymbolsAsync( protected override Task> DetermineDocumentsToSearchAsync( IFieldSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs index 07cc27c05abc8..dcc05a19cd02c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs @@ -25,7 +25,7 @@ internal interface IReferenceFinder /// Implementations of this method must be thread-safe. /// Task> DetermineCascadedSymbolsAsync( - ISymbol symbol, Solution solution, IImmutableSet projects, + ISymbol symbol, Solution solution, IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken); /// @@ -42,7 +42,7 @@ Task> DetermineCascadedSymbolsAsync( /// Implementations of this method must be thread-safe. /// Task> DetermineProjectsToSearchAsync( - ISymbol symbol, Solution solution, IImmutableSet projects, CancellationToken cancellationToken); + ISymbol symbol, Solution solution, IImmutableSet? projects, CancellationToken cancellationToken); /// /// Called by the find references search engine to determine which documents in the supplied @@ -57,7 +57,7 @@ Task> DetermineProjectsToSearchAsync( /// Implementations of this method must be thread-safe. /// Task> DetermineDocumentsToSearchAsync( - ISymbol symbol, Project project, IImmutableSet documents, + ISymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken); /// diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs index a392a388743c8..ac9e7b32fde03 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols.Finders internal class LinkedFileReferenceFinder : IReferenceFinder { public Task> DetermineCascadedSymbolsAsync( - ISymbol symbol, Solution solution, IImmutableSet projects, + ISymbol symbol, Solution solution, IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { return options.Cascade @@ -21,7 +21,7 @@ public Task> DetermineCascadedSymbolsAsync( } public Task> DetermineDocumentsToSearchAsync( - ISymbol symbol, Project project, IImmutableSet documents, + ISymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { return SpecializedTasks.EmptyImmutableArray(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs index 004801382a9bb..86cf5bd2d89a3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs @@ -17,7 +17,7 @@ protected override bool CanFind(ITypeParameterSymbol symbol) protected override Task> DetermineCascadedSymbolsAsync( ITypeParameterSymbol symbol, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -45,7 +45,7 @@ protected override Task> DetermineCascadedSymbolsAsync( protected override Task> DetermineDocumentsToSearchAsync( ITypeParameterSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index 2c5c79936ad3e..b56114f09d41b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -21,7 +21,7 @@ protected override bool CanFind(INamedTypeSymbol symbol) protected override Task> DetermineCascadedSymbolsAsync( INamedTypeSymbol symbol, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -49,7 +49,7 @@ private static void Add(ArrayBuilder result, ImmutableArray> DetermineDocumentsToSearchAsync( INamedTypeSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index 616370b4c300e..d12c73a8fb677 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -20,7 +20,7 @@ protected override bool CanFind(INamespaceSymbol symbol) protected override Task> DetermineDocumentsToSearchAsync( INamespaceSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index 11d14c1a0307f..86b07956abb0b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -18,7 +18,7 @@ protected override bool CanFind(IMethodSymbol symbol) protected override Task> DetermineDocumentsToSearchAsync( IMethodSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index e7ee06dd396a6..2122e4ffa18c5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -23,7 +23,7 @@ protected override bool CanFind(IMethodSymbol symbol) protected override async Task> DetermineCascadedSymbolsAsync( IMethodSymbol symbol, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -66,7 +66,7 @@ private static ImmutableArray GetOtherPartsOfPartial(IMethodSymbol symb protected override async Task> DetermineDocumentsToSearchAsync( IMethodSymbol methodSymbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index eb1ff62d846bd..673cebfe8a546 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -22,7 +22,7 @@ protected override bool CanFind(IParameterSymbol symbol) protected override Task> DetermineDocumentsToSearchAsync( IParameterSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -98,7 +98,7 @@ protected override ValueTask> FindReferencesInDoc protected override async Task> DetermineCascadedSymbolsAsync( IParameterSymbol parameter, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index a6a03d153f9d1..2831f558a81a0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -18,7 +18,7 @@ protected override bool CanFind(IMethodSymbol symbol) protected override async Task> DetermineCascadedSymbolsAsync( IMethodSymbol symbol, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -39,7 +39,7 @@ protected override async Task> DetermineCascadedSymbolsA } protected override async Task> DetermineDocumentsToSearchAsync( - IMethodSymbol symbol, Project project, IImmutableSet documents, + IMethodSymbol symbol, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // First, find any documents with the full name of the accessor (i.e. get_Goo). diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 325a133b2174b..572e03f670c27 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -25,7 +25,7 @@ protected override bool CanFind(IPropertySymbol symbol) protected override async Task> DetermineCascadedSymbolsAsync( IPropertySymbol symbol, Solution solution, - IImmutableSet projects, + IImmutableSet? projects, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -55,7 +55,7 @@ protected override async Task> DetermineCascadedSymbolsA protected override async Task> DetermineDocumentsToSearchAsync( IPropertySymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -117,13 +117,13 @@ protected override async ValueTask> FindReference } private static Task> FindDocumentWithElementAccessExpressionsAsync( - Project project, IImmutableSet documents, CancellationToken cancellationToken) + Project project, IImmutableSet? documents, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync(project, documents, info => info.ContainsElementAccessExpression, cancellationToken); } private static Task> FindDocumentWithIndexerMemberCrefAsync( - Project project, IImmutableSet documents, CancellationToken cancellationToken) + Project project, IImmutableSet? documents, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync(project, documents, info => info.ContainsIndexerMemberCref, cancellationToken); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs index 5f34a6fb7817b..124111318696f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs @@ -16,7 +16,7 @@ protected override bool CanFind(ITypeParameterSymbol symbol) protected override Task> DetermineDocumentsToSearchAsync( ITypeParameterSymbol symbol, Project project, - IImmutableSet documents, + IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs index fdb4af86ff47d..2afa021c19b73 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs @@ -21,8 +21,7 @@ internal partial class SymbolTreeInfo /// s are produced when initially creating our indices. /// They store Names of symbols and the index of their parent symbol. When we /// produce the final though we will then convert - /// these to s. Those nodes will not point to individual - /// strings, but will instead point at . + /// these to s. /// [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] private struct BuilderNode @@ -50,9 +49,9 @@ private string GetDebuggerDisplay() private struct Node { /// - /// Span in of the Name of this Node. + /// The Name of this Node. /// - public readonly TextSpan NameSpan; + public readonly string Name; /// /// Index in of the parent Node of this Node. @@ -61,9 +60,9 @@ private struct Node /// public readonly int ParentIndex; - public Node(TextSpan wordSpan, int parentIndex) + public Node(string name, int parentIndex) { - NameSpan = wordSpan; + Name = name; ParentIndex = parentIndex; } @@ -71,12 +70,12 @@ public Node(TextSpan wordSpan, int parentIndex) public void AssertEquivalentTo(Node node) { - Debug.Assert(node.NameSpan == this.NameSpan); + Debug.Assert(node.Name == this.Name); Debug.Assert(node.ParentIndex == this.ParentIndex); } private string GetDebuggerDisplay() - => NameSpan + ", " + ParentIndex; + => Name + ", " + ParentIndex; } private readonly struct ParameterTypeInfo diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs index f67a1cf9e557b..986de85b5ebda 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Text; @@ -13,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Utilities; using Roslyn.Utilities; @@ -23,13 +25,6 @@ internal partial class SymbolTreeInfo : IChecksummedObject { public Checksum Checksum { get; } - /// - /// To prevent lots of allocations, we concatenate all the names in all our - /// Nodes into one long string. Each Node then just points at the span in - /// this string with the portion they care about. - /// - private readonly string _concatenatedNames; - /// /// The list of nodes that represent symbols. The primary key into the sorting of this /// list is the name. They are sorted case-insensitively with the . @@ -101,27 +96,24 @@ public MultiDictionary.ValueSet GetExtensionMethodI private SymbolTreeInfo( Checksum checksum, - string concatenatedNames, ImmutableArray sortedNodes, Task spellCheckerTask, OrderPreservingMultiDictionary inheritanceMap, MultiDictionary receiverTypeNameToExtensionMethodMap) - : this(checksum, concatenatedNames, sortedNodes, spellCheckerTask, - CreateIndexBasedInheritanceMap(concatenatedNames, sortedNodes, inheritanceMap), + : this(checksum, sortedNodes, spellCheckerTask, + CreateIndexBasedInheritanceMap(sortedNodes, inheritanceMap), receiverTypeNameToExtensionMethodMap) { } private SymbolTreeInfo( Checksum checksum, - string concatenatedNames, ImmutableArray sortedNodes, Task spellCheckerTask, OrderPreservingMultiDictionary inheritanceMap, MultiDictionary? receiverTypeNameToExtensionMethodMap) { Checksum = checksum; - _concatenatedNames = concatenatedNames; _nodes = sortedNodes; _spellCheckerTask = spellCheckerTask; _inheritanceMap = inheritanceMap; @@ -131,10 +123,10 @@ private SymbolTreeInfo( public static SymbolTreeInfo CreateEmpty(Checksum checksum) { var unsortedNodes = ImmutableArray.Create(BuilderNode.RootNode); - SortNodes(unsortedNodes, out var concatenatedNames, out var sortedNodes); + SortNodes(unsortedNodes, out var sortedNodes); - return new SymbolTreeInfo(checksum, concatenatedNames, sortedNodes, - CreateSpellCheckerAsync(checksum, concatenatedNames, sortedNodes), + return new SymbolTreeInfo(checksum, sortedNodes, + CreateSpellCheckerAsync(checksum, sortedNodes), new OrderPreservingMultiDictionary(), new MultiDictionary()); } @@ -142,7 +134,7 @@ public static SymbolTreeInfo CreateEmpty(Checksum checksum) public SymbolTreeInfo WithChecksum(Checksum checksum) { return new SymbolTreeInfo( - checksum, _concatenatedNames, _nodes, _spellCheckerTask, _inheritanceMap, _receiverTypeNameToExtensionMethodMap); + checksum, _nodes, _spellCheckerTask, _inheritanceMap, _receiverTypeNameToExtensionMethodMap); } public Task> FindAsync( @@ -222,18 +214,18 @@ private async Task> FindAsync( CancellationToken cancellationToken) { var comparer = GetComparer(ignoreCase); - var results = ArrayBuilder.GetInstance(); IAssemblySymbol? assemblySymbol = null; + using var results = TemporaryArray.Empty; foreach (var node in FindNodeIndices(name, comparer)) { cancellationToken.ThrowIfCancellationRequested(); assemblySymbol ??= await lazyAssembly.GetValueAsync(cancellationToken).ConfigureAwait(false); - Bind(node, assemblySymbol.GlobalNamespace, results, cancellationToken); + Bind(node, assemblySymbol.GlobalNamespace, ref Unsafe.AsRef(in results), cancellationToken); } - return results.ToImmutableAndFree(); + return results.ToImmutableAndClear(); } private static StringSliceComparer GetComparer(bool ignoreCase) @@ -244,42 +236,42 @@ private static StringSliceComparer GetComparer(bool ignoreCase) } private IEnumerable FindNodeIndices(string name, StringSliceComparer comparer) - => FindNodeIndices(_concatenatedNames, _nodes, name, comparer); + => FindNodeIndices(_nodes, name, comparer); /// /// Gets all the node indices with matching names per the . /// private static IEnumerable FindNodeIndices( - string concatenatedNames, ImmutableArray nodes, + ImmutableArray nodes, string name, StringSliceComparer comparer) { // find any node that matches case-insensitively - var startingPosition = BinarySearch(concatenatedNames, nodes, name); - var nameSlice = new StringSlice(name); + var startingPosition = BinarySearch(nodes, name); + var nameSlice = name.AsMemory(); if (startingPosition != -1) { // yield if this matches by the actual given comparer - if (comparer.Equals(nameSlice, GetNameSlice(concatenatedNames, nodes, startingPosition))) + if (comparer.Equals(nameSlice, GetNameSlice(nodes, startingPosition))) { yield return startingPosition; } var position = startingPosition; - while (position > 0 && s_caseInsensitiveComparer.Equals(GetNameSlice(concatenatedNames, nodes, position - 1), nameSlice)) + while (position > 0 && s_caseInsensitiveComparer.Equals(GetNameSlice(nodes, position - 1), nameSlice)) { position--; - if (comparer.Equals(GetNameSlice(concatenatedNames, nodes, position), nameSlice)) + if (comparer.Equals(GetNameSlice(nodes, position), nameSlice)) { yield return position; } } position = startingPosition; - while (position + 1 < nodes.Length && s_caseInsensitiveComparer.Equals(GetNameSlice(concatenatedNames, nodes, position + 1), nameSlice)) + while (position + 1 < nodes.Length && s_caseInsensitiveComparer.Equals(GetNameSlice(nodes, position + 1), nameSlice)) { position++; - if (comparer.Equals(GetNameSlice(concatenatedNames, nodes, position), nameSlice)) + if (comparer.Equals(GetNameSlice(nodes, position), nameSlice)) { yield return position; } @@ -287,21 +279,21 @@ private static IEnumerable FindNodeIndices( } } - private static StringSlice GetNameSlice( - string concatenatedNames, ImmutableArray nodes, int nodeIndex) + private static ReadOnlyMemory GetNameSlice( + ImmutableArray nodes, int nodeIndex) { - return new StringSlice(concatenatedNames, nodes[nodeIndex].NameSpan); + return nodes[nodeIndex].Name.AsMemory(); } private int BinarySearch(string name) - => BinarySearch(_concatenatedNames, _nodes, name); + => BinarySearch(_nodes, name); /// /// Searches for a name in the ordered list that matches per the . /// - private static int BinarySearch(string concatenatedNames, ImmutableArray nodes, string name) + private static int BinarySearch(ImmutableArray nodes, string name) { - var nameSlice = new StringSlice(name); + var nameSlice = name.AsMemory(); var max = nodes.Length - 1; var min = 0; @@ -310,7 +302,7 @@ private static int BinarySearch(string concatenatedNames, ImmutableArray n var mid = min + ((max - min) >> 1); var comparison = s_caseInsensitiveComparer.Compare( - GetNameSlice(concatenatedNames, nodes, mid), nameSlice); + GetNameSlice(nodes, mid), nameSlice); if (comparison < 0) { min = mid + 1; @@ -349,26 +341,25 @@ private static int BinarySearch(string concatenatedNames, ImmutableArray n private static Task GetSpellCheckerAsync( Solution solution, Checksum checksum, string filePath, - string concatenatedNames, ImmutableArray sortedNodes) + ImmutableArray sortedNodes) { // Create a new task to attempt to load or create the spell checker for this // SymbolTreeInfo. This way the SymbolTreeInfo will be ready immediately // for non-fuzzy searches, and soon afterwards it will be able to perform // fuzzy searches as well. return Task.Run(() => LoadOrCreateSpellCheckerAsync( - solution, checksum, filePath, concatenatedNames, sortedNodes)); + solution, checksum, filePath, sortedNodes)); } private static Task CreateSpellCheckerAsync( - Checksum checksum, string concatenatedNames, ImmutableArray sortedNodes) + Checksum checksum, ImmutableArray sortedNodes) { return Task.FromResult(new SpellChecker( - checksum, sortedNodes.Select(n => new StringSlice(concatenatedNames, n.NameSpan)))); + checksum, sortedNodes.Select(n => n.Name.AsMemory()))); } private static void SortNodes( ImmutableArray unsortedNodes, - out string concatenatedNames, out ImmutableArray sortedNodes) { // Generate index numbers from 0 to Count-1 @@ -396,7 +387,6 @@ private static void SortNodes( var result = ArrayBuilder.GetInstance(unsortedNodes.Length); result.Count = unsortedNodes.Length; - var concatenatedNamesBuilder = new StringBuilder(); string? lastName = null; // Copy nodes into the result array in the appropriate order and fixing @@ -406,24 +396,20 @@ private static void SortNodes( var n = unsortedNodes[i]; var currentName = n.Name; - // Don't bother adding the exact same name the concatenated sequence - // over and over again. This can trivially happen because we'll run - // into the same names with different parents all through metadata - // and source symbols. - if (currentName != lastName) + // De-duplicate identical strings + if (currentName == lastName) { - concatenatedNamesBuilder.Append(currentName); + currentName = lastName; } result[ranking[i]] = new Node( - new TextSpan(concatenatedNamesBuilder.Length - currentName.Length, currentName.Length), + currentName, n.IsRoot ? n.ParentIndex : ranking[n.ParentIndex]); lastName = currentName; } sortedNodes = result.ToImmutableAndFree(); - concatenatedNames = concatenatedNamesBuilder.ToString(); } private static int CompareNodes( @@ -458,7 +444,7 @@ private static int CompareNodes( // returns all the symbols in the container corresponding to the node private void Bind( - int index, INamespaceOrTypeSymbol rootContainer, ArrayBuilder results, CancellationToken cancellationToken) + int index, INamespaceOrTypeSymbol rootContainer, ref TemporaryArray results, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -470,45 +456,33 @@ private void Bind( if (_nodes[node.ParentIndex].IsRoot) { - results.AddRange(rootContainer.GetMembers(GetName(node))); + var members = rootContainer.GetMembers(node.Name); + results.AddRange(members); } else { - using var _ = ArrayBuilder.GetInstance(out var containerSymbols); - - Bind(node.ParentIndex, rootContainer, containerSymbols, cancellationToken); - + using var containerSymbols = TemporaryArray.Empty; + Bind(node.ParentIndex, rootContainer, ref Unsafe.AsRef(in containerSymbols), cancellationToken); +#pragma warning disable RS0042 // Do not copy value foreach (var containerSymbol in containerSymbols) +#pragma warning restore RS0042 // Do not copy value { cancellationToken.ThrowIfCancellationRequested(); if (containerSymbol is INamespaceOrTypeSymbol nsOrType) { - results.AddRange(nsOrType.GetMembers(GetName(node))); + var members = nsOrType.GetMembers(node.Name); + results.AddRange(members); } } } } - private string GetName(Node node) - { - // TODO(cyrusn): We could consider caching the strings we create in the - // Nodes themselves. i.e. we could have a field in the node where the - // string could be stored once created. The reason i'm not doing that now - // is because, in general, we shouldn't actually be allocating that many - // strings here. This data structure is not in a hot path, and does not - // have a usage pattern where may strings are accessed in it. Rather, - // some features generally use it to just see if they can find a symbol - // corresponding to a single name. As such, caching doesn't seem valuable. - return _concatenatedNames.Substring(node.NameSpan.Start, node.NameSpan.Length); - } - #endregion internal void AssertEquivalentTo(SymbolTreeInfo other) { Debug.Assert(Checksum.Equals(other.Checksum)); - Debug.Assert(_concatenatedNames == other._concatenatedNames); Debug.Assert(_nodes.Length == other._nodes.Length); for (int i = 0, n = _nodes.Length; i < n; i++) @@ -538,18 +512,18 @@ private static SymbolTreeInfo CreateSymbolTreeInfo( OrderPreservingMultiDictionary inheritanceMap, MultiDictionary simpleMethods) { - SortNodes(unsortedNodes, out var concatenatedNames, out var sortedNodes); + SortNodes(unsortedNodes, out var sortedNodes); var createSpellCheckerTask = GetSpellCheckerAsync( - solution, checksum, filePath, concatenatedNames, sortedNodes); + solution, checksum, filePath, sortedNodes); return new SymbolTreeInfo( - checksum, concatenatedNames, + checksum, sortedNodes, createSpellCheckerTask, inheritanceMap, simpleMethods); } private static OrderPreservingMultiDictionary CreateIndexBasedInheritanceMap( - string concatenatedNames, ImmutableArray nodes, + ImmutableArray nodes, OrderPreservingMultiDictionary inheritanceMap) { // All names in metadata will be case sensitive. @@ -559,12 +533,12 @@ private static OrderPreservingMultiDictionary CreateIndexBasedInherita foreach (var kvp in inheritanceMap) { var baseName = kvp.Key; - var baseNameIndex = BinarySearch(concatenatedNames, nodes, baseName); + var baseNameIndex = BinarySearch(nodes, baseName); Debug.Assert(baseNameIndex >= 0); foreach (var derivedName in kvp.Value) { - foreach (var derivedNameIndex in FindNodeIndices(concatenatedNames, nodes, derivedName, comparer)) + foreach (var derivedNameIndex in FindNodeIndices(nodes, derivedName, comparer)) { result.Add(baseNameIndex, derivedNameIndex); } @@ -580,15 +554,17 @@ public ImmutableArray GetDerivedMetadataTypes( var baseTypeNameIndex = BinarySearch(baseTypeName); var derivedTypeIndices = _inheritanceMap[baseTypeNameIndex]; - var builder = ArrayBuilder.GetInstance(); - using var _ = ArrayBuilder.GetInstance(out var tempBuilder); + using var builder = TemporaryArray.Empty; + using var tempBuilder = TemporaryArray.Empty; foreach (var derivedTypeIndex in derivedTypeIndices) { tempBuilder.Clear(); - Bind(derivedTypeIndex, compilation.GlobalNamespace, tempBuilder, cancellationToken); + Bind(derivedTypeIndex, compilation.GlobalNamespace, ref Unsafe.AsRef(in tempBuilder), cancellationToken); +#pragma warning disable RS0042 // Do not copy value foreach (var symbol in tempBuilder) +#pragma warning restore RS0042 // Do not copy value { if (symbol is INamedTypeSymbol namedType) { @@ -597,7 +573,7 @@ public ImmutableArray GetDerivedMetadataTypes( } } - return builder.ToImmutableAndFree(); + return builder.ToImmutableAndClear(); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs index c028a29a28f65..330ecd1f9de37 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs @@ -199,7 +199,7 @@ private static Task TryLoadOrCreateMetadataSymbolTreeInfoAsync( loadOnly, createAsync: () => CreateMetadataSymbolTreeInfoAsync(solution, checksum, reference), keySuffix: "_Metadata_" + filePath, - tryReadObject: reader => TryReadSymbolTreeInfo(reader, checksum, (names, nodes) => GetSpellCheckerAsync(solution, checksum, filePath, names, nodes)), + tryReadObject: reader => TryReadSymbolTreeInfo(reader, checksum, nodes => GetSpellCheckerAsync(solution, checksum, filePath, nodes)), cancellationToken: cancellationToken); Contract.ThrowIfFalse(result != null || loadOnly == true, "Result can only be null if 'loadOnly: true' was passed."); return result; diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index a0cbc7acd4bf0..79500639cfca8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -5,6 +5,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -14,7 +15,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Utilities; using Roslyn.Utilities; @@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols internal partial class SymbolTreeInfo : IObjectWritable { private const string PrefixMetadataSymbolTreeInfo = ""; - private static readonly Checksum SerializationFormatChecksum = Checksum.Create("20"); + private static readonly Checksum SerializationFormatChecksum = Checksum.Create("21"); /// /// Loads the SpellChecker for a given assembly symbol (metadata or project). If the @@ -33,14 +33,13 @@ private static Task LoadOrCreateSpellCheckerAsync( Solution solution, Checksum checksum, string filePath, - string concatenatedNames, ImmutableArray sortedNodes) { var result = TryLoadOrCreateAsync( solution, checksum, loadOnly: false, - createAsync: () => CreateSpellCheckerAsync(checksum, concatenatedNames, sortedNodes), + createAsync: () => CreateSpellCheckerAsync(checksum, sortedNodes), keySuffix: "_SpellChecker_" + filePath, tryReadObject: SpellChecker.TryReadFrom, cancellationToken: CancellationToken.None); @@ -138,14 +137,15 @@ async Task CreateWithLoggingAsync() public void WriteTo(ObjectWriter writer) { - writer.WriteString(_concatenatedNames); - writer.WriteInt32(_nodes.Length); - foreach (var node in _nodes) + foreach (var group in GroupByName(_nodes.AsMemory())) { - writer.WriteInt32(node.NameSpan.Start); - writer.WriteInt32(node.NameSpan.Length); - writer.WriteInt32(node.ParentIndex); + writer.WriteString(group.Span[0].Name); + writer.WriteInt32(group.Length); + foreach (var item in group.Span) + { + writer.WriteInt32(item.ParentIndex); + } } writer.WriteInt32(_inheritanceMap.Keys.Count); @@ -181,26 +181,49 @@ public void WriteTo(ObjectWriter writer) } } } + + // sortedNodes is an array of Node instances which is often sorted by Node.Name by the caller. This method + // produces a sequence of spans within sortedNodes for Node instances that all have the same Name, allowing + // serialization to record the string once followed by the remaining properties for the nodes in the group. + static IEnumerable> GroupByName(ReadOnlyMemory sortedNodes) + { + if (sortedNodes.IsEmpty) + yield break; + + var startIndex = 0; + var currentName = sortedNodes.Span[0].Name; + for (var i = 1; i < sortedNodes.Length; i++) + { + var node = sortedNodes.Span[i]; + if (node.Name != currentName) + { + yield return sortedNodes[startIndex..i]; + startIndex = i; + } + } + + yield return sortedNodes[startIndex..sortedNodes.Length]; + } } private static SymbolTreeInfo TryReadSymbolTreeInfo( ObjectReader reader, Checksum checksum, - Func, Task> createSpellCheckerTask) + Func, Task> createSpellCheckerTask) { try { - var concatenatedNames = reader.ReadString(); - var nodeCount = reader.ReadInt32(); var nodes = ArrayBuilder.GetInstance(nodeCount); - for (var i = 0; i < nodeCount; i++) + while (nodes.Count < nodeCount) { - var start = reader.ReadInt32(); - var length = reader.ReadInt32(); - var parentIndex = reader.ReadInt32(); - - nodes.Add(new Node(new TextSpan(start, length), parentIndex)); + var name = reader.ReadString(); + var groupCount = reader.ReadInt32(); + for (var i = 0; i < groupCount; i++) + { + var parentIndex = reader.ReadInt32(); + nodes.Add(new Node(name, parentIndex)); + } } var inheritanceMap = new OrderPreservingMultiDictionary(); @@ -244,9 +267,9 @@ private static SymbolTreeInfo TryReadSymbolTreeInfo( } var nodeArray = nodes.ToImmutableAndFree(); - var spellCheckerTask = createSpellCheckerTask(concatenatedNames, nodeArray); + var spellCheckerTask = createSpellCheckerTask(nodeArray); return new SymbolTreeInfo( - checksum, concatenatedNames, nodeArray, spellCheckerTask, inheritanceMap, + checksum, nodeArray, spellCheckerTask, inheritanceMap, receiverTypeNameToExtensionMethodMap); } catch @@ -263,8 +286,7 @@ internal static SymbolTreeInfo ReadSymbolTreeInfo( ObjectReader reader, Checksum checksum) { return TryReadSymbolTreeInfo(reader, checksum, - (names, nodes) => Task.FromResult( - new SpellChecker(checksum, nodes.Select(n => new StringSlice(names, n.NameSpan))))); + nodes => Task.FromResult(new SpellChecker(checksum, nodes.Select(n => n.Name.AsMemory())))); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs index 09f36f0af4f08..841e2550ef127 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs @@ -40,7 +40,7 @@ public static Task GetInfoForSourceAssemblyAsync( loadOnly, createAsync: () => CreateSourceSymbolTreeInfoAsync(project, checksum, cancellationToken), keySuffix: "_Source_" + project.FilePath, - tryReadObject: reader => TryReadSymbolTreeInfo(reader, checksum, (names, nodes) => GetSpellCheckerAsync(project.Solution, checksum, project.FilePath, names, nodes)), + tryReadObject: reader => TryReadSymbolTreeInfo(reader, checksum, nodes => GetSpellCheckerAsync(project.Solution, checksum, project.FilePath, nodes)), cancellationToken: cancellationToken); Contract.ThrowIfNull(result, "Result should never be null as we passed 'loadOnly: false'."); return result; diff --git a/src/Workspaces/Core/Portable/Utilities/SpellChecker.cs b/src/Workspaces/Core/Portable/Utilities/SpellChecker.cs index 52aecc5d999c2..ce72fc1983d97 100644 --- a/src/Workspaces/Core/Portable/Utilities/SpellChecker.cs +++ b/src/Workspaces/Core/Portable/Utilities/SpellChecker.cs @@ -27,7 +27,7 @@ public SpellChecker(Checksum checksum, BKTree bKTree) _bkTree = bKTree; } - public SpellChecker(Checksum checksum, IEnumerable corpus) + public SpellChecker(Checksum checksum, IEnumerable> corpus) : this(checksum, BKTree.Create(corpus)) { } diff --git a/src/Workspaces/CoreTest/Collections/TemporaryArrayTests.cs b/src/Workspaces/CoreTest/Collections/TemporaryArrayTests.cs new file mode 100644 index 0000000000000..4c0c8d13f9710 --- /dev/null +++ b/src/Workspaces/CoreTest/Collections/TemporaryArrayTests.cs @@ -0,0 +1,191 @@ +// 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.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.Shared.Collections; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests.Collections +{ + public class TemporaryArrayTests + { + [Fact] + public void TestEmptyAndDefault() + { + Assert.Equal(0, TemporaryArray.Empty.Count); + Assert.Equal(0, default(TemporaryArray).Count); + Assert.Equal(0, new TemporaryArray().Count); + + Assert.Throws(() => TemporaryArray.Empty[-1]); + Assert.Throws(() => + { + using var array = TemporaryArray.Empty; + Unsafe.AsRef(in array)[-1] = 1; + }); + + Assert.Throws(() => TemporaryArray.Empty[0]); + Assert.Throws(() => + { + using var array = TemporaryArray.Empty; + Unsafe.AsRef(in array)[0] = 1; + }); + + Assert.False(TemporaryArray.Empty.GetEnumerator().MoveNext()); + } + + [Fact] + public void TestInlineElements() + { + using var array = TemporaryArray.Empty; + for (var i = 0; i < TemporaryArray.TestAccessor.InlineCapacity; i++) + { + Assert.Equal(i, array.Count); + AddAndCheck(); + Assert.False(TemporaryArray.TestAccessor.HasDynamicStorage(in array)); + Assert.Equal(i + 1, TemporaryArray.TestAccessor.InlineCount(in array)); + } + + // The next add forces a transition to dynamic storage + Assert.Equal(TemporaryArray.TestAccessor.InlineCapacity, array.Count); + Assert.False(TemporaryArray.TestAccessor.HasDynamicStorage(in array)); + AddAndCheck(); + Assert.True(TemporaryArray.TestAccessor.HasDynamicStorage(in array)); + Assert.Equal(0, TemporaryArray.TestAccessor.InlineCount(in array)); + + // The next goes directly to existing dynamic storage + Assert.Equal(TemporaryArray.TestAccessor.InlineCapacity + 1, array.Count); + AddAndCheck(); + Assert.True(TemporaryArray.TestAccessor.HasDynamicStorage(in array)); + Assert.Equal(0, TemporaryArray.TestAccessor.InlineCount(in array)); + + // Local functions + void AddAndCheck() + { + var i = array.Count; + Assert.Throws(() => array[i]); + Assert.Throws(() => Unsafe.AsRef(in array)[i] = 1); + + array.Add(i); + Assert.Equal(i + 1, array.Count); + Assert.Equal(i, array[i]); + Unsafe.AsRef(in array)[i] = i + 1; + Assert.Equal(i + 1, array[i]); + } + } + + [Fact] + public void CannotMutateEmpty() + { + Assert.Equal(0, TemporaryArray.Empty.Count); + TemporaryArray.Empty.Add(0); + Assert.Equal(0, TemporaryArray.Empty.Count); + } + + [Fact] + public void TestDisposeFreesBuilder() + { + var array = TemporaryArray.Empty; + array.AddRange(Enumerable.Range(0, TemporaryArray.TestAccessor.InlineCapacity + 1).ToImmutableArray()); + Assert.True(TemporaryArray.TestAccessor.HasDynamicStorage(in array)); + + array.Dispose(); + Assert.False(TemporaryArray.TestAccessor.HasDynamicStorage(in array)); + } + + [Theory] + [CombinatorialData] + public void TestAddRange([CombinatorialRange(0, 6)] int initialItems, [CombinatorialRange(0, 6)] int addedItems) + { + using var array = TemporaryArray.Empty; + for (var i = 0; i < initialItems; i++) + array.Add(i); + + Assert.Equal(initialItems, array.Count); + array.AddRange(Enumerable.Range(0, addedItems).ToImmutableArray()); + Assert.Equal(initialItems + addedItems, array.Count); + + if (array.Count > TemporaryArray.TestAccessor.InlineCapacity) + { + Assert.True(TemporaryArray.TestAccessor.HasDynamicStorage(in array)); + Assert.Equal(0, TemporaryArray.TestAccessor.InlineCount(in array)); + } + else + { + Assert.False(TemporaryArray.TestAccessor.HasDynamicStorage(in array)); + Assert.Equal(array.Count, TemporaryArray.TestAccessor.InlineCount(in array)); + } + + for (var i = 0; i < initialItems; i++) + Assert.Equal(i, array[i]); + + for (var i = 0; i < addedItems; i++) + Assert.Equal(i, array[initialItems + i]); + } + + [Theory] + [CombinatorialData] + public void TestClear([CombinatorialRange(0, 6)] int initialItems) + { + using var array = TemporaryArray.Empty; + for (var i = 0; i < initialItems; i++) + array.Add(i); + + Assert.Equal(initialItems, array.Count); + + array.Clear(); + Assert.Equal(0, array.Count); + + // TemporaryArray.Clear does not move from dynamic back to inline storage, so we condition this assertion + // on the count prior to calling Clear. + Assert.Equal( + initialItems > TemporaryArray.TestAccessor.InlineCapacity, + TemporaryArray.TestAccessor.HasDynamicStorage(in array)); + } + + [Theory] + [CombinatorialData] + public void TestEnumerator([CombinatorialRange(0, 6)] int initialItems) + { + using var array = TemporaryArray.Empty; + for (var i = 0; i < initialItems; i++) + array.Add(i); + + Assert.Equal(initialItems, array.Count); + + var enumerator = array.GetEnumerator(); + for (var i = 0; i < initialItems; i++) + { + Assert.True(enumerator.MoveNext()); + Assert.Equal(i, enumerator.Current); + } + + Assert.False(enumerator.MoveNext()); + } + + [Theory] + [CombinatorialData] + public void TestMoveToImmutable([CombinatorialRange(0, 6)] int initialItems) + { + using var array = TemporaryArray.Empty; + for (var i = 0; i < initialItems; i++) + array.Add(i); + + Assert.Equal(initialItems, array.Count); + + var immutableArray = array.ToImmutableAndClear(); + Assert.Equal(Enumerable.Range(0, initialItems), immutableArray); + + Assert.Equal(0, array.Count); + + // TemporaryArray.MoveToImmutable does not move from dynamic back to inline storage, so we condition this + // assertion on the count prior to calling Clear. + Assert.Equal( + initialItems > TemporaryArray.TestAccessor.InlineCapacity, + TemporaryArray.TestAccessor.HasDynamicStorage(in array)); + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TemporaryArray`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TemporaryArray`1.cs new file mode 100644 index 0000000000000..157e6f583f586 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TemporaryArray`1.cs @@ -0,0 +1,313 @@ +// 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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Shared.Collections +{ + /// + /// Provides temporary storage for a collection of elements. This type is optimized for handling of small + /// collections, particularly for cases where the collection will eventually be discarded or used to produce an + /// . + /// + /// + /// This type stores small collections on the stack, with the ability to transition to dynamic storage if/when + /// larger number of elements are added. + /// + /// The type of elements stored in the collection. + [NonCopyable] + [StructLayout(LayoutKind.Sequential)] + internal struct TemporaryArray : IDisposable + { + /// + /// The number of elements the temporary can store inline. Storing more than this many elements requires the + /// array transition to dynamic storage. + /// + private const int InlineCapacity = 4; + + /// + /// The first inline element. + /// + /// + /// This field is only used when is . In other words, this type + /// stores elements inline or stores them in , but does not use both approaches + /// at the same time. + /// + private T _item0; + + /// + /// The second inline element. + /// + /// + private T _item1; + + /// + /// The third inline element. + /// + /// + private T _item2; + + /// + /// The fourth inline element. + /// + /// + private T _item3; + + /// + /// The number of inline elements held in the array. This value is only used when is + /// . + /// + private int _count; + + /// + /// A builder used for dynamic storage of collections that may exceed the limit for inline elements. + /// + /// + /// This field is initialized to non- the first time the + /// needs to store more than four elements. From that point, is used instead of inline + /// elements, even if items are removed to make the result smaller than four elements. + /// + private ArrayBuilder? _builder; + + private TemporaryArray(in TemporaryArray array) + { + // Intentional copy used for creating an enumerator +#pragma warning disable RS0042 // Do not copy value + this = array; +#pragma warning restore RS0042 // Do not copy value + } + + public static TemporaryArray Empty => default; + + public readonly int Count => _builder?.Count ?? _count; + + public T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get + { + if (_builder is not null) + return _builder[index]; + + if ((uint)index >= _count) + ThrowIndexOutOfRangeException(); + + return index switch + { + 0 => _item0, + 1 => _item1, + 2 => _item2, + _ => _item3, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + if (_builder is not null) + { + _builder[index] = value; + return; + } + + if ((uint)index >= _count) + ThrowIndexOutOfRangeException(); + + _ = index switch + { + 0 => _item0 = value, + 1 => _item1 = value, + 2 => _item2 = value, + _ => _item3 = value, + }; + } + } + + public void Dispose() + { + // Return _builder to the pool if necessary. There is no need to release inline storage since the majority + // case for this type is stack-allocated storage and the GC is already able to reclaim objects from the + // stack after the last use of a reference to them. + Interlocked.Exchange(ref _builder, null)?.Free(); + } + + public void Add(T item) + { + if (_builder is not null) + { + _builder.Add(item); + } + else if (_count < InlineCapacity) + { + // Increase _count before assigning a value since the indexer has a bounds check. + _count++; + this[_count - 1] = item; + } + else + { + Debug.Assert(_count == InlineCapacity); + MoveInlineToBuilder(); + _builder.Add(item); + } + } + + public void AddRange(ImmutableArray items) + { + if (_builder is not null) + { + _builder.AddRange(items); + } + else if (_count + items.Length <= InlineCapacity) + { + foreach (var item in items) + { + // Increase _count before assigning values since the indexer has a bounds check. + _count++; + this[_count - 1] = item; + } + } + else + { + MoveInlineToBuilder(); + _builder.AddRange(items); + } + } + + public void Clear() + { + if (_builder is not null) + { + // Keep using a builder even if we now fit in inline storage to avoid churn in the object pools. + _builder.Clear(); + } + else + { + this = Empty; + } + } + + public readonly Enumerator GetEnumerator() + { + return new Enumerator(in this); + } + + /// + /// Create an with the elements currently held in the temporary array, and clear + /// the array. + /// + /// + public ImmutableArray ToImmutableAndClear() + { + if (_builder is not null) + { + return _builder.ToImmutableAndClear(); + } + else + { + var result = _count switch + { + 0 => ImmutableArray.Empty, + 1 => ImmutableArray.Create(_item0), + 2 => ImmutableArray.Create(_item0, _item1), + 3 => ImmutableArray.Create(_item0, _item1, _item2), + 4 => ImmutableArray.Create(_item0, _item1, _item2, _item3), + _ => throw ExceptionUtilities.Unreachable, + }; + + // Since _builder is null on this path, we can overwrite the whole structure to Empty to reset all + // inline elements to their default value and the _count to 0. + this = Empty; + + return result; + } + } + + /// + /// Transitions the current from inline storage to dynamic storage storage. An + /// instance is taken from the shared pool, and all elements currently in inline + /// storage are added to it. After this point, dynamic storage will be used instead of inline storage. + /// + [MemberNotNull(nameof(_builder))] + private void MoveInlineToBuilder() + { + Debug.Assert(_builder is null); + + var builder = ArrayBuilder.GetInstance(); + for (var i = 0; i < _count; i++) + { + builder.Add(this[i]); + +#if NETCOREAPP + if (RuntimeHelpers.IsReferenceOrContainsReferences()) +#endif + { + this[i] = default!; + } + } + + _count = 0; + _builder = builder; + } + + /// + /// Throws . + /// + /// + /// This helper improves the ability of the JIT to inline callers. + /// + private static void ThrowIndexOutOfRangeException() + => throw new IndexOutOfRangeException(); + + public struct Enumerator + { + private readonly TemporaryArray _array; + + private T _current; + private int _nextIndex; + + public Enumerator(in TemporaryArray array) + { + // Enumerate a copy of the original + _array = new TemporaryArray(in array); + _current = default!; + _nextIndex = 0; + } + + public T Current => _current; + + public bool MoveNext() + { + if (_nextIndex >= _array.Count) + { + return false; + } + else + { + _current = _array[_nextIndex]; + _nextIndex++; + return true; + } + } + } + + internal static class TestAccessor + { + public static int InlineCapacity => TemporaryArray.InlineCapacity; + + public static bool HasDynamicStorage(in TemporaryArray array) + => array._builder is not null; + + public static int InlineCount(in TemporaryArray array) + => array._count; + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index f062a5b2c8c4f..463c527bd47e4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -181,6 +181,7 @@ + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BKTree.Builder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BKTree.Builder.cs index 992951d3ddaa0..a25567f379bcd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BKTree.Builder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BKTree.Builder.cs @@ -94,7 +94,7 @@ private class Builder private readonly Edge[] _compactEdges; private readonly BuilderNode[] _builderNodes; - public Builder(IEnumerable values) + public Builder(IEnumerable> values) { // TODO(cyrusn): Properly handle unicode normalization here. var distinctValues = values.Where(v => v.Length > 0).Distinct(StringSliceComparer.OrdinalIgnoreCase).ToArray(); @@ -109,7 +109,7 @@ public Builder(IEnumerable values) var value = distinctValues[i]; _wordSpans[i] = new TextSpan(characterIndex, value.Length); - foreach (var ch in value) + foreach (var ch in value.Span) { _concatenatedLowerCaseWords[characterIndex] = CaseInsensitiveComparison.ToLower(ch); characterIndex++; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BKTree.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BKTree.cs index 4626aeb257b16..16c948e50a19d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BKTree.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BKTree.cs @@ -65,9 +65,9 @@ private BKTree(char[] concatenatedLowerCaseWords, ImmutableArray nodes, Im } public static BKTree Create(params string[] values) - => Create(values.Select(v => new StringSlice(v))); + => Create(values.Select(v => v.AsMemory())); - public static BKTree Create(IEnumerable values) + public static BKTree Create(IEnumerable> values) => new Builder(values).Create(); public IList Find(string value, int? threshold = null) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/StringSlice.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/StringSlice.cs index 54e2a91640b62..821e3d232e8a6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/StringSlice.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/StringSlice.cs @@ -2,194 +2,43 @@ // 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.Diagnostics; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Utilities { - internal struct StringSlice : IEquatable - { - private readonly string _underlyingString; - private readonly TextSpan _span; - - public StringSlice(string underlyingString, TextSpan span) - { - _underlyingString = underlyingString; - _span = span; - - Debug.Assert(span.Start >= 0); - Debug.Assert(span.End <= underlyingString.Length); - } - - public StringSlice(string value) : this(value, new TextSpan(0, value.Length)) - { - } - - public int Length => _span.Length; - - public char this[int index] => _underlyingString[_span.Start + index]; - - public Enumerator GetEnumerator() - => new(this); - - public override bool Equals(object obj) => Equals((StringSlice)obj); - - public bool Equals(StringSlice other) => EqualsOrdinal(other); - - internal bool EqualsOrdinal(StringSlice other) - { - if (this._span.Length != other._span.Length) - { - return false; - } - - var end = this._span.End; - for (int i = this._span.Start, j = other._span.Start; i < end; i++, j++) - { - if (this._underlyingString[i] != other._underlyingString[j]) - { - return false; - } - } - - return true; - } - - internal bool EqualsOrdinalIgnoreCase(StringSlice other) - { - if (this._span.Length != other._span.Length) - { - return false; - } - - var end = this._span.End; - for (int i = this._span.Start, j = other._span.Start; i < end; i++, j++) - { - var thisChar = this._underlyingString[i]; - var otherChar = other._underlyingString[j]; - - if (!EqualsOrdinalIgnoreCase(thisChar, otherChar)) - { - return false; - } - } - - return true; - } - - private static bool EqualsOrdinalIgnoreCase(char thisChar, char otherChar) - { - // Do a fast check first before converting to lowercase characters. - return - thisChar == otherChar || - CaseInsensitiveComparison.ToLower(thisChar) == CaseInsensitiveComparison.ToLower(otherChar); - } - - public override int GetHashCode() => GetHashCodeOrdinal(); - - internal int GetHashCodeOrdinal() - => Hash.GetFNVHashCode(this._underlyingString, this._span.Start, this._span.Length); - - internal int GetHashCodeOrdinalIgnoreCase() - => Hash.GetCaseInsensitiveFNVHashCode(this._underlyingString, this._span.Start, this._span.Length); - - internal int CompareToOrdinal(StringSlice other) - { - var thisEnd = this._span.End; - var otherEnd = other._span.End; - for (int i = this._span.Start, j = other._span.Start; - i < thisEnd && j < otherEnd; - i++, j++) - { - var diff = this._underlyingString[i] - other._underlyingString[j]; - if (diff != 0) - { - return diff; - } - } - - // Choose the one that is shorter if their prefixes match so far. - return this.Length - other.Length; - } - - internal int CompareToOrdinalIgnoreCase(StringSlice other) - { - var thisEnd = this._span.End; - var otherEnd = other._span.End; - for (int i = this._span.Start, j = other._span.Start; - i < thisEnd && j < otherEnd; - i++, j++) - { - var diff = - CaseInsensitiveComparison.ToLower(this._underlyingString[i]) - - CaseInsensitiveComparison.ToLower(other._underlyingString[j]); - if (diff != 0) - { - return diff; - } - } - - // Choose the one that is shorter if their prefixes match so far. - return this.Length - other.Length; - } - - public struct Enumerator - { - private readonly StringSlice _stringSlice; - private int index; - - public Enumerator(StringSlice stringSlice) - { - _stringSlice = stringSlice; - index = -1; - } - - public bool MoveNext() - { - index++; - return index < _stringSlice.Length; - } - - public char Current => _stringSlice[index]; - } - } - - internal abstract class StringSliceComparer : IComparer, IEqualityComparer + internal abstract class StringSliceComparer : IComparer>, IEqualityComparer> { public static readonly StringSliceComparer Ordinal = new OrdinalComparer(); public static readonly StringSliceComparer OrdinalIgnoreCase = new OrdinalIgnoreCaseComparer(); private class OrdinalComparer : StringSliceComparer { - public override int Compare(StringSlice x, StringSlice y) - => x.CompareToOrdinal(y); + public override int Compare(ReadOnlyMemory x, ReadOnlyMemory y) + => x.Span.CompareTo(y.Span, StringComparison.Ordinal); - public override bool Equals(StringSlice x, StringSlice y) - => x.EqualsOrdinal(y); + public override bool Equals(ReadOnlyMemory x, ReadOnlyMemory y) + => x.Span.Equals(y.Span, StringComparison.Ordinal); - public override int GetHashCode(StringSlice obj) - => obj.GetHashCodeOrdinal(); + public override int GetHashCode(ReadOnlyMemory obj) + => Hash.GetFNVHashCode(obj.Span); } private class OrdinalIgnoreCaseComparer : StringSliceComparer { - public override int Compare(StringSlice x, StringSlice y) - => x.CompareToOrdinalIgnoreCase(y); + public override int Compare(ReadOnlyMemory x, ReadOnlyMemory y) + => CaseInsensitiveComparison.Compare(x.Span, y.Span); - public override bool Equals(StringSlice x, StringSlice y) - => x.EqualsOrdinalIgnoreCase(y); + public override bool Equals(ReadOnlyMemory x, ReadOnlyMemory y) + => CaseInsensitiveComparison.Equals(x.Span, y.Span); - public override int GetHashCode(StringSlice obj) - => obj.GetHashCodeOrdinalIgnoreCase(); + public override int GetHashCode(ReadOnlyMemory obj) + => Hash.GetCaseInsensitiveFNVHashCode(obj.Span); } - public abstract int Compare(StringSlice x, StringSlice y); - public abstract bool Equals(StringSlice x, StringSlice y); - public abstract int GetHashCode(StringSlice obj); + public abstract int Compare(ReadOnlyMemory x, ReadOnlyMemory y); + public abstract bool Equals(ReadOnlyMemory x, ReadOnlyMemory y); + public abstract int GetHashCode(ReadOnlyMemory obj); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefWorkspaceServices.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefWorkspaceServices.cs index 2af5c6ecbca24..151852febb4d2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefWorkspaceServices.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Workspace/Mef/MefWorkspaceServices.cs @@ -160,7 +160,7 @@ public override HostLanguageServices GetLanguageServices(string languageName) var currentServicesMap = _languageServicesMap; if (!currentServicesMap.TryGetValue(languageName, out var languageServices)) { - languageServices = ImmutableInterlocked.GetOrAdd(ref _languageServicesMap, languageName, _ => new MefLanguageServices(this, languageName)); + languageServices = ImmutableInterlocked.GetOrAdd(ref _languageServicesMap, languageName, static (languageName, self) => new MefLanguageServices(self, languageName), this); } if (languageServices.HasServices)