Skip to content

Commit 92ecfc9

Browse files
authored
[syntax-errors] Make async-comprehension-in-sync-comprehension more specific (#17460)
## Summary While adding semantic error support to red-knot, I noticed duplicate diagnostics for code like this: ```py # error: [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.9 (syntax was added in 3.11)" # error: [invalid-syntax] "`asynchronous comprehension` outside of an asynchronous function" [reveal_type(x) async for x in AsyncIterable()] ``` Beyond the duplication, the first error message doesn't make much sense because this syntax is _not_ allowed on Python 3.11 either. To fix this, this PR renames the `async-comprehension-outside-async-function` semantic syntax error to `async-comprehension-in-sync-comprehension` and fixes the rule to avoid applying outside of sync comprehensions at all. ## Test Plan New linter test demonstrating the false positive. The mdtests from my red-knot PR also reflect this change.
1 parent f7b4851 commit 92ecfc9

9 files changed

Lines changed: 25 additions & 21 deletions

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

crates/red_knot_python_semantic/resources/mdtest/snapshots/semantic_syntax_errors.md_-_Semantic_syntax_error_diagnostics_-_`async`_comprehensions_in_synchronous_comprehensions_-_Python_3.10.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/semant
1616
2 | yield n
1717
3 |
1818
4 | async def f():
19-
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)"
19+
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)"
2020
6 | return {n: [x async for x in elements(n)] for n in range(3)}
2121
7 | async def test():
2222
8 | return [[x async for x in elements(n)] async for n in range(3)]
@@ -36,9 +36,9 @@ error: invalid-syntax
3636
--> /src/mdtest_snippet.py:6:19
3737
|
3838
4 | async def f():
39-
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax...
39+
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (synt...
4040
6 | return {n: [x async for x in elements(n)] for n in range(3)}
41-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
41+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
4242
7 | async def test():
4343
8 | return [[x async for x in elements(n)] async for n in range(3)]
4444
|

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ impl SemanticSyntaxContext for Checker<'_> {
615615
| SemanticSyntaxErrorKind::DuplicateMatchKey(_)
616616
| SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_)
617617
| SemanticSyntaxErrorKind::InvalidStarExpression
618-
| SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(_)
618+
| SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_)
619619
| SemanticSyntaxErrorKind::DuplicateParameter(_) => {
620620
if self.settings.preview.is_enabled() {
621621
self.semantic_errors.borrow_mut().push(error);

crates/ruff_linter/src/linter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,7 @@ mod tests {
10221022
",
10231023
PythonVersion::PY310
10241024
)]
1025+
#[test_case("false_positive", "[x async for x in y]", PythonVersion::PY310)]
10251026
fn test_async_comprehension_in_sync_comprehension(
10261027
name: &str,
10271028
contents: &str,

crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_error_on_310_3.10.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
source: crates/ruff_linter/src/linter.rs
33
---
4-
<filename>:1:27: SyntaxError: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
4+
<filename>:1:27: SyntaxError: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
55
|
66
1 | async def f(): return [[x async for x in foo(n)] for n in range(3)]
77
| ^^^^^^^^^^^^^^^^^^^^^
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
source: crates/ruff_linter/src/linter.rs
3+
---

crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_notebook_3.10.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
source: crates/ruff_linter/src/linter.rs
33
---
4-
resources/test/fixtures/syntax_errors/async_comprehension.ipynb:3:5: SyntaxError: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
4+
resources/test/fixtures/syntax_errors/async_comprehension.ipynb:3:5: SyntaxError: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
55
|
66
1 | async def elements(n): yield n
77
2 | [x async for x in elements(5)] # okay, async at top level

crates/ruff_python_parser/src/semantic_errors.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ impl SemanticSyntaxChecker {
573573
elt, generators, ..
574574
}) => {
575575
Self::check_generator_expr(elt, generators, ctx);
576-
Self::async_comprehension_outside_async_function(ctx, generators);
576+
Self::async_comprehension_in_sync_comprehension(ctx, generators);
577577
for generator in generators.iter().filter(|g| g.is_async) {
578578
Self::await_outside_async_function(
579579
ctx,
@@ -590,7 +590,7 @@ impl SemanticSyntaxChecker {
590590
}) => {
591591
Self::check_generator_expr(key, generators, ctx);
592592
Self::check_generator_expr(value, generators, ctx);
593-
Self::async_comprehension_outside_async_function(ctx, generators);
593+
Self::async_comprehension_in_sync_comprehension(ctx, generators);
594594
for generator in generators.iter().filter(|g| g.is_async) {
595595
Self::await_outside_async_function(
596596
ctx,
@@ -801,7 +801,7 @@ impl SemanticSyntaxChecker {
801801
}
802802
}
803803

804-
fn async_comprehension_outside_async_function<Ctx: SemanticSyntaxContext>(
804+
fn async_comprehension_in_sync_comprehension<Ctx: SemanticSyntaxContext>(
805805
ctx: &Ctx,
806806
generators: &[ast::Comprehension],
807807
) {
@@ -813,7 +813,7 @@ impl SemanticSyntaxChecker {
813813
if ctx.in_notebook() && ctx.in_module_scope() {
814814
return;
815815
}
816-
if ctx.in_async_context() && !ctx.in_sync_comprehension() {
816+
if !ctx.in_sync_comprehension() {
817817
return;
818818
}
819819
for generator in generators.iter().filter(|gen| gen.is_async) {
@@ -845,7 +845,7 @@ impl SemanticSyntaxChecker {
845845
// async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
846846
Self::add_error(
847847
ctx,
848-
SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(python_version),
848+
SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(python_version),
849849
generator.range,
850850
);
851851
}
@@ -914,11 +914,11 @@ impl Display for SemanticSyntaxError {
914914
SemanticSyntaxErrorKind::InvalidStarExpression => {
915915
f.write_str("can't use starred expression here")
916916
}
917-
SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(python_version) => {
917+
SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(python_version) => {
918918
write!(
919919
f,
920-
"cannot use an asynchronous comprehension outside of an asynchronous \
921-
function on Python {python_version} (syntax was added in 3.11)",
920+
"cannot use an asynchronous comprehension inside of a synchronous comprehension \
921+
on Python {python_version} (syntax was added in 3.11)",
922922
)
923923
}
924924
SemanticSyntaxErrorKind::YieldOutsideFunction(kind) => {
@@ -1187,7 +1187,7 @@ pub enum SemanticSyntaxErrorKind {
11871187
/// This was discussed in [BPO 33346] and fixed in Python 3.11.
11881188
///
11891189
/// [BPO 33346]: https://github.com/python/cpython/issues/77527
1190-
AsyncComprehensionOutsideAsyncFunction(PythonVersion),
1190+
AsyncComprehensionInSyncComprehension(PythonVersion),
11911191

11921192
/// Represents the use of `yield`, `yield from`, or `await` outside of a function scope.
11931193
///

crates/ruff_python_parser/tests/snapshots/invalid_syntax@nested_async_comprehension_py310.py.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ Module(
780780
|
781781
1 | # parse_options: {"target-version": "3.10"}
782782
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
783-
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
783+
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
784784
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
785785
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
786786
|
@@ -790,7 +790,7 @@ Module(
790790
1 | # parse_options: {"target-version": "3.10"}
791791
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
792792
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
793-
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
793+
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
794794
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
795795
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
796796
|
@@ -800,7 +800,7 @@ Module(
800800
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
801801
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
802802
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
803-
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
803+
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
804804
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
805805
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
806806
|
@@ -810,7 +810,7 @@ Module(
810810
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
811811
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
812812
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
813-
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
813+
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
814814
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
815815
|
816816

@@ -819,5 +819,5 @@ Module(
819819
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
820820
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
821821
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
822-
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
822+
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
823823
|

0 commit comments

Comments
 (0)