refactor(cqrs): 扩展指针类型注册生成覆盖

- 优化 CqrsHandlerRegistryGenerator 对 pointer 类型的 runtime type 递归重建与发射逻辑
- 修复 function pointer 签名默认直出导致隐藏类型漏回退的判定边界
- 补充 pointer precise registration 与 function pointer fallback 回归测试
- 更新 cqrs-rewrite 跟踪与 trace 到 RP-047
This commit is contained in:
gewuyou 2026-04-20 10:17:03 +08:00 committed by GeWuYou
parent 7cf0a75568
commit 5f3964d4c0
4 changed files with 208 additions and 13 deletions

View File

@ -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<RuntimeTypeReferenceSpec> 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<RuntimeTypeReferenceSpec>.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<RuntimeTypeReferenceSpec>.Empty);
}
@ -1149,13 +1186,19 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
string reflectionTypeMetadataName)
{
return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, reflectionAssemblyName, null, 0,
null,
null, null,
ImmutableArray<RuntimeTypeReferenceSpec>.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<RuntimeTypeReferenceSpec>.Empty);
}
public static RuntimeTypeReferenceSpec FromPointer(RuntimeTypeReferenceSpec pointedAtTypeReference)
{
return new RuntimeTypeReferenceSpec(null, null, null, null, 0, pointedAtTypeReference, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
@ -1163,7 +1206,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
RuntimeTypeReferenceSpec genericTypeDefinitionReference,
ImmutableArray<RuntimeTypeReferenceSpec> genericTypeArguments)
{
return new RuntimeTypeReferenceSpec(null, null, null, null, 0, genericTypeDefinitionReference,
return new RuntimeTypeReferenceSpec(null, null, null, null, 0, null, genericTypeDefinitionReference,
genericTypeArguments);
}
}

View File

@ -164,6 +164,45 @@ public class CqrsHandlerRegistryGeneratorTests
""";
private const string HiddenPointerResponseExpected = """
// <auto-generated />
#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<TestApp.Container.HiddenRequest, TestApp.Container.HiddenResponse*>.");
}
}
}
}
""";
private const string MixedDirectAndPreciseRegistrationsExpected = """
// <auto-generated />
#nullable enable
@ -745,6 +784,98 @@ public class CqrsHandlerRegistryGeneratorTests
("CqrsHandlerRegistry.g.cs", HiddenGenericEnvelopeResponseExpected));
}
/// <summary>
/// 验证精确重建路径会递归覆盖隐藏指针元素类型,
/// 使“隐藏 pointed-at 类型 + unsafe 指针响应”的 handler 也能直接生成 closed service type。
/// </summary>
[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<TResponse> { }
public interface INotification { }
public interface IStreamRequest<TResponse> { }
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
}
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<HiddenResponse*>;
public unsafe sealed class HiddenHandler : IRequestHandler<HiddenRequest, HiddenResponse*>
{
}
}
}
""";
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));
});
}
/// <summary>
/// 验证同一个 implementation 同时包含可直接注册接口与需精确重建接口时,
/// 生成器会保留两类注册,并继续按 handler interface 名称稳定排序。
@ -1232,9 +1363,9 @@ public class CqrsHandlerRegistryGeneratorTests
{
}
private unsafe sealed record HiddenRequest() : IRequest<HiddenResponse*>;
private unsafe sealed record HiddenRequest() : IRequest<delegate* unmanaged<HiddenResponse>>;
public unsafe sealed class HiddenHandler : IRequestHandler<HiddenRequest, HiddenResponse*>
public unsafe sealed class HiddenHandler : IRequestHandler<HiddenRequest, delegate* unmanaged<HiddenResponse>>
{
}
}
@ -1341,15 +1472,15 @@ public class CqrsHandlerRegistryGeneratorTests
{
}
private unsafe sealed record AlphaRequest() : IRequest<AlphaResponse*>;
private unsafe sealed record AlphaRequest() : IRequest<delegate* unmanaged<AlphaResponse>>;
private unsafe sealed record BetaRequest() : IRequest<BetaResponse*>;
private unsafe sealed record BetaRequest() : IRequest<delegate* unmanaged<BetaResponse>>;
public unsafe sealed class BetaHandler : IRequestHandler<BetaRequest, BetaResponse*>
public unsafe sealed class BetaHandler : IRequestHandler<BetaRequest, delegate* unmanaged<BetaResponse>>
{
}
public unsafe sealed class AlphaHandler : IRequestHandler<AlphaRequest, AlphaResponse*>
public unsafe sealed class AlphaHandler : IRequestHandler<AlphaRequest, delegate* unmanaged<AlphaResponse>>
{
}
}

View File

@ -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` 作为独立验证步骤

View File

@ -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 的无参构造激活改为类型级缓存工厂