ARTICLE DETAIL

资讯详情

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

Ruff Ty 类型检查规则解析:invalid-type-checking-constant 与 TYPE_CHECKING 常量约束

Ruff Ty 类型检查规则解析:invalid-type-checking-constant 与 TYPE_CHECKING 常量约束 Ruff Ty 类型检查规则解析invalid-type-checking-constant 与 TYPE_CHECKING 常量约束【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff导读TYPE_CHECKING是 Python 类型系统中一个特殊变量在类型检查器眼中它恒为True在运行时却必须为False承担着把仅类型检查器可见的代码与运行时执行的代码隔离的职责。本文以 ruff 仓库中 ty原生类型检查器tycrate实现的invalid-type-checking-constant规则为核心讲解该规则检查什么、为什么必须这样设计、ty 内部是如何在赋值与注解两个代码路径上实施检查的并给出可落地的修正实践。规则定位检查什么规则invalid-type-checking-constant出自 ty_python_semantic 的 lint 文档它检查两类问题给TYPE_CHECKING变量赋了False以外的值给TYPE_CHECKING变量加的注解不是可以从bool赋值的类型。规则文档中给出的两个最小错误示例为TYPE_CHECKING: str # error TYPE_CHECKING # error第一行给TYPE_CHECKING标注了str类型——bool无法赋值给str注解非法第二行在无注解的裸赋值中给它赋了空字符串——不是字面量False同样非法。从 ty 规则总览文档 可以看到该规则的注册信息默认级别为error自 ty 的0.0.1-alpha.1版本起加入。为什么必须这样做TYPE_CHECKING 的双面语义规则文档解释了其背后的核心原因TYPE_CHECKING这个名字被保留用作一个标志位flag用来书写只有类型检查器看得到、运行时不会执行的条件代码。正常情况下它从typing或typing_extensions导入但也可以由开发者在本模块中自行定义。问题的关键在于它的双面语义运行时本地定义时必须赋值为False这样if TYPE_CHECKING:分支永远不会在运行时执行类型检查期类型检查器会一律把它的值视为True从而进入if TYPE_CHECKING:分支分析其中的类型信息典型如import延迟导入、为类型检查而设的引用。一旦违背这一约束——例如赋了True、空字符串、非bool可赋值的注解——类型检查器对该名字的语义假设就会被破坏if TYPE_CHECKING:代码块的仅类型检查可见这一性质也就不再成立。这种语义在 ty 的测试文档 mdtest/known_constants.md 中被系统性地验证。例如从typing导入的TYPE_CHECKING无论使用哪种引用方式其类型都被推导为reveal_type(TYPE_CHECKING) # revealed: Literal[True] reveal_type(typing.TYPE_CHECKING) # revealed: Literal[True]而在用户自行定义TYPE_CHECKING False时即使字面量是False类型检查器依然把它当作True使用TYPE_CHECKING False reveal_type(TYPE_CHECKING) # revealed: Literal[True] if TYPE_CHECKING: ... # 类型检查期可达分支也就是说变量必须写False、类型检查器却按True理解正是该规则的完整设计语义——校验只针对写下的源码而类型推导结果恒定指向Literal[True]。源码实现两处检查路径规则的核心诊断函数位于 crates/ty_python_semantic/src/types/diagnostic.rs#L3010-L3017pub(super) fn report_invalid_type_checking_constant(context: InferContext, node: AnyNodeRef) { let Some(builder) context.report_lint(INVALID_TYPE_CHECKING_CONSTANT, node) else { return; }; builder.into_diagnostic( The name TYPE_CHECKING is reserved for use as a flag; only False can be assigned to it, ); }诊断消息为The name TYPE_CHECKING is reserved for use as a flag; only False can be assigned to itTYPE_CHECKING这个名字被保留用作标志位只有False可以被赋给它。从源码结构看该函数通过report_lint框架上报并在检查点通过行内信息或子诊断给出补充说明。真正判定是否违规的逻辑在类型推导器 crates/ty_python_semantic/src/types/infer/builder.rs 中共分两条路径。路径一无注解的赋值语句在builder.rs的赋值推导分支中约 L3593-L3607源码注释明确指出TYPE_CHECKINGis a special variable that should only be assignedFalseat runtime, but is always consideredTruein type checking.TYPE_CHECKING是特殊变量运行时只应赋False而类型检查期总视为True。参见 mdtest/known_constants.md 中 User-defined TYPE_CHECKING 一节。对应的检查逻辑为当赋值目标是名字恰好为TYPE_CHECKING的名字表达式且右侧值不是布尔字面量False即ExprBooleanLiteral { value: false }时调用report_invalid_type_checking_constant报告错误随后无论字面量是什么都把该名字的类型绑定为Type::bool_literal(true)即Literal[True]。这解释了开篇示例第二行TYPE_CHECKING 为何报错。路径二带类型注解的声明另一处检查发生在处理带注解变量声明/注解赋值时约 L4676-L4703逻辑分为三步先校验注解若KnownClass::Bool的实例类型无法赋值给声明的注解类型declared.inner_type()即注解不接受bool直接报告invalid-type-checking-constant——对应文档示例第一行TYPE_CHECKING: str否则注解可接受bool再校验文件类型与初值若处于 stub 文件.pyi代码内self.in_stub()为真且初值缺失或为...则视为合法stub 中只写TYPE_CHECKING: bool或TYPE_CHECKING: bool ...是被允许的声明方式其余情况下初值只要不是布尔字面量False就报告错误最后无论注解如何都把声明的内部类型改写为Type::bool_literal(true)。综合两条路径一个合法的本地定义需要同时满足两个条件注解类型接受bool推荐直接写bool初值必须是字面量False。ty 的测试文档将这一规则以行为示例固化了下来见 mdtest/known_constants.md 中 Invalid assignment to TYPE_CHECKING 一节包括TYPE_CHECKING True # error赋值不是 False TYPE_CHECKING: bool True # error赋值不是 False TYPE_CHECKING: int 1 # errorbool 不能赋值给 int TYPE_CHECKING: str str # error注解与初值均非法 TYPE_CHECKING: str False # error注解不接受 bool TYPE_CHECKING: Literal[False] False # error注解类型不接受 bool TYPE_CHECKING: Literal[True] False # error同上注意最后两类尽管初值是False但Literal[False]/Literal[True]这类窄化注解仍被判定为非法——因为类型检查器会把该变量最终视为Literal[True]窄到单一字面量的注解与这一推导结果不自洽。唯一的合法注解形式是TYPE_CHECKING: bool False。实践指引合法与非法写法对照完全合法的定义方式# 方式一无注解、直接赋 False最常见 TYPE_CHECKING False # 方式二显式注解为 bool初值 False TYPE_CHECKING: bool False # 方式三stub 文件中.pyi可省略初值或用省略号占位 # TYPE_CHECKING: bool # TYPE_CHECKING: bool ...当TYPE_CHECKING为False时类型检查器依然将其视为True因此下列惯用代码模式延迟导入可以安全通过TYPE_CHECKING False if TYPE_CHECKING: from some_heavy_module import HeavyClass # 仅类型检查可见不产生运行时导入 def f(x: HeavyClass) - None: # 注解可解析 ...必须修正的写法TYPE_CHECKING: str # errorbool 无法赋值给 str TYPE_CHECKING # error初值非 False TYPE_CHECKING True # error初值非 False修正建议若只是为了运行期恒假、类型期恒真的分支控制优先选择从typing或typing_extensionsimport TYPE_CHECKING完全规避自定义带来的约束问题必须自定义时删去不必要注解并写TYPE_CHECKING False若需要显式注解使用bool并同时确保初值为字面量False在.pyistub 文件中可写为TYPE_CHECKING: bool或TYPE_CHECKING: bool ...这是源码中明确放行的两种 stub 声明形态。相关文件索引若希望深入阅读本规则的文档、实现与行为测试可依次查看以下仓库文件规则 lint 文档crates/ty_python_semantic/resources/lint_docs/invalid-type-checking-constant.md诊断上报函数crates/ty_python_semantic/src/types/diagnostic.rs#L3010-L3017类型推导与判定逻辑crates/ty_python_semantic/src/types/infer/builder.rs#L3593-L3607 与 crates/ty_python_semantic/src/types/infer/builder.rs#L4676-L4703行为测试mdtest 用例crates/ty_python_semantic/resources/mdtest/known_constants.mdty 规则注册与默认级别crates/ty/docs/rules.md#L3275-L3294总结invalid-type-checking-constant规则守护的是 Python 类型系统中最容易被误解的常量语义TYPE_CHECKING的源码写False、检查期读True的双面契约。ty 在实现上分别覆盖了裸赋值与注解声明两条路径并对 stub 文件中的省略初值与...占位做了特例放行理解这套规则后你在书写if TYPE_CHECKING:隔离块与相关延迟导入时就能既写出类型检查器认可的代码又不引入任何运行时开销。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表