Fixes how the checker visits typing.cast/typing.NewType arguments (#17538)

This commit is contained in:
David Salvisberg 2025-04-23 09:26:00 +02:00 committed by GitHub
parent e45f23b0ec
commit f36262d970
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 78 additions and 15 deletions

View file

@ -89,3 +89,11 @@ def f():
int # TC006
, 6.0
)
def f():
# Keyword arguments
from typing import cast
cast(typ=int, val=3.0) # TC006
cast(val=3.0, typ=int) # TC006

View file

@ -1539,25 +1539,37 @@ impl<'a> Visitor<'a> for Checker<'a> {
}
}
Some(typing::Callable::Cast) => {
let mut args = arguments.args.iter();
if let Some(arg) = args.next() {
self.visit_type_definition(arg);
if !self.source_type.is_stub() && self.enabled(Rule::RuntimeCastValue) {
flake8_type_checking::rules::runtime_cast_value(self, arg);
for (i, arg) in arguments.arguments_source_order().enumerate() {
match (i, arg) {
(0, ArgOrKeyword::Arg(arg)) => self.visit_cast_type_argument(arg),
(_, ArgOrKeyword::Arg(arg)) => self.visit_non_type_definition(arg),
(_, ArgOrKeyword::Keyword(Keyword { arg, value, .. })) => {
if let Some(id) = arg {
if id == "typ" {
self.visit_cast_type_argument(value);
} else {
self.visit_non_type_definition(value);
}
}
}
}
}
for arg in args {
self.visit_expr(arg);
}
}
Some(typing::Callable::NewType) => {
let mut args = arguments.args.iter();
if let Some(arg) = args.next() {
self.visit_non_type_definition(arg);
}
for arg in args {
self.visit_type_definition(arg);
for (i, arg) in arguments.arguments_source_order().enumerate() {
match (i, arg) {
(1, ArgOrKeyword::Arg(arg)) => self.visit_type_definition(arg),
(_, ArgOrKeyword::Arg(arg)) => self.visit_non_type_definition(arg),
(_, ArgOrKeyword::Keyword(Keyword { arg, value, .. })) => {
if let Some(id) = arg {
if id == "tp" {
self.visit_type_definition(value);
} else {
self.visit_non_type_definition(value);
}
}
}
}
}
}
Some(typing::Callable::TypeVar) => {
@ -2209,6 +2221,15 @@ impl<'a> Checker<'a> {
self.semantic.flags = snapshot;
}
/// Visit an [`Expr`], and treat it as the `typ` argument to `typing.cast`.
fn visit_cast_type_argument(&mut self, arg: &'a Expr) {
self.visit_type_definition(arg);
if !self.source_type.is_stub() && self.enabled(Rule::RuntimeCastValue) {
flake8_type_checking::rules::runtime_cast_value(self, arg);
}
}
/// Visit an [`Expr`], and treat it as a boolean test. This is useful for detecting whether an
/// expressions return value is significant, or whether the calling context only relies on
/// its truthiness.

View file

@ -212,3 +212,37 @@ TC006.py:89:9: TC006 [*] Add quotes to type expression in `typing.cast()`
89 |+ "int" # TC006
90 90 | , 6.0
91 91 | )
92 92 |
TC006.py:98:14: TC006 [*] Add quotes to type expression in `typing.cast()`
|
96 | from typing import cast
97 |
98 | cast(typ=int, val=3.0) # TC006
| ^^^ TC006
99 | cast(val=3.0, typ=int) # TC006
|
= help: Add quotes
Safe fix
95 95 | # Keyword arguments
96 96 | from typing import cast
97 97 |
98 |- cast(typ=int, val=3.0) # TC006
98 |+ cast(typ="int", val=3.0) # TC006
99 99 | cast(val=3.0, typ=int) # TC006
TC006.py:99:23: TC006 [*] Add quotes to type expression in `typing.cast()`
|
98 | cast(typ=int, val=3.0) # TC006
99 | cast(val=3.0, typ=int) # TC006
| ^^^ TC006
|
= help: Add quotes
Safe fix
96 96 | from typing import cast
97 97 |
98 98 | cast(typ=int, val=3.0) # TC006
99 |- cast(val=3.0, typ=int) # TC006
99 |+ cast(val=3.0, typ="int") # TC006