提交 6744226e 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #18745 from CyrusNajmabadi/patternMatcherMorePooling

Pool more data while pattern matching.
......@@ -4066,7 +4066,7 @@ public class Test
// TODO...
}
[Fact]
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/18800")]
public void CS0306ERR_BadTypeArgument01()
{
var source =
......
......@@ -204,4 +204,4 @@ internal static int BinarySearchUpperBound(this int[] array, int value)
return low;
}
}
}
}
\ No newline at end of file
......@@ -105,7 +105,14 @@ public bool IsForeground()
public void AssertIsForeground()
{
Contract.ThrowIfFalse(IsForeground());
var whenCreatedThread = _foregroundThreadDataWhenCreated.Thread;
var currentThread = Thread.CurrentThread;
Contract.ThrowIfFalse(currentThread == whenCreatedThread,
"When created kind : " + _foregroundThreadDataWhenCreated.Kind + "\r\n" +
"When created thread id : " + whenCreatedThread?.ManagedThreadId + "\r\n" +
"When created thread name: " + whenCreatedThread?.Name + "\r\n" +
"Current thread id : " + currentThread?.ManagedThreadId + "\r\n" +
"Current thread name : " + currentThread?.Name);
}
public void AssertIsBackground()
......
......@@ -4,9 +4,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.PatternMatching;
using Microsoft.CodeAnalysis.Shared.Utilities;
......@@ -486,7 +484,7 @@ public void TryMatchSingleWordPattern_CultureAwareSingleWordPreferCaseSensitiveE
private static IList<string> PartListToSubstrings(string identifier, StringBreaks parts)
{
var result = new List<string>();
for (var i = 0; i < parts.Count; i++)
for (int i = 0, n = parts.GetCount(); i < n; i++)
{
var span = parts[i];
result.Add(identifier.Substring(span.Start, span.Length));
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.NamingStyles;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Words = System.Collections.Generic.IEnumerable<string>;
......@@ -20,16 +15,19 @@ internal class NameGenerator
internal static ImmutableArray<IEnumerable<string>> GetBaseNames(ITypeSymbol type)
{
var baseName = TryRemoveInterfacePrefix(type);
var breaks = StringBreaker.BreakIntoWordParts(baseName);
return GetInterleavedPatterns(breaks, baseName);
using (var breaks = StringBreaker.BreakIntoWordParts(baseName))
{
return GetInterleavedPatterns(breaks, baseName);
}
}
private static ImmutableArray<IEnumerable<string>> GetInterleavedPatterns(StringBreaks breaks, string baseName)
{
var result = ArrayBuilder<IEnumerable<string>>.GetInstance();
result.Add(GetWords(0, breaks.Count, breaks, baseName));
var breakCount = breaks.GetCount();
result.Add(GetWords(0, breakCount, breaks, baseName));
for (int length = breaks.Count - 1; length > 0; length--)
for (int length = breakCount - 1; length > 0; length--)
{
// going forward
result.Add(GetLongestForwardSubsequence(length, breaks, baseName));
......@@ -37,13 +35,15 @@ private static ImmutableArray<IEnumerable<string>> GetInterleavedPatterns(String
// going backward
result.Add(GetLongestBackwardSubsequence(length, breaks, baseName));
}
return result.ToImmutable();
}
private static Words GetLongestBackwardSubsequence(int length, StringBreaks breaks, string baseName)
{
var start = breaks.Count - length;
return GetWords(start, breaks.Count, breaks, baseName);
var breakCount = breaks.GetCount();
var start = breakCount - length;
return GetWords(start, breakCount, breaks, baseName);
}
private static Words GetLongestForwardSubsequence(int length, StringBreaks breaks, string baseName)
......
......@@ -42,29 +42,34 @@ protected SearchScope(AbstractAddImportCodeFixProvider<TSimpleNameSyntax> provid
return ImmutableArray<SymbolResult<ISymbol>>.Empty;
}
var query = this.Exact ? SearchQuery.Create(name, ignoreCase: true) : SearchQuery.CreateFuzzy(name);
var symbols = await FindDeclarationsAsync(name, filter, query).ConfigureAwait(false);
if (Exact)
using (var query = this.Exact ? SearchQuery.Create(name, ignoreCase: true) : SearchQuery.CreateFuzzy(name))
{
// We did an exact, case insensitive, search. Case sensitive matches should
// be preferred though over insensitive ones.
return symbols.SelectAsArray(s =>
SymbolResult.Create(s.Name, nameNode, s, weight: s.Name == name ? 0 : 1));
}
var symbols = await FindDeclarationsAsync(name, filter, query).ConfigureAwait(false);
// TODO(cyrusn): It's a shame we have to compute this twice. However, there's no
// great way to store the original value we compute because it happens deep in the
// compiler bowels when we call FindDeclarations.
using (var similarityChecker = new WordSimilarityChecker(name, substringsAreSimilar: false))
{
return symbols.SelectAsArray(s =>
if (Exact)
{
// We did an exact, case insensitive, search. Case sensitive matches should
// be preferred though over insensitive ones.
return symbols.SelectAsArray(s =>
SymbolResult.Create(s.Name, nameNode, s, weight: s.Name == name ? 0 : 1));
}
// TODO(cyrusn): It's a shame we have to compute this twice. However, there's no
// great way to store the original value we compute because it happens deep in the
// compiler bowels when we call FindDeclarations.
var similarityChecker = WordSimilarityChecker.Allocate(name, substringsAreSimilar: false);
var result = symbols.SelectAsArray(s =>
{
var areSimilar = similarityChecker.AreSimilar(s.Name, out var matchCost);
Debug.Assert(areSimilar);
return SymbolResult.Create(s.Name, nameNode, s, matchCost);
});
similarityChecker.Free();
return result;
}
}
}
......
......@@ -483,12 +483,17 @@ private IOperation TryFindFieldOrPropertyAssignmentStatement(IParameterSymbol pa
/// appropriate field/property names based on the user's preference.
/// </summary>
private List<string> GetParameterWordParts(IParameterSymbol parameter)
=> CreateWords(StringBreaker.BreakIntoWordParts(parameter.Name), parameter.Name);
{
using (var breaks = StringBreaker.BreakIntoWordParts(parameter.Name))
{
return CreateWords(breaks, parameter.Name);
}
}
private List<string> CreateWords(StringBreaks wordBreaks, string name)
{
var result = new List<string>(wordBreaks.Count);
for (var i = 0; i < wordBreaks.Count; i++)
var result = new List<string>(wordBreaks.GetCount());
for (int i = 0, n = wordBreaks.GetCount(); i < n; i++)
{
var br = wordBreaks[i];
result.Add(name.Substring(br.Start, br.Length));
......
......@@ -76,27 +76,44 @@ private async Task CreateSpellCheckCodeIssueAsync(CodeFixContext context, TSimpl
return;
}
var similarityChecker = WordSimilarityChecker.Allocate(nameText, substringsAreSimilar: true);
try
{
await CheckItemsAsync(
context, nameNode, nameText,
completionList, similarityChecker).ConfigureAwait(false);
}
finally
{
similarityChecker.Free();
}
}
private async Task CheckItemsAsync(
CodeFixContext context, TSimpleName nameNode, string nameText,
CompletionList completionList, WordSimilarityChecker similarityChecker)
{
var document = context.Document;
var cancellationToken = context.CancellationToken;
var onlyConsiderGenerics = IsGeneric(nameNode);
var results = new MultiDictionary<double, string>();
using (var similarityChecker = new WordSimilarityChecker(nameText, substringsAreSimilar: true))
foreach (var item in completionList.Items)
{
foreach (var item in completionList.Items)
if (onlyConsiderGenerics && !IsGeneric(item))
{
if (onlyConsiderGenerics && !IsGeneric(item))
{
continue;
}
var candidateText = item.FilterText;
if (!similarityChecker.AreSimilar(candidateText, out var matchCost))
{
continue;
}
continue;
}
var insertionText = await GetInsertionTextAsync(document, item, cancellationToken: cancellationToken).ConfigureAwait(false);
results.Add(matchCost, insertionText);
var candidateText = item.FilterText;
if (!similarityChecker.AreSimilar(candidateText, out var matchCost))
{
continue;
}
var insertionText = await GetInsertionTextAsync(document, item, cancellationToken: cancellationToken).ConfigureAwait(false);
results.Add(matchCost, insertionText);
}
var codeActions = results.OrderBy(kvp => kvp.Key)
......@@ -111,7 +128,7 @@ private async Task CreateSpellCheckCodeIssueAsync(CodeFixContext context, TSimpl
// Wrap the spell checking actions into a single top level suggestion
// so as to not clutter the list.
context.RegisterCodeFix(new MyCodeAction(
String.Format(FeaturesResources.Spell_check_0, nameText), codeActions), context.Diagnostics);
string.Format(FeaturesResources.Spell_check_0, nameText), codeActions), context.Diagnostics);
}
else
{
......
......@@ -183,26 +183,31 @@ internal static partial class DeclarationFinder
internal static async Task<ImmutableArray<SymbolAndProjectId>> FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync(
Solution solution, string name, bool ignoreCase, SymbolFilter criteria, CancellationToken cancellationToken)
{
var query = SearchQuery.Create(name, ignoreCase);
var result = ArrayBuilder<SymbolAndProjectId>.GetInstance();
foreach (var projectId in solution.ProjectIds)
using (var query = SearchQuery.Create(name, ignoreCase))
{
var project = solution.GetProject(projectId);
await AddCompilationDeclarationsWithNormalQueryAsync(
project, query, criteria, result, cancellationToken).ConfigureAwait(false);
}
var result = ArrayBuilder<SymbolAndProjectId>.GetInstance();
foreach (var projectId in solution.ProjectIds)
{
var project = solution.GetProject(projectId);
await AddCompilationDeclarationsWithNormalQueryAsync(
project, query, criteria, result, cancellationToken).ConfigureAwait(false);
}
return result.ToImmutableAndFree();
return result.ToImmutableAndFree();
}
}
internal static async Task<ImmutableArray<SymbolAndProjectId>> FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync(
Project project, string name, bool ignoreCase, SymbolFilter filter, CancellationToken cancellationToken)
{
var list = ArrayBuilder<SymbolAndProjectId>.GetInstance();
await AddCompilationDeclarationsWithNormalQueryAsync(
project, SearchQuery.Create(name, ignoreCase),
filter, list, cancellationToken).ConfigureAwait(false);
return list.ToImmutableAndFree();
using (var query = SearchQuery.Create(name, ignoreCase))
{
await AddCompilationDeclarationsWithNormalQueryAsync(
project, query, filter, list, cancellationToken).ConfigureAwait(false);
return list.ToImmutableAndFree();
}
}
internal static async Task<ImmutableArray<SymbolAndProjectId>> FindSourceDeclarationsWithPatternInCurrentProcessAsync(
......@@ -215,36 +220,38 @@ internal static partial class DeclarationFinder
// we only want to check the 'WL' portion. Then, after we get all the candidate symbols
// we'll check if the full name matches the full pattern.
var patternMatcher = new PatternMatcher(pattern);
var query = SearchQuery.CreateCustom(
k => !patternMatcher.GetMatchesForLastSegmentOfPattern(k).IsDefaultOrEmpty);
var symbolAndProjectIds = await SymbolFinder.FindSourceDeclarationsWithCustomQueryAsync(
project, query, criteria, cancellationToken).ConfigureAwait(false);
var result = ArrayBuilder<SymbolAndProjectId>.GetInstance();
// Now see if the symbols the compiler returned actually match the full pattern.
foreach (var symbolAndProjectId in symbolAndProjectIds)
using (var query = SearchQuery.CreateCustom(
k => !patternMatcher.GetMatchesForLastSegmentOfPattern(k).IsDefaultOrEmpty))
{
var symbol = symbolAndProjectId.Symbol;
// As an optimization, don't bother getting the container for this symbol if this
// isn't a dotted pattern. Getting the container could cause lots of string
// allocations that we don't if we're never going to check it.
var matches = !patternMatcher.IsDottedPattern
? new PatternMatches(patternMatcher.GetMatches(GetSearchName(symbol)))
: patternMatcher.GetMatches(GetSearchName(symbol), GetContainer(symbol));
var symbolAndProjectIds = await SymbolFinder.FindSourceDeclarationsWithCustomQueryAsync(
project, query, criteria, cancellationToken).ConfigureAwait(false);
var result = ArrayBuilder<SymbolAndProjectId>.GetInstance();
if (matches.IsEmpty)
// Now see if the symbols the compiler returned actually match the full pattern.
foreach (var symbolAndProjectId in symbolAndProjectIds)
{
// Didn't actually match the full pattern, ignore it.
continue;
var symbol = symbolAndProjectId.Symbol;
// As an optimization, don't bother getting the container for this symbol if this
// isn't a dotted pattern. Getting the container could cause lots of string
// allocations that we don't if we're never going to check it.
var matches = !patternMatcher.IsDottedPattern
? new PatternMatches(patternMatcher.GetMatches(GetSearchName(symbol)))
: patternMatcher.GetMatches(GetSearchName(symbol), GetContainer(symbol));
if (matches.IsEmpty)
{
// Didn't actually match the full pattern, ignore it.
continue;
}
result.Add(symbolAndProjectId);
}
result.Add(symbolAndProjectId);
return result.ToImmutableAndFree();
}
return result.ToImmutableAndFree();
}
private static string GetSearchName(ISymbol symbol)
......
......@@ -5,7 +5,7 @@
namespace Microsoft.CodeAnalysis.FindSymbols
{
internal class SearchQuery
internal class SearchQuery : IDisposable
{
/// <summary>The name being searched for. Is null in the case of custom predicate searching.. But
/// can be used for faster index based searching when it is available.</summary>
......@@ -18,6 +18,8 @@ internal class SearchQuery
///<summary>The predicate to fall back on if faster index searching is not possible.</summary>
private readonly Func<string, bool> _predicate;
private readonly WordSimilarityChecker _wordSimilarityChecker;
private SearchQuery(string name, SearchKind kind)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
......@@ -36,8 +38,8 @@ private SearchQuery(string name, SearchKind kind)
// its 'AreSimilar' method. That way we only create the WordSimilarityChecker
// once and it can cache all the information it needs while it does the AreSimilar
// check against all the possible candidates.
var editDistance = new WordSimilarityChecker(name, substringsAreSimilar: false);
_predicate = editDistance.AreSimilar;
_wordSimilarityChecker = WordSimilarityChecker.Allocate(name, substringsAreSimilar: false);
_predicate = _wordSimilarityChecker.AreSimilar;
break;
default:
throw ExceptionUtilities.UnexpectedValue(kind);
......@@ -50,6 +52,11 @@ private SearchQuery(Func<string, bool> predicate)
_predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
}
public void Dispose()
{
_wordSimilarityChecker?.Free();
}
public static SearchQuery Create(string name, SearchKind kind)
=> new SearchQuery(name, kind);
......
......@@ -14,9 +14,12 @@ public static partial class SymbolFinder
public static async Task<IEnumerable<ISymbol>> FindDeclarationsAsync(
Project project, string name, bool ignoreCase, CancellationToken cancellationToken = default(CancellationToken))
{
var declarations = await DeclarationFinder.FindAllDeclarationsWithNormalQueryAsync(
project, SearchQuery.Create(name, ignoreCase), SymbolFilter.All, cancellationToken).ConfigureAwait(false);
return declarations.SelectAsArray(t => t.Symbol);
using (var query = SearchQuery.Create(name, ignoreCase))
{
var declarations = await DeclarationFinder.FindAllDeclarationsWithNormalQueryAsync(
project, query, SymbolFilter.All, cancellationToken).ConfigureAwait(false);
return declarations.SelectAsArray(t => t.Symbol);
}
}
/// <summary>
......@@ -25,9 +28,12 @@ public static partial class SymbolFinder
public static async Task<IEnumerable<ISymbol>> FindDeclarationsAsync(
Project project, string name, bool ignoreCase, SymbolFilter filter, CancellationToken cancellationToken = default(CancellationToken))
{
var declarations = await DeclarationFinder.FindAllDeclarationsWithNormalQueryAsync(
project, SearchQuery.Create(name, ignoreCase), filter, cancellationToken).ConfigureAwait(false);
return declarations.SelectAsArray(t => t.Symbol);
using (var query = SearchQuery.Create(name, ignoreCase))
{
var declarations = await DeclarationFinder.FindAllDeclarationsWithNormalQueryAsync(
project, query, filter, cancellationToken).ConfigureAwait(false);
return declarations.SelectAsArray(t => t.Symbol);
}
}
}
}
\ No newline at end of file
......@@ -30,10 +30,13 @@ public static Task<IEnumerable<ISymbol>> FindSourceDeclarationsAsync(Solution so
/// </summary>
public static async Task<IEnumerable<ISymbol>> FindSourceDeclarationsAsync(Solution solution, Func<string, bool> predicate, SymbolFilter filter, CancellationToken cancellationToken = default(CancellationToken))
{
var declarations = await FindSourceDeclarationsWithCustomQueryAsync(
solution, SearchQuery.CreateCustom(predicate), filter, cancellationToken).ConfigureAwait(false);
using (var query = SearchQuery.CreateCustom(predicate))
{
var declarations = await FindSourceDeclarationsWithCustomQueryAsync(
solution, query, filter, cancellationToken).ConfigureAwait(false);
return declarations.SelectAsArray(d => d.Symbol);
return declarations.SelectAsArray(d => d.Symbol);
}
}
private static async Task<ImmutableArray<SymbolAndProjectId>> FindSourceDeclarationsWithCustomQueryAsync(
......@@ -74,10 +77,13 @@ public static Task<IEnumerable<ISymbol>> FindSourceDeclarationsAsync(Project pro
/// </summary>
public static async Task<IEnumerable<ISymbol>> FindSourceDeclarationsAsync(Project project, Func<string, bool> predicate, SymbolFilter filter, CancellationToken cancellationToken = default(CancellationToken))
{
var declarations = await FindSourceDeclarationsWithCustomQueryAsync(
project, SearchQuery.CreateCustom(predicate), filter, cancellationToken).ConfigureAwait(false);
using (var query = SearchQuery.CreateCustom(predicate))
{
var declarations = await FindSourceDeclarationsWithCustomQueryAsync(
project, query, filter, cancellationToken).ConfigureAwait(false);
return declarations.SelectAsArray(d => d.Symbol);
return declarations.SelectAsArray(d => d.Symbol);
}
}
internal static async Task<ImmutableArray<SymbolAndProjectId>> FindSourceDeclarationsWithCustomQueryAsync(
......
......@@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.PatternMatching
internal sealed partial class PatternMatcher : IDisposable
{
/// <summary>
/// Encapsulated matches responsible for mathcing an all lowercase pattern against
/// Encapsulated matches responsible for matching an all lowercase pattern against
/// a candidate using CamelCase matching. i.e. this code is responsible for finding the
/// match between "cofipro" and "CodeFixProvider".
/// </summary>
......@@ -36,7 +36,7 @@ public AllLowerCamelCaseMatcher(string candidate, bool includeMatchedSpans, Stri
/// <summary>
/// Returns null if no match was found, 1 if a contiguous match was found, 2 if a
/// match as found that starts at the beginning of the candidate, and 3 if a continguous
/// match as found that starts at the beginning of the candidate, and 3 if a contiguous
/// match was found that starts at the beginning of the candidate.
/// </summary>
public int? TryMatch(out ImmutableArray<TextSpan> matchedSpans)
......@@ -80,7 +80,7 @@ public AllLowerCamelCaseMatcher(string candidate, bool includeMatchedSpans, Stri
// Look for a hump in the candidate that matches the current letter we're on.
var patternCharacter = _patternText[patternIndex];
for (var humpIndex = candidateHumpIndex; humpIndex < _candidateHumps.Count; humpIndex++)
for (int humpIndex = candidateHumpIndex, n = _candidateHumps.GetCount(); humpIndex < n; humpIndex++)
{
// If we've been contiguous, but we jumped past a hump, then we're no longer contiguous.
if (contiguous.HasValue && contiguous.Value)
......@@ -102,7 +102,7 @@ public AllLowerCamelCaseMatcher(string candidate, bool includeMatchedSpans, Stri
// is cofipro, and we've matched the 'f' against the 'F', then the max of
// the pattern we'll want to consume is "fip" against "Fix". We don't want
// consume parts of the pattern once we reach the next hump.
// We matched something. If this was our first match, consider ourselves
// contiguous.
if (contiguous == null)
......@@ -112,43 +112,14 @@ public AllLowerCamelCaseMatcher(string candidate, bool includeMatchedSpans, Stri
var (weight, matchedSpansInReverse) = TryConsumePatternOrMatchNextHump(
patternIndex, humpIndex, contiguous.Value);
if (weight == null)
{
Debug.Assert(matchedSpansInReverse == null);
// Even though we matched this current candidate hump we failed to match
// the remainder of the pattern. Continue to the next candidate hump
// to see if our pattern character will match it and potentially succeed.
continue;
}
Debug.Assert(weight >= 0);
if (weight == CamelCaseMaxWeight)
if (UpdateBestResultIfBetter(
weight, matchedSpansInReverse,
ref bestWeight, ref bestMatchedSpansInReverse,
matchSpanToAdd: null))
{
// We found a path that allowed us to match everything contiguously
// from the beginning. This is the best match possible. So we can
// just stop now and return this result.
//
// Return any previous best results back to the pool.
bestMatchedSpansInReverse?.Free();
return (weight, matchedSpansInReverse);
}
// This is a decent match. But something else could beat it, store
// it if it's the best match we have so far, but keep searching.
if (bestWeight == null || weight > bestWeight)
{
bestWeight = weight;
// Return any previous best results back to the pool. And set the latest
// result as the best result.
bestMatchedSpansInReverse?.Free();
bestMatchedSpansInReverse = matchedSpansInReverse;
}
else
{
// There's already a better result than this latest result. Return
// the latest result to the pool.
matchedSpansInReverse?.Free();
// We found the best result so far. We can stop immediately.
break;
}
}
}
......@@ -178,9 +149,6 @@ public AllLowerCamelCaseMatcher(string candidate, bool includeMatchedSpans, Stri
break;
}
// This is the span of the hump of the candidate we matched.
var candidateMatchSpan = new TextSpan(candidateHump.Start, possibleHumpMatchLength);
// The pattern substring 'f' has matched against 'F', or 'fi' has matched
// against 'Fi'. recurse and let the rest of the pattern match the remainder
// of the candidate.
......@@ -188,55 +156,85 @@ public AllLowerCamelCaseMatcher(string candidate, bool includeMatchedSpans, Stri
var (weight, matchedSpansInReverse) = TryMatch(
patternIndex + possibleHumpMatchLength, humpIndex + 1, contiguous);
if (weight == null)
{
Debug.Assert(matchedSpansInReverse == null);
// Didn't match when we recursed. Try to consume more and see if that gets us
// somewhere.
continue;
}
Debug.Assert(weight <= CamelCaseContiguousBonus);
// If this is our first hump add a 'from start' bonus. Note, if we didn't
// match anything than 'weight' will remain null.
if (humpIndex == 0)
{
weight += CamelCaseMatchesFromStartBonus;
}
if (weight == CamelCaseMaxWeight)
// This is the span of the hump of the candidate we matched.
var matchSpanToAdd = new TextSpan(candidateHump.Start, possibleHumpMatchLength);
if (UpdateBestResultIfBetter(
weight, matchedSpansInReverse,
ref bestWeight, ref bestMatchedSpansInReverse,
matchSpanToAdd))
{
// We found a path that allowed us to match everything contiguously
// from the beginning. This is the best match possible. So we can
// just stop now and return this result.
//
// Return any previous best results back to the pool.
matchedSpansInReverse?.Add(candidateMatchSpan);
bestMatchedSpansInReverse?.Free();
return (weight, matchedSpansInReverse);
// We found the best result so far. We can stop immediately.
break;
}
}
// This is a decent match. But something else could beat it, store
// it if it's the best match we have so far, but keep searching.
if (bestWeight == null || weight > bestWeight)
{
matchedSpansInReverse?.Add(candidateMatchSpan);
return (bestWeight, bestMatchedSpansInReverse);
}
bestWeight = weight;
/// <summary>
/// Updates the currently stored 'best result' if the current result is better.
/// Returns 'true' if no further work is required and we can break early, or
/// 'false' if we need to keep on going.
///
/// If 'weight' is better than 'bestWeight' and matchSpanToAdd is not null, then
/// matchSpanToAdd will be added to matchedSpansInReverse.
/// </summary>
private bool UpdateBestResultIfBetter(
int? weight, ArrayBuilder<TextSpan> matchedSpansInReverse,
ref int? bestWeight, ref ArrayBuilder<TextSpan> bestMatchedSpansInReverse,
TextSpan? matchSpanToAdd)
{
if (!IsBetter(weight, bestWeight))
{
// Even though we matched this current candidate hump we failed to match
// the remainder of the pattern. Continue to the next candidate hump
// to see if our pattern character will match it and potentially succeed.
matchedSpansInReverse?.Free();
// Return any previous best results back to the pool. And set the latest
// result as the best result.
bestMatchedSpansInReverse?.Free();
bestMatchedSpansInReverse = matchedSpansInReverse;
}
else
{
// There's already a better result than this latest result. Return
// the latest result to the pool.
matchedSpansInReverse?.Free();
}
// We need to keep going.
return false;
}
return (bestWeight, bestMatchedSpansInReverse);
if (matchSpanToAdd != null)
{
matchedSpansInReverse?.Add(matchSpanToAdd.Value);
}
// This was result was better than whatever previous best result we had was.
// Free and overwrite the existing best results, and keep going.
bestWeight = weight;
bestMatchedSpansInReverse?.Free();
bestMatchedSpansInReverse = matchedSpansInReverse;
// We found a path that allowed us to match everything contiguously
// from the beginning. This is the best match possible. So we can
// just break out now and return this result.
return weight == CamelCaseMaxWeight;
}
private static bool IsBetter(int? weight, int? currentBestWeight)
{
// If we got no weight, this results is definitely not better.
if (weight == null)
{
return false;
}
// If the current best weight is greater than the weight we just got, then this
// result is definitely not better.
if (currentBestWeight > weight)
{
return false;
}
return true;
}
private bool LowercaseSubstringsMatch(
......
......@@ -32,13 +32,14 @@ public TextChunk(string text, bool allowFuzzingMatching)
this.Text = text;
this.CharacterSpans = StringBreaker.BreakIntoCharacterParts(text);
this.SimilarityChecker = allowFuzzingMatching
? new WordSimilarityChecker(text, substringsAreSimilar: false)
? WordSimilarityChecker.Allocate(text, substringsAreSimilar: false)
: null;
}
public void Dispose()
{
this.SimilarityChecker?.Dispose();
this.CharacterSpans.Dispose();
this.SimilarityChecker?.Free();
}
}
}
......
......@@ -79,10 +79,17 @@ internal sealed partial class PatternMatcher : IDisposable
public void Dispose()
{
_fullPatternSegment.Dispose();
foreach (var segment in _dotSeparatedPatternSegments)
{
segment.Dispose();
}
foreach (var kvp in _stringToWordSpans)
{
kvp.Value.Dispose();
}
_stringToWordSpans.Clear();
}
public bool IsDottedPattern => _dotSeparatedPatternSegments.Length > 1;
......@@ -339,7 +346,7 @@ private static bool ContainsUpperCaseLetter(string pattern)
}
var wordSpans = GetWordSpans(candidate);
for (int i = 0; i < wordSpans.Count; i++)
for (int i = 0, n = wordSpans.GetCount(); i < n; i++)
{
var span = wordSpans[i];
if (PartStartsWith(candidate, span, patternChunk.Text, CompareOptions.IgnoreCase))
......@@ -622,7 +629,7 @@ private bool PartStartsWith(string candidate, TextSpan candidatePart, string pat
{
// f) If the word was not entirely lowercase, then attempt a normal camel cased match.
// i.e. CoFiPro would match CodeFixProvider, but CofiPro would not.
if (patternChunk.CharacterSpans.Count > 0)
if (patternChunk.CharacterSpans.GetCount() > 0)
{
var candidateParts = GetWordSpans(candidate);
var camelCaseWeight = TryUpperCaseCamelCaseMatch(candidate, includeMatchSpans, candidateParts, patternChunk, CompareOptions.None, out var matchedSpans);
......@@ -677,12 +684,14 @@ private bool PartStartsWith(string candidate, TextSpan candidatePart, string pat
int? firstMatch = null;
bool? contiguous = null;
var result = ArrayBuilder<TextSpan>.GetInstance();
var patternChunkCharacterSpansCount = patternChunkCharacterSpans.GetCount();
var candidatePartsCount = candidateParts.GetCount();
var result = ArrayBuilder<TextSpan>.GetInstance();
while (true)
{
// Let's consider our termination cases
if (currentChunkSpan == patternChunkCharacterSpans.Count)
if (currentChunkSpan == patternChunkCharacterSpansCount)
{
Contract.Requires(firstMatch.HasValue);
Contract.Requires(contiguous.HasValue);
......@@ -708,7 +717,7 @@ private bool PartStartsWith(string candidate, TextSpan candidatePart, string pat
result.Free();
return weight;
}
else if (currentCandidate == candidateParts.Count)
else if (currentCandidate == candidatePartsCount)
{
// No match, since we still have more of the pattern to hit
matchedSpans = ImmutableArray<TextSpan>.Empty;
......@@ -723,7 +732,7 @@ private bool PartStartsWith(string candidate, TextSpan candidatePart, string pat
// will be Simple/UI/Element, and the pattern parts will be Si/U/I. We'll match 'Si'
// against 'Simple' first. Then we'll match 'U' against 'UI'. However, we want to
// still keep matching pattern parts against that candidate part.
for (; currentChunkSpan < patternChunkCharacterSpans.Count; currentChunkSpan++)
for (; currentChunkSpan < patternChunkCharacterSpansCount; currentChunkSpan++)
{
var patternChunkCharacterSpan = patternChunkCharacterSpans[currentChunkSpan];
......
......@@ -286,7 +286,7 @@ public static IMethodSymbol CreateEqualsMethod(this Compilation compilation, Imm
public static string GetLocalName(this INamedTypeSymbol containingType)
{
var parts = StringBreaker.BreakIntoWordParts(containingType.Name);
for (var i = parts.Count - 1; i >= 0; i--)
for (var i = parts.GetCount() - 1; i >= 0; i--)
{
var p = parts[i];
if (char.IsLetter(containingType.Name[p.Start]))
......
......@@ -14,9 +14,9 @@ namespace Microsoft.CodeAnalysis.Shared.Utilities
/// bitfields are stored in a 32-bit bitmap.
/// Falls back to a <see cref="List{T}"/> if the encoding won't work.
/// </summary>
internal struct StringBreaks
internal partial struct StringBreaks : IDisposable
{
private readonly List<TextSpan> _spans;
private readonly ArrayBuilder<TextSpan> _spans;
private readonly EncodedSpans _encodedSpans;
// These two values may be adjusted. The remaining constants are
......@@ -32,27 +32,6 @@ internal struct StringBreaks
private const int MaxGap = (1 << BitsForGap) - 1;
private const int MaxLength = (1 << BitsForLength) - 1;
private struct EncodedSpans
{
private const uint Mask = (1u << BitsPerEncodedSpan) - 1u;
private uint _value;
public byte this[int index]
{
get
{
Debug.Assert(index >= 0 && index < MaxShortSpans);
return (byte)((_value >> (index * BitsPerEncodedSpan)) & Mask);
}
set
{
Debug.Assert(index >= 0 && index < MaxShortSpans);
int shift = index * BitsPerEncodedSpan;
_value = (_value & ~(Mask << shift)) | ((uint)value << shift);
}
}
}
public static StringBreaks Create(string text, Func<string, int, TextSpan> spanGenerator)
{
Debug.Assert(text != null);
......@@ -92,9 +71,9 @@ private static bool TryEncodeSpans(string text, Func<string, int, TextSpan> span
return true;
}
private static List<TextSpan> CreateFallbackList(string text, Func<string, int, TextSpan> spanGenerator)
private static ArrayBuilder<TextSpan> CreateFallbackList(string text, Func<string, int, TextSpan> spanGenerator)
{
List<TextSpan> list = new List<TextSpan>();
var list = ArrayBuilder<TextSpan>.GetInstance();
for (int start = 0; start < text.Length;)
{
var span = spanGenerator(text, start);
......@@ -119,29 +98,34 @@ private StringBreaks(EncodedSpans encodedSpans)
_spans = null;
}
private StringBreaks(List<TextSpan> spans)
private StringBreaks(ArrayBuilder<TextSpan> spans)
{
_encodedSpans = default(EncodedSpans);
_spans = spans;
}
public int Count
public void Dispose()
{
get
_spans?.Free();
}
public int GetCount()
{
if (_spans != null)
{
if (_spans != null)
{
return _spans.Count;
}
return _spans.Count;
}
int i;
for (i = 0; i < MaxShortSpans; i++)
int i;
for (i = 0; i < MaxShortSpans; i++)
{
if (_encodedSpans[i] == 0)
{
if (_encodedSpans[i] == 0) break;
break;
}
return i;
}
return i;
}
public TextSpan this[int index]
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
namespace Microsoft.CodeAnalysis.Shared.Utilities
{
internal partial struct StringBreaks
{
private struct EncodedSpans
{
private const uint Mask = (1u << BitsPerEncodedSpan) - 1u;
private uint _value;
public byte this[int index]
{
get
{
Debug.Assert(index >= 0 && index < MaxShortSpans);
return (byte)((_value >> (index * BitsPerEncodedSpan)) & Mask);
}
set
{
Debug.Assert(index >= 0 && index < MaxShortSpans);
int shift = index * BitsPerEncodedSpan;
_value = (_value & ~(Mask << shift)) | ((uint)value << shift);
}
}
}
}
}
......@@ -23,24 +23,23 @@ public SpellChecker(VersionStamp version, BKTree bKTree)
_bkTree = bKTree;
}
public SpellChecker(VersionStamp version, IEnumerable<StringSlice> corpus)
public SpellChecker(VersionStamp version, IEnumerable<StringSlice> corpus)
: this(version, BKTree.Create(corpus))
{
}
public IList<string> FindSimilarWords(string value)
{
return FindSimilarWords(value, substringsAreSimilar: false);
}
=> FindSimilarWords(value, substringsAreSimilar: false);
public IList<string> FindSimilarWords(string value, bool substringsAreSimilar)
{
var result = _bkTree.Find(value, threshold: null);
using (var spellChecker = new WordSimilarityChecker(value, substringsAreSimilar))
{
return result.Where(spellChecker.AreSimilar).ToArray();
}
var checker = WordSimilarityChecker.Allocate(value, substringsAreSimilar);
var array = result.Where(checker.AreSimilar).ToArray();
checker.Free();
return array;
}
internal void WriteTo(ObjectWriter writer)
......@@ -74,7 +73,7 @@ internal static SpellChecker ReadFrom(ObjectReader reader)
}
}
internal class WordSimilarityChecker : IDisposable
internal class WordSimilarityChecker
{
private struct CacheResult
{
......@@ -97,16 +96,37 @@ public CacheResult(string candidate, bool areSimilar, double similarityWeight)
private string _source;
private EditDistance _editDistance;
private readonly int _threshold;
private int _threshold;
/// <summary>
/// Whether or words should be considered similar if one is contained within the other
/// (regardless of edit distance). For example if is true then IService would be considered
/// similar to IServiceFactory despite the edit distance being quite high at 7.
/// </summary>
private readonly bool _substringsAreSimilar;
private bool _substringsAreSimilar;
private static readonly object s_poolGate = new object();
private static readonly Stack<WordSimilarityChecker> s_pool = new Stack<WordSimilarityChecker>();
public static WordSimilarityChecker Allocate(string text, bool substringsAreSimilar)
{
WordSimilarityChecker checker;
lock (s_poolGate)
{
checker = s_pool.Count > 0
? s_pool.Pop()
: new WordSimilarityChecker();
}
checker.Initialize(text, substringsAreSimilar);
return checker;
}
private WordSimilarityChecker()
{
}
public WordSimilarityChecker(string text, bool substringsAreSimilar)
private void Initialize(string text, bool substringsAreSimilar)
{
_source = text ?? throw new ArgumentNullException(nameof(text));
_threshold = GetThreshold(_source);
......@@ -114,29 +134,28 @@ public WordSimilarityChecker(string text, bool substringsAreSimilar)
_substringsAreSimilar = substringsAreSimilar;
}
public void Dispose()
public void Free()
{
_editDistance?.Dispose();
_source = null;
_editDistance = null;
_lastAreSimilarResult = default(CacheResult);
s_pool.Push(this);
}
public static bool AreSimilar(string originalText, string candidateText)
{
return AreSimilar(originalText, candidateText, substringsAreSimilar: false);
}
=> AreSimilar(originalText, candidateText, substringsAreSimilar: false);
public static bool AreSimilar(string originalText, string candidateText, bool substringsAreSimilar)
{
return AreSimilar(originalText, candidateText, substringsAreSimilar, out var unused);
}
=> AreSimilar(originalText, candidateText, substringsAreSimilar, out var unused);
public static bool AreSimilar(string originalText, string candidateText, out double similarityWeight)
{
return AreSimilar(
originalText, candidateText,
originalText, candidateText,
substringsAreSimilar: false, similarityWeight: out similarityWeight);
}
/// <summary>
/// Returns true if 'originalText' and 'candidateText' are likely a misspelling of each other.
/// Returns false otherwise. If it is a likely misspelling a similarityWeight is provided
......@@ -144,21 +163,18 @@ public static bool AreSimilar(string originalText, string candidateText, out dou
/// </summary>
public static bool AreSimilar(string originalText, string candidateText, bool substringsAreSimilar, out double similarityWeight)
{
using (var checker = new WordSimilarityChecker(originalText, substringsAreSimilar))
{
return checker.AreSimilar(candidateText, out similarityWeight);
}
var checker = Allocate(originalText, substringsAreSimilar);
var result = checker.AreSimilar(candidateText, out similarityWeight);
checker.Free();
return result;
}
internal static int GetThreshold(string value)
{
return value.Length <= 4 ? 1 : 2;
}
=> value.Length <= 4 ? 1 : 2;
public bool AreSimilar(string candidateText)
{
return AreSimilar(candidateText, out var similarityWeight);
}
=> AreSimilar(candidateText, out var similarityWeight);
public bool AreSimilar(string candidateText, out double similarityWeight)
{
......@@ -242,4 +258,4 @@ private static double Penalty(string candidateText, string originalText)
return 0;
}
}
}
}
\ No newline at end of file
......@@ -424,6 +424,7 @@
<Compile Include="NamingStyles\Serialization\NamingStylePreferences.cs" />
<Compile Include="NamingStyles\Serialization\SymbolSpecification.cs" />
<Compile Include="Shared\Extensions\SolutionExtensions.cs" />
<Compile Include="Shared\Utilities\StringBreaks.EncodedSpans.cs" />
<Compile Include="Shared\Utilities\TextReaderWithLength.cs" />
<Compile Include="Simplification\DoNotAddImportsAnnotation.cs" />
<Compile Include="SymbolKey\SymbolKey.AnonymousFunctionOrDelegateSymbolKey.cs" />
......
......@@ -50,10 +50,13 @@ public async Task FindLiteralReferencesAsync(object value)
var solution = await GetSolutionAsync().ConfigureAwait(false);
var project = solution.GetProject(projectId);
var result = await DeclarationFinder.FindAllDeclarationsWithNormalQueryInCurrentProcessAsync(
project, SearchQuery.Create(name, searchKind), criteria, this.CancellationToken).ConfigureAwait(false);
using (var query = SearchQuery.Create(name, searchKind))
{
var result = await DeclarationFinder.FindAllDeclarationsWithNormalQueryInCurrentProcessAsync(
project, query, criteria, this.CancellationToken).ConfigureAwait(false);
return result.Select(SerializableSymbolAndProjectId.Dehydrate).ToArray();
return result.Select(SerializableSymbolAndProjectId.Dehydrate).ToArray();
}
}
public async Task<SerializableSymbolAndProjectId[]> FindSolutionSourceDeclarationsWithNormalQueryAsync(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册