Ruff ty 类型检查器的 index-out-of-bounds 规则:静态拦截越界下标访问

发布时间:2026/9/10 12:21:47
Ruff ty 类型检查器的 index-out-of-bounds 规则:静态拦截越界下标访问 Ruff ty 类型检查器的 index-out-of-bounds 规则静态拦截越界下标访问【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff导读index-out-of-bounds是 Ruff 代码仓库中tyRust 编写的 Python 静态类型检查器提供的一条 lint 规则用于在编译期捕获对容器使用越界下标取元素、而运行时必然抛出IndexError的代码。本文基于 规则原始文档结合 类型推导实现 与 mdtest 行为测试讲清该规则的判定对象字面量元组、字符串、bytes、正负整数越界语义、错误消息格式以及与切片边界自动钳制clamping等相邻行为的边界。读完本文你将能精确预判 ty 会在哪些下标表达式上报告该错误、哪些不会并掌握在代码中主动规避这类IndexError的写法。规则速览属性值出处规则代码index-out-of-bounds规则索引默认级别error规则索引加入版本0.0.1-alpha.1规则索引检测对象长度静态可知的 tuple / 字符串 / bytes 字面量下标实现源码规则文档的原始定义只有三条核心陈述lint 文档下方逐一展开并结合源码与测试深化。规则检测什么What it doesChecks for attempts to use an out of bounds index to get an item from a container.该规则检查使用越界下标从容器取元素的写法。从 实现源码 看越界错误被建模为下标推导错误的一种/// An index is out of bounds for a literal tuple/string/bytes subscript. IndexOutOfBounds { kind: SubscriptKind, tuple_ty: Typedb, length: Boxstr, index: i64, },其中kind由 SubscriptKind 枚举 限定为三类容器这正是该规则能判定的全部对象pub(crate) enum SubscriptKind { Tuple, // 显示为 tuple String, // 显示为 string BytesLiteral, // 显示为 bytes literal }也就是说只有长度在静态分析阶段可精确确定的容器才会被越界检查覆盖具体落在三个代码分支元组含固定长度异构元组及其子类见 tuple 整数下标分支要求值类型是携带tuple_spec已知元素与长度的 tuple的实例、下标是整型字面量越界时构造IndexOutOfBounds。字符串字面量见 string 分支仅当被下标对象是字面量字符串如abcde且下标是整型字面量时才检查越界按字符数chars().count()统计长度。bytes 字面量见 bytes 分支规则同上长度按字节数len()统计。规则文档原始示例t (0, 1, 2) # IndexError: tuple index out of range t[3] # errort是长度为 3 的异构元组字面量ty 能精确知道其规格因此用常量3下标必然越界静态即报错不必等到运行时抛IndexError。为什么它是坏味道Why is this badUsing an out of bounds index will raise anIndexErrorat runtime.与大多数静态类型检查诊断不同越界访问既不是类型不匹配也不是名字未定义而是**语义正确但数值必然越界的取值错误**——只要执行流到达该语句就会抛IndexError: tuple index out of range并使程序崩溃。在运行之前就拦截它把昂贵的运行时崩溃转化为编辑器/CI 中廉价的静态错误是该规则的价值所在。一个值得强调的推论错误消息引用 规则索引 时明确指出默认级别即error说明 ty 认为这类问题几乎总是真正的 bug而不是可疑模式。触发范围与不触发范围Examples 的纵深扩展负数下标同样参与越界判定Python 允许用负数从容器尾部取值ty 也完全遵循该语义并做了双向检查。参见 元组 mdtestt (1, a, b) reveal_type(t[0]) # revealed: Literal[1] reveal_type(t[-1]) # revealed: Literal[b] # 合法 reveal_type(t[-2]) # revealed: Literal[a] # 合法 a t[4] # error: [index-out-of-bounds] b t[-4] # error: [index-out-of-bounds] # 越界下限同样报错对长度 3 的元组合法下标范围是-3..2含两端4与-4分别在上下界之外两条都被报告。字符串字面量上可以看到该错误的具体消息文本string mdtests abcde a s[8] # error: [index-out-of-bounds] Index 8 is out of bounds for string Literal[abcde] with length 5 b s[-8] # error: [index-out-of-bounds] Index -8 is out of bounds for string Literal[abcde] with length 5消息格式由 report_index_out_of_bounds 诊断 渲染包含具体下标值、容器类别、被下标的字面量类型、已知长度——足以让开发者一眼定位问题。元组子类与空元组该规则并不局限于裸 tuple 字面量固定长度的元组子类同样会被精确建模tuple mdtestclass HeterogeneousSubclass0(tuple[()]): ... def f0(h0: HeterogeneousSubclass0, i: int): reveal_type(h0[0]) # error: [index-out-of-bounds] 空元组无任何合法下标 reveal_type(h0[-1]) # error: [index-out-of-bounds] class HeterogeneousSubclass1(tuple[I0]): ... def f0(h1: HeterogeneousSubclass1, i: int): reveal_type(h1[0]) # revealed: I0 唯一合法位置 reveal_type(h1[1]) # error: [index-out-of-bounds] reveal_type(h1[-1]) # revealed: I0 -1 指向唯一元素合法注意h0[i]下标是非字面量变量不会报越界错误——因为下标不是编译期常量ty 无法断言越界此时类型回退为Never。也就是说触发条件是字面量下标而非任意整数表达式。bytes 字面量对字节字面量同样适用规则 kind 为bytes literalbvalue[1] # 合法revealed: 118即 ord(bv) bvalue[9] # error: [index-out-of-bounds]什么时候不报错下列场景天然不触发该规则理解边界有助于避免误用长度不静态可知的容器如tuple[str, ...]这类同质变长元组即使写t[3]也无法证明越界tuple mdtestt[0]、t[-1]等一律返回str且不告警。list / dict 等动态容器它们的长度无法在编译期确定[3]是否越界取决于运行时内容交由其他规则或运行时处理。切片越界Python 运行时对切片边界是钳制而非抛错详见下节。越界之后类型回退越界表达式的推断结果统一为Unknown实现中返回Type::unknown()见 下标实现避免把错误类型继续向下游传播。bool作为下标False/True会被换算为整数0/1后再参与越界检查见 subscript.rs 与 string mdtest例如长度 1 的元组上t[True]即越界。与切片语义的边界钳制 vs 越界下标x[i]与切片x[a:b]在越界处理上语义完全不同这也是该规则设计上必须区分二者的原因整数下标越界抛IndexError→ 本规则拦截。切片越界Python 会静默钳制到边界内返回空或截断后的结果从不抛IndexError。因此越界切片是合法代码。可在 元组切片用例 与 字符串切片用例 中看到验证t[0:5]、s[0:6]、s[-10:10]都会得到完整容器而无一报错。步长为 0 的切片不抛IndexError但会在运行时抛ValueError: slice step cannot be zero由 ty 的另一条相邻规则 zero-stepsize-in-slice 负责拦截见测试中的t[0:4:0] # error: [zero-stepsize-in-slice]。因此可以总结为一句判据ty 只对必然抛IndexError的取值下标报告index-out-of-bounds而对必然抛ValueError的切片步长报告zero-stepsize-in-slice越界切片本身则被运行时钳制、不视为错误。实现位置与消息渲染细节规则的完整生命周期位于下标推导subscription路径类型推导遇到下标表达式ExprSubscript对应 Python 的x[0]时进入 subscript.rs 的分发逻辑命中 tuple/string/bytes 字面量且下标为整型字面量的分支后调用对应的py_index求值成功返回精确字面量类型如Literal[a]、Literal[1]失败则构造SubscriptErrorKind::IndexOutOfBounds在 report_diagnostic 匹配分支 中取出kind、长度与下标值交给 report_index_out_of_bounds 产出最终诊断规则标记落在被下标的值表达式value 而非 slice上校验通过后表达式结果类型被替换为Unknown防止错误类型污染后续推导。实现细节上的两个取值口径值得注意可从测试反向印证字符串长度按Unicode 字符数chars()即 Python 语义的字符而非字节计算见 subscript.rsbytes 长度按字节数len()计算见 subscript.rs元组长度则来自其tuple_spec的静态信息。行为契约mdtest 测试即规范ty 将上述规则行为固化为可执行的 mdtest 用例标注了精确的错误代码与 reveal 结果分布在 元组用例、字符串用例 与 bytes 用例 中。任何对规则的改动都必须让这些# error: [index-out-of-bounds]断言与reveal_type期望保持成立因此它们既是测试也是本规则的可运行规范。相关的诊断构造与消息生成则集中在 diagnostic.rs规则定义与默认级别可统一在 ty 规则索引 中查询。小结如何在代码中规避这类错误结合上述规则边界日常编码中可以遵循几条硬性约定长度已知的固定容器不要硬编码越界下标异构元组/元组子类、字符串、bytes 字面量的取值下标尽量落在-len..len-1范围内ty 会在越界的第一时间给出error级反馈下标来自计算时先做防御非字面量下标不会被该规则拦截务必自行保证边界如先判断0 i len(seq)需要部分序列时改用切片切片天然钳制越界s[0:6]不会抛错但若意图是精确取 N 个字符仍应在语义层面确认长度把该规则当作静态安全网它属于 ty 默认开启的error级别规则rules.md无需额外配置即可在类型检查阶段把一类必然崩溃的代码提前挡在门外。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考