ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ruff/ty 类型检查器 unsupported-base 规则详解:当类基类无法解析 MRO 时

ruff/ty 类型检查器 unsupported-base 规则详解:当类基类无法解析 MRO 时 ruff/ty 类型检查器 unsupported-base 规则详解当类基类无法解析 MRO 时【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff本篇指南聚焦 ruff 仓库中类型检查器 ty 的unsupported-base诊断规则对应 crates/ty/docs/rules.md 中收录的 lint源定义位于 crates/ty_python_semantic/src/types/diagnostic.rs。文章会先复现该规则的全部文档语义检测什么、为何危险、触发示例再结合实现源码与 mdtest 测试用例深入剖析触发条件、诊断信息结构、与invalid-base等相邻规则的边界帮助你在使用 ruff/ty 做静态类型检查时准确理解并修复此类告警。规则概述检测什么unsupported-base用于检查类定义中使用了 ty 不支持的基类的写法。其官方文档原文如下Checks for class definitions that have bases which are unsupported by ty.换句话说当你在class Foo(Base): ...的基类位置放入了 ty 无法静态处理的类型时ty 就会针对该基类报出unsupported-base。为什么需要这条规则ty 是一个全静态类型检查器它需要为每个类解析出确定的方法解析顺序Method Resolution OrderMRO才能判断属性查找、方法重写、super()调用等一系列类型行为。而一旦某个基类是复杂类型的实例——例如一个联合类型union type——MRO 就无法被唯一确定If a class has a base that is an instance of a complex type such as a union type, ty will not be able to resolve the method resolution order (MRO) for the class. This will lead to an inferior understanding of your codebase and unpredictable type-checking behavior.后果是双重的一方面 ty 对代码库的理解精度下降成员解析、重写检查等都会退化为不精确的结果另一方面类型检查行为变得不可预测同一份代码在不同检查路径下可能得到不同结论。因此 ty 用一条默认级别为warn的 lint 主动提示你这类写法。从源码确认该规则在 crates/ty_python_semantic/src/types/diagnostic.rs#L530-L537 中声明declare_lint! { #[doc include_str!(../../resources/lint_docs/unsupported-base.md)] pub(crate) static UNSUPPORTED_BASE { summary: detects class bases that are unsupported as ty could not feasibly calculate the classs MRO, status: LintStatus::stable(0.0.1-alpha.7), default_level: Level::Warn, } }几个关键元信息summaryty 无法可行地计算出类的 MRO 时检测不支持的类基类status自0.0.1-alpha.7起标记为 stabledefault_level默认告警级别为Warn区别于同族规则invalid-base的Error原因下文详述。触发示例与行为分析原文档给出了一个典型示例完整复现如下import datetime class A: ... class B: ... if datetime.date.today().weekday() ! 6: C A else: C B class D(C): ... # error: [unsupported-base]逐行解读定义了A、B两个普通类通过运行时条件判断将全局名字C绑定到A或B之一class D(C)把C用作基类。关键在于ty 是静态分析它无法预知datetime.date.today().weekday() ! 6在运行时的真值因此C的静态类型是A | B联合类型。把一个联合类型放在基类位置时D的 MRO 取决于C究竟解析成哪个类而这是运行时才决定的——ty 无法在这种情况下计算出确定、一致的 MRO于是报出unsupported-base。实际报错消息在 mdtest 快照中见 crates/ty_python_semantic/resources/mdtest/snapshots/ 下的unsupported_base_dyn…系列快照此类诊断的完整消息形态为error: 11 [unsupported-base] Unsupported class base with type class A | class B在 crates/ty_python_semantic/resources/mdtest/mro.md 中也有多处断言例如第 303 行# error: 11 [unsupported-base] Unsupported class base with type class A | class B源码实现诊断的生成与消息构成unsupported-base的诊断主体实现在 crates/ty_python_semantic/src/types/diagnostic.rs#L4232-L4256 的report_unsupported_base函数pub(crate) fn report_unsupported_base( context: InferContext, base_node: ast::Expr, base_type: Type, class: StaticClassLiteral, ) { let Some(builder) context.report_lint(UNSUPPORTED_BASE, base_node) else { return; }; let db context.db(); let env context.program_environment(); let mut diagnostic builder.into_diagnostic(Unsupported class base); diagnostic .set_primary_annotation_message(format_args!(Has type {}, base_type.display(db, env))); diagnostic.set_concise_message(format_args!( Unsupported class base with type {}, base_type.display(db, env) )); diagnostic.info(format_args!( ty cannot resolve a consistent method resolution order (MRO) for class {} \ due to this base, class.name(db) )); diagnostic.info(Only class objects or Any are supported as class bases); }从这段代码可以看出诊断的三个信息层级主标注primary annotationHas type \具体类型直接标注在出问题的基类表达式上简明消息concise messageUnsupported class base with type \具体类型即 CLI / IDE 摘要栏展示的一行补充说明info两条固定文案——ty cannot resolve a consistent method resolution order (MRO) for classXdue to this base解释根因和 Only class objects orAnyare supported as class bases给出修复方向。修复方向从源码中得到印证Only class objects orAnyare supported as class bases 这条信息非常关键它划定了 ty 对类基类的支持边界类对象如class D(A)A是类Any显式声明的Any类型ty 对其放弃精确推导。其余一切表达式计算出复杂类型的基类都会触发本规则。这是实现层面的硬约束也是排查时的直接依据把基类位置的表达式改为确定指向某个类对象或用Any显式声明即可消除该告警。触发路径MRO 求解失败后的分流unsupported-base并不是独立扫描出来的而是 ty 在类定义后处理阶段求解 MRO 失败时分流派生的。调用链位于 crates/ty_python_semantic/src/types/infer/builder/post_inference/static_class.rs#L543-L557// Check that the classs MRO is resolvable match class.try_mro(db, None) { Err(mro_error) match mro_error.reason() { StaticMroErrorKind::DuplicateBases(duplicates) { /* 报 duplicate-bases */ } StaticMroErrorKind::InvalidBases(bases) { for (index, base_ty) in bases { let base_node expanded_base_entries[*index].source_node(); report_invalid_or_unsupported_base(context, base_node, *base_ty, class); } } StaticMroErrorKind::UnresolvableMro { .. } { /* 报 inconsistent-mro */ } // ... }, // ... }也就是说class.try_mro失败后按错误种类分流MRO 错误类型派生的规则DuplicateBases基类列表重复duplicate-baseInvalidBases存在无法作为基类的类型invalid-base/unsupported-base由report_invalid_or_unsupported_base分流UnresolvableMro基类顺序导致 MRO 不一致inconsistent-mroInheritanceCycle继承环cyclic-class-definitionPEP 695 泛型与Generic混用invalid-generic-classinvalid-base 与 unsupported-base 的分界report_invalid_or_unsupported_basecrates/ty_python_semantic/src/types/diagnostic.rs#L4107给出了两者精确的分界逻辑若基类类型可赋值给type的实例类型即它确实是个类则直接报unsupported-base若基类是NewType的实例报invalid-base并提示改用X NewType(X, ...)写法否则尝试调用基类的__mro_entries__以一个由 type 实例组成的同构元组为参数调用成功且返回类型可赋值给type 实例元组 → 报unsupported-base该类型理论上能参与 MRO 构造但 ty 无法静态展开调用成功但返回类型不符 → 报invalid-base并说明__mro_entries__没有返回类型元组调用失败无该方法 / 可能未绑定 / 不可调用 / 参数不匹配等→ 报invalid-base并附带__mro_entries__的期望签名说明def __mro_entries__(self, bases: tuple[type, ...], /) - tuple[type, ...]。由此可以理解两条规则的语义差别与默认级别差异invalid-base默认 Error该基类在运行时就会让类定义抛TypeError是必然出错unsupported-base默认 Warn该基类在运行时未必报错只是 ty 静态上无法为其计算 MRO是ty 能力边界导致的降级。mdtest 中 crates/ty_python_semantic/resources/mdtest/mro.md 第 435–442 行专门注释了这一区分exception at runtime, so we issueunsupported-baserather thaninvalid-base:class Bar(Foo()): ... # error: [unsupported-base]Foo()是实例表达式不是类对象但 ty 认为其可能通过某种途径参与 MRO 构造并非必然运行时异常因此归入unsupported-base而不是invalid-base。其他触发场景变长元组解包与动态基类除 MRO 求解失败外static_class.rs中还有一处独立的触发点crates/ty_python_semantic/src/types/infer/builder/post_inference/static_class.rs#L532-L541// Check for starred variable-length tuples that cannot be unpacked for base in class_node.bases() { if let ast::Expr::Starred(starred) base let starred_ty definition_expression_type(db, class_definition, starred.value) let Some(tuple_spec) starred_ty.tuple_instance_spec(db, env) !matches!(tuple_spec.as_ref(), Tuple::Fixed(_)) { report_unsupported_base(context, base, starred_ty, class); } }这段代码针对星号解包基类如class D(*bases)中的变长元组如果被解包的元组长度在静态上无法确定不是固定长度元组ty 无法得知展开后的基类列表于是同样报unsupported-base。mro.md 第 788 行有对应断言class D(D.a): # error: [unsupported-base]此外规则家族中还有一条高度相关的规则unsupported-dynamic-basecrates/ty/docs/rules.md#L6511 指出This is equivalent tounsupported-basebut applies to classes created viatype()rather than class statements.它专门针对通过type(name, bases, namespace)动态创建类的场景声明位置在 crates/ty_python_semantic/src/types/diagnostic.rs#L539-L546默认级别为Ignore。排查时若发现unsupported-base未覆盖动态建类可确认是否应同时关注该规则。综合示例与修复建议把文档示例扩展成一个可对照的完整场景import datetime from typing import Any class A: def method(self) - int: return 1 class B: def method(self) - str: return x if datetime.date.today().weekday() ! 6: C A else: C B class D(C): # warning: [unsupported-base] Unsupported class base with type class A | class B pass class E(Any): # 合法ty 明确支持 Any 作为基类 pass修复思路优先级消除基类表达式的多态性将C A / C B的条件赋值改写为运行时分支中各自独立的类定义或引入显式基类选择函数确保基类位置静态上是单一类对象显式声明Any若确实需要动态基类且不关心该部分的精确检查可让基类表达式带Any类型ty 会停止对其推导改用动态建类确认该动态基类语义后可评估是否走type()路径并配合unsupported-dynamic-base的配置管理告警级别。小结unsupported-base是 ty 在无法为类计算一致 MRO 时给出的warn级诊断覆盖联合类型基类、非类对象基类以及静态长度未知的星号解包元组基类等场景诊断消息明确标注了基类的实际类型并提示Only class objects orAnyare supported as class bases可直接作为修复指引它与invalid-base的分界在于运行时是否必然异常invalid-base对应必然的运行时错误默认 Errorunsupported-base对应 ty 静态能力的边界默认 Warn实现与测试证据分别位于 crates/ty_python_semantic/src/types/diagnostic.rs、crates/ty_python_semantic/src/types/infer/builder/post_inference/static_class.rs 及 crates/ty_python_semantic/resources/mdtest/mro.md读者可沿这些路径继续深入。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表