Skip to content

Consider all TYPE_CHECKING symbols for type-checking blocks #16669

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@
pass # TC005


if False:
pass # TC005

if 0:
pass # TC005


def example():
if TYPE_CHECKING:
pass # TC005
Expand All @@ -32,13 +25,6 @@ class Test:
x: List


if False:
x: List

if 0:
x: List


from typing_extensions import TYPE_CHECKING

if TYPE_CHECKING:
Expand Down
6 changes: 1 addition & 5 deletions crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,7 @@ impl<'a> Checker<'a> {
notebook_index: Option<&'a NotebookIndex>,
target_version: PythonVersion,
) -> Checker<'a> {
let mut 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;
}
let semantic = SemanticModel::new(&settings.typing_modules, path, module);
Self {
parsed,
parsed_type_annotation: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,33 @@ SIM108.py:167:1: SIM108 [*] Use ternary operator `z = 1 if True else other` inst
172 169 | if False:
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
|
175 | z = other
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,102 +16,64 @@ TC005.py:4:5: TC005 [*] Found empty type-checking block
4 |- pass # TC005
5 3 |
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:
8 | pass # TC005
| ^^^^ TC005
9 |
10 | if 0:
|
= help: Delete empty type-checking block

ℹ Safe fix
4 4 | pass # TC005
5 5 |
6 6 |
7 |-if False:
8 |- pass # TC005
9 7 |
10 8 | if 0:
11 9 | pass # TC005

TC005.py:11:5: TC005 [*] Found empty type-checking block
|
10 | if 0:
11 | pass # TC005
| ^^^^ TC005
7 | def example():
8 | if TYPE_CHECKING:
9 | pass # TC005
| ^^^^ TC005
10 | return
|
= help: Delete empty type-checking block

ℹ Safe fix
7 7 | if False:
8 8 | pass # TC005
9 9 |
10 |-if 0:
11 |- pass # TC005
5 5 |
6 6 |
7 7 | def example():
8 |- if TYPE_CHECKING:
9 |- pass # TC005
10 8 | return
11 9 |
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():
15 | if TYPE_CHECKING:
16 | pass # TC005
13 | class Test:
14 | if TYPE_CHECKING:
15 | pass # TC005
| ^^^^ TC005
17 | return
16 | x = 2
|
= help: Delete empty type-checking block

ℹ Safe fix
11 11 |
12 12 |
13 13 |
14 14 | def example():
15 |- if TYPE_CHECKING:
16 |- pass # TC005
17 15 | return
13 13 | class Test:
14 |- if TYPE_CHECKING:
15 |- pass # TC005
16 14 | x = 2
17 15 |
18 16 |
19 17 |

TC005.py:22:9: TC005 [*] Found empty type-checking block
|
20 | class Test:
21 | if TYPE_CHECKING:
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
TC005.py:31:5: TC005 [*] Found empty type-checking block
|
44 | if TYPE_CHECKING:
45 | pass # TC005
30 | if TYPE_CHECKING:
31 | pass # TC005
| ^^^^ TC005
46 |
47 | # https://github.com/astral-sh/ruff/issues/11368
32 |
33 | # https://github.com/astral-sh/ruff/issues/11368
|
= help: Delete empty type-checking block

ℹ Safe fix
41 41 |
42 42 | from typing_extensions import TYPE_CHECKING
43 43 |
44 |-if TYPE_CHECKING:
45 |- pass # TC005
46 44 |
47 45 | # https://github.com/astral-sh/ruff/issues/11368
48 46 | if TYPE_CHECKING:
27 27 |
28 28 | from typing_extensions import TYPE_CHECKING
29 29 |
30 |-if TYPE_CHECKING:
31 |- pass # TC005
32 30 |
33 31 | # https://github.com/astral-sh/ruff/issues/11368
34 32 | if TYPE_CHECKING:
49 changes: 12 additions & 37 deletions crates/ruff_python_semantic/src/analyze/typing.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! 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::name::QualifiedName;
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,
};
use ruff_python_stdlib::typing::{
Expand Down Expand Up @@ -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 {
let ast::StmtIf { test, .. } = stmt;

if semantic.use_new_type_checking_block_detection_semantics() {
return match test.as_ref() {
// 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.
// Ex) `if TYPE_CHECKING:`
Expr::Name(ast::ExprName { id, .. }) => {
id == "TYPE_CHECKING"
match test.as_ref() {
// 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.
// Ex) `if TYPE_CHECKING:`
Expr::Name(ast::ExprName { id, .. }) => {
id == "TYPE_CHECKING"
// Ex) `if TC:` with `from typing import TYPE_CHECKING as TC`
|| semantic.match_typing_expr(test, "TYPE_CHECKING")
}
// Ex) `if typing.TYPE_CHECKING:`
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr == "TYPE_CHECKING",
_ => 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;
}
// Ex) `if typing.TYPE_CHECKING:`
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr == "TYPE_CHECKING",
_ => false,
}

false
}

/// Returns `true` if the [`ast::StmtIf`] is a version-checking block (e.g., `if sys.version_info >= ...:`).
Expand Down
20 changes: 0 additions & 20 deletions crates/ruff_python_semantic/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2014,18 +2014,6 @@ impl<'a> SemanticModel<'a> {
.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
/// containing scope, and across scopes.
pub fn shadowed_bindings(
Expand Down Expand Up @@ -2557,14 +2545,6 @@ bitflags! {
/// [#13824]: https://github.com/astral-sh/ruff/issues/13824
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.
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();

Expand Down
Loading