ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Regression test for 14334

Regression test for 14334 Regression test for #14334【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruffRegression test for this issue.base.py:# error: [invalid-base] class Base(2): ...a.py:# No error here from base import Base它对应 Ruff 上游 issue #14334诊断出现在错误的文件中此前 ty 类型检查器存在一个缺陷——当某个模块base.py内部存在错误时来自该模块的诊断可能被错误地归到**导入它的其他文件**a.py名下导致 a.py 出现本不该出现的报错。这个回归测试用最小化的两文件场景把它钉死 - base.py 中的 class Base(2): ... 是一个非法基类应当产生 invalid-base 诊断 - a.py 仅仅 from base import Base本身没有任何问题**不允许**产生任何诊断。 需要说明仓库中该文档链接指向 GitHub 上游 issue 页面出于链接规范本文不重复输出外部链接具体问题背景可从测试意图推断它验证的是诊断的文件归属正确性而非诊断本身是否存在。 ## 二、invalid-base 规则诊断从何而来 # error: [invalid-base] 断言的是 ty 内置 lint 规则 invalid-base。该规则在 [crates/ty_python_semantic/src/types/diagnostic.rs](https://link.gitcode.com/i/19ae9eac90d611ffb68a6b06791b2236) 中以 declare_lint! 宏声明见第 521-528 行关键元数据如下 - **summary**检测会导致类定义在运行时抛出异常TypeError的类基类 - **status**stable(0.0.1-alpha.1)属于稳定规则 - **default_level**Error即默认按错误级别报告 - 规则文档正文来自 [crates/ty_python_semantic/resources/lint_docs/invalid-base.md](https://link.gitcode.com/i/15a1319e813f2efa2e9630d73d104f66)通过 include_str! 内联其中明确说明基类不是 type 实例的类定义会在运行时抛出 TypeError示例为 class A(42): ...。 ### 底层判定逻辑 判定某个表达式能否作为类基类的核心实现在 [crates/ty_python_semantic/src/types/class_base.rs](https://link.gitcode.com/i/b7209a3e0ea6a73f372bdc1919575003) 的 ClassBase 枚举与 try_from_type 方法中 - ClassBase 只允许少数几种类型作为合法基类Any、动态类型、ClassLiteral、Protocol、Generic、TypedDict 等第 26-43 行 - try_from_type第 137 行起将类型表达式尝试解析为 ClassBase返回 None 即表示不可作为基类。例如 Type::NominalInstance(_) 直接返回 None第 199 行PropertyInstance、FunctionLiteral、TypedDict、TypeVar 等一长串类型同样返回 None第 211-231 行 - 在 [crates/ty_python_semantic/src/types/mro.rs](https://link.gitcode.com/i/a0dfc2f1497ed8cd2155a7cd65ef3cc2) 中多基类场景会逐项调用 ClassBase::try_from_explicit_base解析失败的基类被收集进 invalid_bases 并产生 StaticMroErrorKind::InvalidBases第 180-213 行 - 最终由 report_invalid_base[crates/ty_python_semantic/src/types/diagnostic.rs](https://link.gitcode.com/i/19ae9eac90d611ffb68a6b06791b2236) 第 4258 行起输出诊断主消息为 Invalid class base with type \{type}\附加信息 Definition of class \{name}\ will raise \TypeError\ at runtime。 回归用例中 class Base(2): ... 的基类是整数字面量 2其推断类型为 Literal[2]属于 Type::LiteralValue在 try_from_type 中被明确排除第 213 行因此必然命中 invalid-base 诊断。 ## 三、mdtest 多文件测试语法文件路径标注 本用例的关键语法是**用行内反引号标注嵌入式文件的显式路径** markdown base.py: py # error: [invalid-base] class Base(2): ... 在 Markdown 测试文件中普通py代码块会被自动命名为mdtest_snippet.py见 crates/mdtest/src/parser.rs 中EmbeddedFilePath::Autogenerated与as_str的对应关系第 342-371 行。而形如文件名.py:的行会让紧随其后的代码块获得显式路径解析器通过explicit_path字段捕获该标注parser.rs 第 750-762 行并校验代码块语言与文件扩展名一致第 851-867 行。多文件语义说明详见 crates/ty_test/README.md Multi-file tests 一节相对文件名始终相对于工作区根目录默认是/src/它同时也是导入根等价于运行时sys.path上的一个入口因此上述用例会在内存文件系统中创建两个文件/src/base.py与/src/a.pya.py中的from base import Base按模块名base即可解析工作区根目录由 crates/ty_test/src/lib.rs 第 129-133 行创建并注册为FileRootKind::Project。四、断言机制# error:内联注释的匹配规则# error: [invalid-base]是一个内联诊断断言其完整语法与匹配规则如下依据 crates/ty_test/README.md 与 crates/mdtest/src/assertion.rs断言形式匹配语义# error:匹配该行上的任意诊断# error: [rule-code]匹配规则码为rule-code的诊断方括号必填# error: Some text诊断完整消息必须包含Some text双引号必填不属于匹配文本# error: 8 [rule-code]额外要求诊断起始列为第 8 列从 1 开始计数# error: 8 [rule-code] Some text三要素按列号 → 规则码 → 文本顺序组合放置位置规则行尾注释断言作用于所在行如class Base(2): ... # error: [invalid-base]独立行注释作用于紧接着的下一行非断言代码行多条断言可堆叠但必须每条独占一行、连续排列且下一行紧接被测代码。在 crates/mdtest/src/assertion.rs 中UnparsedAssertion::from_comment第 255-280 行识别revealed/error/snapshot三种断言前缀ErrorAssertionParser::parse第 407-495 行负责把error:后的内容解析为可选的列号、规则码与消息文本任何语法错误如列号出现在规则码之后、未闭合的]、意外的字符等都会产生对应的ErrorAssertionParseError变体第 511-532 行。本用例中的[invalid-base]即被解析为rule Some(invalid-base)。五、匹配引擎诊断如何按文件、按行对上断言断言与诊断的匹配由 crates/mdtest/src/matcher.rs 的match_file第 95 行起完成其流程对理解本回归测试至关重要按文件独立匹配match_file的入参是单个file即每个嵌入式文件base.py、a.py分别调用一次。这从框架层面保证了诊断必须归属于产生它的那个文件——这正是 #14334 回归测试要守护的性质。解析断言Python 文件通过InlineFileAssertions::from_file从注释中提取断言第 114-119 行。诊断排序诊断按其起始 token 所在行号排序第 121-130 行而非简单按诊断 range 起点行号——例如字符串化注解中的语法错误也归到 token 起始行。按行号归并匹配断言与诊断各自按行号升序迭代三种情况分别处理第 145-193 行断言行号 诊断行号逐条用Matcher::match_line校验断言行号 诊断行号断言无法匹配记录失败断言行号 诊断行号诊断无法匹配记录失败两者之一耗尽后剩余项一律记为 unmatched。失败即测试失败任何未匹配的断言或诊断都会进入FailuresByLine最终导致该测试用例失败第 196-203 行。因此本用例的断言语义可以精确表述为base.py第 1 行必须恰好产生一个规则码为invalid-base的诊断a.py不允许产生任何诊断——若回归缺陷复现a.py上出现本应属于base.py的诊断则a.py的匹配产生 unmatched 诊断测试立即失败。六、测试运行方式mdtest 用例通过cargo test集成执行。ty 的所有 Markdown 测试由 crates/ty_python_semantic/tests/mdtest.rs 中的datatest_stable::harness!发现所有位于resources/mdtest/下以.md结尾的文件都会被当作测试套件第 82-85 行其中每个测试用例在独立的内存文件系统与独立的 Salsa 数据库中从零运行互不残留状态。运行单个回归测试# 通过 mdtest 前缀过滤出所有 Markdown 测试 cargo test -p ty_python_semantic -- mdtest # 只跑某个具体文件按测试名过滤 cargo test -p ty_python_semantic --test mdtest -- regression/14334_diagnostics_in_wrong_file.md使用仓库自带的 Python 运行器支持 watch 模式Markdown 变更时自动重跑、Rust 代码变更时自动重编译uv run crates/ty_python_semantic/mdtest.py【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进