Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,59 @@ public TagHelperCollection GetTagHelpers(IAssemblySymbol assembly, CancellationT
builder.Dispose();
}
}

/// <summary>
/// Discovers tag helpers for a known set of types rather than walking an entire assembly.
/// </summary>
/// <remarks>
/// Only type producers run here; assembly-level static tag helpers are intentionally skipped because a
/// caller with a specific type set already owns discovery of the rest of the assembly. Producers examine
/// each type independently, so restricting the input to a subset yields the same descriptors those types
/// would produce during a full assembly walk. Results are not assembly-cached because the input is a
/// per-request slice rather than a whole assembly.
/// </remarks>
public TagHelperCollection GetTagHelpers(ImmutableArray<INamedTypeSymbol> types, CancellationToken cancellationToken = default)
{
if (producers.IsDefaultOrEmpty || types.IsDefaultOrEmpty)
{
return TagHelperCollection.Empty;
}

var builder = new TagHelperCollection.RefBuilder();
try
{
using var _ = ArrayPool<TagHelperProducer>.Shared.GetPooledArraySpan(
minimumLength: producers.Length, clearOnReturn: true, out var typeProducers);

var index = 0;
foreach (var producer in producers)
{
if (producer.SupportsTypes)
{
typeProducers[index++] = producer;
}
}

typeProducers = typeProducers[..index];

foreach (var type in types)
{
cancellationToken.ThrowIfCancellationRequested();

foreach (var producer in typeProducers)
{
if (producer.IsCandidateType(type))
{
producer.AddTagHelpersForType(type, ref builder, cancellationToken);
}
}
}

return builder.ToCollection();
}
finally
{
builder.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// 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.Text;
using System.Threading;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.PooledObjects;
Expand Down Expand Up @@ -132,6 +134,44 @@ private static StaticCompilationTagHelperFeature GetStaticTagHelperFeature(Compi
return tagHelperFeature;
}

/// <summary>
/// Resolves the fallback component type symbols so the slow discovery path can target just those
/// types instead of walking the whole augmented assembly. Uses the compilation's declaration table
/// (no semantic models): a fallback type name is namespace-qualified, so the fast predicate keys off
/// its final segment and over-selects; the caller's descriptor-name filter trims any collisions.
/// </summary>
private static ImmutableArray<INamedTypeSymbol> ResolveFallbackTypes(
Compilation compilation,
ImmutableHashSet<string> fallbackTypeNames,
CancellationToken cancellationToken)
{
if (fallbackTypeNames.IsEmpty)
{
return [];
}

var shortNames = new HashSet<string>(StringComparer.Ordinal);
foreach (var name in fallbackTypeNames)
{
var lastDot = name.LastIndexOf('.');
shortNames.Add(lastDot >= 0 ? name.Substring(lastDot + 1) : name);
}

using var builder = new PooledArrayBuilder<INamedTypeSymbol>();

foreach (var symbol in compilation.GetSymbolsWithName(shortNames.Contains, SymbolFilter.Type, cancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();

if (symbol is INamedTypeSymbol typeSymbol)
{
builder.Add(typeSymbol);
}
}

return builder.ToImmutable();
}

private static SourceGeneratorProjectEngine GetGenerationProjectEngine(
SourceGeneratorProjectItem item,
ImmutableArray<SourceGeneratorProjectItem> imports,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,29 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}

var augmented = compilation.AddSyntaxTrees(fallbackTrees);

// Discover only the fallback components' own types rather than re-walking the whole
// augmented assembly. fastDiscovery already covered every splittable component, so the
// full walk here would rediscover all of them just to keep the few fallback types. The
// fallback types are exactly the ones declared by the discovery-only decl trees we just
// added, and tag-helper producers examine each type independently, so discovering just
// those yields the same descriptors the full walk would for them.
var fallbackTypes = ResolveFallbackTypes(augmented, fallbackTypeNames, cancellationToken);
if (fallbackTypes.IsDefaultOrEmpty)
{
return TagHelperCollection.Empty;
}

var tagHelperFeature = GetStaticTagHelperFeature(augmented);
var all = tagHelperFeature.GetTagHelpers(augmented.Assembly, cancellationToken);
if (all.IsEmpty)
var discovered = tagHelperFeature.GetTagHelpers(fallbackTypes, cancellationToken);
if (discovered.IsEmpty)
{
return TagHelperCollection.Empty;
}

return all.Where(fallbackTypeNames, static (descriptor, names) => names.Contains(StripGenericArity(descriptor.TypeName)));
// A resolved type can be a partial that also produces a non-fallback descriptor name;
// keep only the fallback components' types, matching the ownership split with fastDiscovery.
return discovered.Where(fallbackTypeNames, static (descriptor, names) => names.Contains(StripGenericArity(descriptor.TypeName)));
})
.WithLambdaComparer(static (a, b) => a!.SequenceEqual(b!))
.WithTrackingName("SlowTagHelpers");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ public TagHelperCollection GetTagHelpers(IAssemblySymbol assembly, CancellationT
return _discoverer.GetTagHelpers(assembly, cancellationToken);
}

public TagHelperCollection GetTagHelpers(ImmutableArray<INamedTypeSymbol> types, CancellationToken cancellationToken)
{
if (_discoveryService is null)
{
return [];
}

if (_discoverer is null &&
!_discoveryService.TryGetDiscoverer(compilation, out _discoverer))
{
return [];
}

return _discoverer.GetTagHelpers(types, cancellationToken);
}

TagHelperCollection ITagHelperFeature.GetTagHelpers(CancellationToken cancellationToken)
{
if (_discoveryService is null)
Expand Down
Loading