Skip to content

Commit ef02c07

Browse files
authored
Change SupportedPlatformData to use ImmutableArrays (#76658)
* Change SupportedPlatformData to use ImmutableArrays for it's data I'm looking at a different change to completion and the usage of IEnumerable and List in SupportedPlatformData was tripping it up. It's easy enough to just have it use ImmutableArrays instead. One concern would be additional allocations due to the IEnumerable -> ImmutableArray conversion. However, I don't think is an issue as the IEnumerable member is always passed in from a List except from AbstractSignatureHelpProvider. In that case, it's a constructed Linq Select/Concat expression, which is reused multiple times (and potentially enumerated multiple times in SupportedPlatformData). It's extremely likely that it's better to just realize that array once.
1 parent 05407a5 commit ef02c07

File tree

5 files changed

+34
-29
lines changed

5 files changed

+34
-29
lines changed

src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ private ImmutableArray<CompletionItem> CreateItems(
9898
CompletionContext completionContext,
9999
ImmutableArray<SymbolAndSelectionInfo> symbols,
100100
Func<SymbolAndSelectionInfo, TSyntaxContext> contextLookup,
101-
Dictionary<ISymbol, List<ProjectId>>? invalidProjectMap,
102-
List<ProjectId>? totalProjects)
101+
Dictionary<ISymbol, ArrayBuilder<ProjectId>>? invalidProjectMap,
102+
ImmutableArray<ProjectId> totalProjects)
103103
{
104104
// We might get symbol w/o name but CanBeReferencedByName is still set to true,
105105
// need to filter them out.
@@ -197,21 +197,21 @@ protected static bool TryFindFirstSymbolMatchesTargetTypes(
197197
private static SupportedPlatformData? ComputeSupportedPlatformData(
198198
CompletionContext completionContext,
199199
ImmutableArray<SymbolAndSelectionInfo> symbols,
200-
Dictionary<ISymbol, List<ProjectId>>? invalidProjectMap,
201-
List<ProjectId>? totalProjects)
200+
Dictionary<ISymbol, ArrayBuilder<ProjectId>>? invalidProjectMap,
201+
ImmutableArray<ProjectId> totalProjects)
202202
{
203203
SupportedPlatformData? supportedPlatformData = null;
204204
if (invalidProjectMap != null)
205205
{
206-
List<ProjectId>? invalidProjects = null;
206+
ArrayBuilder<ProjectId>? invalidProjects = null;
207207
foreach (var symbol in symbols)
208208
{
209209
if (invalidProjectMap.TryGetValue(symbol.Symbol, out invalidProjects))
210210
break;
211211
}
212212

213213
if (invalidProjects != null)
214-
supportedPlatformData = new SupportedPlatformData(completionContext.Document.Project.Solution, invalidProjects, totalProjects);
214+
supportedPlatformData = new SupportedPlatformData(completionContext.Document.Project.Solution, invalidProjects.ToImmutable(), totalProjects);
215215
}
216216

217217
return supportedPlatformData;
@@ -299,7 +299,7 @@ private async Task<ImmutableArray<CompletionItem>> GetItemsAsync(
299299
if (relatedDocumentIds.IsEmpty)
300300
{
301301
var itemsForCurrentDocument = await GetSymbolsAsync(completionContext, syntaxContext, position, options, cancellationToken).ConfigureAwait(false);
302-
return CreateItems(completionContext, itemsForCurrentDocument, _ => syntaxContext, invalidProjectMap: null, totalProjects: null);
302+
return CreateItems(completionContext, itemsForCurrentDocument, _ => syntaxContext, invalidProjectMap: null, totalProjects: []);
303303
}
304304

305305
using var _ = PooledDictionary<DocumentId, int>.GetInstance(out var documentIdToIndex);
@@ -315,10 +315,15 @@ private async Task<ImmutableArray<CompletionItem>> GetItemsAsync(
315315

316316
var symbolToContextMap = UnionSymbols(contextAndSymbolLists);
317317
var missingSymbolsMap = FindSymbolsMissingInLinkedContexts(symbolToContextMap, contextAndSymbolLists);
318-
var totalProjects = contextAndSymbolLists.Select(t => t.documentId.ProjectId).ToList();
318+
var totalProjects = contextAndSymbolLists.SelectAsArray(t => t.documentId.ProjectId);
319319

320-
return CreateItems(
320+
var items = CreateItems(
321321
completionContext, [.. symbolToContextMap.Keys], symbol => symbolToContextMap[symbol], missingSymbolsMap, totalProjects);
322+
323+
foreach (var (_, builder) in missingSymbolsMap)
324+
builder.Free();
325+
326+
return items;
322327
}
323328

324329
protected virtual bool IsExclusive()
@@ -395,17 +400,17 @@ protected async Task<ImmutableArray<SymbolAndSelectionInfo>> TryGetSymbolsForCon
395400
/// <param name="symbolToContext">The symbols recommended in the active context.</param>
396401
/// <param name="linkedContextSymbolLists">The symbols recommended in linked documents</param>
397402
/// <returns>The list of projects each recommended symbol did NOT appear in.</returns>
398-
private static Dictionary<ISymbol, List<ProjectId>> FindSymbolsMissingInLinkedContexts(
403+
private static Dictionary<ISymbol, ArrayBuilder<ProjectId>> FindSymbolsMissingInLinkedContexts(
399404
Dictionary<SymbolAndSelectionInfo, TSyntaxContext> symbolToContext,
400405
ImmutableArray<(DocumentId documentId, TSyntaxContext syntaxContext, ImmutableArray<SymbolAndSelectionInfo> symbols)> linkedContextSymbolLists)
401406
{
402-
var missingSymbols = new Dictionary<ISymbol, List<ProjectId>>(LinkedFilesSymbolEquivalenceComparer.Instance);
407+
var missingSymbols = new Dictionary<ISymbol, ArrayBuilder<ProjectId>>(LinkedFilesSymbolEquivalenceComparer.Instance);
403408

404409
foreach (var (documentId, syntaxContext, symbols) in linkedContextSymbolLists)
405410
{
406411
var symbolsMissingInLinkedContext = symbolToContext.Keys.Except(symbols);
407412
foreach (var (symbol, _) in symbolsMissingInLinkedContext)
408-
missingSymbols.GetOrAdd(symbol, m => []).Add(documentId.ProjectId);
413+
missingSymbols.GetOrAdd(symbol, m => ArrayBuilder<ProjectId>.GetInstance()).Add(documentId.ProjectId);
409414
}
410415

411416
return missingSymbols;

src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,8 @@ private static void AddSupportedPlatforms(ArrayBuilder<KeyValuePair<string, stri
236236
{
237237
return new SupportedPlatformData(
238238
solution,
239-
[.. invalidProjects.Split(s_projectSeperators).Select(s => ProjectId.CreateFromSerialized(Guid.Parse(s)))],
240-
candidateProjects.Split(s_projectSeperators).Select(s => ProjectId.CreateFromSerialized(Guid.Parse(s))).ToList());
239+
invalidProjects.Split(s_projectSeperators).SelectAsArray(s => ProjectId.CreateFromSerialized(Guid.Parse(s))),
240+
candidateProjects.Split(s_projectSeperators).SelectAsArray(s => ProjectId.CreateFromSerialized(Guid.Parse(s))));
241241
}
242242

243243
return null;

src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Threading.Tasks;
1111
using Microsoft.CodeAnalysis.Host;
1212
using Microsoft.CodeAnalysis.LanguageService;
13+
using Microsoft.CodeAnalysis.PooledObjects;
1314
using Microsoft.CodeAnalysis.Shared.Collections;
1415
using Microsoft.CodeAnalysis.Shared.Extensions;
1516
using Microsoft.CodeAnalysis.Shared.Utilities;
@@ -86,9 +87,6 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf
8687
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
8788
var mainTokenInformation = BindToken(services, semanticModel, token, cancellationToken);
8889

89-
var candidateProjects = new List<ProjectId> { document.Project.Id };
90-
var invalidProjects = new List<ProjectId>();
91-
9290
var candidateResults = new List<(DocumentId docId, TokenInformation tokenInformation)>
9391
{
9492
(document.Id, mainTokenInformation)
@@ -103,7 +101,6 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf
103101
if (linkedToken != default)
104102
{
105103
// Not in an inactive region, so this file is a candidate.
106-
candidateProjects.Add(linkedDocumentId.ProjectId);
107104
var linkedSymbols = BindToken(services, linkedModel, linkedToken, cancellationToken);
108105
candidateResults.Add((linkedDocumentId, linkedSymbols));
109106
}
@@ -117,7 +114,11 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf
117114
if (bestBinding.tokenInformation.Symbols.IsDefaultOrEmpty)
118115
return default;
119116

117+
// We calculate the set of projects that are candidates for the best binding
118+
var candidateProjects = candidateResults.SelectAsArray(result => result.docId.ProjectId);
119+
120120
// We calculate the set of supported projects
121+
using var _ = ArrayBuilder<ProjectId>.GetInstance(out var invalidProjects);
121122
candidateResults.Remove(bestBinding);
122123
foreach (var (docId, tokenInformation) in candidateResults)
123124
{
@@ -126,7 +127,7 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf
126127
invalidProjects.Add(docId.ProjectId);
127128
}
128129

129-
var supportedPlatforms = new SupportedPlatformData(solution, invalidProjects, candidateProjects);
130+
var supportedPlatforms = new SupportedPlatformData(solution, invalidProjects.ToImmutableAndClear(), candidateProjects);
130131
return (bestBinding.tokenInformation, supportedPlatforms);
131132
}
132133

src/Features/Core/Portable/Shared/Utilities/SupportedPlatformData.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,26 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
#nullable disable
6-
75
using System.Collections.Generic;
6+
using System.Collections.Immutable;
87
using System.Linq;
98
using Microsoft.CodeAnalysis.Shared.Extensions;
109
using Roslyn.Utilities;
1110

1211
namespace Microsoft.CodeAnalysis.Shared.Utilities;
1312

14-
internal sealed class SupportedPlatformData(Solution solution, List<ProjectId> invalidProjects, IEnumerable<ProjectId> candidateProjects)
13+
internal sealed class SupportedPlatformData(Solution solution, ImmutableArray<ProjectId> invalidProjects, ImmutableArray<ProjectId> candidateProjects)
1514
{
1615
// Because completion finds lots of symbols that exist in
1716
// all projects, we'll instead maintain a list of projects
1817
// missing the symbol.
19-
public readonly List<ProjectId> InvalidProjects = invalidProjects;
20-
public readonly IEnumerable<ProjectId> CandidateProjects = candidateProjects;
18+
public readonly ImmutableArray<ProjectId> InvalidProjects = invalidProjects;
19+
public readonly ImmutableArray<ProjectId> CandidateProjects = candidateProjects;
2120
public readonly Solution Solution = solution;
2221

2322
public IList<SymbolDisplayPart> ToDisplayParts()
2423
{
25-
if (InvalidProjects == null || InvalidProjects.Count == 0)
24+
if (InvalidProjects.Length == 0)
2625
return [];
2726

2827
var builder = new List<SymbolDisplayPart>();
@@ -46,5 +45,5 @@ private static string Supported(bool supported)
4645
=> supported ? FeaturesResources.Available : FeaturesResources.Not_Available;
4746

4847
public bool HasValidAndInvalidProjects()
49-
=> InvalidProjects.Any() && InvalidProjects.Count != CandidateProjects.Count();
48+
=> InvalidProjects.Length > 0 && InvalidProjects.Length != CandidateProjects.Length;
5049
}

src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ private static SignatureHelpSymbolParameter ReplaceStructuralTypes(
254254
return itemsForCurrentDocument;
255255
}
256256

257-
var totalProjects = relatedDocuments.Select(d => d.Project.Id).Concat(document.Project.Id);
257+
var totalProjects = relatedDocuments.Concat(document).SelectAsArray(d => d.Project.Id);
258258

259259
var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false);
260260
var compilation = semanticModel.Compilation;
@@ -277,7 +277,7 @@ symbolKeyItem.SymbolKey is not SymbolKey symbolKey ||
277277
symbolKey = SymbolKey.Create(methodSymbol.OriginalDefinition, cancellationToken);
278278
}
279279

280-
var invalidProjectsForCurrentSymbol = new List<ProjectId>();
280+
using var _ = ArrayBuilder<ProjectId>.GetInstance(out var invalidProjectsForCurrentSymbol);
281281
foreach (var relatedDocument in relatedDocuments)
282282
{
283283
// Try to resolve symbolKey in each related compilation,
@@ -289,7 +289,7 @@ symbolKeyItem.SymbolKey is not SymbolKey symbolKey ||
289289
}
290290
}
291291

292-
var platformData = new SupportedPlatformData(document.Project.Solution, invalidProjectsForCurrentSymbol, totalProjects);
292+
var platformData = new SupportedPlatformData(document.Project.Solution, invalidProjectsForCurrentSymbol.ToImmutableAndClear(), totalProjects);
293293
finalItems.Add(UpdateItem(item, platformData));
294294
}
295295

0 commit comments

Comments
 (0)