mirror of https://github.com/astral-sh/ruff
Fix syntax error false positives for `await` outside functions (#21763)
## Summary Fixes #21750 and a related bug in `PLE1142`. We were not properly considering generators to be valid `await` contexts, which caused the `F704` issue. One of the tests I added for this also uncovered an issue in `PLE1142` for comprehensions nested within async generators because we were only checking the current scope rather than traversing the nested context. ## Test Plan Both of these rules are implemented as semantic syntax errors, so I added tests (and fixes) in both Ruff and ty.
This commit is contained in:
parent
392a8e4e50
commit
2250fa6f98
|
|
@ -17,3 +17,24 @@ def _():
|
||||||
|
|
||||||
# Valid yield scope
|
# Valid yield scope
|
||||||
yield 3
|
yield 3
|
||||||
|
|
||||||
|
|
||||||
|
# await is valid in any generator, sync or async
|
||||||
|
(await cor async for cor in f()) # ok
|
||||||
|
(await cor for cor in f()) # ok
|
||||||
|
|
||||||
|
# but not in comprehensions
|
||||||
|
[await cor async for cor in f()] # F704
|
||||||
|
{await cor async for cor in f()} # F704
|
||||||
|
{await cor: 1 async for cor in f()} # F704
|
||||||
|
[await cor for cor in f()] # F704
|
||||||
|
{await cor for cor in f()} # F704
|
||||||
|
{await cor: 1 for cor in f()} # F704
|
||||||
|
|
||||||
|
# or in the iterator of an async generator, which is evaluated in the parent
|
||||||
|
# scope
|
||||||
|
(cor async for cor in await f()) # F704
|
||||||
|
(await cor async for cor in [await c for c in f()]) # F704
|
||||||
|
|
||||||
|
# this is also okay because the comprehension is within the generator scope
|
||||||
|
([await c for c in cor] async for cor in f()) # ok
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,5 @@ def func():
|
||||||
|
|
||||||
# Top-level await
|
# Top-level await
|
||||||
await 1
|
await 1
|
||||||
|
|
||||||
|
([await c for c in cor] async for cor in func()) # ok
|
||||||
|
|
|
||||||
|
|
@ -780,6 +780,10 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||||
match scope.kind {
|
match scope.kind {
|
||||||
ScopeKind::Class(_) => return false,
|
ScopeKind::Class(_) => return false,
|
||||||
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
||||||
|
ScopeKind::Generator {
|
||||||
|
kind: GeneratorKind::Generator,
|
||||||
|
..
|
||||||
|
} => return true,
|
||||||
ScopeKind::Generator { .. }
|
ScopeKind::Generator { .. }
|
||||||
| ScopeKind::Module
|
| ScopeKind::Module
|
||||||
| ScopeKind::Type
|
| ScopeKind::Type
|
||||||
|
|
@ -829,14 +833,19 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||||
self.source_type.is_ipynb()
|
self.source_type.is_ipynb()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn in_generator_scope(&self) -> bool {
|
fn in_generator_context(&self) -> bool {
|
||||||
matches!(
|
for scope in self.semantic.current_scopes() {
|
||||||
&self.semantic.current_scope().kind,
|
if matches!(
|
||||||
ScopeKind::Generator {
|
scope.kind,
|
||||||
kind: GeneratorKind::Generator,
|
ScopeKind::Generator {
|
||||||
..
|
kind: GeneratorKind::Generator,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn in_loop_context(&self) -> bool {
|
fn in_loop_context(&self) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -37,3 +37,88 @@ F704 `await` statement outside of a function
|
||||||
12 |
|
12 |
|
||||||
13 | def _():
|
13 | def _():
|
||||||
|
|
|
|
||||||
|
|
||||||
|
F704 `await` statement outside of a function
|
||||||
|
--> F704.py:27:2
|
||||||
|
|
|
||||||
|
26 | # but not in comprehensions
|
||||||
|
27 | [await cor async for cor in f()] # F704
|
||||||
|
| ^^^^^^^^^
|
||||||
|
28 | {await cor async for cor in f()} # F704
|
||||||
|
29 | {await cor: 1 async for cor in f()} # F704
|
||||||
|
|
|
||||||
|
|
||||||
|
F704 `await` statement outside of a function
|
||||||
|
--> F704.py:28:2
|
||||||
|
|
|
||||||
|
26 | # but not in comprehensions
|
||||||
|
27 | [await cor async for cor in f()] # F704
|
||||||
|
28 | {await cor async for cor in f()} # F704
|
||||||
|
| ^^^^^^^^^
|
||||||
|
29 | {await cor: 1 async for cor in f()} # F704
|
||||||
|
30 | [await cor for cor in f()] # F704
|
||||||
|
|
|
||||||
|
|
||||||
|
F704 `await` statement outside of a function
|
||||||
|
--> F704.py:29:2
|
||||||
|
|
|
||||||
|
27 | [await cor async for cor in f()] # F704
|
||||||
|
28 | {await cor async for cor in f()} # F704
|
||||||
|
29 | {await cor: 1 async for cor in f()} # F704
|
||||||
|
| ^^^^^^^^^
|
||||||
|
30 | [await cor for cor in f()] # F704
|
||||||
|
31 | {await cor for cor in f()} # F704
|
||||||
|
|
|
||||||
|
|
||||||
|
F704 `await` statement outside of a function
|
||||||
|
--> F704.py:30:2
|
||||||
|
|
|
||||||
|
28 | {await cor async for cor in f()} # F704
|
||||||
|
29 | {await cor: 1 async for cor in f()} # F704
|
||||||
|
30 | [await cor for cor in f()] # F704
|
||||||
|
| ^^^^^^^^^
|
||||||
|
31 | {await cor for cor in f()} # F704
|
||||||
|
32 | {await cor: 1 for cor in f()} # F704
|
||||||
|
|
|
||||||
|
|
||||||
|
F704 `await` statement outside of a function
|
||||||
|
--> F704.py:31:2
|
||||||
|
|
|
||||||
|
29 | {await cor: 1 async for cor in f()} # F704
|
||||||
|
30 | [await cor for cor in f()] # F704
|
||||||
|
31 | {await cor for cor in f()} # F704
|
||||||
|
| ^^^^^^^^^
|
||||||
|
32 | {await cor: 1 for cor in f()} # F704
|
||||||
|
|
|
||||||
|
|
||||||
|
F704 `await` statement outside of a function
|
||||||
|
--> F704.py:32:2
|
||||||
|
|
|
||||||
|
30 | [await cor for cor in f()] # F704
|
||||||
|
31 | {await cor for cor in f()} # F704
|
||||||
|
32 | {await cor: 1 for cor in f()} # F704
|
||||||
|
| ^^^^^^^^^
|
||||||
|
33 |
|
||||||
|
34 | # or in the iterator of an async generator, which is evaluated in the parent
|
||||||
|
|
|
||||||
|
|
||||||
|
F704 `await` statement outside of a function
|
||||||
|
--> F704.py:36:23
|
||||||
|
|
|
||||||
|
34 | # or in the iterator of an async generator, which is evaluated in the parent
|
||||||
|
35 | # scope
|
||||||
|
36 | (cor async for cor in await f()) # F704
|
||||||
|
| ^^^^^^^^^
|
||||||
|
37 | (await cor async for cor in [await c for c in f()]) # F704
|
||||||
|
|
|
||||||
|
|
||||||
|
F704 `await` statement outside of a function
|
||||||
|
--> F704.py:37:30
|
||||||
|
|
|
||||||
|
35 | # scope
|
||||||
|
36 | (cor async for cor in await f()) # F704
|
||||||
|
37 | (await cor async for cor in [await c for c in f()]) # F704
|
||||||
|
| ^^^^^^^
|
||||||
|
38 |
|
||||||
|
39 | # this is also okay because the comprehension is within the generator scope
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,6 @@ PLE1142 `await` should be used within an async function
|
||||||
4 | # Top-level await
|
4 | # Top-level await
|
||||||
5 | await 1
|
5 | await 1
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
6 |
|
||||||
|
7 | ([await c for c in cor] async for cor in func()) # ok
|
||||||
|
|
|
|
||||||
|
|
|
||||||
|
|
@ -896,7 +896,7 @@ impl SemanticSyntaxChecker {
|
||||||
// This check is required in addition to avoiding calling this function in `visit_expr`
|
// This check is required in addition to avoiding calling this function in `visit_expr`
|
||||||
// because the generator scope applies to nested parts of the `Expr::Generator` that are
|
// because the generator scope applies to nested parts of the `Expr::Generator` that are
|
||||||
// visited separately.
|
// visited separately.
|
||||||
if ctx.in_generator_scope() {
|
if ctx.in_generator_context() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Self::add_error(
|
Self::add_error(
|
||||||
|
|
@ -2096,11 +2096,11 @@ pub trait SemanticSyntaxContext {
|
||||||
/// Returns `true` if the visitor is in a function scope.
|
/// Returns `true` if the visitor is in a function scope.
|
||||||
fn in_function_scope(&self) -> bool;
|
fn in_function_scope(&self) -> bool;
|
||||||
|
|
||||||
/// Returns `true` if the visitor is in a generator scope.
|
/// Returns `true` if the visitor is within a generator scope.
|
||||||
///
|
///
|
||||||
/// Note that this refers to an `Expr::Generator` precisely, not to comprehensions more
|
/// Note that this refers to an `Expr::Generator` precisely, not to comprehensions more
|
||||||
/// generally.
|
/// generally.
|
||||||
fn in_generator_scope(&self) -> bool;
|
fn in_generator_context(&self) -> bool;
|
||||||
|
|
||||||
/// Returns `true` if the source file is a Jupyter notebook.
|
/// Returns `true` if the source file is a Jupyter notebook.
|
||||||
fn in_notebook(&self) -> bool;
|
fn in_notebook(&self) -> bool;
|
||||||
|
|
|
||||||
|
|
@ -573,7 +573,7 @@ impl SemanticSyntaxContext for SemanticSyntaxCheckerVisitor<'_> {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn in_generator_scope(&self) -> bool {
|
fn in_generator_context(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,10 @@ await C()
|
||||||
def f():
|
def f():
|
||||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||||
await C()
|
await C()
|
||||||
|
|
||||||
|
(await cor async for cor in f()) # ok
|
||||||
|
(await cor for cor in f()) # ok
|
||||||
|
([await c for c in cor] async for cor in f()) # ok
|
||||||
```
|
```
|
||||||
|
|
||||||
Generators are evaluated lazily, so `await` is allowed, even outside of a function.
|
Generators are evaluated lazily, so `await` is allowed, even outside of a function.
|
||||||
|
|
|
||||||
|
|
@ -2845,6 +2845,11 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
|
||||||
match scope.kind() {
|
match scope.kind() {
|
||||||
ScopeKind::Class => return false,
|
ScopeKind::Class => return false,
|
||||||
ScopeKind::Function | ScopeKind::Lambda => return true,
|
ScopeKind::Function | ScopeKind::Lambda => return true,
|
||||||
|
ScopeKind::Comprehension
|
||||||
|
if matches!(scope.node(), NodeWithScopeKind::GeneratorExpression(_)) =>
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
ScopeKind::Comprehension
|
ScopeKind::Comprehension
|
||||||
| ScopeKind::Module
|
| ScopeKind::Module
|
||||||
| ScopeKind::TypeAlias
|
| ScopeKind::TypeAlias
|
||||||
|
|
@ -2894,11 +2899,14 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
|
||||||
matches!(kind, ScopeKind::Function | ScopeKind::Lambda)
|
matches!(kind, ScopeKind::Function | ScopeKind::Lambda)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn in_generator_scope(&self) -> bool {
|
fn in_generator_context(&self) -> bool {
|
||||||
matches!(
|
for scope_info in &self.scope_stack {
|
||||||
self.scopes[self.current_scope()].node(),
|
let scope = &self.scopes[scope_info.file_scope_id];
|
||||||
NodeWithScopeKind::GeneratorExpression(_)
|
if matches!(scope.node(), NodeWithScopeKind::GeneratorExpression(_)) {
|
||||||
)
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn in_notebook(&self) -> bool {
|
fn in_notebook(&self) -> bool {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue