Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch pattern matching to use TemporaryArray #51035

Merged
merged 23 commits into from
May 7, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions src/EditorFeatures/Test/Utilities/PatternMatcherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
using System.Globalization;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.PatternMatching;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
Expand Down Expand Up @@ -418,23 +420,28 @@ public void TryMatchSingleWordPattern_CultureAwareSingleWordPreferCaseSensitiveE
}
}

private static ImmutableArray<string> PartListToSubstrings(string identifier, ArrayBuilder<TextSpan> parts)
private static ImmutableArray<string> PartListToSubstrings(string identifier, ref TemporaryArray<TextSpan> parts)
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
{
using var resultDisposer = ArrayBuilder<string>.GetInstance(out var result);
using var result = TemporaryArray<string>.Empty;
foreach (var span in parts)
{
result.Add(identifier.Substring(span.Start, span.Length));
}

parts.Free();
return result.ToImmutable();
return result.ToImmutableAndClear();
}

private static ImmutableArray<string> BreakIntoCharacterParts(string identifier)
=> PartListToSubstrings(identifier, StringBreaker.GetCharacterParts(identifier));
{
using var parts = TemporaryArray<TextSpan>.Empty;
StringBreaker.AddCharacterParts(identifier, ref parts.AsRef());
return PartListToSubstrings(identifier, ref parts.AsRef());
}

private static ImmutableArray<string> BreakIntoWordParts(string identifier)
=> PartListToSubstrings(identifier, StringBreaker.GetWordParts(identifier));
{
using var parts = TemporaryArray<TextSpan>.Empty;
StringBreaker.AddWordParts(identifier, ref parts.AsRef());
return PartListToSubstrings(identifier, ref parts.AsRef());
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
}

private static PatternMatch? TestNonFuzzyMatchCore(string candidate, string pattern)
{
Expand All @@ -459,8 +466,8 @@ private static IEnumerable<PatternMatch> TryMatchMultiWordPattern(string candida
{
MarkupTestFile.GetSpans(candidate, out candidate, out ImmutableArray<TextSpan> expectedSpans);

using var matchesDisposer = ArrayBuilder<PatternMatch>.GetInstance(out var matches);
PatternMatcher.CreatePatternMatcher(pattern, includeMatchedSpans: true).AddMatches(candidate, matches);
using var matches = TemporaryArray<PatternMatch>.Empty;
PatternMatcher.CreatePatternMatcher(pattern, includeMatchedSpans: true).AddMatches(candidate, ref matches.AsRef());

if (matches.Count == 0)
{
Expand All @@ -469,9 +476,13 @@ private static IEnumerable<PatternMatch> TryMatchMultiWordPattern(string candida
}
else
{
var actualSpans = matches.SelectMany(m => m.MatchedSpans).OrderBy(s => s.Start).ToList();
var flattened = new List<TextSpan>();
foreach (var match in matches)
flattened.AddRange(match.MatchedSpans);

var actualSpans = flattened.OrderBy(s => s.Start).ToList();
Assert.Equal(expectedSpans, actualSpans);
return matches.ToImmutable();
return matches.ToImmutableAndClear();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.Immutable;
using Humanizer;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
Expand All @@ -21,10 +22,10 @@ internal class NameGenerator
internal static ImmutableArray<Words> GetBaseNames(ITypeSymbol type, bool pluralize)
{
var baseName = TryRemoveInterfacePrefix(type);
var parts = StringBreaker.GetWordParts(baseName);
var result = GetInterleavedPatterns(parts, baseName, pluralize);
using var parts = TemporaryArray<TextSpan>.Empty;
StringBreaker.AddWordParts(baseName, ref parts.AsRef());
var result = GetInterleavedPatterns(ref parts.AsRef(), baseName, pluralize);

parts.Free();
return result;
}

Expand All @@ -38,41 +39,42 @@ internal static ImmutableArray<Words> GetBaseNames(IAliasSymbol alias)
name = name.Substring(1);
}

var breaks = StringBreaker.GetWordParts(name);
var result = GetInterleavedPatterns(breaks, name, pluralize: false);
breaks.Free();
using var breaks = TemporaryArray<TextSpan>.Empty;
StringBreaker.AddWordParts(name, ref breaks.AsRef());
var result = GetInterleavedPatterns(ref breaks.AsRef(), name, pluralize: false);
return result;
}

private static ImmutableArray<Words> GetInterleavedPatterns(ArrayBuilder<TextSpan> breaks, string baseName, bool pluralize)
private static ImmutableArray<Words> GetInterleavedPatterns(
ref TemporaryArray<TextSpan> breaks, string baseName, bool pluralize)
{
var result = ArrayBuilder<Words>.GetInstance();
using var result = TemporaryArray<Words>.Empty;
var breakCount = breaks.Count;
result.Add(GetWords(0, breakCount, breaks, baseName, pluralize));
result.Add(GetWords(0, breakCount, ref breaks, baseName, pluralize));

for (var length = breakCount - 1; length > 0; length--)
{
// going forward
result.Add(GetLongestForwardSubsequence(length, breaks, baseName, pluralize));
result.Add(GetLongestForwardSubsequence(length, ref breaks, baseName, pluralize));

// going backward
result.Add(GetLongestBackwardSubsequence(length, breaks, baseName, pluralize));
result.Add(GetLongestBackwardSubsequence(length, ref breaks, baseName, pluralize));
}

return result.ToImmutable();
return result.ToImmutableAndClear();
}

private static Words GetLongestBackwardSubsequence(int length, ArrayBuilder<TextSpan> breaks, string baseName, bool pluralize)
private static Words GetLongestBackwardSubsequence(int length, ref TemporaryArray<TextSpan> breaks, string baseName, bool pluralize)
{
var breakCount = breaks.Count;
var start = breakCount - length;
return GetWords(start, breakCount, breaks, baseName, pluralize);
return GetWords(start, breakCount, ref breaks, baseName, pluralize);
}

private static Words GetLongestForwardSubsequence(int length, ArrayBuilder<TextSpan> breaks, string baseName, bool pluralize)
=> GetWords(0, length, breaks, baseName, pluralize);
private static Words GetLongestForwardSubsequence(int length, ref TemporaryArray<TextSpan> breaks, string baseName, bool pluralize)
=> GetWords(0, length, ref breaks, baseName, pluralize);

private static Words GetWords(int start, int end, ArrayBuilder<TextSpan> breaks, string baseName, bool pluralize)
private static Words GetWords(int start, int end, ref TemporaryArray<TextSpan> breaks, string baseName, bool pluralize)
{
var result = ArrayBuilder<string>.GetInstance();
// Add all the words but the last one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.PatternMatching;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
Expand Down Expand Up @@ -65,14 +66,10 @@ private static async Task<ImmutableArray<INavigateToSearchResult>> FindSearchRes
using (nameMatcher)
using (containerMatcherOpt)
{
using var _1 = ArrayBuilder<PatternMatch>.GetInstance(out var nameMatches);
using var _2 = ArrayBuilder<PatternMatch>.GetInstance(out var containerMatches);

var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds);

var searchResults = await ComputeSearchResultsAsync(
project, priorityDocuments, searchDocument, nameMatcher, containerMatcherOpt,
declaredSymbolInfoKindsSet, nameMatches, containerMatches, cancellationToken).ConfigureAwait(false);
project, priorityDocuments, searchDocument, nameMatcher, containerMatcherOpt, declaredSymbolInfoKindsSet, cancellationToken).ConfigureAwait(false);

return ImmutableArray<INavigateToSearchResult>.CastUp(searchResults);
}
Expand All @@ -81,9 +78,7 @@ private static async Task<ImmutableArray<INavigateToSearchResult>> FindSearchRes
private static async Task<ImmutableArray<SearchResult>> ComputeSearchResultsAsync(
Project project, ImmutableArray<Document> priorityDocuments, Document searchDocument,
PatternMatcher nameMatcher, PatternMatcher containerMatcherOpt,
DeclaredSymbolInfoKindSet kinds,
ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches,
CancellationToken cancellationToken)
DeclaredSymbolInfoKindSet kinds, CancellationToken cancellationToken)
{
using var _ = ArrayBuilder<SearchResult>.GetInstance(out var result);

Expand Down Expand Up @@ -113,9 +108,7 @@ private static async Task<ImmutableArray<SearchResult>> ComputeSearchResultsAsyn
await AddResultIfMatchAsync(
document, declaredSymbolInfo,
nameMatcher, containerMatcherOpt,
kinds,
nameMatches, containerMatches,
result, cancellationToken).ConfigureAwait(false);
kinds, result, cancellationToken).ConfigureAwait(false);
}
}

Expand All @@ -125,29 +118,30 @@ await AddResultIfMatchAsync(
private static async Task AddResultIfMatchAsync(
Document document, DeclaredSymbolInfo declaredSymbolInfo,
PatternMatcher nameMatcher, PatternMatcher containerMatcherOpt,
DeclaredSymbolInfoKindSet kinds,
ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches,
ArrayBuilder<SearchResult> result, CancellationToken cancellationToken)
DeclaredSymbolInfoKindSet kinds, ArrayBuilder<SearchResult> result, CancellationToken cancellationToken)
{
nameMatches.Clear();
containerMatches.Clear();
using var nameMatches = TemporaryArray<PatternMatch>.Empty;
using var containerMatches = TemporaryArray<PatternMatch>.Empty;

cancellationToken.ThrowIfCancellationRequested();
if (kinds.Contains(declaredSymbolInfo.Kind) &&
nameMatcher.AddMatches(declaredSymbolInfo.Name, nameMatches) &&
containerMatcherOpt?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, containerMatches) != false)
nameMatcher.AddMatches(declaredSymbolInfo.Name, ref nameMatches.AsRef()) &&
containerMatcherOpt?.AddMatches(declaredSymbolInfo.FullyQualifiedContainerName, ref containerMatches.AsRef()) != false)
{
result.Add(await ConvertResultAsync(
declaredSymbolInfo, document, nameMatches, containerMatches, cancellationToken).ConfigureAwait(false));
var additionalMatchingProjects = await GetAdditionalProjectsWithMatchAsync(
document, declaredSymbolInfo, cancellationToken).ConfigureAwait(false);

result.Add(ConvertResult(
declaredSymbolInfo, document, ref nameMatches.AsRef(), ref containerMatches.AsRef(), additionalMatchingProjects));
}
}

private static async Task<SearchResult> ConvertResultAsync(
private static SearchResult ConvertResult(
DeclaredSymbolInfo declaredSymbolInfo, Document document,
ArrayBuilder<PatternMatch> nameMatches, ArrayBuilder<PatternMatch> containerMatches,
CancellationToken cancellationToken)
ref TemporaryArray<PatternMatch> nameMatches, ref TemporaryArray<PatternMatch> containerMatches,
ImmutableArray<Project> additionalMatchingProjects)
{
var matchKind = GetNavigateToMatchKind(nameMatches);
var matchKind = GetNavigateToMatchKind(ref nameMatches);

// A match is considered to be case sensitive if all its constituent pattern matches are
// case sensitive.
Expand All @@ -162,8 +156,6 @@ private static async Task<SearchResult> ConvertResultAsync(
// See if we have a match in a linked file. If so, see if we have the same match in other projects that
// this file is linked in. If so, include the full set of projects the match is in so we can display that
// well in the UI.
var additionalMatchingProjects = await GetAdditionalProjectsWithMatchAsync(
document, declaredSymbolInfo, cancellationToken).ConfigureAwait(false);
return new SearchResult(
document, declaredSymbolInfo, kind, matchKind, isCaseSensitive, navigableItem,
matchedSpans.ToImmutable(), additionalMatchingProjects);
Expand Down Expand Up @@ -229,7 +221,7 @@ private static string GetItemKind(DeclaredSymbolInfo declaredSymbolInfo)
}
}

private static NavigateToMatchKind GetNavigateToMatchKind(ArrayBuilder<PatternMatch> nameMatches)
private static NavigateToMatchKind GetNavigateToMatchKind(ref TemporaryArray<PatternMatch> nameMatches)
{
// work backwards through the match kinds. That way our result is as bad as our worst match part. For
// example, say the user searches for `Console.Write` and we find `Console.Write` (exact, exact), and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.NamingStyles;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;

Expand All @@ -28,8 +29,9 @@ public static IdentifierNameParts CreateIdentifierNameParts(ISymbol symbol, Immu
{
var baseName = RemovePrefixesAndSuffixes(symbol, rules, symbol.Name);

var parts = StringBreaker.GetWordParts(baseName);
var words = CreateWords(parts, baseName);
using var parts = TemporaryArray<TextSpan>.Empty;
StringBreaker.AddWordParts(baseName, ref parts.AsRef());
var words = CreateWords(ref parts.AsRef(), baseName);

return new IdentifierNameParts(baseName, words);
}
Expand Down Expand Up @@ -71,15 +73,13 @@ private static string RemovePrefixesAndSuffixes(ISymbol symbol, ImmutableArray<N
return RemovePrefixesAndSuffixes(symbol, rules, newBaseName);
}

private static ImmutableArray<string> CreateWords(ArrayBuilder<TextSpan> parts, string name)
private static ImmutableArray<string> CreateWords(ref TemporaryArray<TextSpan> parts, string name)
{
using var resultDisposer = ArrayBuilder<string>.GetInstance(parts.Count, out var result);
using var words = TemporaryArray<string>.Empty;
foreach (var part in parts)
{
result.Add(name.Substring(part.Start, part.Length));
}
words.Add(name.Substring(part.Start, part.Length));

return result.ToImmutable();
return words.ToImmutableAndClear();
}
}
}
Loading