F507: Fix false negative for non-tuple RHS in %-formatting#24142
F507: Fix false negative for non-tuple RHS in %-formatting#24142MichaReiser merged 2 commits intoastral-sh:mainfrom
F507: Fix false negative for non-tuple RHS in %-formatting#24142Conversation
|
Does this also solve the case where the LHS is an f-string? |
|
No, it doesn't. That's a separate issue. The LHS check happens in if let Expr::StringLiteral(format_string @ ast::ExprStringLiteral { value, .. }) = left.as_ref()So when the LHS is an f-string ( I will make a separate PR for that. |
|
@ntBre Could you please review my PR? |
OK, but perhaps a case with 0 placeholders and 1 item in right hand side should be considered. I'm not sure if this case can be covered without risk of false positive: What happens for this case if banana is an empty tuple? I suppose for this particular case, with a literal string LHS, any use of |
|
Good point! The current implementation already covers that case — For |
F507: Fix false negative for non-tuple RHS in %-formatting
* main: [ty] Avoid eager TypedDict diagnostics in `TypedDict | dict` unions (#24151) `F507`: Fix false negative for non-tuple RHS in `%`-formatting (#24142) [ty] Update `SpecializationBuilder` hook to get both lower/upper bounds (#23848) Fix `%foo?` parsing in IPython assignment expressions (#24152) `E501`/`W505`/formatter: Exclude nested pragma comments from line width calculation (#24071) [ty] Fix Salsa panic propagation (#24141) [ty] Support `type:ignore[ty:code]` suppressions (#24096) [ty] Support narrowing for extended walrus targets (#24129)
Summary
Fixes #24031
F507 (
percent-format-positional-count-mismatch) previously only checked tuple right-hand sides in%-format expressions. This meant literal non-tuple values like'%s %s' % 42were silently ignored.This PR extends F507 to also flag non-tuple RHS values when the format string expects a different number of positional arguments, using
ResolvedPythonType::from()to infer the type of the RHS expression.What gets flagged now
42,3.14,"hello",b"hello",True,None,...,f"hello {name}"-1,1 + 2,not x,"a" + "b",1 if True else 2What is intentionally NOT flagged
Variables, attribute accesses, subscripts, and calls are skipped because they could be tuples at runtime:
Why
ResolvedPythonTypePer reviewer suggestion, using
ResolvedPythonType::from()instead of manually matching literal expression types catches more cases (unary ops, binary ops, ternary, etc.) and is more maintainable.Context
The original implementation only checked
Expr::TuplebecauseListandSetwere intentionally removed in #5986 —'%s' % [1, 2, 3]is valid Python (lists aren't unpacked by%). This PR takes the same conservative approach by only flagging cases where we can statically determine the type is not a tuple.Test plan
ResolvedPythonType(unary, binary, boolean, ternary)