From 5e1e16f86e005b51fbfb22148bcf4dcdf04b21d2 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:17:06 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(generator):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9=E7=BB=A7=E6=89=BF=E5=B1=82=E6=AC=A1=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E4=B8=AD=E6=B3=A8=E5=86=8C=E6=96=B9=E6=B3=95=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现 EnumerateCandidateMethods 方法以搜索基类和接口中的注册方法 - 修改 AutoRegisterExportedCollectionsGenerator 以支持从继承链中查找兼容的注册方法 - 添加完整的单元测试覆盖继承、接口实现和泛型场景 - 修复静态成员和不可访问方法的诊断报告功能 - 增强源代码生成器对复杂继承结构的支持能力 --- ...gisterExportedCollectionsGeneratorTests.cs | 78 +++++++++++++++++++ ...utoRegisterExportedCollectionsGenerator.cs | 23 +++++- 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs b/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs index 1c4927a4..612529c3 100644 --- a/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs +++ b/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs @@ -200,6 +200,84 @@ public class AutoRegisterExportedCollectionsGeneratorTests ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)); } + [Test] + public async Task Generates_Batch_Registration_Method_When_Register_Method_Comes_From_Inherited_Interface() + { + const string source = """ + #nullable enable + using System; + using System.Collections.Generic; + using GFramework.Godot.SourceGenerators.Abstractions; + + namespace GFramework.Godot.SourceGenerators.Abstractions + { + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] + public sealed class RegisterExportedCollectionAttribute : Attribute + { + public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { } + } + } + + namespace TestApp + { + public interface IKeyValue + { + } + + public interface IRegistry + { + void Registry(IKeyValue mapping); + } + + public interface IAssetRegistry : IRegistry + { + } + + public sealed class IntConfig : IKeyValue + { + } + + [AutoRegisterExportedCollections] + public partial class Bootstrapper + { + private readonly IAssetRegistry? _registry = null; + + [RegisterExportedCollection(nameof(_registry), "Registry")] + public List? Values { get; } = new(); + } + } + """; + + const string expected = """ + // + #nullable enable + + namespace TestApp; + + partial class Bootstrapper + { + private void __RegisterExportedCollections_Generated() + { + if (this.Values is not null && this._registry is not null) + { + foreach (var __generatedItem in this.Values) + { + this._registry.Registry(__generatedItem); + } + } + } + } + + """; + + await GeneratorTest.RunAsync( + source, + ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)); + } + [Test] public async Task Reports_Diagnostic_When_Collection_Member_Is_Not_Instance_Readable() { diff --git a/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs b/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs index df0c433b..16effd91 100644 --- a/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs +++ b/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs @@ -268,8 +268,7 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener return false; } - var hasCompatibleMethod = registryType.GetMembers(registerMethodName) - .OfType() + var hasCompatibleMethod = EnumerateCandidateMethods(registryType, registerMethodName) .Any(method => !method.IsStatic && method.Parameters.Length == 1 && @@ -319,6 +318,26 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener return compilation.ClassifyConversion(elementType, parameterType).IsImplicit; } + private static IEnumerable EnumerateCandidateMethods( + INamedTypeSymbol registryType, + string registerMethodName) + { + foreach (var method in registryType.GetMembers(registerMethodName).OfType()) + yield return method; + + for (var baseType = registryType.BaseType; baseType is not null; baseType = baseType.BaseType) + { + foreach (var method in baseType.GetMembers(registerMethodName).OfType()) + yield return method; + } + + foreach (var interfaceType in registryType.AllInterfaces) + { + foreach (var method in interfaceType.GetMembers(registerMethodName).OfType()) + yield return method; + } + } + private static bool TryGetRegistrationAttributeArguments( SourceProductionContext context, ISymbol collectionMember, From 812235a2430addb35e287473b1961d3f937b9d26 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:39:40 +0800 Subject: [PATCH 2/5] =?UTF-8?q?test(generator):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=B3=A8=E5=86=8C=E9=9B=86=E5=90=88=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=99=A8=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了针对注解集合生成批处理注册方法的测试用例 - 添加了当集合元素类型无法推断时报告诊断的测试 - 添加了注册方法使用数组参数时的批处理注册测试 - 添加了从继承接口获取注册方法的批处理注册测试 - 添加了从基类获取注册方法的批处理注册测试 - 添加了当集合成员不可实例读取时报告诊断的测试 - 添加了当注册成员不可实例读取时报告诊断的测试 - 添加了当注册方法对所有者类型不可访问时报告诊断的测试 - 添加了当注册导出集合属性参数无效时报告诊断的测试 - 添加了多个分部声明注解时仅生成一个源文件的测试 - 为枚举候选方法功能添加了详细的XML文档注释 --- ...gisterExportedCollectionsGeneratorTests.cs | 70 +++++++++++++++++++ ...utoRegisterExportedCollectionsGenerator.cs | 14 ++++ 2 files changed, 84 insertions(+) diff --git a/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs b/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs index 612529c3..d5117332 100644 --- a/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs +++ b/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs @@ -278,6 +278,76 @@ public class AutoRegisterExportedCollectionsGeneratorTests ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)); } + [Test] + public async Task Generates_Batch_Registration_Method_When_Register_Method_Comes_From_Base_Class() + { + const string source = """ + #nullable enable + using System; + using System.Collections.Generic; + using GFramework.Godot.SourceGenerators.Abstractions; + + namespace GFramework.Godot.SourceGenerators.Abstractions + { + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] + public sealed class RegisterExportedCollectionAttribute : Attribute + { + public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { } + } + } + + namespace TestApp + { + public class BaseRegistry + { + public void Register(int value) { } + } + + public sealed class DerivedRegistry : BaseRegistry + { + } + + [AutoRegisterExportedCollections] + public partial class Bootstrapper + { + private readonly DerivedRegistry? _registry = new(); + + [RegisterExportedCollection(nameof(_registry), nameof(BaseRegistry.Register))] + public List? Values { get; } = new(); + } + } + """; + + const string expected = """ + // + #nullable enable + + namespace TestApp; + + partial class Bootstrapper + { + private void __RegisterExportedCollections_Generated() + { + if (this.Values is not null && this._registry is not null) + { + foreach (var __generatedItem in this.Values) + { + this._registry.Register(__generatedItem); + } + } + } + } + + """; + + await GeneratorTest.RunAsync( + source, + ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)); + } + [Test] public async Task Reports_Diagnostic_When_Collection_Member_Is_Not_Instance_Readable() { diff --git a/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs b/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs index 16effd91..c790c709 100644 --- a/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs +++ b/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs @@ -318,6 +318,20 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener return compilation.ClassifyConversion(elementType, parameterType).IsImplicit; } + /// + /// 枚举给定注册表类型上可能承载批量注册入口的候选实例方法。 + /// + /// 声明注册表成员的静态类型。 + /// 特性参数中声明的注册方法名称。 + /// + /// 按“当前类型 -> 基类链 -> 已实现接口”顺序返回所有同名方法,供后续签名和可访问性筛选使用。 + /// + /// + /// 生成器需要沿这三条继承路径查找方法,因为用户代码可能通过派生类字段引用基类实现, + /// 或通过接口类型引用由上层接口声明的契约方法。这里故意不做去重:同一个语义方法可能同时经由 + /// 覆盖链、接口继承或显式声明被枚举多次,但当前调用方只使用 Any 判断“是否存在至少一个可用候选”, + /// 因此重复项只会带来额外的符号检查成本,不会改变生成结果或诊断边界。 + /// private static IEnumerable EnumerateCandidateMethods( INamedTypeSymbol registryType, string registerMethodName) From eeef5961d7b8cac80a4012b7e1a08ee8ca655aa2 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:04:14 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat(godot):=20=E6=B7=BB=E5=8A=A0=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E9=9B=86=E5=90=88=E8=87=AA=E5=8A=A8=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=99=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现了 AutoRegisterExportedCollectionsGenerator 源生成器 - 支持扫描标记了 AutoRegisterExportedCollectionsAttribute 的 partial 类型 - 为使用 RegisterExportedCollectionAttribute 声明的集合成员生成集中注册方法 - 提供详细的诊断支持,包括 GF_AutoExport_001 到 GF_AutoExport_008 错误码 - 支持从基类和接口继承链查找注册方法 - 实现了完整的单元测试覆盖各种使用场景 - 验证集合可枚举性、元素类型推导和注册表成员可访问性 - 生成安全的空值检查代码防止运行时异常 - 支持泛型类型约束和复杂继承关系的处理 --- ...gisterExportedCollectionsGeneratorTests.cs | 129 ++++++++++++++++++ ...utoRegisterExportedCollectionsGenerator.cs | 40 ++++-- 2 files changed, 161 insertions(+), 8 deletions(-) diff --git a/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs b/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs index d5117332..1a9afec6 100644 --- a/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs +++ b/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs @@ -278,6 +278,66 @@ public class AutoRegisterExportedCollectionsGeneratorTests ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)); } + [Test] + public async Task Reports_Diagnostic_When_Register_Method_Is_Only_Explicitly_Implemented_Interface_Member() + { + const string source = """ + using System; + using System.Collections.Generic; + using GFramework.Godot.SourceGenerators.Abstractions; + + namespace GFramework.Godot.SourceGenerators.Abstractions + { + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] + public sealed class RegisterExportedCollectionAttribute : Attribute + { + public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { } + } + } + + namespace TestApp + { + public interface IRegistry + { + void Register(int value); + } + + public sealed class ExplicitRegistry : IRegistry + { + void IRegistry.Register(int value) { } + } + + [AutoRegisterExportedCollections] + public partial class Bootstrapper + { + private readonly ExplicitRegistry _registry = new(); + + [RegisterExportedCollection(nameof(_registry), "Register")] + public List {|#0:Values|} { get; } = new(); + } + } + """; + + var test = new CSharpSourceGeneratorTest + { + TestState = + { + Sources = { source } + }, + DisabledDiagnostics = { "GF_Common_Trace_001" }, + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck + }; + + test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoExport_003", DiagnosticSeverity.Error) + .WithLocation(0) + .WithArguments("Register", "_registry", "Values")); + + await test.RunAsync(); + } + [Test] public async Task Generates_Batch_Registration_Method_When_Register_Method_Comes_From_Base_Class() { @@ -348,6 +408,75 @@ public class AutoRegisterExportedCollectionsGeneratorTests ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)); } + [Test] + public async Task Generates_Batch_Registration_Method_When_Registry_Member_Comes_From_Base_Class() + { + const string source = """ + #nullable enable + using System; + using System.Collections.Generic; + using GFramework.Godot.SourceGenerators.Abstractions; + + namespace GFramework.Godot.SourceGenerators.Abstractions + { + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] + public sealed class RegisterExportedCollectionAttribute : Attribute + { + public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { } + } + } + + namespace TestApp + { + public sealed class IntRegistry + { + public void Register(int value) { } + } + + public abstract class BootstrapperBase + { + protected readonly IntRegistry? _registry = new(); + } + + [AutoRegisterExportedCollections] + public partial class Bootstrapper : BootstrapperBase + { + [RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))] + public List? Values { get; } = new(); + } + } + """; + + const string expected = """ + // + #nullable enable + + namespace TestApp; + + partial class Bootstrapper + { + private void __RegisterExportedCollections_Generated() + { + if (this.Values is not null && this._registry is not null) + { + foreach (var __generatedItem in this.Values) + { + this._registry.Register(__generatedItem); + } + } + } + } + + """; + + await GeneratorTest.RunAsync( + source, + ("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)); + } + [Test] public async Task Reports_Diagnostic_When_Collection_Member_Is_Not_Instance_Readable() { diff --git a/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs b/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs index c790c709..1db4f290 100644 --- a/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs +++ b/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs @@ -222,8 +222,7 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener out var registerMethodName)) return false; - var registryMember = ownerType.GetMembers(registryMemberName) - .FirstOrDefault(member => member is IFieldSymbol or IPropertySymbol); + var registryMember = FindRegistryMember(ownerType, registryMemberName); if (registryMember is null) { @@ -236,7 +235,8 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener return false; } - if (!IsInstanceReadableMember(registryMember)) + if (!IsInstanceReadableMember(registryMember) || + !compilation.IsSymbolAccessibleWithin(registryMember, ownerType)) { context.ReportDiagnostic(Diagnostic.Create( AutoRegisterExportedCollectionsDiagnostics.RegistryMemberMustBeInstanceReadable, @@ -318,19 +318,40 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener return compilation.ClassifyConversion(elementType, parameterType).IsImplicit; } + private static ISymbol? FindRegistryMember( + INamedTypeSymbol ownerType, + string registryMemberName) + { + for (var currentType = ownerType; currentType is not null; currentType = currentType.BaseType) + { + // Search the owner hierarchy one level at a time so the generator follows the same + // name-hiding order as `this.` in generated code. + var candidateMember = currentType.GetMembers(registryMemberName) + .FirstOrDefault(static member => member is IFieldSymbol or IPropertySymbol); + + if (candidateMember is not null) + return candidateMember; + } + + return null; + } + /// /// 枚举给定注册表类型上可能承载批量注册入口的候选实例方法。 /// /// 声明注册表成员的静态类型。 /// 特性参数中声明的注册方法名称。 /// - /// 按“当前类型 -> 基类链 -> 已实现接口”顺序返回所有同名方法,供后续签名和可访问性筛选使用。 + /// 按“当前类型 -> 基类链 -> 接口继承链(仅当静态类型本身是接口)”顺序返回所有同名方法, + /// 供后续签名和可访问性筛选使用。 /// /// - /// 生成器需要沿这三条继承路径查找方法,因为用户代码可能通过派生类字段引用基类实现, - /// 或通过接口类型引用由上层接口声明的契约方法。这里故意不做去重:同一个语义方法可能同时经由 - /// 覆盖链、接口继承或显式声明被枚举多次,但当前调用方只使用 Any 判断“是否存在至少一个可用候选”, - /// 因此重复项只会带来额外的符号检查成本,不会改变生成结果或诊断边界。 + /// 生成器需要沿当前类型和基类链查找方法,因为用户代码可能通过派生类字段引用基类实现; + /// 当注册表成员本身声明为接口类型时,还要继续沿接口继承链查找由父接口声明的契约方法。 + /// 对类或结构体不遍历 ,避免把仅能通过接口调用的显式实现 + /// 误判为可由 this.<registry>.<method>(...) 直接访问的方法。 + /// 这里故意不做去重:同一个语义方法可能同时经由覆盖链、接口继承或显式声明被枚举多次,但当前调用方只使用 + /// Any 判断“是否存在至少一个可用候选”,因此重复项只会带来额外的符号检查成本,不会改变生成结果或诊断边界。 /// private static IEnumerable EnumerateCandidateMethods( INamedTypeSymbol registryType, @@ -345,6 +366,9 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener yield return method; } + if (registryType.TypeKind != TypeKind.Interface) + yield break; + foreach (var interfaceType in registryType.AllInterfaces) { foreach (var method in interfaceType.GetMembers(registerMethodName).OfType()) From 56bc078288b03259d319df80d89a6d377d1ea2fb Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:05:16 +0800 Subject: [PATCH 4/5] =?UTF-8?q?refactor(generator):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=A3=B0=E6=98=8E=E5=85=B3=E9=94=AE=E5=AD=97?= =?UTF-8?q?=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将条件判断语句替换为 switch 表达式以提高可读性 - 添加对 partial interface 类型的支持 - 为不支持的类型添加异常处理机制 - 简化代码结构并提升维护性 --- .../AutoRegisterExportedCollectionsGenerator.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs b/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs index 1db4f290..d6fe8db3 100644 --- a/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs +++ b/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs @@ -491,11 +491,15 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener private static string GetTypeDeclarationKeyword(INamedTypeSymbol typeSymbol) { - return typeSymbol.IsRecord - ? typeSymbol.TypeKind == TypeKind.Struct ? "partial record struct" : "partial record" - : typeSymbol.TypeKind == TypeKind.Struct - ? "partial struct" - : "partial class"; + return typeSymbol switch + { + { IsRecord: true, TypeKind: TypeKind.Struct } => "partial record struct", + { IsRecord: true } => "partial record", + { TypeKind: TypeKind.Struct } => "partial struct", + { TypeKind: TypeKind.Class } => "partial class", + { TypeKind: TypeKind.Interface } => "partial interface", + _ => throw new NotSupportedException($"Unsupported type: {typeSymbol.TypeKind}") + }; } private static string GetTypeDeclarationName(INamedTypeSymbol typeSymbol) From 973a3c3cb49c6729a1b01b0b1b87b0bc0624fecd Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:18:47 +0800 Subject: [PATCH 5/5] =?UTF-8?q?test(registration):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=B3=A8=E5=86=8C=E5=AF=BC=E5=87=BA=E9=9B=86?= =?UTF-8?q?=E5=90=88=E7=94=9F=E6=88=90=E5=99=A8=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了批量注册方法生成的基础功能测试 - 添加了集合元素类型推断失败时的诊断报告测试 - 添加了数组参数注册方法的生成测试 - 添加了从继承接口获取注册方法的测试 - 添加了显式接口实现成员不可访问的诊断测试 - 添加了从基类获取注册方法的测试 - 添加了从基类获取注册器成员的测试 - 添加了非实例可读集合成员的诊断测试 - 添加了非实例可读注册器成员的诊断测试 - 添加了注册方法不可访问的诊断测试 - 添加了属性参数无效时的诊断测试 - 添加了多个分部声明时只生成一个源文件的测试 --- .../AutoRegisterExportedCollectionsGeneratorTests.cs | 3 +-- .../AutoRegisterExportedCollectionsGenerator.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs b/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs index 1a9afec6..3a67d3c5 100644 --- a/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs +++ b/GFramework.Godot.SourceGenerators.Tests/Registration/AutoRegisterExportedCollectionsGeneratorTests.cs @@ -327,8 +327,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests { Sources = { source } }, - DisabledDiagnostics = { "GF_Common_Trace_001" }, - TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck + DisabledDiagnostics = { "GF_Common_Trace_001" } }; test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoExport_003", DiagnosticSeverity.Error) diff --git a/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs b/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs index d6fe8db3..12589767 100644 --- a/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs +++ b/GFramework.Godot.SourceGenerators/Registration/AutoRegisterExportedCollectionsGenerator.cs @@ -357,15 +357,23 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener INamedTypeSymbol registryType, string registerMethodName) { + // Start from the declared registry type so directly declared overloads win the cheap checks + // before we expand into inherited declarations. foreach (var method in registryType.GetMembers(registerMethodName).OfType()) yield return method; + // Concrete registry types can inherit callable implementations from base classes. When the + // registry itself is an interface, BaseType is null and this phase intentionally yields nothing. for (var baseType = registryType.BaseType; baseType is not null; baseType = baseType.BaseType) { foreach (var method in baseType.GetMembers(registerMethodName).OfType()) yield return method; } + // Only interface-typed registry members should search interface inheritance. For classes or + // structs this avoids accepting explicit interface implementations that generated code cannot + // call through `this..(...)`. AllInterfaces is already transitive, so the + // same semantic contract may appear multiple times; that is safe because the caller only uses Any(). if (registryType.TypeKind != TypeKind.Interface) yield break;