invalid-super-argument 规则深度解析:ruff(ty 类型检查器)如何静态校验super()调用实参
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
super()是 Python 面向对象编程中委托方法调用的基石,但它的实参契约(第一个参数必须是类、第二个参数必须是该类的实例或子类)常常被误用,直到运行时才抛出TypeError。本文围绕 ruff 仓库中 ty(类型检查器)的invalid-super-argument规则文档展开,结合其声明与实现源码,讲清楚该规则检测什么、为什么值得检查、错误消息如何生成,以及它与其他super()相关规则的分工,帮助读者理解类型检查器如何在编译期把这类错误提前暴露出来。
规则文档与定位
本规则的用户文档位于 crates/ty_python_semantic/resources/lint_docs/invalid-super-argument.md,以"What it does / Why is this bad / Examples / References"的标准格式阐述规则语义。它属于本仓库 type-checker(ty)一侧(而非ruff_linter的传统 lint 规则集),通过include_str!宏直接嵌入到 Rust 源码中的规则声明处,见 crates/ty_python_semantic/src/types/diagnostic.rs:
declare_lint! { #[doc = include_str!("../../resources/lint_docs/invalid-super-argument.md")] pub(crate) static INVALID_SUPER_ARGUMENT = { summary: "detects invalid arguments for `super()`", status: LintStatus::stable("0.0.1-alpha.1"), default_level: Level::Error, } }从源码声明可以确认三条事实:
- 规则名称:
invalid-super-argument,人类可读摘要为"detects invalid arguments forsuper()"; - 默认级别为
Error:与一般先以 warning 放行的风格不同,这类问题默认即按错误上报; - 稳定于
0.0.1-alpha.1:属于 ty 类型检查器早期便确立的稳定诊断。
规则检测什么:两类无效实参
根据规则文档,该规则检测super()调用中的两类问题:
- 第一个实参不是合法的类字面量(invalid class literal);
- 第二个实参不是第一个实参的实例或子类。
对应地,crates/ty_python_semantic/src/types/bound_super.rs 中定义了BoundSuperError枚举,把"出错的形态"细分为可区分的错误变体:
pub(super) enum BoundSuperError<'db> { /// The pivot class (first argument) is not a valid class object. InvalidPivotClassType { pivot_class: Type<'db> }, /// The owner (second argument) has an abstract / structural type, /// so the runtime isinstance/issubclass relationship is ill-defined. AbstractOwnerType { ... }, /// The isinstance/issubclass condition fails for concrete types. FailingConditionCheck { ... }, ... }也就是说,实现层把"第一个参数不是类"(InvalidPivotClassType)与"第二个参数与第一个参数不满足继承/实例关系"(FailingConditionCheck)拆成独立分支,以便生成精确的错误消息;此外对第二参数为抽象/结构类型或类型变量约束无法满足的情形(AbstractOwnerType)也单独给出提示。这些分支统一在BoundSuperError::report_diagnostic中通过context.report_lint(&INVALID_SUPER_ARGUMENT, node)上报告警,触发位置见 bound_super.rs。
运行时契约:为什么会TypeError
文档"Why is this bad?"解释了被检查对象背后的语义。super(type, obj)的合法条件为:
- 第一个实参必须是类(class);
- 第二个实参必须满足下列二者之一:
isinstance(obj, type)为True,或issubclass(obj, type)为True。
违反这一关系时,Python 解释器会在运行时抛出TypeError。由于这类错误发生在方法调用的时刻、难以从语法层面察觉,类型检查器在静态分析阶段报出invalid-super-argument,相当于把运行时崩溃提前到编辑期。
文档示例逐行拆解
规则文档给出了完整可运行的示例。沿用原文档并补充注释说明:
class A: ... class B(A): ... super(A, B()) # it's okay! `A` satisfies `isinstance(B(), A)` # `A()` is not a class super(A(), B()) # error # `A()` does not satisfy `isinstance(A(), B)` super(B, A()) # error # `A` does not satisfy `issubclass(A, B)` super(B, A) # error逐个分析:
| 调用 | 判定 | 原因 |
|---|---|---|
super(A, B()) | ✅ 合法 | B继承自A,B()是A的实例,满足isinstance(B(), A) |
super(A(), B()) | ❌error | 第一个实参A()是实例而非类,不满足"第一个参数必须是类" |
super(B, A()) | ❌error | A()不是B的实例(A是父类,实例方向相反),isinstance(A(), B)不成立 |
super(B, A) | ❌error | A不是B的子类,issubclass(A, B)不成立 |
注意第三个与第四个例子体现了两个独立的检查维度:super第二实参既可以传实例(须满足isinstance)也可以传类(须满足issubclass),两种形态都必须与第一个实参构成"祖先/后代"方向一致的继承链。这与 mdtest 用例 crates/ty_python_semantic/resources/mdtest/class/super.md 中的行为一致——例如super(B, C())会因从B起查找b属性失败而报[unresolved-attribute],而super(A, C()).aa可以正常解析,直观反映了 super 沿 MRO 从 pivot class(第一个实参)之后开始查找的语义。
源码级佐证:报错信息的三类典型形态
文档只描述"报 error",而实现给出了比文档更细的错误消息分类。结合 bound_super.rs 的report_diagnostic,可以归纳出实际场景下用户会看到的信息形态:
第一个参数根本不是类(
InvalidPivotClassType):当第一个实参被解析为实例等非类对象时,消息为Argument is not a valid class,并标注其实际类型,例如Argument has type ...;当实参是types.GenericAlias之类的别名实例时,会给出专门消息(如types.GenericAliasinstance ... is not a valid class)。第二个参数为抽象/结构类型(
AbstractOwnerType):例如以Callable这类抽象类型作第二实参时,运行时isinstance/issubclass关系无法被可靠满足,诊断会给出... is an abstract/structural type in super(...);若第二参数是带边界或约束的类型变量,还会附带类型变量边界信息,帮助定位是"哪一个约束不满足"。继承/实例关系检查失败(
FailingConditionCheck):消息形如... is not an instance or subclass of ... in super(A, B) call,与文档中super(B, A)/super(B, A())的报错场景一一对应;当涉及类型变量时,还会追加一条 info,指出bounds_or_constraints中具体哪一项与 pivot class 不兼容。
这种"枚举区分错误形态 → 逐分支定制消息 → 类型变量附加边界提示"的结构,是 ty 诊断体系里"对Type与类型变量给出可读信息"的通用做法,同一文件 diagnostic.rs 中大量report_*函数也遵循此模式。
与其他 super() 相关规则的边界
invalid-super-argument只关心显式双实参形式的super(pivot, owner)是否满足契约。而super()还有隐式零参/单参形态,以及在不同类上下文中的特殊性。本仓库把这些问题拆分为不同规则,避免一个规则职责过载:
- unavailable-implicit-super-arguments:检测
super()调用中隐式实参不可用的情形(例如解释器无法从__class__单元或所在函数推导出 pivot class 与 owner),对应BoundSuperError::UnavailableImplicitArguments变体,声明见 diagnostic.rs; - super-call-in-named-tuple-method:检测在
NamedTuple类方法中调用super()(该场景运行时必然异常),声明见 diagnostic.rs; - 本次讨论的
invalid-super-argument则专门负责实参静态类型契约校验。
从数据流看,BoundSuperType::build(见 bound_super.rs)在构造受绑定的 super 对象时会同时校验实参关系并返回Result;一旦校验失败,错误被转换为对应 lint 上报。因此这三条规则共同覆盖了super()从"参数类型错误"到"隐式参数不可得"再到"调用上下文非法"的完整出错面。
如何验证与测试:mdtest 驱动
ty 系列的规则行为采用mdtest(markdown 驱动的测试)验证:测试用例以 Markdown 文档编写,代码块中内嵌# error: [invalid-super-argument]之类的注解标记,由 crates/ty_python_semantic/mdtest.py 生成并跑出快照。仓库内可见的证据包括:
- 测试主体用例文档 crates/ty_python_semantic/resources/mdtest/class/super.md,其中包含
# error: [invalid-super-argument]标注,覆盖"第二参数为函数/普通对象"等边界输入,例如对函数对象super(object, x)即标注该错误; - 生成的快照存放于 crates/ty_python_semantic/resources/mdtest/snapshots,含
super.md ... Invalid Usages ...系列.snap文件; - 测试入口位于 crates/ty_python_semantic/tests/mdtest.rs。
读者若想复现或扩展这类用例,只需在super.md中新增带# error: [invalid-super-argument]注解的代码块并重新生成快照即可。规则的完整清单与说明也汇总在 crates/ty/docs/rules.md。
小结
invalid-super-argument是 ty 类型检查器对super(type, obj)运行时契约的静态建模:它校验第一个实参是否为类、第二个实参是否满足isinstance/issubclass关系,把本会在运行期触发的TypeError提前到静态分析阶段以Error级别暴露。通过本文可以掌握:该规则的声明位置与默认配置(diagnostic.rs)、判定与报错的三类实现分支(bound_super.rs),以及它与隐式实参、NamedTuple上下文等相邻规则的边界,从而在理解既有代码报错时能快速定位问题根因。
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考