diff --git a/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs index daae4e6..a69a6f1 100644 --- a/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs @@ -234,7 +234,6 @@ public class ContextGetGeneratorTests public static IReadOnlyList GetModels(this object contextAware) => default!; public static T GetSystem(this object contextAware) => default!; public static T GetUtility(this object contextAware) => default!; - public static T GetService(this object contextAware) => default!; } } @@ -258,6 +257,7 @@ public class ContextGetGeneratorTests private ICombatSystem _system = null!; private IUiUtility _utility = null!; private IStrategy _service = null!; + private IReadOnlyList _services = null!; private Godot.Node _node = null!; } } @@ -279,6 +279,96 @@ public class ContextGetGeneratorTests _models = this.GetModels(); _system = this.GetSystem(); _utility = this.GetUtility(); + } + } + + """; + + await GeneratorTest.RunAsync( + source, + ("TestApp_BattlePanel.ContextGet.g.cs", expected)); + Assert.Pass(); + } + + [Test] + public async Task Generates_Explicit_Service_Binding_For_GetAll_Class() + { + var source = """ + using System; + using GFramework.SourceGenerators.Abstractions.Rule; + + namespace GFramework.SourceGenerators.Abstractions.Rule + { + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public sealed class GetAllAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Field, Inherited = false)] + public sealed class GetServiceAttribute : Attribute { } + } + + namespace GFramework.Core.Abstractions.Rule + { + public interface IContextAware { } + } + + namespace GFramework.Core.Rule + { + public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { } + } + + namespace GFramework.Core.Abstractions.Model + { + public interface IModel { } + } + + namespace GFramework.Core.Abstractions.Systems + { + public interface ISystem { } + } + + namespace GFramework.Core.Abstractions.Utility + { + public interface IUtility { } + } + + namespace GFramework.Core.Extensions + { + public static class ContextAwareServiceExtensions + { + public static T GetModel(this object contextAware) => default!; + public static T GetService(this object contextAware) => default!; + } + } + + namespace TestApp + { + public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { } + public interface IStrategy { } + + [GetAll] + public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase + { + private IInventoryModel _model = null!; + + [GetService] + private IStrategy _service = null!; + } + } + """; + + const string expected = """ + // + #nullable enable + + using GFramework.Core.Extensions; + + namespace TestApp; + + partial class BattlePanel + { + private void __InjectContextBindings_Generated() + { + _model = this.GetModel(); _service = this.GetService(); } } diff --git a/GFramework.SourceGenerators/README.md b/GFramework.SourceGenerators/README.md index 59e3d47..94ace29 100644 --- a/GFramework.SourceGenerators/README.md +++ b/GFramework.SourceGenerators/README.md @@ -43,4 +43,7 @@ public partial class InventoryPanel } ``` -`[GetAll]` 作用于类本身,会自动扫描字段并推断对应的 `GetX` 调用;已显式标记字段的优先级更高。 +`[GetAll]` 作用于类本身,会自动扫描字段并推断 `Model`、`System`、`Utility` 相关的 `GetX` 调用;已显式标记字段的优先级更高。 + +`Service` 和 `Services` 绑定不会在 `[GetAll]` 下自动推断。对于普通引用类型字段,请显式使用 `[GetService]` 或 +`[GetServices]`,避免将非上下文服务字段误判为服务依赖。 diff --git a/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs b/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs index 55a9537..6e11580 100644 --- a/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs +++ b/GFramework.SourceGenerators/Rule/ContextGetGenerator.cs @@ -582,12 +582,7 @@ public sealed class ContextGetGenerator : IIncrementalGenerator return true; } - if (elementType.IsReferenceType) - { - binding = new BindingInfo(fieldSymbol, BindingKind.Services, elementType); - return true; - } - + // Service collections stay opt-in for the same reason as single services. return false; } @@ -609,12 +604,7 @@ public sealed class ContextGetGenerator : IIncrementalGenerator return true; } - if (fieldSymbol.Type.IsReferenceType) - { - binding = new BindingInfo(fieldSymbol, BindingKind.Service, fieldSymbol.Type); - return true; - } - + // Service bindings stay opt-in because arbitrary reference types are too ambiguous to infer safely. return false; } @@ -721,12 +711,11 @@ public sealed class ContextGetGenerator : IIncrementalGenerator if (readOnlyList is null || fieldType is not INamedTypeSymbol targetType) return false; - foreach (var candidateType in EnumerateCollectionTypeCandidates(targetType)) - { - if (candidateType.TypeArguments.Length != 1) - continue; + var allTypeCandidates = EnumerateCollectionTypeCandidates(targetType) + .SelectMany(candidateType => candidateType.TypeArguments); - var candidateElementType = candidateType.TypeArguments[0]; + foreach (var candidateElementType in allTypeCandidates) + { var expectedSourceType = readOnlyList.Construct(candidateElementType); if (!expectedSourceType.IsAssignableTo(targetType)) continue;