mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:10 +00:00
[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.
This commit is contained in:
parent
f7b48510b5
commit
92ecfc908b
9 changed files with 25 additions and 21 deletions
|
@ -19,7 +19,7 @@ async def elements(n):
|
||||||
yield n
|
yield n
|
||||||
|
|
||||||
async def f():
|
async def f():
|
||||||
# error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)"
|
# error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)"
|
||||||
return {n: [x async for x in elements(n)] for n in range(3)}
|
return {n: [x async for x in elements(n)] for n in range(3)}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/semant
|
||||||
2 | yield n
|
2 | yield n
|
||||||
3 |
|
3 |
|
||||||
4 | async def f():
|
4 | async def f():
|
||||||
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)"
|
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)"
|
||||||
6 | return {n: [x async for x in elements(n)] for n in range(3)}
|
6 | return {n: [x async for x in elements(n)] for n in range(3)}
|
||||||
7 | async def test():
|
7 | async def test():
|
||||||
8 | return [[x async for x in elements(n)] async for n in range(3)]
|
8 | return [[x async for x in elements(n)] async for n in range(3)]
|
||||||
|
@ -36,9 +36,9 @@ error: invalid-syntax
|
||||||
--> /src/mdtest_snippet.py:6:19
|
--> /src/mdtest_snippet.py:6:19
|
||||||
|
|
|
|
||||||
4 | async def f():
|
4 | async def f():
|
||||||
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax...
|
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (synt...
|
||||||
6 | return {n: [x async for x in elements(n)] for n in range(3)}
|
6 | return {n: [x async for x in elements(n)] for n in range(3)}
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||||
7 | async def test():
|
7 | async def test():
|
||||||
8 | return [[x async for x in elements(n)] async for n in range(3)]
|
8 | return [[x async for x in elements(n)] async for n in range(3)]
|
||||||
|
|
|
|
||||||
|
|
|
@ -615,7 +615,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||||
| SemanticSyntaxErrorKind::DuplicateMatchKey(_)
|
| SemanticSyntaxErrorKind::DuplicateMatchKey(_)
|
||||||
| SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_)
|
| SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_)
|
||||||
| SemanticSyntaxErrorKind::InvalidStarExpression
|
| SemanticSyntaxErrorKind::InvalidStarExpression
|
||||||
| SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(_)
|
| SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_)
|
||||||
| SemanticSyntaxErrorKind::DuplicateParameter(_) => {
|
| SemanticSyntaxErrorKind::DuplicateParameter(_) => {
|
||||||
if self.settings.preview.is_enabled() {
|
if self.settings.preview.is_enabled() {
|
||||||
self.semantic_errors.borrow_mut().push(error);
|
self.semantic_errors.borrow_mut().push(error);
|
||||||
|
|
|
@ -1022,6 +1022,7 @@ mod tests {
|
||||||
",
|
",
|
||||||
PythonVersion::PY310
|
PythonVersion::PY310
|
||||||
)]
|
)]
|
||||||
|
#[test_case("false_positive", "[x async for x in y]", PythonVersion::PY310)]
|
||||||
fn test_async_comprehension_in_sync_comprehension(
|
fn test_async_comprehension_in_sync_comprehension(
|
||||||
name: &str,
|
name: &str,
|
||||||
contents: &str,
|
contents: &str,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/linter.rs
|
source: crates/ruff_linter/src/linter.rs
|
||||||
---
|
---
|
||||||
<filename>:1:27: SyntaxError: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
<filename>:1:27: SyntaxError: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||||
|
|
|
|
||||||
1 | async def f(): return [[x async for x in foo(n)] for n in range(3)]
|
1 | async def f(): return [[x async for x in foo(n)] for n in range(3)]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/linter.rs
|
||||||
|
---
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/linter.rs
|
source: crates/ruff_linter/src/linter.rs
|
||||||
---
|
---
|
||||||
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)
|
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)
|
||||||
|
|
|
|
||||||
1 | async def elements(n): yield n
|
1 | async def elements(n): yield n
|
||||||
2 | [x async for x in elements(5)] # okay, async at top level
|
2 | [x async for x in elements(5)] # okay, async at top level
|
||||||
|
|
|
@ -573,7 +573,7 @@ impl SemanticSyntaxChecker {
|
||||||
elt, generators, ..
|
elt, generators, ..
|
||||||
}) => {
|
}) => {
|
||||||
Self::check_generator_expr(elt, generators, ctx);
|
Self::check_generator_expr(elt, generators, ctx);
|
||||||
Self::async_comprehension_outside_async_function(ctx, generators);
|
Self::async_comprehension_in_sync_comprehension(ctx, generators);
|
||||||
for generator in generators.iter().filter(|g| g.is_async) {
|
for generator in generators.iter().filter(|g| g.is_async) {
|
||||||
Self::await_outside_async_function(
|
Self::await_outside_async_function(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -590,7 +590,7 @@ impl SemanticSyntaxChecker {
|
||||||
}) => {
|
}) => {
|
||||||
Self::check_generator_expr(key, generators, ctx);
|
Self::check_generator_expr(key, generators, ctx);
|
||||||
Self::check_generator_expr(value, generators, ctx);
|
Self::check_generator_expr(value, generators, ctx);
|
||||||
Self::async_comprehension_outside_async_function(ctx, generators);
|
Self::async_comprehension_in_sync_comprehension(ctx, generators);
|
||||||
for generator in generators.iter().filter(|g| g.is_async) {
|
for generator in generators.iter().filter(|g| g.is_async) {
|
||||||
Self::await_outside_async_function(
|
Self::await_outside_async_function(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -801,7 +801,7 @@ impl SemanticSyntaxChecker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn async_comprehension_outside_async_function<Ctx: SemanticSyntaxContext>(
|
fn async_comprehension_in_sync_comprehension<Ctx: SemanticSyntaxContext>(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
generators: &[ast::Comprehension],
|
generators: &[ast::Comprehension],
|
||||||
) {
|
) {
|
||||||
|
@ -813,7 +813,7 @@ impl SemanticSyntaxChecker {
|
||||||
if ctx.in_notebook() && ctx.in_module_scope() {
|
if ctx.in_notebook() && ctx.in_module_scope() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ctx.in_async_context() && !ctx.in_sync_comprehension() {
|
if !ctx.in_sync_comprehension() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for generator in generators.iter().filter(|gen| gen.is_async) {
|
for generator in generators.iter().filter(|gen| gen.is_async) {
|
||||||
|
@ -845,7 +845,7 @@ impl SemanticSyntaxChecker {
|
||||||
// async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
// async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
||||||
Self::add_error(
|
Self::add_error(
|
||||||
ctx,
|
ctx,
|
||||||
SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(python_version),
|
SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(python_version),
|
||||||
generator.range,
|
generator.range,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -914,11 +914,11 @@ impl Display for SemanticSyntaxError {
|
||||||
SemanticSyntaxErrorKind::InvalidStarExpression => {
|
SemanticSyntaxErrorKind::InvalidStarExpression => {
|
||||||
f.write_str("can't use starred expression here")
|
f.write_str("can't use starred expression here")
|
||||||
}
|
}
|
||||||
SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(python_version) => {
|
SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(python_version) => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"cannot use an asynchronous comprehension outside of an asynchronous \
|
"cannot use an asynchronous comprehension inside of a synchronous comprehension \
|
||||||
function on Python {python_version} (syntax was added in 3.11)",
|
on Python {python_version} (syntax was added in 3.11)",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SemanticSyntaxErrorKind::YieldOutsideFunction(kind) => {
|
SemanticSyntaxErrorKind::YieldOutsideFunction(kind) => {
|
||||||
|
@ -1187,7 +1187,7 @@ pub enum SemanticSyntaxErrorKind {
|
||||||
/// This was discussed in [BPO 33346] and fixed in Python 3.11.
|
/// This was discussed in [BPO 33346] and fixed in Python 3.11.
|
||||||
///
|
///
|
||||||
/// [BPO 33346]: https://github.com/python/cpython/issues/77527
|
/// [BPO 33346]: https://github.com/python/cpython/issues/77527
|
||||||
AsyncComprehensionOutsideAsyncFunction(PythonVersion),
|
AsyncComprehensionInSyncComprehension(PythonVersion),
|
||||||
|
|
||||||
/// Represents the use of `yield`, `yield from`, or `await` outside of a function scope.
|
/// Represents the use of `yield`, `yield from`, or `await` outside of a function scope.
|
||||||
///
|
///
|
||||||
|
|
|
@ -780,7 +780,7 @@ Module(
|
||||||
|
|
|
|
||||||
1 | # parse_options: {"target-version": "3.10"}
|
1 | # parse_options: {"target-version": "3.10"}
|
||||||
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
|
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||||
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
||||||
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
||||||
|
|
|
|
||||||
|
@ -790,7 +790,7 @@ Module(
|
||||||
1 | # parse_options: {"target-version": "3.10"}
|
1 | # parse_options: {"target-version": "3.10"}
|
||||||
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
|
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
|
||||||
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||||
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
||||||
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
||||||
|
|
|
|
||||||
|
@ -800,7 +800,7 @@ Module(
|
||||||
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
|
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
|
||||||
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
||||||
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||||
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
||||||
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
||||||
|
|
|
|
||||||
|
@ -810,7 +810,7 @@ Module(
|
||||||
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
||||||
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
||||||
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||||
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
||||||
|
|
|
|
||||||
|
|
||||||
|
@ -819,5 +819,5 @@ Module(
|
||||||
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
||||||
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
||||||
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||||
|
|
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue