Skip to content

Commit e2f862f

Browse files
Merge pull request #69336 from CyrusNajmabadi/patternMatcherCopies
Fix issues where internal pattern matching state was being copied
2 parents e6736ca + 007cbc8 commit e2f862f

7 files changed

Lines changed: 63 additions & 24 deletions

File tree

src/EditorFeatures/Test/Utilities/PatternMatcherTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,19 @@ public void TryMatchSingleWordPattern_CultureAwareSingleWordPreferCaseSensitiveE
414414
}
415415
}
416416

417+
[Fact]
418+
public void TestCachingOfPriorResult()
419+
{
420+
using var matcher = PatternMatcher.CreatePatternMatcher("Goo", includeMatchedSpans: true, allowFuzzyMatching: true);
421+
matcher.Matches("Go");
422+
423+
// Ensure that the above call ended up caching the result.
424+
Assert.True(((PatternMatcher.SimplePatternMatcher)matcher).GetTestAccessor().LastCacheResultIs(areSimilar: true, candidateText: "Go"));
425+
426+
matcher.Matches("DefNotAMatch");
427+
Assert.True(((PatternMatcher.SimplePatternMatcher)matcher).GetTestAccessor().LastCacheResultIs(areSimilar: false, candidateText: "DefNotAMatch"));
428+
}
429+
417430
private static ImmutableArray<string> PartListToSubstrings(string identifier, in TemporaryArray<TextSpan> parts)
418431
{
419432
using var result = TemporaryArray<string>.Empty;

src/Workspaces/Core/Portable/FindSymbols/SearchQuery.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace Microsoft.CodeAnalysis.FindSymbols
99
{
10-
internal readonly struct SearchQuery : IDisposable
10+
internal struct SearchQuery : IDisposable
1111
{
1212
/// <summary>The name being searched for. Is null in the case of custom predicate searching.. But
1313
/// can be used for faster index based searching when it is available.</summary>
@@ -20,7 +20,10 @@ namespace Microsoft.CodeAnalysis.FindSymbols
2020
///<summary>The predicate to fall back on if faster index searching is not possible.</summary>
2121
private readonly Func<string, bool> _predicate;
2222

23-
private readonly WordSimilarityChecker? _wordSimilarityChecker;
23+
/// <summary>
24+
/// Not readonly as this is mutable struct.
25+
/// </summary>
26+
private WordSimilarityChecker _wordSimilarityChecker;
2427

2528
private SearchQuery(string name, SearchKind kind)
2629
{
@@ -41,7 +44,7 @@ private SearchQuery(string name, SearchKind kind)
4144
// once and it can cache all the information it needs while it does the AreSimilar
4245
// check against all the possible candidates.
4346
_wordSimilarityChecker = new WordSimilarityChecker(name, substringsAreSimilar: false);
44-
_predicate = _wordSimilarityChecker.Value.AreSimilar;
47+
_predicate = _wordSimilarityChecker.AreSimilar;
4548
break;
4649
default:
4750
throw ExceptionUtilities.UnexpectedValue(kind);
@@ -54,8 +57,8 @@ private SearchQuery(Func<string, bool> predicate)
5457
_predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
5558
}
5659

57-
public void Dispose()
58-
=> _wordSimilarityChecker?.Dispose();
60+
public readonly void Dispose()
61+
=> _wordSimilarityChecker.Dispose();
5962

6063
public static SearchQuery Create(string name, SearchKind kind)
6164
=> new(name, kind);
@@ -69,7 +72,7 @@ public static SearchQuery CreateFuzzy(string name)
6972
public static SearchQuery CreateCustom(Func<string, bool> predicate)
7073
=> new(predicate);
7174

72-
public Func<string, bool> GetPredicate()
75+
public readonly Func<string, bool> GetPredicate()
7376
=> _predicate;
7477
}
7578
}

src/Workspaces/Core/Portable/PatternMatching/ContainerPatternMatcher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ private bool AddMatches(string container, ref TemporaryArray<PatternMatch> match
8080
i--, j--)
8181
{
8282
var containerName = containerParts[j];
83-
if (!MatchPatternSegment(containerName, _patternSegments[i], ref tempContainerMatches.AsRef(), fuzzyMatch))
83+
if (!MatchPatternSegment(containerName, ref _patternSegments[i], ref tempContainerMatches.AsRef(), fuzzyMatch))
8484
{
8585
// This container didn't match the pattern piece. So there's no match at all.
8686
return false;

src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.TextChunk.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ private struct TextChunk : IDisposable
3232
/// </summary>
3333
public TemporaryArray<TextSpan> PatternHumps;
3434

35-
public readonly WordSimilarityChecker? SimilarityChecker;
35+
/// <summary>
36+
/// Not readonly as this value caches data within it, and so it needs to be able to mutate.
37+
/// </summary>
38+
public WordSimilarityChecker SimilarityChecker;
3639

3740
public readonly bool IsLowercase;
3841

@@ -44,15 +47,15 @@ public TextChunk(string text, bool allowFuzzingMatching)
4447

4548
this.SimilarityChecker = allowFuzzingMatching
4649
? new WordSimilarityChecker(text, substringsAreSimilar: false)
47-
: null;
50+
: default;
4851

4952
IsLowercase = !ContainsUpperCaseLetter(text);
5053
}
5154

5255
public void Dispose()
5356
{
5457
this.PatternHumps.Dispose();
55-
this.SimilarityChecker?.Dispose();
58+
this.SimilarityChecker.Dispose();
5659
}
5760
}
5861
}

src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -125,22 +125,22 @@ private static bool ContainsUpperCaseLetter(string pattern)
125125

126126
private PatternMatch? MatchPatternChunk(
127127
string candidate,
128-
in TextChunk patternChunk,
128+
ref TextChunk patternChunk,
129129
bool punctuationStripped,
130130
bool fuzzyMatch)
131131
{
132132
return fuzzyMatch
133-
? FuzzyMatchPatternChunk(candidate, patternChunk, punctuationStripped)
133+
? FuzzyMatchPatternChunk(candidate, ref patternChunk, punctuationStripped)
134134
: NonFuzzyMatchPatternChunk(candidate, patternChunk, punctuationStripped);
135135
}
136136

137137
private static PatternMatch? FuzzyMatchPatternChunk(
138138
string candidate,
139-
in TextChunk patternChunk,
139+
ref TextChunk patternChunk,
140140
bool punctuationStripped)
141141
{
142-
Contract.ThrowIfNull(patternChunk.SimilarityChecker);
143-
if (patternChunk.SimilarityChecker.Value.AreSimilar(candidate))
142+
Contract.ThrowIfTrue(patternChunk.SimilarityChecker.IsDefault);
143+
if (patternChunk.SimilarityChecker.AreSimilar(candidate))
144144
{
145145
return new PatternMatch(
146146
PatternMatchKind.Fuzzy, punctuationStripped, isCaseSensitive: false, matchedSpan: null);
@@ -307,7 +307,7 @@ private static bool ContainsSpaceOrAsterisk(string text)
307307
/// <returns>If there's only one match, then the return value is that match. Otherwise it is null.</returns>
308308
private bool MatchPatternSegment(
309309
string candidate,
310-
in PatternSegment segment,
310+
ref PatternSegment segment,
311311
ref TemporaryArray<PatternMatch> matches,
312312
bool fuzzyMatch)
313313
{
@@ -326,7 +326,7 @@ private bool MatchPatternSegment(
326326
if (!ContainsSpaceOrAsterisk(segment.TotalTextChunk.Text))
327327
{
328328
var match = MatchPatternChunk(
329-
candidate, segment.TotalTextChunk, punctuationStripped: false, fuzzyMatch: fuzzyMatch);
329+
candidate, ref segment.TotalTextChunk, punctuationStripped: false, fuzzyMatch: fuzzyMatch);
330330
if (match != null)
331331
{
332332
matches.Add(match.Value);
@@ -354,7 +354,7 @@ private bool MatchPatternSegment(
354354
if (subWordTextChunks.Length == 1)
355355
{
356356
var result = MatchPatternChunk(
357-
candidate, subWordTextChunks[0], punctuationStripped: true, fuzzyMatch: fuzzyMatch);
357+
candidate, ref subWordTextChunks[0], punctuationStripped: true, fuzzyMatch: fuzzyMatch);
358358
if (result == null)
359359
{
360360
return false;
@@ -367,11 +367,11 @@ private bool MatchPatternSegment(
367367
{
368368
using var tempMatches = TemporaryArray<PatternMatch>.Empty;
369369

370-
foreach (var subWordTextChunk in subWordTextChunks)
370+
for (int i = 0, n = subWordTextChunks.Length; i < n; i++)
371371
{
372372
// Try to match the candidate with this word
373373
var result = MatchPatternChunk(
374-
candidate, subWordTextChunk, punctuationStripped: true, fuzzyMatch: fuzzyMatch);
374+
candidate, ref subWordTextChunks[i], punctuationStripped: true, fuzzyMatch: fuzzyMatch);
375375
if (result == null)
376376
return false;
377377

src/Workspaces/Core/Portable/PatternMatching/SimplePatternMatcher.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#nullable disable
66

7+
using System;
78
using System.Globalization;
89
using Microsoft.CodeAnalysis.PooledObjects;
910
using Microsoft.CodeAnalysis.Shared.Collections;
@@ -12,7 +13,7 @@ namespace Microsoft.CodeAnalysis.PatternMatching
1213
{
1314
internal partial class PatternMatcher
1415
{
15-
private sealed partial class SimplePatternMatcher : PatternMatcher
16+
internal sealed partial class SimplePatternMatcher : PatternMatcher
1617
{
1718
private PatternSegment _fullPatternSegment;
1819

@@ -48,8 +49,17 @@ public override bool AddMatches(string candidate, ref TemporaryArray<PatternMatc
4849
return false;
4950
}
5051

51-
return MatchPatternSegment(candidate, in _fullPatternSegment, ref matches, fuzzyMatch: false) ||
52-
MatchPatternSegment(candidate, in _fullPatternSegment, ref matches, fuzzyMatch: true);
52+
return MatchPatternSegment(candidate, ref _fullPatternSegment, ref matches, fuzzyMatch: false) ||
53+
MatchPatternSegment(candidate, ref _fullPatternSegment, ref matches, fuzzyMatch: true);
54+
}
55+
56+
public TestAccessor GetTestAccessor()
57+
=> new(this);
58+
59+
public readonly struct TestAccessor(SimplePatternMatcher simplePatternMatcher)
60+
{
61+
public readonly bool LastCacheResultIs(bool areSimilar, string candidateText)
62+
=> simplePatternMatcher._fullPatternSegment.TotalTextChunk.SimilarityChecker.LastCacheResultIs(areSimilar, candidateText);
5363
}
5464
}
5565
}

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/WordSimilarityChecker.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ private readonly struct CacheResult(string candidate, bool areSimilar, double si
3232
/// </summary>
3333
private readonly bool _substringsAreSimilar;
3434

35+
public readonly bool IsDefault => _source is null;
36+
3537
public WordSimilarityChecker(string text, bool substringsAreSimilar)
3638
{
3739
_source = text ?? throw new ArgumentNullException(nameof(text));
@@ -41,7 +43,12 @@ public WordSimilarityChecker(string text, bool substringsAreSimilar)
4143
}
4244

4345
public readonly void Dispose()
44-
=> _editDistance.Dispose();
46+
{
47+
if (this.IsDefault)
48+
return;
49+
50+
_editDistance.Dispose();
51+
}
4552

4653
public static bool AreSimilar(string originalText, string candidateText)
4754
=> AreSimilar(originalText, candidateText, substringsAreSimilar: false);
@@ -156,5 +163,8 @@ private static double Penalty(string candidateText, string originalText)
156163

157164
return 0;
158165
}
166+
167+
public readonly bool LastCacheResultIs(bool areSimilar, string candidateText)
168+
=> _lastAreSimilarResult.AreSimilar == areSimilar && _lastAreSimilarResult.CandidateText == candidateText;
159169
}
160170
}

0 commit comments

Comments
 (0)