Skip to content

Commit 629f42d

Browse files
committed
Consider all TYPE_CHECKING symbols for type-checking blocks (#16669)
## Summary This PR stabilizes the preview behavior introduced in #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](#15719 (comment))). 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? -->
1 parent bb17271 commit 629f42d

File tree

6 files changed

+78
-152
lines changed

6 files changed

+78
-152
lines changed

crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC005.py

-14
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,6 @@
44
pass # TC005
55

66

7-
if False:
8-
pass # TC005
9-
10-
if 0:
11-
pass # TC005
12-
13-
147
def example():
158
if TYPE_CHECKING:
169
pass # TC005
@@ -32,13 +25,6 @@ class Test:
3225
x: List
3326

3427

35-
if False:
36-
x: List
37-
38-
if 0:
39-
x: List
40-
41-
4228
from typing_extensions import TYPE_CHECKING
4329

4430
if TYPE_CHECKING:

crates/ruff_linter/src/checkers/ast/mod.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,7 @@ impl<'a> Checker<'a> {
246246
notebook_index: Option<&'a NotebookIndex>,
247247
target_version: PythonVersion,
248248
) -> Checker<'a> {
249-
let mut semantic = SemanticModel::new(&settings.typing_modules, path, module);
250-
if settings.preview.is_enabled() {
251-
// Set the feature flag to test `TYPE_CHECKING` semantic changes
252-
semantic.flags |= SemanticModelFlags::NEW_TYPE_CHECKING_BLOCK_DETECTION;
253-
}
249+
let semantic = SemanticModel::new(&settings.typing_modules, path, module);
254250
Self {
255251
parsed,
256252
parsed_type_annotation: None,

crates/ruff_linter/src/rules/flake8_simplify/snapshots/ruff_linter__rules__flake8_simplify__tests__SIM108_SIM108.py.snap

+27
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,33 @@ SIM108.py:167:1: SIM108 [*] Use ternary operator `z = 1 if True else other` inst
226226
172 169 | if False:
227227
173 170 | z = 1
228228

229+
SIM108.py:172:1: SIM108 [*] Use ternary operator `z = 1 if False else other` instead of `if`-`else`-block
230+
|
231+
170 | z = other
232+
171 |
233+
172 | / if False:
234+
173 | | z = 1
235+
174 | | else:
236+
175 | | z = other
237+
| |_____________^ SIM108
238+
176 |
239+
177 | if 1:
240+
|
241+
= help: Replace `if`-`else`-block with `z = 1 if False else other`
242+
243+
Unsafe fix
244+
169 169 | else:
245+
170 170 | z = other
246+
171 171 |
247+
172 |-if False:
248+
173 |- z = 1
249+
174 |-else:
250+
175 |- z = other
251+
172 |+z = 1 if False else other
252+
176 173 |
253+
177 174 | if 1:
254+
178 175 | z = True
255+
229256
SIM108.py:177:1: SIM108 [*] Use ternary operator `z = True if 1 else other` instead of `if`-`else`-block
230257
|
231258
175 | z = other

crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__empty-type-checking-block_TC005.py.snap

+38-76
Original file line numberDiff line numberDiff line change
@@ -16,102 +16,64 @@ TC005.py:4:5: TC005 [*] Found empty type-checking block
1616
4 |- pass # TC005
1717
5 3 |
1818
6 4 |
19-
7 5 | if False:
19+
7 5 | def example():
2020

21-
TC005.py:8:5: TC005 [*] Found empty type-checking block
21+
TC005.py:9:9: TC005 [*] Found empty type-checking block
2222
|
23-
7 | if False:
24-
8 | pass # TC005
25-
| ^^^^ TC005
26-
9 |
27-
10 | if 0:
28-
|
29-
= help: Delete empty type-checking block
30-
31-
Safe fix
32-
4 4 | pass # TC005
33-
5 5 |
34-
6 6 |
35-
7 |-if False:
36-
8 |- pass # TC005
37-
9 7 |
38-
10 8 | if 0:
39-
11 9 | pass # TC005
40-
41-
TC005.py:11:5: TC005 [*] Found empty type-checking block
42-
|
43-
10 | if 0:
44-
11 | pass # TC005
45-
| ^^^^ TC005
23+
7 | def example():
24+
8 | if TYPE_CHECKING:
25+
9 | pass # TC005
26+
| ^^^^ TC005
27+
10 | return
4628
|
4729
= help: Delete empty type-checking block
4830

4931
Safe fix
50-
7 7 | if False:
51-
8 8 | pass # TC005
52-
9 9 |
53-
10 |-if 0:
54-
11 |- pass # TC005
32+
5 5 |
33+
6 6 |
34+
7 7 | def example():
35+
8 |- if TYPE_CHECKING:
36+
9 |- pass # TC005
37+
10 8 | return
38+
11 9 |
5539
12 10 |
56-
13 11 |
57-
14 12 | def example():
5840

59-
TC005.py:16:9: TC005 [*] Found empty type-checking block
41+
TC005.py:15:9: TC005 [*] Found empty type-checking block
6042
|
61-
14 | def example():
62-
15 | if TYPE_CHECKING:
63-
16 | pass # TC005
43+
13 | class Test:
44+
14 | if TYPE_CHECKING:
45+
15 | pass # TC005
6446
| ^^^^ TC005
65-
17 | return
47+
16 | x = 2
6648
|
6749
= help: Delete empty type-checking block
6850

6951
Safe fix
52+
11 11 |
7053
12 12 |
71-
13 13 |
72-
14 14 | def example():
73-
15 |- if TYPE_CHECKING:
74-
16 |- pass # TC005
75-
17 15 | return
54+
13 13 | class Test:
55+
14 |- if TYPE_CHECKING:
56+
15 |- pass # TC005
57+
16 14 | x = 2
58+
17 15 |
7659
18 16 |
77-
19 17 |
78-
79-
TC005.py:22:9: TC005 [*] Found empty type-checking block
80-
|
81-
20 | class Test:
82-
21 | if TYPE_CHECKING:
83-
22 | pass # TC005
84-
| ^^^^ TC005
85-
23 | x = 2
86-
|
87-
= help: Delete empty type-checking block
88-
89-
Safe fix
90-
18 18 |
91-
19 19 |
92-
20 20 | class Test:
93-
21 |- if TYPE_CHECKING:
94-
22 |- pass # TC005
95-
23 21 | x = 2
96-
24 22 |
97-
25 23 |
9860

99-
TC005.py:45:5: TC005 [*] Found empty type-checking block
61+
TC005.py:31:5: TC005 [*] Found empty type-checking block
10062
|
101-
44 | if TYPE_CHECKING:
102-
45 | pass # TC005
63+
30 | if TYPE_CHECKING:
64+
31 | pass # TC005
10365
| ^^^^ TC005
104-
46 |
105-
47 | # https://github.com/astral-sh/ruff/issues/11368
66+
32 |
67+
33 | # https://github.com/astral-sh/ruff/issues/11368
10668
|
10769
= help: Delete empty type-checking block
10870

10971
Safe fix
110-
41 41 |
111-
42 42 | from typing_extensions import TYPE_CHECKING
112-
43 43 |
113-
44 |-if TYPE_CHECKING:
114-
45 |- pass # TC005
115-
46 44 |
116-
47 45 | # https://github.com/astral-sh/ruff/issues/11368
117-
48 46 | if TYPE_CHECKING:
72+
27 27 |
73+
28 28 | from typing_extensions import TYPE_CHECKING
74+
29 29 |
75+
30 |-if TYPE_CHECKING:
76+
31 |- pass # TC005
77+
32 30 |
78+
33 31 | # https://github.com/astral-sh/ruff/issues/11368
79+
34 32 | if TYPE_CHECKING:

crates/ruff_python_semantic/src/analyze/typing.rs

+12-37
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
//! Analysis rules for the `typing` module.
22
3-
use ruff_python_ast::helpers::{any_over_expr, is_const_false, map_subscript};
3+
use ruff_python_ast::helpers::{any_over_expr, map_subscript};
44
use ruff_python_ast::identifier::Identifier;
55
use ruff_python_ast::name::QualifiedName;
66
use ruff_python_ast::{
7-
self as ast, Expr, ExprCall, ExprName, Int, Operator, ParameterWithDefault, Parameters, Stmt,
7+
self as ast, Expr, ExprCall, ExprName, Operator, ParameterWithDefault, Parameters, Stmt,
88
StmtAssign,
99
};
1010
use ruff_python_stdlib::typing::{
@@ -391,44 +391,19 @@ pub fn is_mutable_expr(expr: &Expr, semantic: &SemanticModel) -> bool {
391391
pub fn is_type_checking_block(stmt: &ast::StmtIf, semantic: &SemanticModel) -> bool {
392392
let ast::StmtIf { test, .. } = stmt;
393393

394-
if semantic.use_new_type_checking_block_detection_semantics() {
395-
return match test.as_ref() {
396-
// As long as the symbol's name is "TYPE_CHECKING" we will treat it like `typing.TYPE_CHECKING`
397-
// for this specific check even if it's defined somewhere else, like the current module.
398-
// Ex) `if TYPE_CHECKING:`
399-
Expr::Name(ast::ExprName { id, .. }) => {
400-
id == "TYPE_CHECKING"
394+
match test.as_ref() {
395+
// As long as the symbol's name is "TYPE_CHECKING" we will treat it like `typing.TYPE_CHECKING`
396+
// for this specific check even if it's defined somewhere else, like the current module.
397+
// Ex) `if TYPE_CHECKING:`
398+
Expr::Name(ast::ExprName { id, .. }) => {
399+
id == "TYPE_CHECKING"
401400
// Ex) `if TC:` with `from typing import TYPE_CHECKING as TC`
402401
|| semantic.match_typing_expr(test, "TYPE_CHECKING")
403-
}
404-
// Ex) `if typing.TYPE_CHECKING:`
405-
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr == "TYPE_CHECKING",
406-
_ => false,
407-
};
408-
}
409-
410-
// Ex) `if False:`
411-
if is_const_false(test) {
412-
return true;
413-
}
414-
415-
// Ex) `if 0:`
416-
if matches!(
417-
test.as_ref(),
418-
Expr::NumberLiteral(ast::ExprNumberLiteral {
419-
value: ast::Number::Int(Int::ZERO),
420-
..
421-
})
422-
) {
423-
return true;
424-
}
425-
426-
// Ex) `if typing.TYPE_CHECKING:`
427-
if semantic.match_typing_expr(test, "TYPE_CHECKING") {
428-
return true;
402+
}
403+
// Ex) `if typing.TYPE_CHECKING:`
404+
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr == "TYPE_CHECKING",
405+
_ => false,
429406
}
430-
431-
false
432407
}
433408

434409
/// Returns `true` if the [`ast::StmtIf`] is a version-checking block (e.g., `if sys.version_info >= ...:`).

crates/ruff_python_semantic/src/model.rs

-20
Original file line numberDiff line numberDiff line change
@@ -2014,18 +2014,6 @@ impl<'a> SemanticModel<'a> {
20142014
.intersects(SemanticModelFlags::DEFERRED_CLASS_BASE)
20152015
}
20162016

2017-
/// Return `true` if we should use the new semantics to recognize
2018-
/// type checking blocks. Previously we only recognized type checking
2019-
/// blocks if `TYPE_CHECKING` was imported from a typing module.
2020-
///
2021-
/// With this feature flag enabled we recognize any symbol named
2022-
/// `TYPE_CHECKING`, regardless of where it comes from to mirror
2023-
/// what mypy and pyright do.
2024-
pub const fn use_new_type_checking_block_detection_semantics(&self) -> bool {
2025-
self.flags
2026-
.intersects(SemanticModelFlags::NEW_TYPE_CHECKING_BLOCK_DETECTION)
2027-
}
2028-
20292017
/// Return an iterator over all bindings shadowed by the given [`BindingId`], within the
20302018
/// containing scope, and across scopes.
20312019
pub fn shadowed_bindings(
@@ -2557,14 +2545,6 @@ bitflags! {
25572545
/// [#13824]: https://github.com/astral-sh/ruff/issues/13824
25582546
const NO_TYPE_CHECK = 1 << 30;
25592547

2560-
/// The model special-cases any symbol named `TYPE_CHECKING`.
2561-
///
2562-
/// Previously we only recognized `TYPE_CHECKING` if it was part of
2563-
/// one of the configured `typing` modules. This flag exists to
2564-
/// test out the semantic change only in preview. This flag will go
2565-
/// away once this change has been stabilized.
2566-
const NEW_TYPE_CHECKING_BLOCK_DETECTION = 1 << 31;
2567-
25682548
/// The context is in any type annotation.
25692549
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();
25702550

0 commit comments

Comments
 (0)