From 4ef9406ee913a865c9932f7b194df7dc5f206cc4 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Wed, 22 Apr 2026 18:52:26 +0800
Subject: [PATCH] =?UTF-8?q?fix(source-generators):=20=E6=94=B6=E5=8F=A3PR2?=
=?UTF-8?q?69=E5=89=A9=E4=BD=99review=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 Cqrs handler registry 对 Roslyn error type 的直接引用,改走安全的运行时类型查找
- 补充 SchemaConfigGenerator 根 type 非字符串诊断回归与 Cqrs 未解析类型回归测试
- 更新 analyzer-warning-reduction 的 RP-024 跟踪与验证记录
---
...RegistryGenerator.RuntimeTypeReferences.cs | 7 +-
.../Config/SchemaConfigGenerator.cs | 1 +
.../Config/SchemaConfigGeneratorTests.cs | 39 ++++++++
.../Cqrs/CqrsHandlerRegistryGeneratorTests.cs | 97 +++++++++++++++++++
.../analyzer-warning-reduction-tracking.md | 11 ++-
.../analyzer-warning-reduction-trace.md | 32 ++++++
6 files changed, 184 insertions(+), 3 deletions(-)
diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.RuntimeTypeReferences.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.RuntimeTypeReferences.cs
index 14eee2d8..ddc9cb37 100644
--- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.RuntimeTypeReferences.cs
+++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.RuntimeTypeReferences.cs
@@ -253,6 +253,11 @@ public sealed partial class CqrsHandlerRegistryGenerator
private static bool CanReferenceFromGeneratedRegistry(Compilation compilation, ITypeSymbol type)
{
+ // Roslyn error symbols stringify to unresolved type names; emitting them via typeof(...) would turn
+ // an existing user-code error into a second generator-produced compile error instead of falling back.
+ if (type.TypeKind == TypeKind.Error)
+ return false;
+
switch (type)
{
case IArrayTypeSymbol arrayType:
@@ -274,7 +279,7 @@ public sealed partial class CqrsHandlerRegistryGenerator
case ITypeParameterSymbol:
return false;
default:
- // Treat other Roslyn type kinds, such as dynamic or unresolved error types, as referenceable for now.
+ // Treat other Roslyn type kinds, such as dynamic, as referenceable for now.
// If a real-world case proves unsafe, tighten this branch instead of broadening the named-type path above.
return true;
}
diff --git a/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs b/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs
index 2da81e0e..dabfac77 100644
--- a/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs
+++ b/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs
@@ -221,6 +221,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
private static bool TryValidateSchemaRoot(string filePath, JsonElement root, out Diagnostic? diagnostic)
{
if (!root.TryGetProperty("type", out var rootTypeElement) ||
+ rootTypeElement.ValueKind != JsonValueKind.String ||
!IsSchemaType(rootTypeElement.GetString() ?? string.Empty, "object"))
{
diagnostic = Diagnostic.Create(
diff --git a/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs
index ebbbd484..f1f5afb1 100644
--- a/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs
@@ -72,6 +72,45 @@ public class SchemaConfigGeneratorTests
});
}
+ ///
+ /// 验证根节点 type 元数据不是字符串时,会返回根对象约束诊断,而不是抛出 JSON 访问异常。
+ ///
+ [Test]
+ public void Run_Should_Report_Diagnostic_When_Root_Type_Metadata_Is_Not_A_String()
+ {
+ const string source = """
+ namespace TestApp
+ {
+ public sealed class Dummy
+ {
+ }
+ }
+ """;
+
+ const string schema = """
+ {
+ "type": 123,
+ "required": ["id"],
+ "properties": {
+ "id": { "type": "integer" }
+ }
+ }
+ """;
+
+ var result = SchemaGeneratorTestDriver.Run(
+ source,
+ ("monster.schema.json", schema));
+
+ var diagnostic = result.Results.Single().Diagnostics.Single();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(diagnostic.Id, Is.EqualTo("GF_ConfigSchema_002"));
+ Assert.That(diagnostic.Severity, Is.EqualTo(DiagnosticSeverity.Error));
+ Assert.That(diagnostic.GetMessage(), Does.Contain("monster.schema.json"));
+ });
+ }
+
///
/// 验证 schema 文件名若生成无效根类型标识符时,会在生成前产生命名明确的诊断。
///
diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
index e9a9bfa2..311e4f65 100644
--- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
@@ -1375,6 +1375,103 @@ public class CqrsHandlerRegistryGeneratorTests
});
}
+ ///
+ /// 验证 handler 合同里出现未解析错误类型时,生成器会改为运行时精确查找该类型,
+ /// 而不会把无效类型名直接写进生成代码中的 typeof(...)。
+ ///
+ [Test]
+ public void Emits_Runtime_Type_Lookup_When_Handler_Contract_Contains_Unresolved_Error_Types()
+ {
+ const string source = """
+ using System;
+
+ namespace Microsoft.Extensions.DependencyInjection
+ {
+ public interface IServiceCollection { }
+
+ public static class ServiceCollectionServiceExtensions
+ {
+ public static void AddTransient(IServiceCollection services, Type serviceType, Type implementationType) { }
+ }
+ }
+
+ namespace GFramework.Core.Abstractions.Logging
+ {
+ public interface ILogger
+ {
+ void Debug(string msg);
+ }
+ }
+
+ namespace GFramework.Cqrs.Abstractions.Cqrs
+ {
+ public interface IRequest { }
+ public interface INotification { }
+ public interface IStreamRequest { }
+
+ public interface IRequestHandler where TRequest : IRequest { }
+ public interface INotificationHandler where TNotification : INotification { }
+ public interface IStreamRequestHandler where TRequest : IStreamRequest { }
+ }
+
+ namespace GFramework.Cqrs
+ {
+ public interface ICqrsHandlerRegistry
+ {
+ void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger);
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class CqrsHandlerRegistryAttribute : Attribute
+ {
+ public CqrsHandlerRegistryAttribute(Type registryType) { }
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly)]
+ public sealed class CqrsReflectionFallbackAttribute : Attribute
+ {
+ public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { }
+ }
+ }
+
+ namespace TestApp
+ {
+ using GFramework.Cqrs.Abstractions.Cqrs;
+
+ public sealed record BrokenRequest() : IRequest;
+
+ public sealed class BrokenHandler : IRequestHandler
+ {
+ }
+ }
+ """;
+
+ var execution = ExecuteGenerator(source);
+ var inputCompilationErrors = execution.InputCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+ 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(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0246"));
+ Assert.That(generatedCompilationErrors, Is.Empty);
+ Assert.That(generatorErrors, Is.Empty);
+ Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
+ Assert.That(
+ execution.GeneratedSources[0].content,
+ Does.Contain("registryAssembly.GetType(\"MissingResponse\", throwOnError: false, ignoreCase: false);"));
+ Assert.That(
+ execution.GeneratedSources[0].content,
+ Does.Contain("internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry"));
+ });
+ }
+
///
/// 验证当 fallback metadata 仍然必需且 runtime 提供了承载契约时,
/// 生成器会继续产出注册器并发射程序集级 CqrsReflectionFallbackAttribute。
diff --git a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md
index 50163cb8..3fe51813 100644
--- a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md
+++ b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md
@@ -7,8 +7,8 @@
## 当前恢复点
-- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-023`
-- 当前阶段:`Phase 23`
+- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-024`
+- 当前阶段:`Phase 24`
- 当前焦点:
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
@@ -25,6 +25,8 @@
`FilterConfiguration` 的公共 API 兼容形状,并将 analyzer 兼容性处理收敛到局部 pragma
- 已完成当前 PR #269 第三轮 follow-up:继续收口 `SchemaConfigGenerator` 的根类型标识符校验与 XML 文档转义,
并补齐 `LoggingConfigurationTests`、`CollectionExtensionsTests`、`Cqrs` helper 抽取与 `ai-plan` 命令文本修正
+ - 已完成当前 PR #269 第四轮 follow-up:将 `CqrsHandlerRegistryGenerator` 的 Roslyn error type 直接引用改为
+ 运行时精确查找路径,并为 `SchemaConfigGenerator` 补上根 `type` 非字符串时的防御与回归测试
- `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义
- `EasyEvents.AddEvent()` 的重复注册路径已恢复为 `ArgumentException`,以保持既有异常契约
- `Option` 已声明 `IEquatable