Skip to content

Commit 67e5888

Browse files
committed
Merge branch 'main' into dcreager/lazy-generic-class
* main: (24 commits) [`airflow`] Extend `AIR301` rule (#17598) [`airflow`] update existing `AIR302` rules with better suggestions (#17542) red_knot_project: sort diagnostics from checking files [red-knot] fix detecting a metaclass on a not-explicitly-specialized generic base (#17621) [red-knot] fix inheritance-cycle detection for generic classes (#17620) [`pylint`] Detect `global` declarations in module scope (`PLE0118`) (#17411) Add Semantic Error Test for LateFutureImport (#17612) [red-knot] change TypeVarInstance to be interned, not tracked (#17616) [red-knot] Special case `@final`, `@override` (#17608) [red-knot] add TODO comment in specialization code (#17615) [`semantic-syntax-errors`] test for `LoadBeforeGlobalDeclaration` - ruff linter (#17592) [syntax-errors] `nonlocal` declaration at module level (#17559) [`airflow`] Apply auto fix to cases where name has been changed in Airflow 3 (`AIR311`) (#17571) [syntax-errors] Make `async-comprehension-in-sync-comprehension` more specific (#17460) Bump 0.11.7 (#17613) [red-knot] Use iterative approach to collect overloads (#17607) red_knot_python_semantic: avoid Rust's screaming snake case convention in mdtest red_knot_python_semantic: improve diagnostics for unsupported boolean conversions red_knot_python_semantic: add "return type span" helper method red_knot_python_semantic: move parameter span helper method ...
2 parents 0f0cb90 + aba21a5 commit 67e5888

File tree

88 files changed

+1696
-625
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+1696
-625
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# Changelog
22

3+
## 0.11.7
4+
5+
### Preview features
6+
7+
- \[`airflow`\] Apply auto fixes to cases where the names have changed in Airflow 3 (`AIR301`) ([#17355](https://github.com/astral-sh/ruff/pull/17355))
8+
- \[`perflint`\] Implement fix for `manual-dict-comprehension` (`PERF403`) ([#16719](https://github.com/astral-sh/ruff/pull/16719))
9+
- [syntax-errors] Make duplicate parameter names a semantic error ([#17131](https://github.com/astral-sh/ruff/pull/17131))
10+
11+
### Bug fixes
12+
13+
- \[`airflow`\] Fix typos in provider package names (`AIR302`, `AIR312`) ([#17574](https://github.com/astral-sh/ruff/pull/17574))
14+
- \[`flake8-type-checking`\] Visit keyword arguments in checks involving `typing.cast`/`typing.NewType` arguments ([#17538](https://github.com/astral-sh/ruff/pull/17538))
15+
- \[`pyupgrade`\] Preserve parenthesis when fixing native literals containing newlines (`UP018`) ([#17220](https://github.com/astral-sh/ruff/pull/17220))
16+
- \[`refurb`\] Mark the `FURB161` fix unsafe except for integers and booleans ([#17240](https://github.com/astral-sh/ruff/pull/17240))
17+
18+
### Rule changes
19+
20+
- \[`perflint`\] Allow list function calls to be replaced with a comprehension (`PERF401`) ([#17519](https://github.com/astral-sh/ruff/pull/17519))
21+
- \[`pycodestyle`\] Auto-fix redundant boolean comparison (`E712`) ([#17090](https://github.com/astral-sh/ruff/pull/17090))
22+
- \[`pylint`\] make fix unsafe if delete comments (`PLR1730`) ([#17459](https://github.com/astral-sh/ruff/pull/17459))
23+
24+
### Documentation
25+
26+
- Add fix safety sections to docs for several rules ([#17410](https://github.com/astral-sh/ruff/pull/17410),[#17440](https://github.com/astral-sh/ruff/pull/17440),[#17441](https://github.com/astral-sh/ruff/pull/17441),[#17443](https://github.com/astral-sh/ruff/pull/17443),[#17444](https://github.com/astral-sh/ruff/pull/17444))
27+
328
## 0.11.6
429

530
### Preview features

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
149149
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
150150

151151
# For a specific version.
152-
curl -LsSf https://astral.sh/ruff/0.11.6/install.sh | sh
153-
powershell -c "irm https://astral.sh/ruff/0.11.6/install.ps1 | iex"
152+
curl -LsSf https://astral.sh/ruff/0.11.7/install.sh | sh
153+
powershell -c "irm https://astral.sh/ruff/0.11.7/install.ps1 | iex"
154154
```
155155

156156
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -183,7 +183,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
183183
```yaml
184184
- repo: https://github.com/astral-sh/ruff-pre-commit
185185
# Ruff version.
186-
rev: v0.11.6
186+
rev: v0.11.7
187187
hooks:
188188
# Run the linter.
189189
- id: ruff

crates/red_knot/tests/cli.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -935,13 +935,6 @@ fn check_specific_paths() -> anyhow::Result<()> {
935935
success: false
936936
exit_code: 1
937937
----- stdout -----
938-
error: lint:unresolved-import: Cannot resolve import `does_not_exist`
939-
--> <temp_dir>/project/tests/test_main.py:2:8
940-
|
941-
2 | import does_not_exist # error: unresolved-import
942-
| ^^^^^^^^^^^^^^
943-
|
944-
945938
error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
946939
--> <temp_dir>/project/main.py:2:5
947940
|
@@ -958,6 +951,13 @@ fn check_specific_paths() -> anyhow::Result<()> {
958951
4 | print(z)
959952
|
960953
954+
error: lint:unresolved-import: Cannot resolve import `does_not_exist`
955+
--> <temp_dir>/project/tests/test_main.py:2:8
956+
|
957+
2 | import does_not_exist # error: unresolved-import
958+
| ^^^^^^^^^^^^^^
959+
|
960+
961961
Found 3 diagnostics
962962
963963
----- stderr -----
@@ -972,13 +972,6 @@ fn check_specific_paths() -> anyhow::Result<()> {
972972
success: false
973973
exit_code: 1
974974
----- stdout -----
975-
error: lint:unresolved-import: Cannot resolve import `does_not_exist`
976-
--> <temp_dir>/project/tests/test_main.py:2:8
977-
|
978-
2 | import does_not_exist # error: unresolved-import
979-
| ^^^^^^^^^^^^^^
980-
|
981-
982975
error: lint:unresolved-import: Cannot resolve import `main2`
983976
--> <temp_dir>/project/other.py:2:6
984977
|
@@ -988,6 +981,13 @@ fn check_specific_paths() -> anyhow::Result<()> {
988981
4 | print(z)
989982
|
990983
984+
error: lint:unresolved-import: Cannot resolve import `does_not_exist`
985+
--> <temp_dir>/project/tests/test_main.py:2:8
986+
|
987+
2 | import does_not_exist # error: unresolved-import
988+
| ^^^^^^^^^^^^^^
989+
|
990+
991991
Found 2 diagnostics
992992
993993
----- stderr -----

crates/red_knot_project/src/lib.rs

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -187,30 +187,66 @@ impl Project {
187187
.map(IOErrorDiagnostic::to_diagnostic),
188188
);
189189

190-
let result = Arc::new(std::sync::Mutex::new(diagnostics));
191-
let inner_result = Arc::clone(&result);
192-
193-
let db = db.clone();
194-
let project_span = project_span.clone();
195-
196-
rayon::scope(move |scope| {
197-
for file in &files {
198-
let result = inner_result.clone();
199-
let db = db.clone();
200-
let project_span = project_span.clone();
201-
202-
scope.spawn(move |_| {
203-
let check_file_span =
204-
tracing::debug_span!(parent: &project_span, "check_file", ?file);
205-
let _entered = check_file_span.entered();
206-
207-
let file_diagnostics = check_file_impl(&db, file);
208-
result.lock().unwrap().extend(file_diagnostics);
209-
});
190+
let file_diagnostics = Arc::new(std::sync::Mutex::new(vec![]));
191+
192+
{
193+
let file_diagnostics = Arc::clone(&file_diagnostics);
194+
let db = db.clone();
195+
let project_span = project_span.clone();
196+
197+
rayon::scope(move |scope| {
198+
for file in &files {
199+
let result = Arc::clone(&file_diagnostics);
200+
let db = db.clone();
201+
let project_span = project_span.clone();
202+
203+
scope.spawn(move |_| {
204+
let check_file_span =
205+
tracing::debug_span!(parent: &project_span, "check_file", ?file);
206+
let _entered = check_file_span.entered();
207+
208+
let file_diagnostics = check_file_impl(&db, file);
209+
result.lock().unwrap().extend(file_diagnostics);
210+
});
211+
}
212+
});
213+
}
214+
215+
let mut file_diagnostics = Arc::into_inner(file_diagnostics)
216+
.unwrap()
217+
.into_inner()
218+
.unwrap();
219+
// We sort diagnostics in a way that keeps them in source order
220+
// and grouped by file. After that, we fall back to severity
221+
// (with fatal messages sorting before info messages) and then
222+
// finally the diagnostic ID.
223+
file_diagnostics.sort_by(|d1, d2| {
224+
if let (Some(span1), Some(span2)) = (d1.primary_span(), d2.primary_span()) {
225+
let order = span1
226+
.file()
227+
.path(db)
228+
.as_str()
229+
.cmp(span2.file().path(db).as_str());
230+
if order.is_ne() {
231+
return order;
232+
}
233+
234+
if let (Some(range1), Some(range2)) = (span1.range(), span2.range()) {
235+
let order = range1.start().cmp(&range2.start());
236+
if order.is_ne() {
237+
return order;
238+
}
239+
}
210240
}
241+
// Reverse so that, e.g., Fatal sorts before Info.
242+
let order = d1.severity().cmp(&d2.severity()).reverse();
243+
if order.is_ne() {
244+
return order;
245+
}
246+
d1.id().cmp(&d2.id())
211247
});
212-
213-
Arc::into_inner(result).unwrap().into_inner().unwrap()
248+
diagnostics.extend(file_diagnostics);
249+
diagnostics
214250
}
215251

216252
pub(crate) fn check_file(self, db: &dyn Db, file: File) -> Vec<Diagnostic> {

crates/red_knot_python_semantic/resources/mdtest/conditional/if_expression.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ def _(flag: bool):
4242
class NotBoolable:
4343
__bool__: int = 3
4444

45-
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
45+
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
4646
3 if NotBoolable() else 4
4747
```

crates/red_knot_python_semantic/resources/mdtest/conditional/if_statement.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,10 @@ def _(flag: bool):
154154
class NotBoolable:
155155
__bool__: int = 3
156156

157-
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
157+
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
158158
if NotBoolable():
159159
...
160-
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
160+
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
161161
elif NotBoolable():
162162
...
163163
```

crates/red_knot_python_semantic/resources/mdtest/conditional/match.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ class NotBoolable:
292292
def _(target: int, flag: NotBoolable):
293293
y = 1
294294
match target:
295-
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
295+
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
296296
case 1 if flag:
297297
y = 2
298298
case 2:

crates/red_knot_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ async def elements(n):
1919
yield n
2020

2121
async def f():
22-
# error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)"
22+
# error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)"
2323
return {n: [x async for x in elements(n)] for n in range(3)}
2424
```
2525

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<!-- snapshot-diagnostics -->
2+
3+
# Different ways that `unsupported-bool-conversion` can occur
4+
5+
## Has a `__bool__` method, but has incorrect parameters
6+
7+
```py
8+
class NotBoolable:
9+
def __bool__(self, foo):
10+
return False
11+
12+
a = NotBoolable()
13+
14+
# error: [unsupported-bool-conversion]
15+
10 and a and True
16+
```
17+
18+
## Has a `__bool__` method, but has an incorrect return type
19+
20+
```py
21+
class NotBoolable:
22+
def __bool__(self) -> str:
23+
return "wat"
24+
25+
a = NotBoolable()
26+
27+
# error: [unsupported-bool-conversion]
28+
10 and a and True
29+
```
30+
31+
## Has a `__bool__` attribute, but it's not callable
32+
33+
```py
34+
class NotBoolable:
35+
__bool__: int = 3
36+
37+
a = NotBoolable()
38+
39+
# error: [unsupported-bool-conversion]
40+
10 and a and True
41+
```
42+
43+
## Part of a union where at least one member has incorrect `__bool__` method
44+
45+
```py
46+
class NotBoolable1:
47+
def __bool__(self) -> str:
48+
return "wat"
49+
50+
class NotBoolable2:
51+
pass
52+
53+
class NotBoolable3:
54+
__bool__: int = 3
55+
56+
def get() -> NotBoolable1 | NotBoolable2 | NotBoolable3:
57+
return NotBoolable2()
58+
59+
# error: [unsupported-bool-conversion]
60+
10 and get() and True
61+
```

0 commit comments

Comments
 (0)