[syntax-errors] Async comprehension in sync comprehension (#17177)

Summary
--

Detect async comprehensions nested in sync comprehensions in async
functions before Python 3.11, when this was [changed].

The actual logic of this rule is very straightforward, but properly
tracking the async scopes took a bit of work. An alternative to the
current approach is to offload the `in_async_context` check into the
`SemanticSyntaxContext` trait, but that actually required much more
extensive changes to the `TestContext` and also to ruff's semantic
model, as you can see in the changes up to
31554b473507034735bd410760fde6341d54a050. This version has the benefit
of mostly centralizing the state tracking in `SemanticSyntaxChecker`,
although there was some subtlety around deferred function body traversal
that made the changes to `Checker` more intrusive too (hence the new
linter test).

The `Checkpoint` struct/system is obviously overkill for now since it's
only tracking a single `bool`, but I thought it might be more useful
later.

[changed]: https://github.com/python/cpython/issues/77527

Test Plan
--

New inline tests and a new linter integration test.
This commit is contained in:
Brent Westbrook 2025-04-08 12:50:52 -04:00 committed by GitHub
parent dc02732d4d
commit 058439d5d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 2076 additions and 28 deletions

View file

@ -0,0 +1,6 @@
# parse_options: {"target-version": "3.10"}
async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
async def i(): return [([y async for y in range(1)], [z 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)]

View file

@ -0,0 +1,2 @@
# parse_options: {"target-version": "3.10"}
async def test(): return [[x async for x in elements(n)] async for n in range(3)]

View file

@ -0,0 +1,9 @@
# parse_options: {"target-version": "3.10"}
# this case fails if exit_expr doesn't run
async def f():
[_ for n in range(3)]
[_ async for n in range(3)]
# and this fails without exit_stmt
async def f():
def g(): ...
[_ async for n in range(3)]

View file

@ -0,0 +1,4 @@
# parse_options: {"target-version": "3.11"}
async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
async def h(): return [{x async for x in foo(n)} for n in range(3)] # set

View file

@ -1,8 +1,10 @@
//! [`SemanticSyntaxChecker`] for AST-based syntax errors.
//!
//! This checker is not responsible for traversing the AST itself. Instead, its
//! [`SemanticSyntaxChecker::visit_stmt`] and [`SemanticSyntaxChecker::visit_expr`] methods should
//! be called in a parent `Visitor`'s `visit_stmt` and `visit_expr` methods, respectively.
//! [`SemanticSyntaxChecker::enter_stmt`] and [`SemanticSyntaxChecker::enter_expr`] methods should
//! be called in a parent `Visitor`'s `visit_stmt` and `visit_expr` methods, respectively, and
//! followed by matching calls to [`SemanticSyntaxChecker::exit_stmt`] and
//! [`SemanticSyntaxChecker::exit_expr`].
use std::fmt::Display;
@ -10,13 +12,18 @@ use ruff_python_ast::{
self as ast,
comparable::ComparableExpr,
visitor::{walk_expr, Visitor},
Expr, ExprContext, IrrefutablePatternKind, Pattern, PythonVersion, Stmt, StmtExpr,
StmtImportFrom,
Expr, ExprContext, IrrefutablePatternKind, Pattern, PySourceType, PythonVersion, Stmt,
StmtExpr, StmtImportFrom,
};
use ruff_text_size::{Ranged, TextRange, TextSize};
use rustc_hash::FxHashSet;
#[derive(Debug)]
pub struct Checkpoint {
in_async_context: bool,
}
#[derive(Debug, Default)]
pub struct SemanticSyntaxChecker {
/// The checker has traversed past the `__future__` import boundary.
///
@ -33,12 +40,20 @@ pub struct SemanticSyntaxChecker {
/// Python considers it a syntax error to import from `__future__` after any other
/// non-`__future__`-importing statements.
seen_futures_boundary: bool,
/// The checker is currently in an `async` context: either the body of an `async` function or an
/// `async` comprehension.
///
/// Note that this should be updated *after* checking the current statement or expression
/// because the parent context is what matters.
in_async_context: bool,
}
impl SemanticSyntaxChecker {
pub fn new() -> Self {
pub fn new(source_type: PySourceType) -> Self {
Self {
seen_futures_boundary: false,
in_async_context: source_type.is_ipynb(),
}
}
}
@ -473,7 +488,27 @@ impl SemanticSyntaxChecker {
}
}
pub fn visit_stmt<Ctx: SemanticSyntaxContext>(&mut self, stmt: &ast::Stmt, ctx: &Ctx) {
/// Check `stmt` for semantic syntax errors and update the checker's internal state.
///
/// This should be followed by a call to [`SemanticSyntaxChecker::exit_stmt`] to reset any state
/// specific to scopes introduced by `stmt`, such as whether the body of a function is async.
///
/// Note that this method should only be called when traversing `stmt` *and* its children. For
/// example, if traversal of function bodies needs to be deferred, avoid calling `enter_stmt` on
/// the function itself until the deferred body is visited too. Failing to defer `enter_stmt` in
/// this case will break any internal state that depends on function scopes, such as `async`
/// context detection.
#[must_use]
pub fn enter_stmt<Ctx: SemanticSyntaxContext>(
&mut self,
stmt: &ast::Stmt,
ctx: &Ctx,
) -> Checkpoint {
// check for errors
self.check_stmt(stmt, ctx);
let checkpoint = self.checkpoint();
// update internal state
match stmt {
Stmt::Expr(StmtExpr { value, .. })
@ -484,26 +519,63 @@ impl SemanticSyntaxChecker {
self.seen_futures_boundary = true;
}
}
Stmt::FunctionDef(ast::StmtFunctionDef { is_async, .. }) => {
self.in_async_context = *is_async;
self.seen_futures_boundary = true;
}
_ => {
self.seen_futures_boundary = true;
}
}
// check for errors
self.check_stmt(stmt, ctx);
checkpoint
}
pub fn visit_expr<Ctx: SemanticSyntaxContext>(&mut self, expr: &Expr, ctx: &Ctx) {
pub fn exit_stmt(&mut self, checkpoint: Checkpoint) {
self.restore_checkpoint(checkpoint);
}
/// Check `expr` for semantic syntax errors and update the checker's internal state.
///
/// This should be followed by a call to [`SemanticSyntaxChecker::exit_expr`] to reset any state
/// specific to scopes introduced by `expr`, such as whether the body of a comprehension is
/// async.
#[must_use]
pub fn enter_expr<Ctx: SemanticSyntaxContext>(&mut self, expr: &Expr, ctx: &Ctx) -> Checkpoint {
self.check_expr(expr, ctx);
let checkpoint = self.checkpoint();
match expr {
Expr::ListComp(ast::ExprListComp { generators, .. })
| Expr::SetComp(ast::ExprSetComp { generators, .. })
| Expr::DictComp(ast::ExprDictComp { generators, .. }) => {
self.in_async_context = generators.iter().any(|g| g.is_async);
}
_ => {}
}
checkpoint
}
pub fn exit_expr(&mut self, checkpoint: Checkpoint) {
self.restore_checkpoint(checkpoint);
}
fn check_expr<Ctx: SemanticSyntaxContext>(&mut self, expr: &Expr, ctx: &Ctx) {
match expr {
Expr::ListComp(ast::ExprListComp {
elt, generators, ..
})
| Expr::SetComp(ast::ExprSetComp {
elt, generators, ..
})
| Expr::Generator(ast::ExprGenerator {
}) => {
Self::check_generator_expr(elt, generators, ctx);
self.async_comprehension_outside_async_function(ctx, generators);
}
Expr::Generator(ast::ExprGenerator {
elt, generators, ..
}) => Self::check_generator_expr(elt, generators, ctx),
}) => {
Self::check_generator_expr(elt, generators, ctx);
}
Expr::DictComp(ast::ExprDictComp {
key,
value,
@ -512,6 +584,7 @@ impl SemanticSyntaxChecker {
}) => {
Self::check_generator_expr(key, generators, ctx);
Self::check_generator_expr(value, generators, ctx);
self.async_comprehension_outside_async_function(ctx, generators);
}
Expr::Name(ast::ExprName {
range,
@ -623,11 +696,64 @@ impl SemanticSyntaxChecker {
);
}
}
}
impl Default for SemanticSyntaxChecker {
fn default() -> Self {
Self::new()
fn async_comprehension_outside_async_function<Ctx: SemanticSyntaxContext>(
&self,
ctx: &Ctx,
generators: &[ast::Comprehension],
) {
let python_version = ctx.python_version();
if python_version >= PythonVersion::PY311 {
return;
}
for generator in generators {
if generator.is_async && !self.in_async_context {
// test_ok nested_async_comprehension_py311
// # parse_options: {"target-version": "3.11"}
// async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
// async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
// async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
// test_ok nested_async_comprehension_py310
// # parse_options: {"target-version": "3.10"}
// # this case fails if exit_expr doesn't run
// async def f():
// [_ for n in range(3)]
// [_ async for n in range(3)]
// # and this fails without exit_stmt
// async def f():
// def g(): ...
// [_ async for n in range(3)]
// test_ok all_async_comprehension_py310
// # parse_options: {"target-version": "3.10"}
// async def test(): return [[x async for x in elements(n)] async for n in range(3)]
// test_err nested_async_comprehension_py310
// # parse_options: {"target-version": "3.10"}
// async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
// async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
// async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
// async def i(): return [([y async for y in range(1)], [z 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(
ctx,
SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(python_version),
generator.range,
);
}
}
}
fn checkpoint(&self) -> Checkpoint {
Checkpoint {
in_async_context: self.in_async_context,
}
}
#[allow(clippy::needless_pass_by_value)]
fn restore_checkpoint(&mut self, checkpoint: Checkpoint) {
self.in_async_context = checkpoint.in_async_context;
}
}
@ -693,6 +819,13 @@ impl Display for SemanticSyntaxError {
SemanticSyntaxErrorKind::InvalidStarExpression => {
f.write_str("can't use starred expression here")
}
SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(python_version) => {
write!(
f,
"cannot use an asynchronous comprehension outside of an asynchronous \
function on Python {python_version} (syntax was added in 3.11)",
)
}
}
}
}
@ -892,6 +1025,25 @@ pub enum SemanticSyntaxErrorKind {
/// for *x in xs: ...
/// ```
InvalidStarExpression,
/// Represents the use of an asynchronous comprehension inside of a synchronous comprehension
/// before Python 3.11.
///
/// ## Examples
///
/// Before Python 3.11, code like this produces a syntax error because of the implicit function
/// scope introduced by the outer comprehension:
///
/// ```python
/// async def elements(n): yield n
///
/// async def test(): return { n: [x async for x in elements(n)] for n in range(3)}
/// ```
///
/// This was discussed in [BPO 33346] and fixed in Python 3.11.
///
/// [BPO 33346]: https://github.com/python/cpython/issues/77527
AsyncComprehensionOutsideAsyncFunction(PythonVersion),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -1233,7 +1385,7 @@ pub struct SemanticSyntaxCheckerVisitor<Ctx> {
impl<Ctx> SemanticSyntaxCheckerVisitor<Ctx> {
pub fn new(context: Ctx) -> Self {
Self {
checker: SemanticSyntaxChecker::new(),
checker: SemanticSyntaxChecker::new(PySourceType::Python),
context,
}
}
@ -1248,13 +1400,15 @@ where
Ctx: SemanticSyntaxContext,
{
fn visit_stmt(&mut self, stmt: &'_ Stmt) {
self.checker.visit_stmt(stmt, &self.context);
let checkpoint = self.checker.enter_stmt(stmt, &self.context);
ruff_python_ast::visitor::walk_stmt(self, stmt);
self.checker.exit_stmt(checkpoint);
}
fn visit_expr(&mut self, expr: &'_ Expr) {
self.checker.visit_expr(expr, &self.context);
let checkpoint = self.checker.enter_expr(expr, &self.context);
ruff_python_ast::visitor::walk_expr(self, expr);
self.checker.exit_expr(checkpoint);
}
}

View file

@ -0,0 +1,823 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/nested_async_comprehension_py310.py
---
## AST
```
Module(
ModModule {
range: 0..467,
body: [
FunctionDef(
StmtFunctionDef {
range: 44..111,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("f"),
range: 54..55,
},
type_params: None,
parameters: Parameters {
range: 55..57,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Return(
StmtReturn {
range: 59..111,
value: Some(
ListComp(
ExprListComp {
range: 66..111,
elt: ListComp(
ExprListComp {
range: 67..92,
elt: Name(
ExprName {
range: 68..69,
id: Name("x"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 70..91,
target: Name(
ExprName {
range: 80..81,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 85..91,
func: Name(
ExprName {
range: 85..88,
id: Name("foo"),
ctx: Load,
},
),
arguments: Arguments {
range: 88..91,
args: [
Name(
ExprName {
range: 89..90,
id: Name("n"),
ctx: Load,
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
generators: [
Comprehension {
range: 93..110,
target: Name(
ExprName {
range: 97..98,
id: Name("n"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 102..110,
func: Name(
ExprName {
range: 102..107,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 107..110,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 108..109,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
),
},
),
],
},
),
FunctionDef(
StmtFunctionDef {
range: 122..192,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("g"),
range: 132..133,
},
type_params: None,
parameters: Parameters {
range: 133..135,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Return(
StmtReturn {
range: 137..192,
value: Some(
ListComp(
ExprListComp {
range: 144..192,
elt: DictComp(
ExprDictComp {
range: 145..173,
key: Name(
ExprName {
range: 146..147,
id: Name("x"),
ctx: Load,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 149..150,
value: Int(
1,
),
},
),
generators: [
Comprehension {
range: 151..172,
target: Name(
ExprName {
range: 161..162,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 166..172,
func: Name(
ExprName {
range: 166..169,
id: Name("foo"),
ctx: Load,
},
),
arguments: Arguments {
range: 169..172,
args: [
Name(
ExprName {
range: 170..171,
id: Name("n"),
ctx: Load,
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
generators: [
Comprehension {
range: 174..191,
target: Name(
ExprName {
range: 178..179,
id: Name("n"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 183..191,
func: Name(
ExprName {
range: 183..188,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 188..191,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 189..190,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
),
},
),
],
},
),
FunctionDef(
StmtFunctionDef {
range: 200..267,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("h"),
range: 210..211,
},
type_params: None,
parameters: Parameters {
range: 211..213,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Return(
StmtReturn {
range: 215..267,
value: Some(
ListComp(
ExprListComp {
range: 222..267,
elt: SetComp(
ExprSetComp {
range: 223..248,
elt: Name(
ExprName {
range: 224..225,
id: Name("x"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 226..247,
target: Name(
ExprName {
range: 236..237,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 241..247,
func: Name(
ExprName {
range: 241..244,
id: Name("foo"),
ctx: Load,
},
),
arguments: Arguments {
range: 244..247,
args: [
Name(
ExprName {
range: 245..246,
id: Name("n"),
ctx: Load,
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
generators: [
Comprehension {
range: 249..266,
target: Name(
ExprName {
range: 253..254,
id: Name("n"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 258..266,
func: Name(
ExprName {
range: 258..263,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 263..266,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 264..265,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
),
},
),
],
},
),
FunctionDef(
StmtFunctionDef {
range: 277..371,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("i"),
range: 287..288,
},
type_params: None,
parameters: Parameters {
range: 288..290,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Return(
StmtReturn {
range: 292..371,
value: Some(
ListComp(
ExprListComp {
range: 299..371,
elt: Tuple(
ExprTuple {
range: 300..352,
elts: [
ListComp(
ExprListComp {
range: 301..328,
elt: Name(
ExprName {
range: 302..303,
id: Name("y"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 304..327,
target: Name(
ExprName {
range: 314..315,
id: Name("y"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 319..327,
func: Name(
ExprName {
range: 319..324,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 324..327,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 325..326,
value: Int(
1,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
ListComp(
ExprListComp {
range: 330..351,
elt: Name(
ExprName {
range: 331..332,
id: Name("z"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 333..350,
target: Name(
ExprName {
range: 337..338,
id: Name("z"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 342..350,
func: Name(
ExprName {
range: 342..347,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 347..350,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 348..349,
value: Int(
2,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
],
ctx: Load,
parenthesized: true,
},
),
generators: [
Comprehension {
range: 353..370,
target: Name(
ExprName {
range: 357..358,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 362..370,
func: Name(
ExprName {
range: 362..367,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 367..370,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 368..369,
value: Int(
5,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
),
},
),
],
},
),
FunctionDef(
StmtFunctionDef {
range: 372..466,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("j"),
range: 382..383,
},
type_params: None,
parameters: Parameters {
range: 383..385,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Return(
StmtReturn {
range: 387..466,
value: Some(
ListComp(
ExprListComp {
range: 394..466,
elt: Tuple(
ExprTuple {
range: 395..447,
elts: [
ListComp(
ExprListComp {
range: 396..417,
elt: Name(
ExprName {
range: 397..398,
id: Name("y"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 399..416,
target: Name(
ExprName {
range: 403..404,
id: Name("y"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 408..416,
func: Name(
ExprName {
range: 408..413,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 413..416,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 414..415,
value: Int(
1,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
ListComp(
ExprListComp {
range: 419..446,
elt: Name(
ExprName {
range: 420..421,
id: Name("z"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 422..445,
target: Name(
ExprName {
range: 432..433,
id: Name("z"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 437..445,
func: Name(
ExprName {
range: 437..442,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 442..445,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 443..444,
value: Int(
2,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
],
ctx: Load,
parenthesized: true,
},
),
generators: [
Comprehension {
range: 448..465,
target: Name(
ExprName {
range: 452..453,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 457..465,
func: Name(
ExprName {
range: 457..462,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 462..465,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 463..464,
value: Int(
5,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
),
},
),
],
},
),
],
},
)
```
## Semantic Syntax Errors
|
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
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function 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
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
|
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
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)
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)]
|
|
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
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)
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)]
|
|
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
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)
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
|
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)]
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)
|

View file

@ -0,0 +1,141 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/all_async_comprehension_py310.py
---
## AST
```
Module(
ModModule {
range: 0..126,
body: [
FunctionDef(
StmtFunctionDef {
range: 44..125,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("test"),
range: 54..58,
},
type_params: None,
parameters: Parameters {
range: 58..60,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Return(
StmtReturn {
range: 62..125,
value: Some(
ListComp(
ExprListComp {
range: 69..125,
elt: ListComp(
ExprListComp {
range: 70..100,
elt: Name(
ExprName {
range: 71..72,
id: Name("x"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 73..99,
target: Name(
ExprName {
range: 83..84,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 88..99,
func: Name(
ExprName {
range: 88..96,
id: Name("elements"),
ctx: Load,
},
),
arguments: Arguments {
range: 96..99,
args: [
Name(
ExprName {
range: 97..98,
id: Name("n"),
ctx: Load,
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
generators: [
Comprehension {
range: 101..124,
target: Name(
ExprName {
range: 111..112,
id: Name("n"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 116..124,
func: Name(
ExprName {
range: 116..121,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 121..124,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 122..123,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
),
},
),
],
},
),
],
},
)
```

View file

@ -0,0 +1,265 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/nested_async_comprehension_py310.py
---
## AST
```
Module(
ModModule {
range: 0..259,
body: [
FunctionDef(
StmtFunctionDef {
range: 87..159,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("f"),
range: 97..98,
},
type_params: None,
parameters: Parameters {
range: 98..100,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Expr(
StmtExpr {
range: 106..127,
value: ListComp(
ExprListComp {
range: 106..127,
elt: Name(
ExprName {
range: 107..108,
id: Name("_"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 109..126,
target: Name(
ExprName {
range: 113..114,
id: Name("n"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 118..126,
func: Name(
ExprName {
range: 118..123,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 123..126,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 124..125,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
},
),
Expr(
StmtExpr {
range: 132..159,
value: ListComp(
ExprListComp {
range: 132..159,
elt: Name(
ExprName {
range: 133..134,
id: Name("_"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 135..158,
target: Name(
ExprName {
range: 145..146,
id: Name("n"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 150..158,
func: Name(
ExprName {
range: 150..155,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 155..158,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 156..157,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
},
),
],
},
),
FunctionDef(
StmtFunctionDef {
range: 195..258,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("f"),
range: 205..206,
},
type_params: None,
parameters: Parameters {
range: 206..208,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
FunctionDef(
StmtFunctionDef {
range: 214..226,
is_async: false,
decorator_list: [],
name: Identifier {
id: Name("g"),
range: 218..219,
},
type_params: None,
parameters: Parameters {
range: 219..221,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Expr(
StmtExpr {
range: 223..226,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 223..226,
},
),
},
),
],
},
),
Expr(
StmtExpr {
range: 231..258,
value: ListComp(
ExprListComp {
range: 231..258,
elt: Name(
ExprName {
range: 232..233,
id: Name("_"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 234..257,
target: Name(
ExprName {
range: 244..245,
id: Name("n"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 249..257,
func: Name(
ExprName {
range: 249..254,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 254..257,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 255..256,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
},
),
],
},
),
],
},
)
```

View file

@ -0,0 +1,401 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/nested_async_comprehension_py311.py
---
## AST
```
Module(
ModModule {
range: 0..277,
body: [
FunctionDef(
StmtFunctionDef {
range: 44..111,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("f"),
range: 54..55,
},
type_params: None,
parameters: Parameters {
range: 55..57,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Return(
StmtReturn {
range: 59..111,
value: Some(
ListComp(
ExprListComp {
range: 66..111,
elt: ListComp(
ExprListComp {
range: 67..92,
elt: Name(
ExprName {
range: 68..69,
id: Name("x"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 70..91,
target: Name(
ExprName {
range: 80..81,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 85..91,
func: Name(
ExprName {
range: 85..88,
id: Name("foo"),
ctx: Load,
},
),
arguments: Arguments {
range: 88..91,
args: [
Name(
ExprName {
range: 89..90,
id: Name("n"),
ctx: Load,
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
generators: [
Comprehension {
range: 93..110,
target: Name(
ExprName {
range: 97..98,
id: Name("n"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 102..110,
func: Name(
ExprName {
range: 102..107,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 107..110,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 108..109,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
),
},
),
],
},
),
FunctionDef(
StmtFunctionDef {
range: 122..192,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("g"),
range: 132..133,
},
type_params: None,
parameters: Parameters {
range: 133..135,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Return(
StmtReturn {
range: 137..192,
value: Some(
ListComp(
ExprListComp {
range: 144..192,
elt: DictComp(
ExprDictComp {
range: 145..173,
key: Name(
ExprName {
range: 146..147,
id: Name("x"),
ctx: Load,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 149..150,
value: Int(
1,
),
},
),
generators: [
Comprehension {
range: 151..172,
target: Name(
ExprName {
range: 161..162,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 166..172,
func: Name(
ExprName {
range: 166..169,
id: Name("foo"),
ctx: Load,
},
),
arguments: Arguments {
range: 169..172,
args: [
Name(
ExprName {
range: 170..171,
id: Name("n"),
ctx: Load,
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
generators: [
Comprehension {
range: 174..191,
target: Name(
ExprName {
range: 178..179,
id: Name("n"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 183..191,
func: Name(
ExprName {
range: 183..188,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 188..191,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 189..190,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
),
},
),
],
},
),
FunctionDef(
StmtFunctionDef {
range: 200..267,
is_async: true,
decorator_list: [],
name: Identifier {
id: Name("h"),
range: 210..211,
},
type_params: None,
parameters: Parameters {
range: 211..213,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Return(
StmtReturn {
range: 215..267,
value: Some(
ListComp(
ExprListComp {
range: 222..267,
elt: SetComp(
ExprSetComp {
range: 223..248,
elt: Name(
ExprName {
range: 224..225,
id: Name("x"),
ctx: Load,
},
),
generators: [
Comprehension {
range: 226..247,
target: Name(
ExprName {
range: 236..237,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 241..247,
func: Name(
ExprName {
range: 241..244,
id: Name("foo"),
ctx: Load,
},
),
arguments: Arguments {
range: 244..247,
args: [
Name(
ExprName {
range: 245..246,
id: Name("n"),
ctx: Load,
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: true,
},
],
},
),
generators: [
Comprehension {
range: 249..266,
target: Name(
ExprName {
range: 253..254,
id: Name("n"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 258..266,
func: Name(
ExprName {
range: 258..263,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 263..266,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 264..265,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
),
},
),
],
},
),
],
},
)
```