Skip to content

Feat/context aware injection generator#147

Merged
GeWuYou merged 5 commits into
mainfrom
feat/context-aware-injection-generator
Mar 28, 2026
Merged

Feat/context aware injection generator#147
GeWuYou merged 5 commits into
mainfrom
feat/context-aware-injection-generator

Conversation

@GeWuYou
Copy link
Copy Markdown
Owner

@GeWuYou GeWuYou commented Mar 28, 2026

Summary by Sourcery

引入一个源码生成器,用于为符合条件的类自动生成与上下文相关的注入方法,并将其接入诊断和核心 API。

新特性:

  • 添加用于上下文字段和类注入的特性和抽象(GetService / GetSystem / GetModel / GetUtility 及其集合变体,以及 GetAll)。
  • 添加 ContextGetGenerator 增量源码生成器,用于合成一个上下文注入方法,根据特性或类型推断将字段绑定到上下文中的服务(services)、系统(systems)、模型(models)和工具(utilities)。
  • 为上下文感知注入的非法用法引入诊断信息,例如嵌套类、静态或只读字段、非法字段类型以及缺失上下文感知能力等情况。
  • 添加扩展方法和辅助类型,以支持生成器的符号检查,并计算完整类名和类型可赋值性。

缺陷修复:

  • ContextAwareServiceExtensions 中的上下文感知服务访问器对服务、系统、模型和工具返回非可空类型。

增强项:

  • 扩展 INamedTypeSymbol 工具,添加对部分类声明的检查,以及用于生成器输出的命名空间/类名辅助方法。
  • 在源码生成器项目中添加对 netstandard 兼容的 IsExternalInit shim,以支持 init-only 和 record 特性。

文档:

  • 添加 README,记录上下文 Get 注入生成器的说明、使用模式以及基于特性的配置。

测试:

  • ContextGetGenerator 添加单元测试套件,覆盖显式特性绑定、GetAll 推断、IContextAware 使用,以及针对非法字段类型和非上下文感知类的错误诊断。
Original summary in English

Summary by Sourcery

Introduce a source generator that auto-generates context-aware injection methods for eligible classes and wire it into the diagnostics and core APIs.

New Features:

  • Add attributes and abstractions for context-aware field and class injection (GetService / GetSystem / GetModel / GetUtility and collection variants, plus GetAll).
  • Add ContextGetGenerator incremental source generator to synthesize a context injection method that binds fields to context services, systems, models, and utilities based on attributes or type inference.
  • Introduce diagnostics for invalid usage of context-aware injection, such as nested classes, static or readonly fields, invalid field types, and missing context-awareness.
  • Add extension methods and helper types to support symbol inspection for generators and to compute full class names and type assignability.

Bug Fixes:

  • Make context-aware service accessors in ContextAwareServiceExtensions return non-nullable types for services, systems, models, and utilities.

Enhancements:

  • Extend INamedTypeSymbol utilities with partial-declaration checks and namespace/class-name helpers for generator output.
  • Add netstandard-compatible IsExternalInit shims in source generator projects to support init-only and record features.

Documentation:

  • Add README documenting the context Get injection generator, usage patterns, and attribute-based configuration.

Tests:

  • Add unit test suite for ContextGetGenerator covering explicit attribute bindings, GetAll inference, IContextAware usage, and error diagnostics for invalid field types and non-context-aware classes.

GeWuYou added 2 commits March 28, 2026 11:29
- 移除ContextAwareServiceExtensions中GetService/GetSystem/GetModel/GetUtility方法的可空返回值
- 添加ContextGetGenerator源码生成器,支持通过特性自动生成上下文注入代码
- 新增GetService/GetServices/GetSystem/GetSystems/GetModel/GetModels/GetUtility/GetUtilities/GetAll特性
- 添加ContextGetDiagnostics提供注入相关的编译时诊断检查
- 实现INamedTypeSymbol扩展方法AreAllDeclarationsPartial用于检查partial类声明
- 添加ITypeSymbol扩展方法IsAssignableTo用于类型兼容性判断
- 创建FieldCandidateInfo和TypeCandidateInfo记录类型用于存储生成器候选信息
- 添加IsExternalInit内部类型支持低版本.NET框架的init-only setter功能
- 更新AnalyzerReleases.Unshipped.md添加新的诊断规则条目
- 创建完整的单元测试验证生成器功能和各种边界情况
- 将 ContextSymbols 的创建提取为独立方法 CreateContextSymbols
- 将源码生成逻辑提取为独立方法 GenerateSources
- 将绑定收集逻辑提取为独立方法 CollectBindings
- 将显式绑定添加逻辑提取为独立方法 AddExplicitBindings
- 将推断绑定添加逻辑提取为独立方法 AddInferredBindings
- 将绑定推断检查逻辑提取为独立方法 CanInferBinding
- 优化了代码组织结构和可读性
@deepsource-io
Copy link
Copy Markdown

deepsource-io Bot commented Mar 28, 2026

DeepSource Code Review

We reviewed changes in c681c48...6e3954e on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
C# Mar 28, 2026 5:06a.m. Review ↗
Secrets Mar 28, 2026 5:06a.m. Review ↗

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 28, 2026

审阅者指南

实现了一个新的 Roslyn 增量源生成器,用于为带注解的类自动生成具备上下文感知能力的 Get* 注入方法;同时引入了相应的公共 Attribute、诊断信息、辅助工具和测试;并通过将 Get* 方法改为非可空返回值以及增强符号工具,略微收紧了核心的上下文感知扩展 API。

运行时 Context Get 注入用法的时序图

sequenceDiagram
    participant InventoryPanel
    participant InjectMethod
    participant ContextExtensions
    participant Context

    InventoryPanel->>InjectMethod: __InjectContextBindings_Generated()
    InjectMethod->>ContextExtensions: GetModel~IInventoryModel~(InventoryPanel)
    ContextExtensions->>Context: resolve IInventoryModel
    Context-->>ContextExtensions: IInventoryModel (non-null)
    ContextExtensions-->>InjectMethod: IInventoryModel (non-null)
    InjectMethod->>InventoryPanel: assign _model

    InjectMethod->>ContextExtensions: GetServices~IInventoryStrategy~(InventoryPanel)
    ContextExtensions->>Context: resolve IReadOnlyList~IInventoryStrategy~
    Context-->>ContextExtensions: IReadOnlyList~IInventoryStrategy~
    ContextExtensions-->>InjectMethod: IReadOnlyList~IInventoryStrategy~
    InjectMethod->>InventoryPanel: assign _strategies
Loading

ContextGetGenerator 及相关类型的类图

classDiagram
    class ContextGetGenerator {
        +Initialize(context IncrementalGeneratorInitializationContext) void
        -IsFieldCandidate(node SyntaxNode) bool
        -TransformField(context GeneratorSyntaxContext) FieldCandidateInfo
        -IsTypeCandidate(node SyntaxNode) bool
        -TransformType(context GeneratorSyntaxContext) TypeCandidateInfo
        -Execute(context SourceProductionContext, compilation Compilation, fieldCandidates ImmutableArray~FieldCandidateInfo~, typeCandidates ImmutableArray~TypeCandidateInfo~) void
        -GenerateSources(context SourceProductionContext, descriptors ImmutableArray~ResolvedBindingDescriptor~, symbols ContextSymbols, workItems Dictionary~INamedTypeSymbol, TypeWorkItem~) void
        -CollectBindings(context SourceProductionContext, workItem TypeWorkItem, descriptors ImmutableArray~ResolvedBindingDescriptor~, symbols ContextSymbols) List~BindingInfo~
        -CanGenerateForType(context SourceProductionContext, workItem TypeWorkItem, symbols ContextSymbols) bool
        -GenerateSource(typeSymbol INamedTypeSymbol, bindings IReadOnlyList~BindingInfo~) string
        -GetHintName(typeSymbol INamedTypeSymbol) string
    }

    class BindingKind {
        <<enumeration>>
        Service
        Services
        System
        Systems
        Model
        Models
        Utility
        Utilities
    }

    class BindingDescriptor {
        +Kind BindingKind
        +MetadataName string
        +AttributeName string
        +IsCollection bool
    }

    class ResolvedBindingDescriptor {
        +Definition BindingDescriptor
        +AttributeSymbol INamedTypeSymbol
    }

    class BindingInfo {
        +Field IFieldSymbol
        +Kind BindingKind
        +TargetType ITypeSymbol
    }

    class ContextSymbols {
        +ContextAwareAttribute INamedTypeSymbol
        +IContextAware INamedTypeSymbol
        +ContextAwareBase INamedTypeSymbol
        +IModel INamedTypeSymbol
        +ISystem INamedTypeSymbol
        +IUtility INamedTypeSymbol
        +IReadOnlyList INamedTypeSymbol
        +GodotNode INamedTypeSymbol
    }

    class TypeWorkItem {
        +TypeSymbol INamedTypeSymbol
        +FieldCandidates List~FieldCandidateInfo~
        +GetAllDeclaration ClassDeclarationSyntax
    }

    class FieldCandidateInfo {
        +Variable VariableDeclaratorSyntax
        +FieldSymbol IFieldSymbol
    }

    class TypeCandidateInfo {
        +Declaration ClassDeclarationSyntax
        +TypeSymbol INamedTypeSymbol
    }

    class ContextGetDiagnostics {
        <<static>>
        +NestedClassNotSupported DiagnosticDescriptor
        +StaticFieldNotSupported DiagnosticDescriptor
        +ReadOnlyFieldNotSupported DiagnosticDescriptor
        +InvalidBindingType DiagnosticDescriptor
        +ContextAwareTypeRequired DiagnosticDescriptor
        +MultipleBindingAttributesNotSupported DiagnosticDescriptor
    }

    class ITypeSymbolExtensions {
        +IsAssignableTo(typeSymbol ITypeSymbol, targetType INamedTypeSymbol) bool
    }

    class INamedTypeSymbolExtensions {
        +AreAllDeclarationsPartial(symbol INamedTypeSymbol) bool
        +GetFullClassName(symbol INamedTypeSymbol) string
        +GetNamespace(symbol INamedTypeSymbol) string
    }

    class GetAllAttribute {
        <<attribute>>
    }

    class GetServiceAttribute {
        <<attribute>>
    }

    class GetServicesAttribute {
        <<attribute>>
    }

    class GetSystemAttribute {
        <<attribute>>
    }

    class GetSystemsAttribute {
        <<attribute>>
    }

    class GetModelAttribute {
        <<attribute>>
    }

    class GetModelsAttribute {
        <<attribute>>
    }

    class GetUtilityAttribute {
        <<attribute>>
    }

    class GetUtilitiesAttribute {
        <<attribute>>
    }

    ContextGetGenerator --> FieldCandidateInfo : uses
    ContextGetGenerator --> TypeCandidateInfo : uses
    ContextGetGenerator --> BindingDescriptor : uses
    ContextGetGenerator --> ResolvedBindingDescriptor : uses
    ContextGetGenerator --> BindingInfo : uses
    ContextGetGenerator --> ContextSymbols : uses
    ContextGetGenerator --> TypeWorkItem : aggregates
    ContextGetGenerator --> ContextGetDiagnostics : reports
    ContextGetGenerator --> ITypeSymbolExtensions : calls
    ContextGetGenerator --> INamedTypeSymbolExtensions : calls

    TypeWorkItem --> FieldCandidateInfo : contains

    FieldCandidateInfo --> IFieldSymbol : wraps
    TypeCandidateInfo --> INamedTypeSymbol : wraps

    GetAllAttribute <.. ContextGetGenerator : reflected
    GetServiceAttribute <.. ContextGetGenerator : reflected
    GetServicesAttribute <.. ContextGetGenerator : reflected
    GetSystemAttribute <.. ContextGetGenerator : reflected
    GetSystemsAttribute <.. ContextGetGenerator : reflected
    GetModelAttribute <.. ContextGetGenerator : reflected
    GetModelsAttribute <.. ContextGetGenerator : reflected
    GetUtilityAttribute <.. ContextGetGenerator : reflected
    GetUtilitiesAttribute <.. ContextGetGenerator : reflected
Loading

ContextGetGenerator 增量流水线流程图

flowchart TD
    A[来自编译的语法节点] --> B[筛选带有 Get* Attribute 的字段节点]
    A --> C[筛选带有 GetAll Attribute 的类节点]

    B --> D[使用 SemanticModel 转换为 FieldCandidateInfo]
    C --> E[使用 SemanticModel 转换为 TypeCandidateInfo]

    D --> F[收集字段候选]
    E --> G[收集类型候选]

    F --> H[与 Compilation 合并]
    G --> H

    H --> I[根据 Attribute 元数据解析 BindingDescriptor]
    H --> J[为具备上下文感知能力的类型创建 ContextSymbols]

    I --> K[按包含类型收集 TypeWorkItem]
    G --> K
    F --> K

    K --> L[验证类型:非嵌套、partial、具备上下文感知能力]
    L --> M{能为该类型生成吗?}
    M -- No --> N[报告诊断并跳过]
    M -- Yes --> O[收集显式绑定]
    O --> P[为 GetAll 收集推断绑定]

    P --> Q{是否存在任何绑定或 GetAll?}
    Q -- No --> R[跳过该类型的代码生成]
    Q -- Yes --> S[生成 __InjectContextBindings_Generated 方法]

    S --> T[将生成的源码添加到编译中]
Loading

文件级变更

变更 详情 文件
添加 Roslyn 增量生成器,用于基于字段 Attribute 和类型推断发出具备上下文感知能力的 __InjectContextBindings_Generated 方法。
  • 引入实现 IIncrementalGenerator 的 ContextGetGenerator,用于扫描带有 Get* 和 GetAll Attribute 的字段和类型声明,并按类型收集工作项。
  • 解析 GetService/GetServices/GetSystem/GetSystems/GetModel/GetModels/GetUtility/GetUtilities Attribute 的绑定描述符,并通过符号分析将字段映射到绑定种类和目标类型。
  • 生成一个 partial 类方法 __InjectContextBindings_Generated,将每个参与字段从相应的扩展方法(GetService/GetSystem/GetModel/GetUtility 及其复数变体)中赋值。
  • 禁止为嵌套类或非 partial 类,以及 static/readonly 字段生成代码,并避免为 Godot.Node 字段或无效类型/集合推断绑定。
GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
为新生成器添加诊断信息并将其接入 analyzer 发布配置。
  • 为不支持的嵌套类、static/readonly 字段、无效绑定类型、非上下文感知类型以及单个字段上的多个绑定 Attribute 定义 DiagnosticDescriptor。
  • 将新的诊断 ID GF_ContextGet_001 到 GF_ContextGet_006 注册为未发布的 analyzer 条目,并映射到 ContextGetDiagnostics。
GFramework.SourceGenerators/Diagnostics/ContextGetDiagnostics.cs
GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md
暴露用于声明式上下文感知注入的公共 Attribute API,并编写使用文档。
  • 在命名空间 SourceGenerators.Abstractions.Rule 下引入 GetService/GetServices/GetSystem/GetSystems/GetModel/GetModels/GetUtility/GetUtilities 字段 Attribute 和类级别的 GetAllAttribute,并设置为不允许多重使用。
  • 新增 README,描述什么是具备上下文感知能力的类型、如何使用字段级 Attribute 与 GetAll,以及如何在用户代码中调用生成的 __InjectContextBindings_Generated 方法。
GFramework.SourceGenerators.Abstractions/Rule/GetAllAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetServiceAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetServicesAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetSystemAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetSystemsAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetModelAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetModelsAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetUtilityAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetUtilitiesAttribute.cs
GFramework.SourceGenerators/README.md
添加生成器使用的通用符号辅助类型和扩展方法。
  • 添加 ITypeSymbolExtensions.IsAssignableTo,以使用 SymbolEqualityComparer 并遍历接口和基类型来执行接口/基类型可赋值性检查。
  • 添加 INamedTypeSymbolExtensions.AreAllDeclarationsPartial,并修复 GetFullClassName/GetNamespace,使其成为真正的扩展方法,并返回完整的嵌套类名和命名空间字符串。
  • 引入 FieldCandidateInfo 和 TypeCandidateInfo 记录,用于保存字段和类型的语法/符号配对。
  • 添加 IsExternalInit stub,以在较旧目标上支持 C# 9 特性(在 common 项目和生成器项目中)。
GFramework.SourceGenerators.Common/Extensions/ITypeSymbolExtensions.cs
GFramework.SourceGenerators.Common/Extensions/INamedTypeSymbolExtensions.cs
GFramework.SourceGenerators.Common/Info/FieldCandidateInfo.cs
GFramework.SourceGenerators.Common/Info/TypeCandidateInfo.cs
GFramework.SourceGenerators.Common/Internals/IsExternalInit.cs
GFramework.SourceGenerators/Internals/IsExternalInit.cs
使用 Roslyn 测试基础设施加固并测试生成器行为。
  • 为带有 [ContextAware] 的类上的显式绑定、带有 [GetAll] 的类上的推断绑定(包括过滤 Godot.Node),以及 IContextAware 实现添加测试。
  • 添加负向测试,以确保在类不是上下文感知类型或 GetModels 字段不是 IReadOnlyList 时会产生诊断,并验证预期的诊断 ID、代码范围和参数。
  • 使用 GeneratorTest/CSharpSourceGeneratorTest 框架来断言生成源码的 hint 名称和内容。
GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
收紧核心的上下文感知扩展 API,要求 Get* 结果为非可空。
  • 将 ContextAwareServiceExtensions.GetService/GetSystem/GetModel/GetUtility 的返回类型从可空 T? 改为非可空 T,同时保留从上下文中检索的现有行为。
  • 确保生成器始终发出与新的方法签名一致的非可空赋值。
GFramework.Core/Extensions/ContextAwareServiceExtensions.cs

提示与命令

与 Sourcery 交互

  • 触发新的审阅: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审阅评论。
  • 从审阅评论生成 GitHub issue: 在审阅评论下回复,请求 Sourcery 从该评论创建一个 issue。你也可以在审阅评论中回复 @sourcery-ai issue 来从中创建一个 issue。
  • 生成 pull request 标题: 在 pull request 标题中任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title 以(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文中任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary 以在任意时间(重新)生成摘要。
  • 生成审阅者指南: 在 pull request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成审阅者指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,即可标记为已解决所有 Sourcery 评论。适用于你已经处理完所有评论且不希望再看到它们时。
  • 关闭所有 Sourcery 审阅: 在 pull request 中评论 @sourcery-ai dismiss,即可关闭所有已有的 Sourcery 审阅。若想重新开始新的审阅特别有用——别忘了评论 @sourcery-ai review 来触发新的审阅!

自定义你的体验

访问你的 控制面板 以:

  • 启用或停用审阅功能,例如 Sourcery 生成的 pull request 摘要、审阅者指南等。
  • 更改审阅语言。
  • 添加、移除或编辑自定义审阅说明。
  • 调整其他审阅设置。

获取帮助

Original review guide in English

Reviewer's Guide

Implements a new Roslyn incremental source generator that auto-generates context-aware Get* injection methods for annotated classes, introduces the corresponding public attributes, diagnostics, helpers, and tests, and slightly tightens the core context-aware extension API by making Get* methods non-nullable and enhancing symbol utilities.

Sequence diagram for runtime Context Get injection usage

sequenceDiagram
    participant InventoryPanel
    participant InjectMethod
    participant ContextExtensions
    participant Context

    InventoryPanel->>InjectMethod: __InjectContextBindings_Generated()
    InjectMethod->>ContextExtensions: GetModel~IInventoryModel~(InventoryPanel)
    ContextExtensions->>Context: resolve IInventoryModel
    Context-->>ContextExtensions: IInventoryModel (non-null)
    ContextExtensions-->>InjectMethod: IInventoryModel (non-null)
    InjectMethod->>InventoryPanel: assign _model

    InjectMethod->>ContextExtensions: GetServices~IInventoryStrategy~(InventoryPanel)
    ContextExtensions->>Context: resolve IReadOnlyList~IInventoryStrategy~
    Context-->>ContextExtensions: IReadOnlyList~IInventoryStrategy~
    ContextExtensions-->>InjectMethod: IReadOnlyList~IInventoryStrategy~
    InjectMethod->>InventoryPanel: assign _strategies
Loading

Class diagram for ContextGetGenerator and related types

classDiagram
    class ContextGetGenerator {
        +Initialize(context IncrementalGeneratorInitializationContext) void
        -IsFieldCandidate(node SyntaxNode) bool
        -TransformField(context GeneratorSyntaxContext) FieldCandidateInfo
        -IsTypeCandidate(node SyntaxNode) bool
        -TransformType(context GeneratorSyntaxContext) TypeCandidateInfo
        -Execute(context SourceProductionContext, compilation Compilation, fieldCandidates ImmutableArray~FieldCandidateInfo~, typeCandidates ImmutableArray~TypeCandidateInfo~) void
        -GenerateSources(context SourceProductionContext, descriptors ImmutableArray~ResolvedBindingDescriptor~, symbols ContextSymbols, workItems Dictionary~INamedTypeSymbol, TypeWorkItem~) void
        -CollectBindings(context SourceProductionContext, workItem TypeWorkItem, descriptors ImmutableArray~ResolvedBindingDescriptor~, symbols ContextSymbols) List~BindingInfo~
        -CanGenerateForType(context SourceProductionContext, workItem TypeWorkItem, symbols ContextSymbols) bool
        -GenerateSource(typeSymbol INamedTypeSymbol, bindings IReadOnlyList~BindingInfo~) string
        -GetHintName(typeSymbol INamedTypeSymbol) string
    }

    class BindingKind {
        <<enumeration>>
        Service
        Services
        System
        Systems
        Model
        Models
        Utility
        Utilities
    }

    class BindingDescriptor {
        +Kind BindingKind
        +MetadataName string
        +AttributeName string
        +IsCollection bool
    }

    class ResolvedBindingDescriptor {
        +Definition BindingDescriptor
        +AttributeSymbol INamedTypeSymbol
    }

    class BindingInfo {
        +Field IFieldSymbol
        +Kind BindingKind
        +TargetType ITypeSymbol
    }

    class ContextSymbols {
        +ContextAwareAttribute INamedTypeSymbol
        +IContextAware INamedTypeSymbol
        +ContextAwareBase INamedTypeSymbol
        +IModel INamedTypeSymbol
        +ISystem INamedTypeSymbol
        +IUtility INamedTypeSymbol
        +IReadOnlyList INamedTypeSymbol
        +GodotNode INamedTypeSymbol
    }

    class TypeWorkItem {
        +TypeSymbol INamedTypeSymbol
        +FieldCandidates List~FieldCandidateInfo~
        +GetAllDeclaration ClassDeclarationSyntax
    }

    class FieldCandidateInfo {
        +Variable VariableDeclaratorSyntax
        +FieldSymbol IFieldSymbol
    }

    class TypeCandidateInfo {
        +Declaration ClassDeclarationSyntax
        +TypeSymbol INamedTypeSymbol
    }

    class ContextGetDiagnostics {
        <<static>>
        +NestedClassNotSupported DiagnosticDescriptor
        +StaticFieldNotSupported DiagnosticDescriptor
        +ReadOnlyFieldNotSupported DiagnosticDescriptor
        +InvalidBindingType DiagnosticDescriptor
        +ContextAwareTypeRequired DiagnosticDescriptor
        +MultipleBindingAttributesNotSupported DiagnosticDescriptor
    }

    class ITypeSymbolExtensions {
        +IsAssignableTo(typeSymbol ITypeSymbol, targetType INamedTypeSymbol) bool
    }

    class INamedTypeSymbolExtensions {
        +AreAllDeclarationsPartial(symbol INamedTypeSymbol) bool
        +GetFullClassName(symbol INamedTypeSymbol) string
        +GetNamespace(symbol INamedTypeSymbol) string
    }

    class GetAllAttribute {
        <<attribute>>
    }

    class GetServiceAttribute {
        <<attribute>>
    }

    class GetServicesAttribute {
        <<attribute>>
    }

    class GetSystemAttribute {
        <<attribute>>
    }

    class GetSystemsAttribute {
        <<attribute>>
    }

    class GetModelAttribute {
        <<attribute>>
    }

    class GetModelsAttribute {
        <<attribute>>
    }

    class GetUtilityAttribute {
        <<attribute>>
    }

    class GetUtilitiesAttribute {
        <<attribute>>
    }

    ContextGetGenerator --> FieldCandidateInfo : uses
    ContextGetGenerator --> TypeCandidateInfo : uses
    ContextGetGenerator --> BindingDescriptor : uses
    ContextGetGenerator --> ResolvedBindingDescriptor : uses
    ContextGetGenerator --> BindingInfo : uses
    ContextGetGenerator --> ContextSymbols : uses
    ContextGetGenerator --> TypeWorkItem : aggregates
    ContextGetGenerator --> ContextGetDiagnostics : reports
    ContextGetGenerator --> ITypeSymbolExtensions : calls
    ContextGetGenerator --> INamedTypeSymbolExtensions : calls

    TypeWorkItem --> FieldCandidateInfo : contains

    FieldCandidateInfo --> IFieldSymbol : wraps
    TypeCandidateInfo --> INamedTypeSymbol : wraps

    GetAllAttribute <.. ContextGetGenerator : reflected
    GetServiceAttribute <.. ContextGetGenerator : reflected
    GetServicesAttribute <.. ContextGetGenerator : reflected
    GetSystemAttribute <.. ContextGetGenerator : reflected
    GetSystemsAttribute <.. ContextGetGenerator : reflected
    GetModelAttribute <.. ContextGetGenerator : reflected
    GetModelsAttribute <.. ContextGetGenerator : reflected
    GetUtilityAttribute <.. ContextGetGenerator : reflected
    GetUtilitiesAttribute <.. ContextGetGenerator : reflected
Loading

Flow diagram for ContextGetGenerator incremental pipeline

flowchart TD
    A[Syntax nodes from compilation] --> B[Filter field nodes with Get* attributes]
    A --> C[Filter class nodes with GetAll attribute]

    B --> D[Transform to FieldCandidateInfo using SemanticModel]
    C --> E[Transform to TypeCandidateInfo using SemanticModel]

    D --> F[Collect field candidates]
    E --> G[Collect type candidates]

    F --> H[Combine with Compilation]
    G --> H

    H --> I[Resolve BindingDescriptors from attribute metadata]
    H --> J[Create ContextSymbols for context aware types]

    I --> K[Collect TypeWorkItem per containing type]
    G --> K
    F --> K

    K --> L[Validate type: non nested, partial, context aware]
    L --> M{Can generate for type?}
    M -- No --> N[Report diagnostics and skip]
    M -- Yes --> O[Collect explicit bindings]
    O --> P[Collect inferred bindings for GetAll]

    P --> Q{Any bindings or GetAll?}
    Q -- No --> R[Skip generation for type]
    Q -- Yes --> S[Generate __InjectContextBindings_Generated method]

    S --> T[Add generated source to compilation]
Loading

File-Level Changes

Change Details Files
Add Roslyn incremental generator to emit context-aware __InjectContextBindings_Generated methods based on field attributes and type inference.
  • Introduce ContextGetGenerator implementing IIncrementalGenerator to scan field and type declarations with Get* and GetAll attributes and collect work items per type.
  • Resolve binding descriptors for GetService/GetServices/GetSystem/GetSystems/GetModel/GetModels/GetUtility/GetUtilities attributes and map fields to binding kinds and target types via symbol analysis.
  • Generate a partial class method __InjectContextBindings_Generated that assigns each participating field from the appropriate extension method (GetService/GetSystem/GetModel/GetUtility and plural variants).
  • Disallow generation for nested or non-partial classes and for static/readonly fields, and avoid inferring bindings for Godot.Node fields or invalid types/collections.
GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
Add diagnostics for the new generator and wire them into analyzer releases.
  • Define DiagnosticDescriptors for unsupported nested classes, static/readonly fields, invalid binding types, non-context-aware types, and multiple binding attributes on a single field.
  • Register new diagnostic IDs GF_ContextGet_001 through GF_ContextGet_006 as unshipped analyzer entries mapped to ContextGetDiagnostics.
GFramework.SourceGenerators/Diagnostics/ContextGetDiagnostics.cs
GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md
Expose public attribute API for declarative context-aware injection and document usage.
  • Introduce GetService/GetServices/GetSystem/GetSystems/GetModel/GetModels/GetUtility/GetUtilities field attributes and a class-level GetAllAttribute under the SourceGenerators.Abstractions.Rule namespace with non-multiple usage constraints.
  • Add README describing what constitutes a context-aware type, how to use field-level attributes versus GetAll, and how to invoke the generated __InjectContextBindings_Generated method in user code.
GFramework.SourceGenerators.Abstractions/Rule/GetAllAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetServiceAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetServicesAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetSystemAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetSystemsAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetModelAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetModelsAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetUtilityAttribute.cs
GFramework.SourceGenerators.Abstractions/Rule/GetUtilitiesAttribute.cs
GFramework.SourceGenerators/README.md
Add common symbol helper types and extensions used by the generator.
  • Add ITypeSymbolExtensions.IsAssignableTo to perform interface/base-type assignability checks using SymbolEqualityComparer and traversing interfaces and base types.
  • Add INamedTypeSymbolExtensions.AreAllDeclarationsPartial, and fix GetFullClassName/GetNamespace to be proper extension methods returning full nested class name and namespace string.
  • Introduce FieldCandidateInfo and TypeCandidateInfo records to carry syntax/symbol pairs for fields and types.
  • Add IsExternalInit stub to support C# 9 features on older targets in common and generator projects.
GFramework.SourceGenerators.Common/Extensions/ITypeSymbolExtensions.cs
GFramework.SourceGenerators.Common/Extensions/INamedTypeSymbolExtensions.cs
GFramework.SourceGenerators.Common/Info/FieldCandidateInfo.cs
GFramework.SourceGenerators.Common/Info/TypeCandidateInfo.cs
GFramework.SourceGenerators.Common/Internals/IsExternalInit.cs
GFramework.SourceGenerators/Internals/IsExternalInit.cs
Harden and test the generator behavior with Roslyn testing infrastructure.
  • Add tests for explicit bindings on [ContextAware] classes, inferred bindings on [GetAll] classes (including filtering Godot.Node), and for IContextAware implementations.
  • Add negative tests to ensure diagnostics are raised when a class is not context-aware or when a GetModels field is not an IReadOnlyList, including expected diagnostic IDs, spans, and arguments.
  • Use GeneratorTest/ CSharpSourceGeneratorTest harness to assert on generated source hint names and contents.
GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
Tighten core context-aware extension API by requiring non-nullable Get* results.
  • Change ContextAwareServiceExtensions.GetService/GetSystem/GetModel/GetUtility to return non-nullable T instead of nullable T?, while preserving existing behavior of retrieving from the context.
  • Ensure the generator always emits non-nullable assignments consistent with the new method signatures.
GFramework.Core/Extensions/ContextAwareServiceExtensions.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Comment thread GFramework.SourceGenerators/Rule/ContextGetGenerator.cs
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并给出了一些高层次的反馈:

  • IsFieldCandidate/IsTypeCandidate 中的特性检测当前依赖于 attribute.Name.ToString().Contains("Get")/"GetAll"),这种方式比较脆弱(别名、限定名或无关的特性也可能命中);建议改为基于语义、针对已知特性符号进行检查。
  • TryResolveCollectionElement 目前只识别 IReadOnlyList<T> 这一种集合形态,因此像 [GetModels] 这样的特性在数组或其它可赋值的列表接口上将无法工作;如果这不是有意为之,建议遍历已实现的接口(如 AllInterfaces),以支持更宽泛的集合类型。
  • IsExternalInit shim 在 GFramework.SourceGenerators.CommonGFramework.SourceGenerators 中都有一份重复实现;建议抽取到一个共享文件或项目中,以避免出现差异,并将该兼容性补丁集中维护在单一位置。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The attribute detection in `IsFieldCandidate`/`IsTypeCandidate` currently relies on `attribute.Name.ToString().Contains("Get")/"GetAll")`, which is brittle (aliasing, qualification, or unrelated attributes can match); consider switching to semantic checks against the known attribute symbols instead.
- `TryResolveCollectionElement` only recognizes `IReadOnlyList<T>` as the collection shape, so attributes like `[GetModels]` won’t work with arrays or other list interfaces even if they are assignable; if that’s not intentional, you may want to inspect implemented interfaces (e.g., `AllInterfaces`) to allow a broader set of collection types.
- The `IsExternalInit` shim is duplicated in both `GFramework.SourceGenerators.Common` and `GFramework.SourceGenerators`; consider factoring this into a shared file or project to avoid divergence and keep the workaround in a single place.

## Individual Comments

### Comment 1
<location path="GFramework.Core/Extensions/ContextAwareServiceExtensions.cs" line_range="23" />
<code_context>
     /// <returns>指定类型的服务实例,如果未找到则返回 null</returns>
     /// <exception cref="ArgumentNullException">当 contextAware 参数为 null 时抛出</exception>
-    public static TService? GetService<TService>(this IContextAware contextAware) where TService : class
+    public static TService GetService<TService>(this IContextAware contextAware) where TService : class
     {
         ArgumentNullException.ThrowIfNull(contextAware);
</code_context>
<issue_to_address>
**issue (bug_risk):** GetService 的可空性契约现在声称永远不会返回 null,这可能与底层上下文实现不一致。

将返回类型从 `TService?` 修改为 `TService` 表示调用方现在会假设该方法永远不会返回 null。除非 `context.GetService<TService>()` 被保证要么返回服务实例,要么抛出异常,否则这会与实际行为不符,进而导致隐藏的 null 和 NRE。可以选择保持可空返回类型,或者显式使用 `!` 抑制并在文档中声明该保证,或者在服务缺失时抛出异常以强制保证非空。
</issue_to_address>

### Comment 2
<location path="GFramework.SourceGenerators/Rule/ContextGetGenerator.cs" line_range="128-130" />
<code_context>
+            })
+            return false;
+
+        return fieldDeclaration.AttributeLists
+            .SelectMany(static list => list.Attributes)
+            .Any(static attribute => attribute.Name.ToString().Contains("Get", StringComparison.Ordinal));
+    }
+
</code_context>
<issue_to_address>
**suggestion:** 使用字符串 Contains("Get") 来根据特性名预筛选候选项比较脆弱,且可能产生误报。

该谓词会将名称文本包含 "Get" 的任意特性都标记为候选,这会无意中包含不相关的特性(例如 `[Target]``[ForgetMe]`,或者只在命名空间别名中包含 "Get" 的情况)。虽然后续基于符号的过滤能保证正确性,但会增加工作量,也可能带来令人意外的匹配结果。可以考虑仅限制在简单标识符名上(例如 `attribute.Name is IdentifierNameSyntax id && id.Identifier.ValueText.StartsWith("Get", ...)`),或者只匹配一组已知的特性标识符,以避免偶然命中并减少增量管线中的噪音。

```suggestion
        return fieldDeclaration.AttributeLists
            .SelectMany(static list => list.Attributes)
            .Any(static attribute =>
                attribute.Name is IdentifierNameSyntax id &&
                id.Identifier.ValueText.StartsWith("Get", StringComparison.Ordinal));
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The attribute detection in IsFieldCandidate/IsTypeCandidate currently relies on attribute.Name.ToString().Contains("Get")/"GetAll"), which is brittle (aliasing, qualification, or unrelated attributes can match); consider switching to semantic checks against the known attribute symbols instead.
  • TryResolveCollectionElement only recognizes IReadOnlyList<T> as the collection shape, so attributes like [GetModels] won’t work with arrays or other list interfaces even if they are assignable; if that’s not intentional, you may want to inspect implemented interfaces (e.g., AllInterfaces) to allow a broader set of collection types.
  • The IsExternalInit shim is duplicated in both GFramework.SourceGenerators.Common and GFramework.SourceGenerators; consider factoring this into a shared file or project to avoid divergence and keep the workaround in a single place.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The attribute detection in `IsFieldCandidate`/`IsTypeCandidate` currently relies on `attribute.Name.ToString().Contains("Get")/"GetAll")`, which is brittle (aliasing, qualification, or unrelated attributes can match); consider switching to semantic checks against the known attribute symbols instead.
- `TryResolveCollectionElement` only recognizes `IReadOnlyList<T>` as the collection shape, so attributes like `[GetModels]` won’t work with arrays or other list interfaces even if they are assignable; if that’s not intentional, you may want to inspect implemented interfaces (e.g., `AllInterfaces`) to allow a broader set of collection types.
- The `IsExternalInit` shim is duplicated in both `GFramework.SourceGenerators.Common` and `GFramework.SourceGenerators`; consider factoring this into a shared file or project to avoid divergence and keep the workaround in a single place.

## Individual Comments

### Comment 1
<location path="GFramework.Core/Extensions/ContextAwareServiceExtensions.cs" line_range="23" />
<code_context>
     /// <returns>指定类型的服务实例,如果未找到则返回 null</returns>
     /// <exception cref="ArgumentNullException">当 contextAware 参数为 null 时抛出</exception>
-    public static TService? GetService<TService>(this IContextAware contextAware) where TService : class
+    public static TService GetService<TService>(this IContextAware contextAware) where TService : class
     {
         ArgumentNullException.ThrowIfNull(contextAware);
</code_context>
<issue_to_address>
**issue (bug_risk):** The nullability contract of GetService now claims to never return null, which may be inconsistent with the underlying context implementation.

Changing the return type from `TService?` to `TService` means callers now assume this method never returns null. Unless `context.GetService<TService>()` is guaranteed to either return a service or throw, this mismatches the actual behavior and can lead to hidden nulls and NREs. Either keep the nullable return type, explicitly suppress with `!` and document the guarantee, or enforce non-null by throwing when the service is missing.
</issue_to_address>

### Comment 2
<location path="GFramework.SourceGenerators/Rule/ContextGetGenerator.cs" line_range="128-130" />
<code_context>
+            })
+            return false;
+
+        return fieldDeclaration.AttributeLists
+            .SelectMany(static list => list.Attributes)
+            .Any(static attribute => attribute.Name.ToString().Contains("Get", StringComparison.Ordinal));
+    }
+
</code_context>
<issue_to_address>
**suggestion:** Using string Contains("Get") on attribute names to preselect candidates is fragile and may yield false positives.

This predicate marks any attribute whose name text contains "Get" as a candidate, which can unintentionally include unrelated attributes (e.g., `[Target]`, `[ForgetMe]`, or names with "Get" only in a namespace alias). While later symbol filtering preserves correctness, it increases work and may lead to surprising matches. Consider restricting this to the simple identifier name (e.g., `attribute.Name is IdentifierNameSyntax id && id.Identifier.ValueText.StartsWith("Get", ...)`) or matching only a known set of attribute identifiers to avoid incidental matches and reduce noise in the incremental pipeline.

```suggestion
        return fieldDeclaration.AttributeLists
            .SelectMany(static list => list.Attributes)
            .Any(static attribute =>
                attribute.Name is IdentifierNameSyntax id &&
                id.Identifier.ValueText.StartsWith("Get", StringComparison.Ordinal));
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread GFramework.Core/Extensions/ContextAwareServiceExtensions.cs
Comment thread GFramework.SourceGenerators/Rule/ContextGetGenerator.cs Outdated
GeWuYou added 3 commits March 28, 2026 12:54
- 修改GetService方法文档,将返回值描述从"返回null"改为"抛出异常"
- 为GetService方法添加InvalidOperationException异常说明
- 删除冗余的IsExternalInit类文件
- 重构属性匹配逻辑,使用预定义集合进行候选属性名称验证
- 添加辅助方法HasCandidateAttribute、TryGetAttributeSimpleName等提升代码可读性
- 改进集合类型推断逻辑,支持接口类型的遍历匹配
- 更新单元测试以验证完全限定名属性和只读列表类型的支持
- 修正诊断错误位置信息的准确性
- 引入通用的 GetRequiredComponent 方法来统一服务、系统、模型和工具的获取逻辑
- 添加对架构上下文组件的空值检查和异常处理
- 实现更清晰的错误消息以指示未注册的组件类型
- 为所有组件类型(服务、系统、模型、工具)添加缺失的单元测试
- 测试验证当上下文返回空组件时抛出正确的 InvalidOperationException
- 改进代码可维护性并减少重复的获取组件逻辑
- 为 GetSystem 方法添加 InvalidOperationException 异常文档说明
- 为 GetModel 方法添加 InvalidOperationException 异常文档说明
- 为 GetUtility 方法添加 InvalidOperationException 异常文档说明
- 清理文件末尾多余空行
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant