diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs index c42bd319da702..9a7e27a3f107c 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs @@ -108,11 +108,23 @@ private ImmutableArray CreateItems( // We might get symbol w/o name but CanBeReferencedByName is still set to true, // need to filter them out. // https://github.com/dotnet/roslyn/issues/47690 - var symbolGroups = from symbol in symbols - let texts = GetDisplayAndSuffixAndInsertionText(symbol.Symbol, contextLookup(symbol)) - where !string.IsNullOrWhiteSpace(texts.displayText) - group symbol by texts into g - select g; + // + // Use SymbolReferenceEquivalenceComparer.Instance as the value comparer as we + // don't want symbols with just the same name to necessarily match + // (as the default comparer on SymbolAndSelectionInfo does) + var symbolGroups = new MultiDictionary<(string displayText, string suffix, string insertionText), SymbolAndSelectionInfo>( + capacity: symbols.Length, + comparer: EqualityComparer<(string, string, string)>.Default, + valueComparer: SymbolReferenceEquivalenceComparer.Instance); + + foreach (var symbol in symbols) + { + var texts = GetDisplayAndSuffixAndInsertionText(symbol.Symbol, contextLookup(symbol)); + if (!string.IsNullOrWhiteSpace(texts.displayText)) + { + symbolGroups.Add(texts, symbol); + } + } using var _ = ArrayBuilder.GetInstance(out var itemListBuilder); var typeConvertibilityCache = new Dictionary(SymbolEqualityComparer.Default); @@ -120,8 +132,12 @@ group symbol by texts into g foreach (var symbolGroup in symbolGroups) { var includeItemInTargetTypedCompletion = false; - var arbitraryFirstContext = contextLookup(symbolGroup.First()); - var symbolList = symbolGroup.ToImmutableArray(); + using var symbolListBuilder = TemporaryArray.Empty; + foreach (var symbol in symbolGroup.Value) + symbolListBuilder.Add(symbol); + + var symbolList = symbolListBuilder.ToImmutableAndClear(); + var arbitraryFirstContext = contextLookup(symbolList[0]); if (completionContext.CompletionOptions.TargetTypedCompletionFilter) { @@ -151,6 +167,20 @@ group symbol by texts into g return itemListBuilder.ToImmutableAndClear(); } + /// + /// Alternative comparer to SymbolAndSelectionInfo's default which considers both the full symbol and preselect. + /// + private sealed class SymbolReferenceEquivalenceComparer : IEqualityComparer + { + public static readonly SymbolReferenceEquivalenceComparer Instance = new(); + + public bool Equals(SymbolAndSelectionInfo x, SymbolAndSelectionInfo y) + => x.Symbol == y.Symbol && x.Preselect == y.Preselect; + + public int GetHashCode(SymbolAndSelectionInfo symbol) + => Hash.Combine(symbol.Symbol.GetHashCode(), symbol.Preselect ? 1 : 0); + } + protected static bool TryFindFirstSymbolMatchesTargetTypes( Func contextLookup, ImmutableArray symbolList,