mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
Consider all TYPE_CHECKING
symbols for type-checking blocks (#16669)
## Summary This PR stabilizes the preview behavior introduced in https://github.com/astral-sh/ruff/pull/15719 to recognize all symbols named `TYPE_CHECKING` as type-checking checks in `if TYPE_CHECKING` conditions. This ensures compatibility with mypy and pyright. This PR also stabilizes the new behavior that removes `if 0:` and `if False` to be no longer considered type checking blocks. Since then, this syntax has been removed from the typing spec and was only used for Python modules that don't have a `typing` module ([comment](https://github.com/astral-sh/ruff/pull/15719#issuecomment-2612787793)). The preview behavior was first released with Ruff 0.9.5 (6th of February), which was about a month ago. There are no open issues or PRs for the changed behavior ## Test Plan The snapshots for `SIM108` change because `SIM108` ignored type checking blocks but it can no simplify `if 0` or `if False` blocks again because they're no longer considered type checking blocks. The changes in the `TC005` snapshot or only due to that `if 0` and `if False` are no longer recognized as type checking blocks <!-- How was it tested? -->
This commit is contained in:
parent
3d2f2a2f8d
commit
92193a3254
6 changed files with 78 additions and 152 deletions
|
@ -4,13 +4,6 @@ if TYPE_CHECKING:
|
||||||
pass # TC005
|
pass # TC005
|
||||||
|
|
||||||
|
|
||||||
if False:
|
|
||||||
pass # TC005
|
|
||||||
|
|
||||||
if 0:
|
|
||||||
pass # TC005
|
|
||||||
|
|
||||||
|
|
||||||
def example():
|
def example():
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
pass # TC005
|
pass # TC005
|
||||||
|
@ -32,13 +25,6 @@ if TYPE_CHECKING:
|
||||||
x: List
|
x: List
|
||||||
|
|
||||||
|
|
||||||
if False:
|
|
||||||
x: List
|
|
||||||
|
|
||||||
if 0:
|
|
||||||
x: List
|
|
||||||
|
|
||||||
|
|
||||||
from typing_extensions import TYPE_CHECKING
|
from typing_extensions import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
|
@ -246,11 +246,7 @@ impl<'a> Checker<'a> {
|
||||||
notebook_index: Option<&'a NotebookIndex>,
|
notebook_index: Option<&'a NotebookIndex>,
|
||||||
target_version: PythonVersion,
|
target_version: PythonVersion,
|
||||||
) -> Checker<'a> {
|
) -> Checker<'a> {
|
||||||
let mut semantic = SemanticModel::new(&settings.typing_modules, path, module);
|
let semantic = SemanticModel::new(&settings.typing_modules, path, module);
|
||||||
if settings.preview.is_enabled() {
|
|
||||||
// Set the feature flag to test `TYPE_CHECKING` semantic changes
|
|
||||||
semantic.flags |= SemanticModelFlags::NEW_TYPE_CHECKING_BLOCK_DETECTION;
|
|
||||||
}
|
|
||||||
Self {
|
Self {
|
||||||
parsed,
|
parsed,
|
||||||
parsed_type_annotation: None,
|
parsed_type_annotation: None,
|
||||||
|
|
|
@ -226,6 +226,33 @@ SIM108.py:167:1: SIM108 [*] Use ternary operator `z = 1 if True else other` inst
|
||||||
172 169 | if False:
|
172 169 | if False:
|
||||||
173 170 | z = 1
|
173 170 | z = 1
|
||||||
|
|
||||||
|
SIM108.py:172:1: SIM108 [*] Use ternary operator `z = 1 if False else other` instead of `if`-`else`-block
|
||||||
|
|
|
||||||
|
170 | z = other
|
||||||
|
171 |
|
||||||
|
172 | / if False:
|
||||||
|
173 | | z = 1
|
||||||
|
174 | | else:
|
||||||
|
175 | | z = other
|
||||||
|
| |_____________^ SIM108
|
||||||
|
176 |
|
||||||
|
177 | if 1:
|
||||||
|
|
|
||||||
|
= help: Replace `if`-`else`-block with `z = 1 if False else other`
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
169 169 | else:
|
||||||
|
170 170 | z = other
|
||||||
|
171 171 |
|
||||||
|
172 |-if False:
|
||||||
|
173 |- z = 1
|
||||||
|
174 |-else:
|
||||||
|
175 |- z = other
|
||||||
|
172 |+z = 1 if False else other
|
||||||
|
176 173 |
|
||||||
|
177 174 | if 1:
|
||||||
|
178 175 | z = True
|
||||||
|
|
||||||
SIM108.py:177:1: SIM108 [*] Use ternary operator `z = True if 1 else other` instead of `if`-`else`-block
|
SIM108.py:177:1: SIM108 [*] Use ternary operator `z = True if 1 else other` instead of `if`-`else`-block
|
||||||
|
|
|
|
||||||
175 | z = other
|
175 | z = other
|
||||||
|
|
|
@ -16,102 +16,64 @@ TC005.py:4:5: TC005 [*] Found empty type-checking block
|
||||||
4 |- pass # TC005
|
4 |- pass # TC005
|
||||||
5 3 |
|
5 3 |
|
||||||
6 4 |
|
6 4 |
|
||||||
7 5 | if False:
|
7 5 | def example():
|
||||||
|
|
||||||
TC005.py:8:5: TC005 [*] Found empty type-checking block
|
TC005.py:9:9: TC005 [*] Found empty type-checking block
|
||||||
|
|
|
|
||||||
7 | if False:
|
7 | def example():
|
||||||
8 | pass # TC005
|
8 | if TYPE_CHECKING:
|
||||||
| ^^^^ TC005
|
9 | pass # TC005
|
||||||
9 |
|
| ^^^^ TC005
|
||||||
10 | if 0:
|
10 | return
|
||||||
|
|
|
|
||||||
= help: Delete empty type-checking block
|
= help: Delete empty type-checking block
|
||||||
|
|
||||||
ℹ Safe fix
|
ℹ Safe fix
|
||||||
4 4 | pass # TC005
|
5 5 |
|
||||||
5 5 |
|
6 6 |
|
||||||
6 6 |
|
7 7 | def example():
|
||||||
7 |-if False:
|
8 |- if TYPE_CHECKING:
|
||||||
8 |- pass # TC005
|
9 |- pass # TC005
|
||||||
9 7 |
|
10 8 | return
|
||||||
10 8 | if 0:
|
11 9 |
|
||||||
11 9 | pass # TC005
|
|
||||||
|
|
||||||
TC005.py:11:5: TC005 [*] Found empty type-checking block
|
|
||||||
|
|
|
||||||
10 | if 0:
|
|
||||||
11 | pass # TC005
|
|
||||||
| ^^^^ TC005
|
|
||||||
|
|
|
||||||
= help: Delete empty type-checking block
|
|
||||||
|
|
||||||
ℹ Safe fix
|
|
||||||
7 7 | if False:
|
|
||||||
8 8 | pass # TC005
|
|
||||||
9 9 |
|
|
||||||
10 |-if 0:
|
|
||||||
11 |- pass # TC005
|
|
||||||
12 10 |
|
12 10 |
|
||||||
13 11 |
|
|
||||||
14 12 | def example():
|
|
||||||
|
|
||||||
TC005.py:16:9: TC005 [*] Found empty type-checking block
|
TC005.py:15:9: TC005 [*] Found empty type-checking block
|
||||||
|
|
|
|
||||||
14 | def example():
|
13 | class Test:
|
||||||
15 | if TYPE_CHECKING:
|
14 | if TYPE_CHECKING:
|
||||||
16 | pass # TC005
|
15 | pass # TC005
|
||||||
| ^^^^ TC005
|
| ^^^^ TC005
|
||||||
17 | return
|
16 | x = 2
|
||||||
|
|
|
|
||||||
= help: Delete empty type-checking block
|
= help: Delete empty type-checking block
|
||||||
|
|
||||||
ℹ Safe fix
|
ℹ Safe fix
|
||||||
|
11 11 |
|
||||||
12 12 |
|
12 12 |
|
||||||
13 13 |
|
13 13 | class Test:
|
||||||
14 14 | def example():
|
14 |- if TYPE_CHECKING:
|
||||||
15 |- if TYPE_CHECKING:
|
15 |- pass # TC005
|
||||||
16 |- pass # TC005
|
16 14 | x = 2
|
||||||
17 15 | return
|
17 15 |
|
||||||
18 16 |
|
18 16 |
|
||||||
19 17 |
|
|
||||||
|
|
||||||
TC005.py:22:9: TC005 [*] Found empty type-checking block
|
TC005.py:31:5: TC005 [*] Found empty type-checking block
|
||||||
|
|
|
|
||||||
20 | class Test:
|
30 | if TYPE_CHECKING:
|
||||||
21 | if TYPE_CHECKING:
|
31 | pass # TC005
|
||||||
22 | pass # TC005
|
|
||||||
| ^^^^ TC005
|
|
||||||
23 | x = 2
|
|
||||||
|
|
|
||||||
= help: Delete empty type-checking block
|
|
||||||
|
|
||||||
ℹ Safe fix
|
|
||||||
18 18 |
|
|
||||||
19 19 |
|
|
||||||
20 20 | class Test:
|
|
||||||
21 |- if TYPE_CHECKING:
|
|
||||||
22 |- pass # TC005
|
|
||||||
23 21 | x = 2
|
|
||||||
24 22 |
|
|
||||||
25 23 |
|
|
||||||
|
|
||||||
TC005.py:45:5: TC005 [*] Found empty type-checking block
|
|
||||||
|
|
|
||||||
44 | if TYPE_CHECKING:
|
|
||||||
45 | pass # TC005
|
|
||||||
| ^^^^ TC005
|
| ^^^^ TC005
|
||||||
46 |
|
32 |
|
||||||
47 | # https://github.com/astral-sh/ruff/issues/11368
|
33 | # https://github.com/astral-sh/ruff/issues/11368
|
||||||
|
|
|
|
||||||
= help: Delete empty type-checking block
|
= help: Delete empty type-checking block
|
||||||
|
|
||||||
ℹ Safe fix
|
ℹ Safe fix
|
||||||
41 41 |
|
27 27 |
|
||||||
42 42 | from typing_extensions import TYPE_CHECKING
|
28 28 | from typing_extensions import TYPE_CHECKING
|
||||||
43 43 |
|
29 29 |
|
||||||
44 |-if TYPE_CHECKING:
|
30 |-if TYPE_CHECKING:
|
||||||
45 |- pass # TC005
|
31 |- pass # TC005
|
||||||
46 44 |
|
32 30 |
|
||||||
47 45 | # https://github.com/astral-sh/ruff/issues/11368
|
33 31 | # https://github.com/astral-sh/ruff/issues/11368
|
||||||
48 46 | if TYPE_CHECKING:
|
34 32 | if TYPE_CHECKING:
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
//! Analysis rules for the `typing` module.
|
//! Analysis rules for the `typing` module.
|
||||||
|
|
||||||
use ruff_python_ast::helpers::{any_over_expr, is_const_false, map_subscript};
|
use ruff_python_ast::helpers::{any_over_expr, map_subscript};
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::name::QualifiedName;
|
use ruff_python_ast::name::QualifiedName;
|
||||||
use ruff_python_ast::{
|
use ruff_python_ast::{
|
||||||
self as ast, Expr, ExprCall, ExprName, Int, Operator, ParameterWithDefault, Parameters, Stmt,
|
self as ast, Expr, ExprCall, ExprName, Operator, ParameterWithDefault, Parameters, Stmt,
|
||||||
StmtAssign,
|
StmtAssign,
|
||||||
};
|
};
|
||||||
use ruff_python_stdlib::typing::{
|
use ruff_python_stdlib::typing::{
|
||||||
|
@ -391,44 +391,19 @@ pub fn is_mutable_expr(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
pub fn is_type_checking_block(stmt: &ast::StmtIf, semantic: &SemanticModel) -> bool {
|
pub fn is_type_checking_block(stmt: &ast::StmtIf, semantic: &SemanticModel) -> bool {
|
||||||
let ast::StmtIf { test, .. } = stmt;
|
let ast::StmtIf { test, .. } = stmt;
|
||||||
|
|
||||||
if semantic.use_new_type_checking_block_detection_semantics() {
|
match test.as_ref() {
|
||||||
return match test.as_ref() {
|
// As long as the symbol's name is "TYPE_CHECKING" we will treat it like `typing.TYPE_CHECKING`
|
||||||
// As long as the symbol's name is "TYPE_CHECKING" we will treat it like `typing.TYPE_CHECKING`
|
// for this specific check even if it's defined somewhere else, like the current module.
|
||||||
// for this specific check even if it's defined somewhere else, like the current module.
|
// Ex) `if TYPE_CHECKING:`
|
||||||
// Ex) `if TYPE_CHECKING:`
|
Expr::Name(ast::ExprName { id, .. }) => {
|
||||||
Expr::Name(ast::ExprName { id, .. }) => {
|
id == "TYPE_CHECKING"
|
||||||
id == "TYPE_CHECKING"
|
|
||||||
// Ex) `if TC:` with `from typing import TYPE_CHECKING as TC`
|
// Ex) `if TC:` with `from typing import TYPE_CHECKING as TC`
|
||||||
|| semantic.match_typing_expr(test, "TYPE_CHECKING")
|
|| semantic.match_typing_expr(test, "TYPE_CHECKING")
|
||||||
}
|
}
|
||||||
// Ex) `if typing.TYPE_CHECKING:`
|
// Ex) `if typing.TYPE_CHECKING:`
|
||||||
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr == "TYPE_CHECKING",
|
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr == "TYPE_CHECKING",
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ex) `if False:`
|
|
||||||
if is_const_false(test) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ex) `if 0:`
|
|
||||||
if matches!(
|
|
||||||
test.as_ref(),
|
|
||||||
Expr::NumberLiteral(ast::ExprNumberLiteral {
|
|
||||||
value: ast::Number::Int(Int::ZERO),
|
|
||||||
..
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ex) `if typing.TYPE_CHECKING:`
|
|
||||||
if semantic.match_typing_expr(test, "TYPE_CHECKING") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the [`ast::StmtIf`] is a version-checking block (e.g., `if sys.version_info >= ...:`).
|
/// Returns `true` if the [`ast::StmtIf`] is a version-checking block (e.g., `if sys.version_info >= ...:`).
|
||||||
|
|
|
@ -2014,18 +2014,6 @@ impl<'a> SemanticModel<'a> {
|
||||||
.intersects(SemanticModelFlags::DEFERRED_CLASS_BASE)
|
.intersects(SemanticModelFlags::DEFERRED_CLASS_BASE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if we should use the new semantics to recognize
|
|
||||||
/// type checking blocks. Previously we only recognized type checking
|
|
||||||
/// blocks if `TYPE_CHECKING` was imported from a typing module.
|
|
||||||
///
|
|
||||||
/// With this feature flag enabled we recognize any symbol named
|
|
||||||
/// `TYPE_CHECKING`, regardless of where it comes from to mirror
|
|
||||||
/// what mypy and pyright do.
|
|
||||||
pub const fn use_new_type_checking_block_detection_semantics(&self) -> bool {
|
|
||||||
self.flags
|
|
||||||
.intersects(SemanticModelFlags::NEW_TYPE_CHECKING_BLOCK_DETECTION)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return an iterator over all bindings shadowed by the given [`BindingId`], within the
|
/// Return an iterator over all bindings shadowed by the given [`BindingId`], within the
|
||||||
/// containing scope, and across scopes.
|
/// containing scope, and across scopes.
|
||||||
pub fn shadowed_bindings(
|
pub fn shadowed_bindings(
|
||||||
|
@ -2557,14 +2545,6 @@ bitflags! {
|
||||||
/// [#13824]: https://github.com/astral-sh/ruff/issues/13824
|
/// [#13824]: https://github.com/astral-sh/ruff/issues/13824
|
||||||
const NO_TYPE_CHECK = 1 << 30;
|
const NO_TYPE_CHECK = 1 << 30;
|
||||||
|
|
||||||
/// The model special-cases any symbol named `TYPE_CHECKING`.
|
|
||||||
///
|
|
||||||
/// Previously we only recognized `TYPE_CHECKING` if it was part of
|
|
||||||
/// one of the configured `typing` modules. This flag exists to
|
|
||||||
/// test out the semantic change only in preview. This flag will go
|
|
||||||
/// away once this change has been stabilized.
|
|
||||||
const NEW_TYPE_CHECKING_BLOCK_DETECTION = 1 << 31;
|
|
||||||
|
|
||||||
/// The context is in any type annotation.
|
/// The context is in any type annotation.
|
||||||
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();
|
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue