Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Where+Select
  • Loading branch information
CyrusNajmabadi committed Aug 5, 2025
commit 1d162fe7b1d5c98dc872533c5966926c6fd49917
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ private static async Task<Solution> FixAllByDocumentAsync(
// the tree won't be the same.
var documentIdToDiagnosticsMap = diagnostics
.GroupBy(diagnostic => diagnostic.Location.SourceTree)
.Where(group => group.Key is not null)
.SelectAsArray(group => (id: solution.GetRequiredDocument(group.Key!).Id, diagnostics: group.ToImmutableArray()));
.SelectAsArray(group => group.Key is not null, group => (id: solution.GetRequiredDocument(group.Key!).Id, diagnostics: group.ToImmutableArray()));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where+SelectAsArray becomes SelectAsArray (with predicate passed to the latter).


var newSolution = solution;

Expand Down
132 changes: 77 additions & 55 deletions src/Dependencies/Collections/Extensions/IEnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -330,10 +330,38 @@ public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)

return source.Where((Func<T?, bool>)s_notNullTest)!;
}
private static bool TryGetBuilder<TSource, TResult>(
[NotNullWhen(true)] IEnumerable<TSource>? source,
[NotNullWhen(true)] out ArrayBuilder<TResult>? builder)
{
if (source is null)
{
builder = null;
return false;
}

#if NET
if (source.TryGetNonEnumeratedCount(out var count))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reasonably optimize further if the count is < 4, using TemporaryArray?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems hard to do in a generalized fashion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or, put another way. can i postpone that till later. i'd rather get this in and focus on other stuff for now.

{
if (count == 0)
{
builder = null;
return false;
}

builder = ArrayBuilder<TResult>.GetInstance(count);
return true;
}
#endif

builder = ArrayBuilder<TResult>.GetInstance();
return true;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optimized a bunch of these helpers if the count can be determined (fast path to returning empty array, and also properly sizing the dest buffer).


public static ImmutableArray<T> WhereAsArray<T, TArg>(this IEnumerable<T> values, Func<T, TArg, bool> predicate, TArg arg)
{
var result = ArrayBuilder<T>.GetInstance();
if (!TryGetBuilder<T, T>(values, out var result))
return [];

foreach (var value in values)
{
Expand All @@ -349,25 +377,34 @@ public static T[] AsArray<T>(this IEnumerable<T> source)

public static ImmutableArray<TResult> SelectAsArray<TSource, TResult>(this IEnumerable<TSource>? source, Func<TSource, TResult> selector)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extensions that work on null receiver are odd.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't disagree. however, a lot of collect/ienumrable methods do this. so this is keeping things mostly consistent. would prefer to not rock the boat there for now.

{
if (source == null)
{
return ImmutableArray<TResult>.Empty;
}
if (!TryGetBuilder<TSource, TResult>(source, out var builder))
return [];

var builder = ArrayBuilder<TResult>.GetInstance();
builder.AddRange(source.Select(selector));
return builder.ToImmutableAndFree();

}

public static ImmutableArray<TResult> SelectAsArray<TItem, TResult>(this IEnumerable<TItem>? source, Func<TItem, bool> predicate, Func<TItem, TResult> selector)
{
if (!TryGetBuilder<TItem, TResult>(source, out var builder))
return [];

foreach (var item in source)
{
if (predicate(item))
builder.Add(selector(item));
}

return builder.ToImmutableAndFree();
}

public static ImmutableArray<TResult> SelectAsArray<TSource, TResult>(this IEnumerable<TSource>? source, Func<TSource, int, TResult> selector)
{
if (source == null)
return ImmutableArray<TResult>.Empty;
if (!TryGetBuilder<TSource, TResult>(source, out var builder))
return [];

var builder = ArrayBuilder<TResult>.GetInstance();

int index = 0;
var index = 0;
foreach (var element in source)
{
builder.Add(selector(element, index));
Expand All @@ -379,42 +416,33 @@ public static ImmutableArray<TResult> SelectAsArray<TSource, TResult>(this IEnum

public static ImmutableArray<TResult> SelectAsArray<TSource, TResult>(this IReadOnlyCollection<TSource>? source, Func<TSource, TResult> selector)
{
if (source == null)
return ImmutableArray<TResult>.Empty;
if (source is null or { Count: 0 })
return [];

var builder = new TResult[source.Count];
var index = 0;
var builder = new FixedSizeArrayBuilder<TResult>(source.Count);
foreach (var item in source)
{
builder[index] = selector(item);
index++;
}
builder.Add(selector(item));

return ImmutableCollectionsMarshal.AsImmutableArray(builder);
return builder.MoveToImmutable();
}

public static ImmutableArray<TResult> SelectAsArray<TSource, TResult, TArg>(this IReadOnlyCollection<TSource>? source, Func<TSource, TArg, TResult> selector, TArg arg)
{
if (source == null)
return ImmutableArray<TResult>.Empty;
if (source is null or { Count: 0 })
return [];

var builder = new TResult[source.Count];
var index = 0;
var builder = new FixedSizeArrayBuilder<TResult>(source.Count);
foreach (var item in source)
{
builder[index] = selector(item, arg);
index++;
}
builder.Add(selector(item, arg));

return ImmutableCollectionsMarshal.AsImmutableArray(builder);
return builder.MoveToImmutable();
}

public static ImmutableArray<TResult> SelectManyAsArray<TSource, TResult>(this IEnumerable<TSource>? source, Func<TSource, IEnumerable<TResult>> selector)
{
if (source == null)
return ImmutableArray<TResult>.Empty;
if (!TryGetBuilder<TSource, TResult>(source, out var builder))
return [];

var builder = ArrayBuilder<TResult>.GetInstance();
foreach (var item in source)
builder.AddRange(selector(item));

Expand All @@ -423,10 +451,9 @@ public static ImmutableArray<TResult> SelectManyAsArray<TSource, TResult>(this I

public static ImmutableArray<TResult> SelectManyAsArray<TItem, TArg, TResult>(this IEnumerable<TItem>? source, Func<TItem, TArg, IEnumerable<TResult>> selector, TArg arg)
{
if (source == null)
return ImmutableArray<TResult>.Empty;
if (!TryGetBuilder<TItem, TResult>(source, out var builder))
return [];

var builder = ArrayBuilder<TResult>.GetInstance();
foreach (var item in source)
builder.AddRange(selector(item, arg));

Expand All @@ -435,8 +462,8 @@ public static ImmutableArray<TResult> SelectManyAsArray<TItem, TArg, TResult>(th

public static ImmutableArray<TResult> SelectManyAsArray<TItem, TResult>(this IReadOnlyCollection<TItem>? source, Func<TItem, IEnumerable<TResult>> selector)
{
if (source == null)
return ImmutableArray<TResult>.Empty;
if (source is null or { Count: 0 })
return [];

// Basic heuristic. Assume each element in the source adds one item to the result.
var builder = ArrayBuilder<TResult>.GetInstance(source.Count);
Expand All @@ -448,8 +475,8 @@ public static ImmutableArray<TResult> SelectManyAsArray<TItem, TResult>(this IRe

public static ImmutableArray<TResult> SelectManyAsArray<TItem, TArg, TResult>(this IReadOnlyCollection<TItem>? source, Func<TItem, TArg, IEnumerable<TResult>> selector, TArg arg)
{
if (source == null)
return ImmutableArray<TResult>.Empty;
if (source is null or { Count: 0 })
return [];

// Basic heuristic. Assume each element in the source adds one item to the result.
var builder = ArrayBuilder<TResult>.GetInstance(source.Count);
Expand All @@ -461,10 +488,9 @@ public static ImmutableArray<TResult> SelectManyAsArray<TItem, TArg, TResult>(th

public static ImmutableArray<TResult> SelectManyAsArray<TSource, TResult>(this IEnumerable<TSource>? source, Func<TSource, OneOrMany<TResult>> selector)
{
if (source == null)
return ImmutableArray<TResult>.Empty;
if (!TryGetBuilder<TSource, TResult>(source, out var builder))
return [];

var builder = ArrayBuilder<TResult>.GetInstance();
foreach (var item in source)
selector(item).AddRangeTo(builder);

Expand All @@ -476,12 +502,11 @@ public static ImmutableArray<TResult> SelectManyAsArray<TSource, TResult>(this I
/// </summary>
public static async ValueTask<ImmutableArray<TResult>> SelectAsArrayAsync<TItem, TResult>(this IEnumerable<TItem> source, Func<TItem, ValueTask<TResult>> selector)
{
var builder = ArrayBuilder<TResult>.GetInstance();
if (!TryGetBuilder<TItem, TResult>(source, out var builder))
return [];

foreach (var item in source)
{
builder.Add(await selector(item).ConfigureAwait(false));
}

return builder.ToImmutableAndFree();
}
Expand All @@ -491,12 +516,11 @@ public static async ValueTask<ImmutableArray<TResult>> SelectAsArrayAsync<TItem,
/// </summary>
public static async ValueTask<ImmutableArray<TResult>> SelectAsArrayAsync<TItem, TResult>(this IEnumerable<TItem> source, Func<TItem, CancellationToken, ValueTask<TResult>> selector, CancellationToken cancellationToken)
{
var builder = ArrayBuilder<TResult>.GetInstance();
if (!TryGetBuilder<TItem, TResult>(source, out var builder))
return [];

foreach (var item in source)
{
builder.Add(await selector(item, cancellationToken).ConfigureAwait(false));
}

return builder.ToImmutableAndFree();
}
Expand All @@ -506,24 +530,22 @@ public static async ValueTask<ImmutableArray<TResult>> SelectAsArrayAsync<TItem,
/// </summary>
public static async ValueTask<ImmutableArray<TResult>> SelectAsArrayAsync<TItem, TArg, TResult>(this IEnumerable<TItem> source, Func<TItem, TArg, CancellationToken, ValueTask<TResult>> selector, TArg arg, CancellationToken cancellationToken)
{
var builder = ArrayBuilder<TResult>.GetInstance();
if (!TryGetBuilder<TItem, TResult>(source, out var builder))
return [];

foreach (var item in source)
{
builder.Add(await selector(item, arg, cancellationToken).ConfigureAwait(false));
}

return builder.ToImmutableAndFree();
}

public static async ValueTask<ImmutableArray<TResult>> SelectManyAsArrayAsync<TItem, TArg, TResult>(this IEnumerable<TItem> source, Func<TItem, TArg, CancellationToken, ValueTask<IEnumerable<TResult>>> selector, TArg arg, CancellationToken cancellationToken)
{
var builder = ArrayBuilder<TResult>.GetInstance();
if (!TryGetBuilder<TItem, TResult>(source, out var builder))
return [];

foreach (var item in source)
{
builder.AddRange(await selector(item, arg, cancellationToken).ConfigureAwait(false));
}

return builder.ToImmutableAndFree();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ protected override ImmutableArray<TextSpan> GetBlockCommentsInDocument(Document
var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken);
// Only search for block comments intersecting the lines in the selections.
return root.DescendantTrivia(linesContainingSelections)
.Where(trivia => trivia.Kind() is SyntaxKind.MultiLineCommentTrivia or SyntaxKind.MultiLineDocumentationCommentTrivia)
.SelectAsArray(blockCommentTrivia => blockCommentTrivia.Span);
.SelectAsArray(trivia => trivia.Kind() is SyntaxKind.MultiLineCommentTrivia or SyntaxKind.MultiLineDocumentationCommentTrivia, blockCommentTrivia => blockCommentTrivia.Span);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal abstract class AbstractBraceCompletionServiceFactory(
string languageName) : IBraceCompletionServiceFactory
{
private readonly ImmutableArray<IBraceCompletionService> _braceCompletionServices =
braceCompletionServices.Where(s => s.Metadata.Language == languageName).SelectAsArray(s => s.Value);
braceCompletionServices.SelectAsArray(s => s.Metadata.Language == languageName, s => s.Value);

public IBraceCompletionService? TryGetService(ParsedDocument document, int openingPosition, char openingBrace, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ when node.Parent.IsKind(SyntaxKind.IncompleteMember):
var text = await Document.GetValueTextAsync(CancellationToken).ConfigureAwait(false);
text.GetLineAndOffset(namePosition, out var line, out var lineOffset);
var items = symbol.GetMembers()
.Where(FilterInterfaceMember)
.SelectAsArray(CreateCompletionItem);
.SelectAsArray(FilterInterfaceMember, CreateCompletionItem);

return items;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,7 @@ internal static void MergePartialEdits(

if (partialTypeEdits.Any(static e => e.SyntaxMaps.HasMap))
{
var newMaps = partialTypeEdits.Where(static edit => edit.SyntaxMaps.HasMap).SelectAsArray(static edit => edit.SyntaxMaps);
var newMaps = partialTypeEdits.SelectAsArray(static edit => edit.SyntaxMaps.HasMap, static edit => edit.SyntaxMaps);

mergedMatchingNodes = node => newMaps[newMaps.IndexOf(static (m, node) => m.NewTree == node.SyntaxTree, node)].MatchingNodes!(node);
mergedRuntimeRudeEdits = node => newMaps[newMaps.IndexOf(static (m, node) => m.NewTree == node.SyntaxTree, node)].RuntimeRudeEdits?.Invoke(node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,7 @@
.GetProjectDependencyGraph()
.GetDependencySets(cancellationToken)
.SelectAsArray(projectIdSet =>
projectIdSet.Where(id => allProjectIdSet.Contains(id))
.SelectAsArray(id => _solution.GetRequiredProject(id)));
projectIdSet .SelectAsArray(id => allProjectIdSet.Contains(id), id => _solution.GetRequiredProject(id)));

Check failure on line 490 in src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs#L490

src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs(490,29): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)

Check failure on line 490 in src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs#L490

src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs(490,29): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)

Contract.ThrowIfFalse(orderedProjects.SelectMany(s => s).Count() == filteredProjects.SelectMany(s => s).Count());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ private ImmutableArray<ISignatureHelpProvider> GetProviders(string language)
{
return _providersByLanguage.GetOrAdd(language, language =>
_allProviders
.Where(p => p.Metadata.Language == language)
.SelectAsArray(p => p.Value));
.SelectAsArray(p => p.Metadata.Language == language, p => p.Value));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ private static ImmutableDictionary<string, int> GetUniqueHashedFileExtensionsAnd
var sourceFiles = projectFileInfo.Documents
.Concat(projectFileInfo.AdditionalDocuments)
.Concat(projectFileInfo.AnalyzerConfigDocuments)
.Where(d => !d.IsGenerated)
.SelectAsArray(d => d.FilePath);
.SelectAsArray(d => !d.IsGenerated, d => d.FilePath);
var allFiles = contentFiles.Concat(sourceFiles);
var fileCounts = new Dictionary<string, int>();
foreach (var file in allFiles)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ public DiagnosticSourceManager([ImportMany] IEnumerable<IDiagnosticSourceProvide
}

public ImmutableArray<string> GetDocumentSourceProviderNames(ClientCapabilities clientCapabilities)
=> _nameToDocumentProviderMap.Where(kvp => kvp.Value.IsEnabled(clientCapabilities)).SelectAsArray(kvp => kvp.Key);
=> _nameToDocumentProviderMap.SelectAsArray(kvp => kvp.Value.IsEnabled(clientCapabilities), kvp => kvp.Key);

public ImmutableArray<string> GetWorkspaceSourceProviderNames(ClientCapabilities clientCapabilities)
=> _nameToWorkspaceProviderMap.Where(kvp => kvp.Value.IsEnabled(clientCapabilities)).SelectAsArray(kvp => kvp.Key);
=> _nameToWorkspaceProviderMap.SelectAsArray(kvp => kvp.Value.IsEnabled(clientCapabilities), kvp => kvp.Key);

public ValueTask<ImmutableArray<IDiagnosticSource>> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken)
=> CreateDiagnosticSourcesAsync(context, providerName, _nameToDocumentProviderMap, isDocument: true, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ internal sealed class OnAutoInsertHandler(
if (!onAutoInsertEnabled)
return SpecializedTasks.Null<LSP.VSInternalDocumentOnAutoInsertResponseItem>();

var servicesForDocument = _braceCompletionServices.Where(s => s.Metadata.Language == document.Project.Language).SelectAsArray(s => s.Value);
var servicesForDocument = _braceCompletionServices.SelectAsArray(s => s.Metadata.Language == document.Project.Language, s => s.Value);
var isRazorRequest = context.ServerKind == WellKnownLspServerKinds.RazorLspServer;
var position = ProtocolConversions.PositionToLinePosition(request.Position);
return GetOnAutoInsertResponseAsync(_globalOptions, servicesForDocument, document, position, request.Character, request.Options, isRazorRequest, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,7 @@ .. registeredWorkspaces.Where(workspace => workspace.Kind == WorkspaceKind.Misce
_trackedDocuments.Keys.Where(static trackedDocument => trackedDocument.ParsedUri?.Scheme == SourceGeneratedDocumentUri.Scheme)
// We know we have a non null URI with a source generated scheme.
.Select(uri => (identity: SourceGeneratedDocumentUri.DeserializeIdentity(workspaceCurrentSolution, uri.ParsedUri!), _trackedDocuments[uri].Text))
.Where(tuple => tuple.identity.HasValue)
.SelectAsArray(tuple => (tuple.identity!.Value, DateTime.Now, tuple.Text));
.SelectAsArray(tuple => tuple.identity.HasValue, tuple => (tuple.identity!.Value, DateTime.Now, tuple.Text));

// First we check if normal document text matches the workspace solution.
// This does not look at source generated documents.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal static class OnAutoInsert
public static Task<VSInternalDocumentOnAutoInsertResponseItem?> GetOnAutoInsertResponseAsync(Document document, LinePosition linePosition, string character, FormattingOptions formattingOptions, CancellationToken cancellationToken)
{
var globalOptions = document.Project.Solution.Services.ExportProvider.GetService<IGlobalOptionService>();
var services = document.Project.Solution.Services.ExportProvider.GetExports<IBraceCompletionService, LanguageMetadata>().Where(s => s.Metadata.Language == LanguageNames.CSharp).SelectAsArray(s => s.Value);
var services = document.Project.Solution.Services.ExportProvider.GetExports<IBraceCompletionService, LanguageMetadata>().SelectAsArray(s => s.Metadata.Language == LanguageNames.CSharp, s => s.Value);

return OnAutoInsertHandler.GetOnAutoInsertResponseAsync(globalOptions, services, document, linePosition, character, formattingOptions, isRazorRequest: true, cancellationToken);
}
Expand Down
Loading
Loading