
Ruff/ty 类型检查器 invalid-parameter-default 规则深度解析参数默认值必须可赋值给注解类型【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff在 Python 函数中写出def f(a: int )这类“注解类型与默认值自相矛盾”的签名会破坏类型系统的一致性让类型检查器无法准确推理你的代码。Ruff 单仓中的 ty 类型检查器为此提供了一条名为invalid-parameter-default的内置 lint默认以error级别启用在类型推断阶段直接拦截此类声明。本文以该规则的官方文档 invalid-parameter-default.md 为骨架结合其规则注册、类型推断实现与 mdtest 行为测试讲清它的判定语义、豁免场景、配置方法以及它在代码库中的运行原理。读完你将能准确预测哪些默认值写法会触发该错误、如何修复以及如何按项目需要调整它的严重级别。规则概览从声明到文档的单点事实源invalid-parameter-default定义在类型语义 crate 的 lint 注册表中位于 diagnostic.rsdeclare_lint! { #[doc include_str!(../../resources/lint_docs/invalid-parameter-default.md)] pub(crate) static INVALID_PARAMETER_DEFAULT { summary: detects default values that cant be assigned to the parameters annotated type, status: LintStatus::stable(0.0.1-alpha.1), default_level: Level::Error, } }由注册信息可以确认三个关键事实属性值规则名称invalid-parameter-default默认级别error启用并生成错误诊断稳定版本0.0.1-alpha.1一句话摘要检测“无法赋值给参数注解类型”的默认值值得注意的实现细节是#[doc include_str!(...)]本文主体所依据的 Markdown 文档 invalid-parameter-default.md 会被原样注入到 Rust 文档中再经代码生成流程汇总进 crates/ty/docs/rules.md 的规则参考页。也就是说规则描述、规则实现与规则文档三者共享同一份内容源不会出现文档与代码各说各话的情况。What it does检测“默认值装不进注解类型”的签名规则的官方定义非常精炼Checks for default values that cant be assigned to the parameters annotated type. 检查那些无法赋值给参数注解类型的默认值。文档给出的最小触发示例是def f(a: int ): ... # errora被注解为int默认值却是字符串——不能赋值给int于是类型检查器报告invalid-parameter-default。下面几种同样属于典型的误配写法def f(x: int foo): ... # errorstr 不能赋值给 int def g(y: list[int] None): ... # errorNone 不能赋值给 list[int] def h(z: float []): ... # errorlist[Unknown] 不能赋值给 float # 修正思路要么改注解要么改默认值让两者彼此一致 def f(x: int 0): ... # ok def g(y: list[int] | None None): ... # ok def h(z: float 0.0): ... # ok判定基准是“可赋值性”assignable-to不是“子类型”subtype一个容易误判的细节是该检查依据的是可赋值关系而非严格的子类型关系。ty 在 function.rs 的类型推断中对is_assignable_to进行了判断因此像Any这种“万金油”类型不会被误伤。mdtest 测试 parameters.md 中专门验证了这一语义# error: [invalid-parameter-default] def f(x: int foo): reveal_type(x) # revealed: int # The check is assignable-to, not subtype-of, so this is fine: from typing import Any def g(x: Any foo): reveal_type(x) # revealed: Any同时测试还揭示了诊断发生后的推理回退策略一旦报错参数的推断类型将直接采用注解类型int而忽略默认值类型确保函数体内不会再被默认值的“污染类型”误导。Why is this bad为什么默认值与注解不一致有害规则的动机说明同样简短而关键This breaks the rules of the type system and weakens a type checkers ability to accurately reason about your code. 这会破坏类型系统的规则削弱类型检查器对你代码进行准确推理的能力。展开来说参数注解是函数对调用方做出的类型契约调用者传入的值必须满足注解约束而默认值本质上是一个隐式的调用参数——它与其他实参一样被绑定到该参数上。若默认值本身就不满足注解类型检查器面对函数体内“参数一定是注解类型”的假设就会失去根基容易推导出矛盾结论这种签名也很可能反映开发者笔误例如写错类型或忘记修正默认值属于值得在 CI 中拦截的真实缺陷。实现原理类型推断阶段的逐参数校验该 lint 不是基于模式匹配的“语法嗅探”而是深度嵌在 ty 的函数参数类型推断流程中。入口是 function.rs 的infer_parameter_definition其校验逻辑可概括为四步分别解析两侧类型通过parameter.annotation取得注解表达式解析出declared_ty声明类型若存在默认值表达式再解析出其推断类型default_ty。执行可赋值性检查调用default_ty.is_assignable_to(db, env, declared_ty)若结果为false意味着默认值无法赋值给注解类型进入上报分支。过滤已知的更精准诊断若默认值是 TypedDict 字面量且本身非法会先由is_invalid_typed_dict_literal判断并抑制本规则避免与更具体的错误重复详见后文。放行合法的省略号哨兵对“存根/重载/抽象方法/TYPE_CHECKING/协议方法中把...作为默认值”这一惯例写法做显式豁免。触发报告时生成的诊断消息为Default value of type {default_ty} is not assignable to annotated parameter type {declared_ty}即def f(a: int )会被报告为“类型str的默认值不可赋值给注解参数类型int”直接点明冲突双方。没有注解时不触发规则但默认值参与类型推断需要澄清的是该规则只在参数带有注解时才有意义。若参数既无注解又有默认值如def f(afoo)不存在可违反的契约因此不会触发invalid-parameter-default此时 ty 会把参数推断类型置为Unknown与默认值类型的联合见 parameters.mddef f(afoo, b0, cTrue, dNone): reveal_type(a) # revealed: Unknown | Literal[foo] reveal_type(d) # revealed: Unknown | None这种保守的联合类型意味着函数体内既不能把a当作确定的str也允许调用方传入更宽泛的实参如g(1.5)这正是无注解默认参数应具备的开放语义。例外情形何时允许...作为“不合类型”的默认值Python 生态中存在大量“把...省略号字面量当作占位默认值”的惯用法典型场景是.pyi存根文件与overload/抽象方法/Protocol的签名声明——它们只描述接口形状不提供可运行实现。ty 若对这些写法一律报错将造成大量误报。因此实现function.rs对默认值为...且满足下列任一上下文的情况做了豁免当前位于**存根文件stub**中self.in_stub()当前函数是overload重载或抽象方法self.in_function_overload_or_abstractmethod()默认值表达式位于if TYPE_CHECKING:块内当前方法所属类是Protocol。mdtest 中对应场景的用例parameters.md可以当作文档化行为来阅读from typing import Protocol class Foo(Protocol): def x(self, y: bool ...): ... # okProtocol 方法签名可用 ... def yT - T: ... # ok from abc import abstractmethod class Bar: abstractmethod def x(self, y: bool ...): ... # ok抽象方法 from typing import TYPE_CHECKING if TYPE_CHECKING: def foo(x: bool ...): ... # okTYPE_CHECKING 块视同存根语义ty 把if TYPE_CHECKING块内的代码视同存根文件处理测试注释明确写道“We generally view code inif TYPE_CHECKINGblocks as having the same semantics and exemptions to code in stub files”。换句话说普通运行时函数里写def real(x: bool ...)仍会被判为非法因为...对bool并不可赋值。与 TypedDict 字面量的协同避免重复诊断当默认值是对应注解类型的字典字面量时规则做了专门的处理见 function.rs// Avoid duplicate diagnostics: invalid TypedDict literals already emit specific errors. let suppress_invalid_default is_invalid_typed_dict_literal(db, env, declared_ty, default_expr.into());如果默认的 TypedDict 字面量确实非法ty 已经有更具体、更可操作的规则去覆盖它例如 parameters.md 中演示的missing-typed-dict-key缺键、invalid-argument-type值的类型错误与invalid-key多余键from typing import TypedDict class Foo(TypedDict): x: int y: int # error: [missing-typed-dict-key] def missing_key(a: Foo {x: 42}): ... # error: [invalid-argument-type] def wrong_type(a: Foo {x: s, y: 1}): ... # error: [invalid-key] def extra_key(a: Foo {x: 1, y: 2, z: 3}): ...在这些场景下若invalid-parameter-default再次叠加报错只会造成噪音因此实现显式抑制了重复上报而“字面量本身合法、只是与注解不匹配”的场景仍会正常交给本规则处理。这是规则设计上“各司其职、避免重复诊断”的典型体现。如何配置在 pyproject.toml / ty.toml 中调整级别与 ty 的全部 lint 一致invalid-parameter-default的启用与严重程度通过配置节rules控制参见 crates/ty/docs/configuration.md。合法的严重级别为级别含义ignore关闭该规则warn启用生成警告级诊断error启用生成错误级诊断本规则默认值# pyproject.toml [tool.ty.rules] invalid-parameter-default warn # 降为警告便于渐进修复存量代码 # ty.toml 写法与之对应 # [rules] # invalid-parameter-default warn配置示例中还支持用all键先统一设定再单独覆盖。需要留意的是默认情况下只要产生 warning 或 error 诊断ty 进程就会以退出码 1 结束可将terminal.error-on-warning设为false使纯 warning 场景返回 0因此将本规则调成warn后仓库仍能在“不阻断发布但持续提示”的状态下运行。在仓库中验证mdtest 与文档生成闭环如果你希望亲手验证该规则的行为仓库提供了两种途径行为测试ty 的测试采用 Markdown 驱动的 mdtest 框架规则用例以# error: [invalid-parameter-default]这样的内联注释标注预期诊断运行于 parameters.md相关用例分布于该文件的 “Default value type must be assignable to annotated type”“Stub functions” 等小节。规则名与诊断的对应关系也出现在其他测试如sentinels、stubs/ellipsis等用于覆盖边界场景。文档联动rules.md 中的规则条目含默认级别、稳定版本与源码链接以及本规则页面均由generate_all代码生成源头正是 diagnostic.rs 中的declare_lint!与 lint_docs 目录 下的 Markdown。文档中的 Python 代码块统一按 ruff.toml 的配置以preview true格式化保证示例代码风格一致。小结invalid-parameter-default是 ty 类型检查器在函数签名入口处设置的一道“契约一致性”闸门它对每个带注解且带默认值的参数执行可赋值性校验在函数体推理开始前就排除注解与默认值自相矛盾的情况并配套了省略号哨兵豁免、TypedDict 专用诊断去重等精细设计。实际编码中只要记住一条原则——默认值必须是注解类型的合法取值除非你正处在 stub、重载、抽象方法、Protocol或TYPE_CHECKING这类“声明优先”的上下文——就能干净地避开这一错误类别让签名真正成为可靠的类型契约。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考