ty 类型检查器中的函数装饰器推断循环:mdtest 回归测试 3593 深入解读

发布时间:2026/9/10 13:26:59
ty 类型检查器中的函数装饰器推断循环:mdtest 回归测试 3593 深入解读 ty 类型检查器中的函数装饰器推断循环mdtest 回归测试 #3593 深入解读【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff导读在 Rust 仓库 ruff 中crates/ty_python_semantic承载着新一代类型检查器 ty 的类型推断核心。本文围绕 regression/3593_function_known_decorators_cycle.md 这一回归测试文档展开剖析函数装饰器推断循环Function decorator inference cycle这一底层问题当装饰器识别、前向引用与描述符协议互相纠缠时Salsa 增量查询如何避免死循环并给出正确的诊断与退化类型。读完本文你将掌握 mdtest 测试格式的读写方法、ty 中装饰器推断查询的设计意图以及如何亲手运行这一回归测试验证行为。关联文档在仓库中的角色该文档位于 crates/ty_python_semantic/resources/mdtest/regression/是 ty 类型检查器的基于 Markdown 的测试mdtest套件中的一份回归测试。按 resources/README.md 的说明Markdown files within themdtest/subdirectory are tests of type inference and type checking; executed by thetests/mdtest.rsintegration test.即resources/mdtest/下的每一个 Markdown 文件都是一个类型推断/类型检查测试用例由 tests/mdtest.rs 集成测试执行该测试通过datatest_stable::harness!以r\.md$模式扫描整个resources/mdtest目录见 tests/mdtest.rs。与之并列的还有resources/lint_docs/lint 规则文档测试与snapshots/目录错误消息快照。这份回归测试专门针对 ty 仓库的#3593 号 issue用最小化代码复现并固化函数装饰器推断中出现循环依赖时的类型检查行为。它验证两件事一是推断过程能够收敛不 panic、不无限递归、不触发 Salsa 循环死锁二是收敛后产出的诊断与类型符合预期。mdtest 回归测试格式速览mdtest 文件的核心格式是一个可选的 TOML 环境配置块 若干 Python或.pyi、.ipynb、.toml代码块代码块内通过注释声明预期诊断。本文件完整呈现了这一格式[environment] python-version 3.14[environment]表用于配置被测代码的运行环境此处声明python-version 3.14使测试在最新的 Python 语义如typing.Self、overload等特性下运行。除 Python 版本外该格式还支持其他环境选项可参考 mdtest_config.md 与 ruff.toml。代码块内的错误标注语法为# error: [错误代码]紧随其后的语句行必须产生对应错误代码的诊断reveal_type(...)则通过# revealed: ...声明期望推断出的类型。实际执行时crates/ruff_mdtest/src/lib.rs 会把 Markdown 中提取的代码写入内存文件系统/src根目录逐个代码块运行类型检查器再交给matcher逐行匹配错误标注与内联快照。python-version会被解析进 linter 配置用于决定解析器版本见 crates/ruff_mdtest/src/lib.rs。回归测试代码逐段解读原文测试代码共三段逻辑恰好覆盖循环推断的三个侧面。前向引用属性与无效属性访问from typing import Self, overload, reveal_type class C: a: D # error: [invalid-attribute-access] C.a # error: [invalid-attribute-access] reveal_type(C().a) # revealed: Unknown | D类C声明了注解属性a: D其中D是前向引用——它在文件更下方才被定义。类型检查器在推断C.a的类型时必须解析D而解析D又会触及其函数定义含装饰器的推断由此埋下循环的种子。由于C.a只有注解、没有类体赋值运行时该属性并不真实存在因此访问C.a与C().a都会报出invalid-attribute-access错误。值得注意的是reveal_type(C().a)的期望结果Unknown | D。可以这样理解在循环尚未完全收敛时a的推断类型有一部分退化为Unknown未知检查器将退化结果与声明的D类型取并集而不是直接报错或返回单一类型——这正是循环恢复cycle recovery机制的直观体现与 cycle/basic.md 中多处 Divergent/Unknown 退化约定一致。装饰器推断循环的核心overload描述符方法class D: overload # error: [invalid-overload] # error: [invalid-overload] def __get__() - Self: pass类D定义了一个__get__方法且用overload标注。__get__是描述符协议descriptor protocol的方法定义了__get__的属性值即非数据描述符会在属性访问时被调用描述符的完整语义可见 descriptor_protocol.md其中展示了__get__/__set__/__delete__的优先级链与重载区分实例/类访问的例子。这里的__get__声明显然是不完整的它没有参数真正的描述符__get__至少应接收instance与owner它只有overload声明而没有对应的实现返回类型Self需要解析当前类的类型这本身又依赖D的类推断。因此检查器对这两处缺陷分别报出invalid-overload错误注释中连写两个# error:。结合文件名中的 function_known_decorators 可以推断overload属于已知装饰器known decorator检查器在识别装饰器时需要查询其类型与标志而这个查询正处在D自身定义推断的依赖环上——这就是本回归测试要固化的函数装饰器推断循环。循环从何而来function_known_decorators查询理解这份回归测试关键在于 crates/ty_python_semantic/src/types/infer.rs 中的function_known_decorators查询。它的注释直接点明了设计动机Infer decorator expression types for a function definition. This is a lightweight query that avoids the cycle risk of callinginfer_definition_typeswhen we need to check decorators while already inside definition inference (e.g. checkingSelfin astaticmethod).即在定义推断内部需要检查装饰器时例如在staticmethod中检查Self直接调用完整的infer_definition_types会有 Salsa 循环风险因此抽出一个轻量查询只做装饰器相关推断。该查询被声明为#[salsa::tracked]并显式提供循环恢复值#[salsa::tracked( returns(ref), cycle_initial|_, _, _| FunctionDecoratorInference::default(), ... )] pub(crate) fn function_known_decoratorsdb(...)cycle_initial保证一旦查询在增量计算中形成环Salsa 立即以FunctionDecoratorInference::default()作为占位结果返回而不是死锁或无限递归。FunctionDecoratorInference结构infer.rs只保存装饰器表达式类型、绑定、被调用函数、已知装饰器标志与诊断——紧凑且可安全地默认实例化。调用方则通过function_known_decorator_flags读取其中的known_decorators()标志集合。在 crates/ty_python_semantic/src/types/infer/builder/function.rs 中可以看到该查询的实际消费方式function.rs#L92-L96方法接收者分类MethodReceiverKind依据function_known_decorator_flags判断是否带staticmethod/classmethodfunction.rs#L427-L436推断函数定义时若有装饰器列表则调用function_known_decorators并把其中的诊断、表达式类型、绑定合并进当前推断上下文function.rs#L443-L478遍历装饰器通过FunctionDecorators::from_decorator_type识别staticmethod、classmethod、final、no_type_check、abstractmethod等已知装饰器未识别的则进入通用装饰器应用路径。由此本回归测试的意义就落到了实处当reveal_type(C().a)需要解析D而解析D又需要识别其overload装饰器时function_known_decorators的轻量性与cycle_initial兜底保证了推断能够收敛最终以Unknown | D的退化并集类型安全返回同时不吞掉D内部__get__声明的invalid-overload诊断。更广泛的循环推断场景佐证装饰器/定义推断循环并非孤例。在 cycle/basic.md 中同属 ty 的 mdtest 套件固化了一整组循环推断问题自引用装饰函数对应 ty #4308f lambda: f; assert f后再property def f(xlambda: f): ...解析装饰函数的可调用签名时不能急切推断默认值否则默认值引用自身会使断言的可达性检查重入导致推断不收敛自引用函数默认值对应 ty #1402参数默认值引用可调用对象自身时类型回退为Unknown防止栈溢出装饰器诊断的自引用对应 ty #4440显示装饰器签名可能触发推断自引用默认值因此错误报告被推迟到函数推断结束之后递归 lambda、递归增长元组、字面量在循环恢复中的加宽等共同定义了循环恢复cycle recovery的语义边界。本回归测试#3593与这些案例属于同一类设计目标让 Salsa 增量查询的循环恢复在类型检查层面可预测、可验证。而 descriptor_protocol.md 则为__get__描述符提供了完整的正例如Ten类通过__get__返回Literal[10]与本文测试中不完整__get__ 循环的反例形成对照帮助读者理解描述符在属性访问推断中的角色。如何运行与验证该测试方式一cargo 集成测试mdtest 由ty_python_semantic的集成测试驱动tests/mdtest.rs可用 cargo 过滤运行本用例cargo test --package ty_python_semantic --test mdtest 3593_function_known_decorators_cycle方式二mdtest 专属运行器推荐仓库提供了带 watch 模式的 Markdown 测试运行器 crates/ty_python_semantic/mdtest.py它基于uv脚本模式文件头部声明了rich与watchfiles依赖可精确过滤部分路径cd crates/ty_python_semantic uv run mdtest.py regression/3593_function_known_decorators_cycle.md运行器的过滤器参数支持loops/for.md或invalid-argument-type.md这类部分路径内部会自动去除.md后缀见 mdtest.py。该运行器会自动编译cargo test --testmdtest产物、以--exact mdtest::相对路径执行单个用例并支持--enable-external外部依赖测试、--no-lockfile-upgrades、--no-snapshot-updates等开关不传参数时则进入文件监听模式任一.rs、.pyi或 mdtest 文档变更都会触发对应重编译与重跑见 mdtest.py。在 mdtest.py 中可以看到它同时托管mdtest与lint_doc两套用例快照统一存放在resources/mdtest/snapshots/下。若修改了内联快照期望运行器默认会自动更新过期的快照INSTA_FORCE_PASS1、INSTA_OUTPUTnone等环境变量由 mdtest.py 注入。小结3593_function_known_decorators_cycle.md是一份浓缩的问题档案它以 mdtest 格式用最少代码复现了 ty 类型检查器在装饰器识别与前向引用相交织时产生的推断循环并固化了期望行为——C().a的类型退化为Unknown | D__get__的不完整overload声明报出invalid-overload同时整个推断过程必须收敛。其背后是 function_known_decorators 这一带cycle_initial兜底的轻量 Salsa 查询以及 cycle/basic.md 中一整套循环恢复语义。对类型检查器开发者而言这份文档既是理解装饰器推断为何会成环、如何安全恢复的最佳入口也是新增装饰器特性时必须守护的回归基线。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考