diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs index 5be2c5ab..6421e0ca 100644 --- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs +++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs @@ -155,7 +155,41 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator reflectionFallbackHandlerTypeMetadataName); } - private static void Execute(SourceProductionContext context, GenerationEnvironment generationEnvironment, + /// + /// 执行 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) diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs index 4b23a86a..6db73364 100644 --- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs @@ -1244,6 +1244,9 @@ public class CqrsHandlerRegistryGeneratorTests var execution = ExecuteGenerator( source, allowUnsafe: true); + var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics + .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) + .ToArray(); var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); @@ -1252,6 +1255,7 @@ public class CqrsHandlerRegistryGeneratorTests Assert.Multiple(() => { + Assert.That(generatedCompilationErrors, Is.Empty); Assert.That(execution.GeneratedSources, Is.Empty); Assert.That(missingContractDiagnostic, Is.Not.Null); Assert.That( @@ -1355,12 +1359,16 @@ public class CqrsHandlerRegistryGeneratorTests var execution = ExecuteGenerator( source, allowUnsafe: true); + var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics + .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) + .ToArray(); var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); Assert.Multiple(() => { + Assert.That(generatedCompilationErrors, Is.Empty); Assert.That(generatorErrors, Is.Empty); Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1)); Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs")); @@ -1464,14 +1472,24 @@ public class CqrsHandlerRegistryGeneratorTests var runResult = driver.GetRunResult(); Assert.That(runResult.Results, Has.Length.EqualTo(1)); + var generatedSyntaxTrees = runResult.Results[0].GeneratedSources + .Select(static sourceResult => sourceResult.SyntaxTree) + .ToHashSet(); var generatedSources = runResult.Results[0].GeneratedSources .Select(static sourceResult => (filename: sourceResult.HintName, content: sourceResult.SourceText.ToString())) .ToArray(); + var compilationDiagnostics = updatedCompilation.GetDiagnostics().ToArray(); + var generatedCompilationDiagnostics = compilationDiagnostics + .Where(diagnostic => + diagnostic.Location.SourceTree is not null && + generatedSyntaxTrees.Contains(diagnostic.Location.SourceTree)) + .ToArray(); return new GeneratorExecutionResult( generatedSources, generatorDiagnostics.ToArray(), - updatedCompilation.GetDiagnostics().ToArray()); + compilationDiagnostics, + generatedCompilationDiagnostics); } /// @@ -1479,9 +1497,11 @@ public class CqrsHandlerRegistryGeneratorTests /// /// 本轮生成产生的源文件集合。 /// 生成器自身报告的诊断集合。 - /// 将生成结果并回编译后的编译诊断集合。 + /// 将生成结果并回编译后的完整编译诊断集合。 + /// 仅来自生成源文件的编译诊断集合。 private sealed record GeneratorExecutionResult( (string filename, string content)[] GeneratedSources, Diagnostic[] GeneratorDiagnostics, - Diagnostic[] CompilationDiagnostics); + Diagnostic[] CompilationDiagnostics, + Diagnostic[] GeneratedCompilationDiagnostics); }