diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs index 6421e0ca..7888f613 100644 --- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs +++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs @@ -412,6 +412,13 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator return true; } + if (type is IPointerTypeSymbol pointerType && + TryCreateRuntimeTypeReference(compilation, pointerType.PointedAtType, out var pointedAtTypeReference)) + { + runtimeTypeReference = RuntimeTypeReferenceSpec.FromPointer(pointedAtTypeReference!); + return true; + } + if (type is INamedTypeSymbol genericNamedType && genericNamedType.IsGenericType && !genericNamedType.IsUnboundGenericType && @@ -520,6 +527,17 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator return true; case IPointerTypeSymbol pointerType: return CanReferenceFromGeneratedRegistry(compilation, pointerType.PointedAtType); + case IFunctionPointerTypeSymbol functionPointerType: + if (!CanReferenceFromGeneratedRegistry(compilation, functionPointerType.Signature.ReturnType)) + return false; + + foreach (var parameter in functionPointerType.Signature.Parameters) + { + if (!CanReferenceFromGeneratedRegistry(compilation, parameter.Type)) + return false; + } + + return true; case ITypeParameterSymbol: return false; default: @@ -975,6 +993,18 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator : $"{elementExpression}.MakeArrayType({runtimeTypeReference.ArrayRank})"; } + if (runtimeTypeReference.PointerElementTypeReference is not null) + { + var pointedAtExpression = AppendRuntimeTypeReferenceResolution( + builder, + runtimeTypeReference.PointerElementTypeReference, + $"{variableBaseName}PointedAt", + reflectedArgumentNames, + indent); + + return $"{pointedAtExpression}.MakePointerType()"; + } + if (runtimeTypeReference.GenericTypeDefinitionReference is not null) { var genericTypeDefinitionExpression = AppendRuntimeTypeReferenceResolution( @@ -1091,6 +1121,12 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator return true; } + if (runtimeTypeReference.PointerElementTypeReference is not null && + ContainsExternalAssemblyTypeLookup(runtimeTypeReference.PointerElementTypeReference)) + { + return true; + } + if (runtimeTypeReference.GenericTypeDefinitionReference is not null && ContainsExternalAssemblyTypeLookup(runtimeTypeReference.GenericTypeDefinitionReference)) { @@ -1129,18 +1165,19 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator string? ReflectionAssemblyName, RuntimeTypeReferenceSpec? ArrayElementTypeReference, int ArrayRank, + RuntimeTypeReferenceSpec? PointerElementTypeReference, RuntimeTypeReferenceSpec? GenericTypeDefinitionReference, ImmutableArray GenericTypeArguments) { public static RuntimeTypeReferenceSpec FromDirectReference(string typeDisplayName) { - return new RuntimeTypeReferenceSpec(typeDisplayName, null, null, null, 0, null, + return new RuntimeTypeReferenceSpec(typeDisplayName, null, null, null, 0, null, null, ImmutableArray.Empty); } public static RuntimeTypeReferenceSpec FromReflectionLookup(string reflectionTypeMetadataName) { - return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, null, null, 0, null, + return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, null, null, 0, null, null, ImmutableArray.Empty); } @@ -1149,13 +1186,19 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator string reflectionTypeMetadataName) { return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, reflectionAssemblyName, null, 0, - null, + null, null, ImmutableArray.Empty); } public static RuntimeTypeReferenceSpec FromArray(RuntimeTypeReferenceSpec elementTypeReference, int arrayRank) { - return new RuntimeTypeReferenceSpec(null, null, null, elementTypeReference, arrayRank, null, + return new RuntimeTypeReferenceSpec(null, null, null, elementTypeReference, arrayRank, null, null, + ImmutableArray.Empty); + } + + public static RuntimeTypeReferenceSpec FromPointer(RuntimeTypeReferenceSpec pointedAtTypeReference) + { + return new RuntimeTypeReferenceSpec(null, null, null, null, 0, pointedAtTypeReference, null, ImmutableArray.Empty); } @@ -1163,7 +1206,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator RuntimeTypeReferenceSpec genericTypeDefinitionReference, ImmutableArray genericTypeArguments) { - return new RuntimeTypeReferenceSpec(null, null, null, null, 0, genericTypeDefinitionReference, + return new RuntimeTypeReferenceSpec(null, null, null, null, 0, null, genericTypeDefinitionReference, genericTypeArguments); } } diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs index 6db73364..c0b4508f 100644 --- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs @@ -164,6 +164,45 @@ public class CqrsHandlerRegistryGeneratorTests """; + private const string HiddenPointerResponseExpected = """ + // + #nullable enable + + [assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))] + + namespace GFramework.Generated.Cqrs; + + internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry + { + public void Register(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::GFramework.Core.Abstractions.Logging.ILogger logger) + { + if (services is null) + throw new global::System.ArgumentNullException(nameof(services)); + if (logger is null) + throw new global::System.ArgumentNullException(nameof(logger)); + + var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly; + + var implementationType0 = typeof(global::TestApp.Container.HiddenHandler); + if (implementationType0 is not null) + { + var serviceType0_0Argument0 = registryAssembly.GetType("TestApp.Container+HiddenRequest", throwOnError: false, ignoreCase: false); + var serviceType0_0Argument1PointedAt = registryAssembly.GetType("TestApp.Container+HiddenResponse", throwOnError: false, ignoreCase: false); + if (serviceType0_0Argument0 is not null && serviceType0_0Argument1PointedAt is not null) + { + var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1PointedAt.MakePointerType()); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( + services, + serviceType0_0, + implementationType0); + logger.Debug("Registered CQRS handler TestApp.Container.HiddenHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); + } + } + } + } + + """; + private const string MixedDirectAndPreciseRegistrationsExpected = """ // #nullable enable @@ -745,6 +784,98 @@ public class CqrsHandlerRegistryGeneratorTests ("CqrsHandlerRegistry.g.cs", HiddenGenericEnvelopeResponseExpected)); } + /// + /// 验证精确重建路径会递归覆盖隐藏指针元素类型, + /// 使“隐藏 pointed-at 类型 + unsafe 指针响应”的 handler 也能直接生成 closed service type。 + /// + [Test] + public async Task Generates_Precise_Service_Type_For_Hidden_Pointer_Response() + { + 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) { } + } + } + + namespace TestApp + { + using GFramework.Cqrs.Abstractions.Cqrs; + + public sealed class Container + { + private unsafe struct HiddenResponse + { + } + + private unsafe sealed record HiddenRequest() : IRequest; + + public unsafe sealed class HiddenHandler : IRequestHandler + { + } + } + } + """; + + 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")); + Assert.That(execution.GeneratedSources[0].content, Is.EqualTo(HiddenPointerResponseExpected)); + }); + } + /// /// 验证同一个 implementation 同时包含可直接注册接口与需精确重建接口时, /// 生成器会保留两类注册,并继续按 handler interface 名称稳定排序。 @@ -1232,9 +1363,9 @@ public class CqrsHandlerRegistryGeneratorTests { } - private unsafe sealed record HiddenRequest() : IRequest; + private unsafe sealed record HiddenRequest() : IRequest>; - public unsafe sealed class HiddenHandler : IRequestHandler + public unsafe sealed class HiddenHandler : IRequestHandler> { } } @@ -1341,15 +1472,15 @@ public class CqrsHandlerRegistryGeneratorTests { } - private unsafe sealed record AlphaRequest() : IRequest; + private unsafe sealed record AlphaRequest() : IRequest>; - private unsafe sealed record BetaRequest() : IRequest; + private unsafe sealed record BetaRequest() : IRequest>; - public unsafe sealed class BetaHandler : IRequestHandler + public unsafe sealed class BetaHandler : IRequestHandler> { } - public unsafe sealed class AlphaHandler : IRequestHandler + public unsafe sealed class AlphaHandler : IRequestHandler> { } } diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md index 7e4a4198..a0843460 100644 --- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md +++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md @@ -7,12 +7,14 @@ CQRS 迁移与收敛。 ## 当前恢复点 -- 恢复点编号:`CQRS-REWRITE-RP-046` +- 恢复点编号:`CQRS-REWRITE-RP-047` - 当前阶段:`Phase 8` - 当前焦点: - 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口 - 已完成 generated registry 激活路径收敛:`CqrsHandlerRegistrar` 现优先复用缓存工厂委托,避免重复 `ConstructorInfo.Invoke` - 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物 + - 已补充 pointer 响应类型的 precise runtime type 生成,避免这类 handler 再退回程序集级 reflection fallback + - 已收紧 function pointer 签名的可直接生成判定,仅在其返回值与参数类型都可安全引用时才走静态注册路径 - 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,继续扩大 generator 覆盖,并选择下一个收益明确的 dispatch / invoker 反射收敛点 ## 当前状态摘要 @@ -34,6 +36,10 @@ CQRS 迁移与收敛。 - generated registry 类型首次分析后,会缓存一个可复用的激活工厂,而不是在后续容器注册时重复走 `ConstructorInfo.Invoke` - 若运行环境不允许动态方法,仍保留原有的反射激活回退,避免阻塞 generated registry 路径 - `GFramework.Cqrs.Tests` 已补充“私有无参构造 registry 仍可激活”的回归覆盖 +- `2026-04-20` 已完成一轮 generator 覆盖面扩展: + - `CqrsHandlerRegistryGenerator` 现可为 pointer 类型递归重建 runtime type,并通过 `MakePointerType()` 生成精确 service type + - function pointer 签名不再默认视为“可直接引用”;只有当返回值与每个参数类型都可从 generated registry 安全引用时,才允许直接生成 + - 含隐藏类型的 function pointer handler 仍会保留原有 fallback / 诊断路径,避免此次覆盖扩展误伤已有回退边界 - 当前主线优先级: - generator 覆盖面继续扩大 - dispatch/invoker 反射占比继续下降 @@ -58,9 +64,12 @@ CQRS 迁移与收敛。 - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false` - 结果:通过 - 备注:`63/63` 测试通过;当前沙箱限制了 MSBuild named pipe,验证需在提权环境下运行 +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 备注:`14/14` 测试通过;本轮覆盖 pointer precise registration 与 function pointer fallback 边界 ## 下一步 -1. 继续 `Phase 8` 主线,优先选择下一个收益明确的 dispatch / invoker 反射收敛点继续推进 +1. 继续 `Phase 8` 主线,优先再找一个收益明确的 generator 覆盖缺口或 dispatch / invoker 反射收敛点继续推进 2. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 CQRS API / 命名空间表述 3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤 diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md index 9353cdd9..98023113 100644 --- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md +++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md @@ -2,6 +2,18 @@ ## 2026-04-20 +### 阶段:pointer precise runtime type 覆盖扩展(CQRS-REWRITE-RP-047) + +- 已在 `CqrsHandlerRegistryGenerator` 中补充 pointer 类型的 runtime type 递归建模与源码发射,precise registration 现可通过 `MakePointerType()` 还原隐藏 pointer 响应类型 +- 已同步收紧 function pointer 签名的可直接生成判定,只有当签名中的返回值与参数类型均可从 generated registry 安全引用时才走静态注册 +- 已保留含隐藏类型 function pointer handler 的 fallback / 诊断回归覆盖,确保 pointer 支持扩展不会误删原有程序集级 fallback 契约边界 +- 定向验证与 `CqrsHandlerRegistryGeneratorTests` 全组验证均已通过: + - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~Generates_Precise_Service_Type_For_Hidden_Pointer_Response|FullyQualifiedName~Reports_Diagnostic_And_Skips_Registry_When_Fallback_Metadata_Is_Required_But_Runtime_Contract_Lacks_Fallback_Attribute|FullyQualifiedName~Emits_Assembly_Level_Fallback_Metadata_When_Fallback_Is_Required_And_Runtime_Contract_Is_Available"` + - `3/3` passed + - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"` + - `14/14` passed + - 当前沙箱限制 MSBuild named pipe,因此验证在提权环境下执行 + ### 阶段:generated registry 激活反射收敛(CQRS-REWRITE-RP-046) - 已在 `CqrsHandlerRegistrar` 中将 generated registry 的无参构造激活改为类型级缓存工厂