mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
refactor(cqrs): 扩展指针类型注册生成覆盖
- 优化 CqrsHandlerRegistryGenerator 对 pointer 类型的 runtime type 递归重建与发射逻辑 - 修复 function pointer 签名默认直出导致隐藏类型漏回退的判定边界 - 补充 pointer precise registration 与 function pointer fallback 回归测试 - 更新 cqrs-rewrite 跟踪与 trace 到 RP-047
This commit is contained in:
parent
7cf0a75568
commit
5f3964d4c0
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@ -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` 作为独立验证步骤
|
||||
|
||||
@ -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 的无参构造激活改为类型级缓存工厂
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user