using GFramework.SourceGenerators.Common.Constants;
namespace GFramework.Cqrs.SourceGenerators.Cqrs;
///
/// 为当前编译程序集生成 CQRS 处理器注册器,以减少运行时的程序集反射扫描成本。
///
[Generator]
public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
{
private const string CqrsContractsNamespace = $"{PathContests.CqrsAbstractionsNamespace}.Cqrs";
private const string CqrsRuntimeNamespace = PathContests.CqrsNamespace;
private const string LoggingNamespace = $"{PathContests.CoreAbstractionsNamespace}.Logging";
private const string IRequestHandlerMetadataName = $"{CqrsContractsNamespace}.IRequestHandler`2";
private const string INotificationHandlerMetadataName = $"{CqrsContractsNamespace}.INotificationHandler`1";
private const string IStreamRequestHandlerMetadataName = $"{CqrsContractsNamespace}.IStreamRequestHandler`2";
private const string ICqrsHandlerRegistryMetadataName = $"{CqrsRuntimeNamespace}.ICqrsHandlerRegistry";
private const string CqrsHandlerRegistryAttributeMetadataName =
$"{CqrsRuntimeNamespace}.CqrsHandlerRegistryAttribute";
private const string CqrsReflectionFallbackAttributeMetadataName =
$"{CqrsRuntimeNamespace}.CqrsReflectionFallbackAttribute";
private const string ILoggerMetadataName = $"{LoggingNamespace}.ILogger";
private const string IServiceCollectionMetadataName = "Microsoft.Extensions.DependencyInjection.IServiceCollection";
private const string GeneratedNamespace = "GFramework.Generated.Cqrs";
private const string GeneratedTypeName = "__GFrameworkGeneratedCqrsHandlerRegistry";
private const string HintName = "CqrsHandlerRegistry.g.cs";
private static readonly DiagnosticDescriptor MissingReflectionFallbackContractDiagnostic = new(
"GF_Cqrs_001",
"Cannot emit CQRS registry without reflection fallback contract",
"Cannot generate CQRS handler registry because fallback metadata is required for handler(s): {0}, but runtime contract '{1}' is unavailable",
"GFramework.Cqrs.SourceGenerators",
DiagnosticSeverity.Error,
true);
///
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var generationEnvironment = context.CompilationProvider
.Select(static (compilation, _) => CreateGenerationEnvironment(compilation));
// Restrict semantic analysis to type declarations that can actually contribute implemented interfaces.
var handlerCandidates = context.SyntaxProvider.CreateSyntaxProvider(
static (node, _) => IsHandlerCandidate(node),
static (syntaxContext, _) => TransformHandlerCandidate(syntaxContext))
.Where(static candidate => candidate is not null)
.Collect();
context.RegisterSourceOutput(
generationEnvironment.Combine(handlerCandidates),
static (productionContext, pair) => Execute(productionContext, pair.Left, pair.Right));
}
private static GenerationEnvironment CreateGenerationEnvironment(Compilation compilation)
{
var generationEnabled = compilation.GetTypeByMetadataName(IRequestHandlerMetadataName) is not null &&
compilation.GetTypeByMetadataName(INotificationHandlerMetadataName) is not null &&
compilation.GetTypeByMetadataName(IStreamRequestHandlerMetadataName) is not null &&
compilation.GetTypeByMetadataName(ICqrsHandlerRegistryMetadataName) is not null &&
compilation.GetTypeByMetadataName(
CqrsHandlerRegistryAttributeMetadataName) is not null &&
compilation.GetTypeByMetadataName(ILoggerMetadataName) is not null &&
compilation.GetTypeByMetadataName(IServiceCollectionMetadataName) is not null;
var supportsReflectionFallbackAttribute =
compilation.GetTypeByMetadataName(CqrsReflectionFallbackAttributeMetadataName) is not null;
return new GenerationEnvironment(generationEnabled, supportsReflectionFallbackAttribute);
}
private static bool IsHandlerCandidate(SyntaxNode node)
{
return node is TypeDeclarationSyntax
{
BaseList.Types.Count: > 0
};
}
private static HandlerCandidateAnalysis? TransformHandlerCandidate(GeneratorSyntaxContext context)
{
if (context.Node is not TypeDeclarationSyntax typeDeclaration)
return null;
if (context.SemanticModel.GetDeclaredSymbol(typeDeclaration) is not INamedTypeSymbol type)
return null;
if (!IsConcreteHandlerType(type))
return null;
var handlerInterfaces = GetSupportedHandlerInterfaces(type);
if (handlerInterfaces.IsDefaultOrEmpty)
return null;
return CreateHandlerCandidateAnalysis(context.SemanticModel.Compilation, type, handlerInterfaces);
}
///
/// 收集当前实现类型已经关闭的 CQRS handler 接口,并按稳定显示名排序以保证生成输出可重复。
///
/// 当前语义模型发现的具体 handler 实现类型。
/// 可由 CQRS 注册器生成器处理的 handler 接口集合。
private static ImmutableArray GetSupportedHandlerInterfaces(INamedTypeSymbol type)
{
return type.AllInterfaces
.Where(IsSupportedHandlerInterface)
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
.ToImmutableArray();
}
///
/// 将单个实现类型的 handler 接口拆分为直接注册、实现类型反射注册、精确反射注册和兜底 fallback 四类结果。
///
/// 当前生成轮次的编译上下文,用于判断类型可访问性。
/// 需要分析的 handler 实现类型。
/// 该实现类型声明的受支持 CQRS handler 接口。
/// 供最终生成阶段消费的 handler 候选分析结果。
private static HandlerCandidateAnalysis CreateHandlerCandidateAnalysis(
Compilation compilation,
INamedTypeSymbol type,
ImmutableArray handlerInterfaces)
{
var implementationTypeDisplayName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var implementationLogName = GetLogDisplayName(type);
var canReferenceImplementation = CanReferenceFromGeneratedRegistry(compilation, type);
var registrations = ImmutableArray.CreateBuilder(handlerInterfaces.Length);
var reflectedImplementationRegistrations =
ImmutableArray.CreateBuilder(handlerInterfaces.Length);
var preciseReflectedRegistrations =
ImmutableArray.CreateBuilder(handlerInterfaces.Length);
string? reflectionFallbackHandlerTypeMetadataName = null;
foreach (var handlerInterface in handlerInterfaces)
{
if (TryAddStaticHandlerRegistration(
compilation,
handlerInterface,
canReferenceImplementation,
implementationTypeDisplayName,
implementationLogName,
registrations,
reflectedImplementationRegistrations,
preciseReflectedRegistrations))
{
continue;
}
// Concrete closed handler contracts should now always map to either direct registrations,
// reflected implementation registrations, or precise runtime type references.
// If a future Roslyn type shape still slips through this net, keep the generator conservative:
// preserve the static registrations we do understand, and let the runtime recover the remaining
// interfaces via the existing assembly-level targeted reflection fallback contract.
reflectionFallbackHandlerTypeMetadataName ??= GetReflectionTypeMetadataName(type);
}
return new HandlerCandidateAnalysis(
implementationTypeDisplayName,
implementationLogName,
registrations.ToImmutable(),
reflectedImplementationRegistrations.ToImmutable(),
preciseReflectedRegistrations.ToImmutable(),
canReferenceImplementation ? null : GetReflectionTypeMetadataName(type),
reflectionFallbackHandlerTypeMetadataName);
}
///
/// 尝试为单个 handler 接口选择无需程序集级 fallback 的注册表示。
///
/// 当前生成轮次的编译上下文。
/// 正在分类的关闭 handler 接口。
/// 生成代码是否可直接引用实现类型。
/// 实现类型在生成源码中的全限定显示名。
/// 实现类型用于日志输出的稳定显示名。
/// 直接类型注册集合。
/// 实现类型需要反射解析、接口可直接引用的注册集合。
/// 接口类型需要运行时精确构造的注册集合。
///
/// 当当前接口已经被添加到某个静态注册集合时返回 ;否则调用方应记录 reflection fallback 元数据。
///
private static bool TryAddStaticHandlerRegistration(
Compilation compilation,
INamedTypeSymbol handlerInterface,
bool canReferenceImplementation,
string implementationTypeDisplayName,
string implementationLogName,
ImmutableArray.Builder registrations,
ImmutableArray.Builder reflectedImplementationRegistrations,
ImmutableArray.Builder preciseReflectedRegistrations)
{
var canReferenceHandlerInterface = CanReferenceFromGeneratedRegistry(compilation, handlerInterface);
if (canReferenceImplementation && canReferenceHandlerInterface)
{
registrations.Add(new HandlerRegistrationSpec(
handlerInterface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
implementationTypeDisplayName,
GetLogDisplayName(handlerInterface),
implementationLogName));
return true;
}
if (!canReferenceImplementation && canReferenceHandlerInterface)
{
reflectedImplementationRegistrations.Add(new ReflectedImplementationRegistrationSpec(
handlerInterface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
GetLogDisplayName(handlerInterface)));
return true;
}
if (!TryCreatePreciseReflectedRegistration(compilation, handlerInterface, out var preciseReflectedRegistration))
return false;
preciseReflectedRegistrations.Add(preciseReflectedRegistration);
return true;
}
///
/// 执行 CQRS handler registry 生成管线的最终发射阶段,负责将候选 handler 分析结果汇总为单个
/// CqrsHandlerRegistry.g.cs,并在需要时附带程序集级 reflection fallback 元数据。
///
/// 用于报告诊断并发射生成源码的源生产上下文。
///
/// 当前编译轮次可用的 runtime 合同快照。
/// 只有当 CQRS 注册器生成所需的基础契约齐备时,才允许继续生成;当存在
/// CqrsReflectionFallbackAttribute 时,才允许输出依赖 fallback 元数据恢复的注册结果。
///
///
/// 来自语法和语义分析阶段的 handler 候选结果。
/// 集合中可能包含 占位项,且同一实现类型可能因 partial 声明重复出现,后续会统一去重并聚合。
///
///
///
/// 该方法负责发射两类生成结果:注册器类型本体,以及在静态类型信息不足时用于运行时补全注册的程序集级
/// CqrsReflectionFallbackAttribute 元数据。生成这些结果的目标是把可静态确定的 handler 注册尽量前移到编译期,
/// 从而减少运行时程序集扫描成本,同时保留对少数复杂类型形态的兼容回退路径。
///
///
/// 该阶段依赖两个语义前提:一是 runtime 已提供 CQRS 注册器生成所需的基础合同;二是只要存在任何 handler
/// 需要通过 reflection fallback 恢复,就必须同时存在承载该元数据的
/// CqrsReflectionFallbackAttribute。如果基础合同缺失,生成器会静默跳过本轮发射;如果候选集合去重后没有任何可注册
/// handler,也会直接跳过 AddSource,避免输出空注册器。
///
///
/// 当 fallback handler 元数据非空但 runtime 缺少 CqrsReflectionFallbackAttribute 时,
/// 该方法会报告 GF_Cqrs_001 并停止发射源码。这样可以避免生成一个表面可用、但会静默漏掉部分 handler 注册的半成品
/// registry。只有在静态注册结果与 fallback 契约同时成立时,才允许调用 AddSource。
///
///
private static void Execute(
SourceProductionContext context,
GenerationEnvironment generationEnvironment,
ImmutableArray candidates)
{
if (!generationEnvironment.GenerationEnabled)
return;
var registrations = CollectRegistrations(candidates);
if (registrations.Count == 0)
return;
var fallbackHandlerTypeMetadataNames = registrations
.Select(static registration => registration.ReflectionFallbackHandlerTypeMetadataName)
.Where(static typeMetadataName => !string.IsNullOrWhiteSpace(typeMetadataName))
.Distinct(StringComparer.Ordinal)
.Cast()
.ToArray();
if (!CanEmitGeneratedRegistry(
generationEnvironment.SupportsReflectionFallbackAttribute,
fallbackHandlerTypeMetadataNames.Length))
{
ReportMissingReflectionFallbackContractDiagnostic(
context,
fallbackHandlerTypeMetadataNames);
return;
}
context.AddSource(
HintName,
GenerateSource(generationEnvironment, registrations, fallbackHandlerTypeMetadataNames));
}
///
/// 判断当前轮次是否允许输出生成注册器。
///
///
/// runtime 合同中是否存在 CqrsReflectionFallbackAttribute,以承载生成器无法静态精确表达的 handler 回退元数据。
///
///
/// 当前轮次需要依赖程序集级 reflection fallback 元数据恢复的 handler 数量。
///
///
/// 当没有 handler 依赖 fallback,或 runtime 已提供承载该元数据的特性契约时返回 ;
/// 否则返回 ,调用方必须放弃生成以避免输出会静默漏注册的半成品注册器。
///
private static bool CanEmitGeneratedRegistry(
bool supportsReflectionFallbackAttribute,
int fallbackHandlerTypeCount)
{
return fallbackHandlerTypeCount == 0 || supportsReflectionFallbackAttribute;
}
///
/// 报告当前轮次因缺少 fallback 元数据承载契约而无法安全生成注册器的诊断。
///
/// 源生产上下文。
/// 需要通过程序集级 reflection fallback 元数据恢复的 handler 元数据名称。
private static void ReportMissingReflectionFallbackContractDiagnostic(
SourceProductionContext context,
IReadOnlyList fallbackHandlerTypeMetadataNames)
{
var handlerList = string.Join(
", ",
fallbackHandlerTypeMetadataNames.OrderBy(static name => name, StringComparer.Ordinal));
context.ReportDiagnostic(Diagnostic.Create(
MissingReflectionFallbackContractDiagnostic,
Location.None,
handlerList,
CqrsReflectionFallbackAttributeMetadataName));
}
private static List CollectRegistrations(
ImmutableArray candidates)
{
var registrations = new List();
// Partial declarations surface the same symbol through multiple syntax nodes.
// Collapse them by implementation type so direct and reflected registrations stay stable and duplicate-free.
var uniqueCandidates = new Dictionary(StringComparer.Ordinal);
foreach (var candidate in candidates)
{
if (candidate is null)
continue;
uniqueCandidates[candidate.Value.ImplementationTypeDisplayName] = candidate.Value;
}
foreach (var candidate in uniqueCandidates.Values)
{
registrations.Add(new ImplementationRegistrationSpec(
candidate.ImplementationTypeDisplayName,
candidate.ImplementationLogName,
candidate.Registrations,
candidate.ReflectedImplementationRegistrations,
candidate.PreciseReflectedRegistrations,
candidate.ReflectionTypeMetadataName,
candidate.ReflectionFallbackHandlerTypeMetadataName));
}
registrations.Sort(static (left, right) =>
StringComparer.Ordinal.Compare(left.ImplementationLogName, right.ImplementationLogName));
return registrations;
}
private static bool IsConcreteHandlerType(INamedTypeSymbol type)
{
return type.TypeKind is TypeKind.Class or TypeKind.Struct &&
!type.IsAbstract &&
!ContainsGenericParameters(type);
}
private static bool ContainsGenericParameters(INamedTypeSymbol type)
{
for (var current = type; current is not null; current = current.ContainingType)
{
if (current.TypeParameters.Length > 0)
return true;
}
return false;
}
private static bool IsSupportedHandlerInterface(INamedTypeSymbol interfaceType)
{
if (!interfaceType.IsGenericType)
return false;
var definitionMetadataName = GetFullyQualifiedMetadataName(interfaceType.OriginalDefinition);
return string.Equals(definitionMetadataName, IRequestHandlerMetadataName, StringComparison.Ordinal) ||
string.Equals(definitionMetadataName, INotificationHandlerMetadataName, StringComparison.Ordinal) ||
string.Equals(definitionMetadataName, IStreamRequestHandlerMetadataName, StringComparison.Ordinal);
}
private static string GetFullyQualifiedMetadataName(INamedTypeSymbol type)
{
var nestedTypes = new Stack();
for (var current = type; current is not null; current = current.ContainingType)
{
nestedTypes.Push(current.MetadataName);
}
var builder = new StringBuilder();
if (!type.ContainingNamespace.IsGlobalNamespace)
{
builder.Append(type.ContainingNamespace.ToDisplayString());
builder.Append('.');
}
while (nestedTypes.Count > 0)
{
builder.Append(nestedTypes.Pop());
if (nestedTypes.Count > 0)
builder.Append('.');
}
return builder.ToString();
}
private static string GetReflectionTypeMetadataName(INamedTypeSymbol type)
{
var nestedTypes = new Stack();
for (var current = type; current is not null; current = current.ContainingType)
{
nestedTypes.Push(current.MetadataName);
}
var builder = new StringBuilder();
if (!type.ContainingNamespace.IsGlobalNamespace)
{
builder.Append(type.ContainingNamespace.ToDisplayString());
builder.Append('.');
}
var isFirstType = true;
while (nestedTypes.Count > 0)
{
if (!isFirstType)
builder.Append('+');
builder.Append(nestedTypes.Pop());
isFirstType = false;
}
return builder.ToString();
}
private static string GetTypeSortKey(ITypeSymbol type)
{
return type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
private static string GetLogDisplayName(ITypeSymbol type)
{
return GetTypeSortKey(type).Replace("global::", string.Empty);
}
}