十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

深入解析 ty 类型检查器规则 parameter-already-assigned:同一函数参数被传入多个值

深入解析 ty 类型检查器规则 parameter-already-assigned:同一函数参数被传入多个值 深入解析 ty 类型检查器规则 parameter-already-assigned同一函数参数被传入多个值【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruffty 是 Astral 开发的高性能 Python 类型检查器其完整 Rust 源码位于本仓库的crates/ty*系列 crate 中。parameter-already-assigned是 ty 类型推导阶段内置的一项稳定诊断用于在函数调用参数绑定时发现「同一个参数被传入多个值」的调用例如f(1, x2)。读完本文你将掌握该规则的全部触发形态、报错信息含义、底层绑定算法原理以及它与*args/**kwargs展开、functools.partial等复杂调用场景的交互边界并能用ty check在本地复现验证。规则速览它检测什么规则文档位于 crates/ty_python_semantic/resources/lint_docs/parameter-already-assigned.md对规则的定位描述得非常简洁What it doesChecks for calls which provide more than one argument for a single parameter.也就是说只要一次调用中有两个及以上的实参最终落到了同一个形参上ty 就会报出该诊断。最常见的形态是位置实参 关键字实参叠在一起命中同一参数def f(x: int) - int: return x f(1, x2) # error在源码层面规则本体在 crates/ty_python_semantic/src/types/diagnostic.rs 中通过宏declare_lint!声明其注册信息为规则名parameter-already-assignedsummarydetects multiple arguments for the same parameter状态stable(0.0.1-alpha.1)自 0.0.1-alpha.1 起即稳定启用默认级别Level::Error默认按错误级上报会影响ty check的退出状态注意这是一条类型检查type checker规则而不是纯文本风格 lint 规则——它需要先完成参数的类型推导才能确定各实参与形参的对应关系因此实现位于类型语义 cratety_python_semantic的调用绑定阶段而不是文本扫描阶段。为什么这是错误运行时必然抛 TypeError文档中 Why is it bad? 给出的理由只有一句话但背后是 Python 语言的硬性调用约定Providing multiple values for a single parameter will raise aTypeErrorat runtime.以f(1, x2)为例CPython 在真正调用时会发现形参x已经被第 1 个位置实参占用而关键字实参x2又要再赋给x一次于是抛出TypeError: f() got multiple values for argument x这种错误纯属手滑静态类型检查器完全可以提前拦截让错误在 CI 阶段而非线上运行时暴露。需要区分的是另一类重复赋值f(x1, x2)同一个关键字在字面上写了两次在 CPython 中属于语法错误SyntaxError甚至等不到运行时。从实现上看ty 的参数绑定代码刻意不重复上报这类字面重复关键字详见下文实现剖析避免和语法层面的报错重复。触发该诊断的典型代码形态ty 的文档化测试mdtest在 crates/ty_python_semantic/resources/mdtest/call/function.md 中给出了与规则文档完全一致的最小触发用例并标注了期望的错误位置第 18 列对应x2和完整报错文案def f(x: int) - int: return 1 # error: 18 [parameter-already-assigned] Multiple values provided for parameter x of function f reveal_type(f(1, x2)) # revealed: int把调用包进reveal_type(...)只是为了拿到推导结果的类型诊断本身仍精确地定位到重复实参x2上。以下场景同样会触发该规则位置实参 关键字实参重叠最常见def f(a: int, b: str) - bool: return True f(1, bs, a2) # a 已被位置实参占用再传 a2 → error*关键字实参命中已被args 类展开占用的形参def f(a: str, b: str, c: float) - None: ... def _(args: tuple[str, str, str]) - None: # error: [invalid-argument-type] # error: [parameter-already-assigned] Multiple values provided for parameter c of function f f(*args, c1.0)关键字实参 携带已知键的**kwargs展开ty 对此采取保守策略from typing import TypedDict def f(a: str, b: str, c: float) - None: ... class CKwargs(TypedDict): c: float def _(args: list[str]) - None: # error: [invalid-argument-type] # error: [parameter-already-assigned] f(*args, **CKwargs(c1.0))functools.partial构造阶段from functools import partial def f(a: int, b: str) - bool: return True p partial(f, 1, a2) # error: [parameter-already-assigned]namedtuple等特殊构造器mdtest 中甚至覆盖了collections.namedtuple场景见 crates/ty_python_semantic/resources/mdtest/named_tuple.md当typename、field_names等参数同时被位置与关键字实参赋值时也会报出该错误。诊断信息的可读性设计当规则触发时ty 输出的错误文案统一为Multiple values provided for parameter x of function f这里的消息渲染逻辑在 crates/ty_python_semantic/src/types/call/bind.rs 中Self::ParameterAlreadyAssigned { argument_index, parameter, } { let range context.get_range(node, *argument_index); if let Some(builder) context.report_lint(PARAMETER_ALREADY_ASSIGNED, range) { let mut diag builder.into_diagnostic(format_args!( Multiple values provided for parameter {parameter}{}, callable_description .map(|description| format!( of {description})) .unwrap_or_default() )); // ... } }参数名与人称后缀of function f、of ...来自callable_description因此报错会明确指出是哪个函数/可调用对象的哪个参数出了问题对partial返回、方法调用等场景也能给出可读的函数名。若参数没有可显示的名字或冲突来自位置参数positional标志为真ParameterContext的Display实现bind.rs会退化为输出1 起始的参数序号如1或1 (x)保证即使对匿名/合成形参也能定位。此外代码在生成诊断后会附加可选的上下文信息当错误来自某个被展开的复合调用如 union 调用分支时会附加compound_diag上下文当callable_ty持有函数签名 span 时还会追加一条 signature here 的辅助子诊断用Annotation::primary把读者引导到函数签名定义处bind.rs。实现剖析在参数绑定阶段按已匹配标记拦截真正触发规则的逻辑不在诊断渲染层而在调用绑定的核心函数assign_argumentcrates/ty_python_semantic/src/types/call/bind.rs中。ty 在逐个分配实参时会维护一张parameter_info表记录每个形参是否已被matched。每当要把一个实参赋给某个形参时先做如下检查if self.parameter_info[parameter_index].matched !parameter.is_variadic() !parameter.is_keyword_variadic() // Repeated explicit keywords are already reported as syntax errors. !matches!( argument, Argument::Keyword(name) if self.arguments.iter().take(argument_index).any(|(previous, _)| { matches!(previous, Argument::Keyword(previous_name) if previous_name name) }) ) { self.errors.push(BindingError::ParameterAlreadyAssigned { argument_index: self.get_argument_index(argument_index), parameter: ParameterContext::new(parameter, parameter_index, positional), }); }四个条件缺一不可值得逐条拆解parameter_info[parameter_index].matched为真该形参已经被更早的实参占用。顺序敏感的设计意味着只有后来的重复者会收到诊断而第一个成功匹配的实参不受影响。!parameter.is_variadic()形参本身若是*args这类变长参数任何数量的位置实参都合法永远不会重复赋值。!parameter.is_keyword_variadic()同理**kwargs会吞下任意未命名形参的关键字不存在冲突。排除字面重复的关键字实参如果检测到同一个关键字名在实参列表中反复出现如f(x1, x2)这一般已被语法层判为 SyntaxError这里跳过以免重复报错。错误通过枚举BindingError::ParameterAlreadyAssigned携带现场信息bind.rs包括argument_index: Optionusize触发冲突的实参位置供渲染层画下划线定位parameter: ParameterContext冲突形参的名字、序号与是否位置匹配标志。其中argument_index经过get_argument_index的特殊处理bind.rs如果冲突实参是编译器为某些特殊调用**合成synthetic**的实参它并不存在于调用点源码中此时返回None渲染层便会把诊断整体挂到整个Call节点上而不是指向一个不存在的参数位置。从枚举的定义和 match 结构看ParameterAlreadyAssigned与MissingArguments、UnknownArgument、TooManyPositionalArguments、PositionalOnlyParameterAsKwarg等属于同一参数绑定错误家族统一走report_lint→ 构造诊断的流程错误发生与否的决定性逻辑全部收敛在bind.rs这一处绑定引擎中。与 *args/**kwargs 展开的交互边界变长实参展开是最容易产生假重复的地方ty 为此有非常细致的处理在 mdtest 中甚至为它单独写了回归说明当一个变长可迭代对象被*展开、其后又跟着显式关键字实参时变参展开不应贪心地吞掉那些已有显式关键字绑定的形参从而避免产生假的 parameter already assigned 错误。 —— crates/ty_python_semantic/resources/mdtest/call/function.md对应的用例为def f(a: str, b: str, c: float) - None: ... # 显式关键字实参优先于变长展开 # 列表展开只应填充 a 和 b把 c 留给关键字实参 —— 不报错 def _(args: list[str]) - None: f(*args, c1.0) # 定长元组展开同理 —— 不报错 def _(args: tuple[str, str]) - None: f(*args, c1.0) # 但若定长元组过长确实撞上了 c则按预期报错 def _(args: tuple[str, str, str]) - None: f(*args, c1.0)而一旦没有显式关键字兜底ty 的处理又回归保守f(*args, 1.0)变长展开后紧跟位置实参、f(*args1, *args2)多个变长展开、f(*args, **kwargs)键完全未知的关键字变参等都属于位置上模棱两可的调用ty 会倾向于给出参数类型或数量上的诊断而非凭空猜测function.md。在绑定引擎内部上述先到先得、显式优先的规则同样体现在 bind.rs 的一段注释与代码中当调用是若干定长元组类型的 union时多出来的实参位置仍需按正常位置匹配流程走一遍从而对更长的 union 分支报出与具体更长元组一致的错误too-many-positional-arguments或当显式关键字撞上该形参时给出parameter-already-assigned而不是静默丢弃多余位置——这保证了错误行为的一致性不因调用方类型是单个长元组还是元组 union而产生差异。functools.partial 与变参回退场景partial把一部分实参预先绑定在构造阶段因此重复赋值的检测点前移到了partial(...)表达式本身。ty 专门在 crates/ty_python_semantic/resources/mdtest/call/functools_partial.md 中覆盖了两类情况from functools import partial def f(a: int, b: str) - bool: return True # partial 构造时位置实参 1 已占用 a再传 a2 → error p partial(f, 1, a2) # error: [parameter-already-assigned]def f(a: int, **kwargs: str) - bool: return True # a 的类型本应是 int却收到 strinvalid-argument-type # 同时 a 已被位置实参占用又被显式关键字赋值parameter-already-assigned # error: [invalid-argument-type] # error: [parameter-already-assigned] p partial(f, 1, ahello) reveal_type(p) # revealed: partial[(**kwargs: str) - bool]注意第二个用例展示了**重复赋值与类型不匹配可以同时上报**ahello既撞上了已绑定的a1其类型str也不满足int标注两条诊断互不遮蔽——因为规则触发点在assign_argument的是否已匹配检查与后续的类型断言是两条独立路径。这也意味着即便参数重复ty 仍会继续完成剩余绑定与类型检查而不是中止整个调用分析。与同族参数错误规则的对照要写出无歧义的调用代码除了避免重复赋值还需同时规避同族错误。它们共享同一个BindingError枚举与诊断渲染框架在 bind.rs 等处被统一处理规则检测对象典型形态missing-argument必填形参没有任何实参f()f需要xunknown-argument关键字实参不对应任何形参f(x1, y2)too-many-positional-arguments位置实参数量超过形参容量len([], 1)positional-only-parameter-as-kwarg对纯位置形参使用关键字传参f(1, x2)x为/前纯位置参数时parameter-already-assigned同一形参收到多个值f(1, x2)值得注意的是parameter-already-assigned与too-many-positional-arguments在定长元组展开场景下可以同时出现如前文f(*args, c1.0)的长元组用例读者在阅读诊断输出时不必惊讶于错误列表同时出现两条这正是 ty 有意为之的完整性问题描述。如何在本地复现与验证规则文档本身不提供运行方式但从仓库结构可以推导出完整的复现路径。ty 的可执行入口位于 crates/ty/src/main.rs其参数分派在 crates/ty/src/lib.rs支持check、server、explain、version等子命令# 在工作区根目录构建 ty 后对示例文件运行类型检查 cargo run -p ty -- check path/to/file.py # 或直接查看本规则的文档说明rule 名使用连字符形式 cargo run -p ty -- explain rule parameter-already-assignedty check会把parameter-already-assigned按默认的Error级别输出因此命中该错误的文件会让命令以非零状态退出适合直接接进 CI。若想观察期望的诊断如何在测试中被断言可查看仓库内的两类测试资源mdtest 期望注解在.py代码块中以# error: 18 [parameter-already-assigned] Multiple values provided for parameter \x of function f 的形式标注行/列、规则名与完整文案例如 crates/ty_python_semantic/resources/mdtest/call/function.md 与 crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md快照snapshot对展开/子调用场景的精确落点有快照文件例如 crates/ty_python_semantic/resources/mdtest/paramspec_subcall_error_location.md 中记录了错误Multiple values provided for parameter \a of function foo 在 ParamSpec 子调用场景下的精确输出可用于对照你自己的报错格式。小结parameter-already-assigned虽然身材小巧规则文档仅十几行却是 Python 调用约定中一类必然崩溃错误的精确建模。它真正有价值的部分隐藏在绑定引擎的边界处理里先到先得 显式关键字优先、跳过已报语法错误的字面重复关键字、对*args/**kwargs/定长元组 union 分别给出保守或精确的策略、以及把functools.partial、namedtuple等预绑定式构造器也纳入检测范围。在代码评审或 CI 中看到这条错误时可以直接断定某次调用的同一形参被两条实参路径同时命中修复方向是删掉冗余的位置实参、或合并关键字实参让每个形参恰好只被赋值一次。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表