mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:14:52 +00:00
[syntax-errors] Detect yield from
inside async function (#20051)
<!-- Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull requests.) - Does this pull request include references to any relevant issues? --> ## Summary This PR implements https://docs.astral.sh/ruff/rules/yield-from-in-async-function/ as a syntax semantic error ## Test Plan <!-- How was it tested? --> I have written a simple inline test as directed in [https://github.com/astral-sh/ruff/issues/17412](https://github.com/astral-sh/ruff/issues/17412) --------- Signed-off-by: 11happy <soni5happy@gmail.com> Co-authored-by: Alex Waygood <alex.waygood@gmail.com> Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
This commit is contained in:
parent
5d7c17c20a
commit
4c3e1930f6
11 changed files with 140 additions and 33 deletions
|
@ -0,0 +1 @@
|
|||
async def f(): yield from x
|
|
@ -709,6 +709,16 @@ impl SemanticSyntaxChecker {
|
|||
}
|
||||
Expr::YieldFrom(_) => {
|
||||
Self::yield_outside_function(ctx, expr, YieldOutsideFunctionKind::YieldFrom);
|
||||
if ctx.in_function_scope() && ctx.in_async_context() {
|
||||
// test_err yield_from_in_async_function
|
||||
// async def f(): yield from x
|
||||
|
||||
Self::add_error(
|
||||
ctx,
|
||||
SemanticSyntaxErrorKind::YieldFromInAsyncFunction,
|
||||
expr.range(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Expr::Await(_) => {
|
||||
Self::yield_outside_function(ctx, expr, YieldOutsideFunctionKind::Await);
|
||||
|
@ -989,6 +999,9 @@ impl Display for SemanticSyntaxError {
|
|||
SemanticSyntaxErrorKind::AnnotatedNonlocal(name) => {
|
||||
write!(f, "annotated name `{name}` can't be nonlocal")
|
||||
}
|
||||
SemanticSyntaxErrorKind::YieldFromInAsyncFunction => {
|
||||
f.write_str("`yield from` statement in async function; use `async for` instead")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1346,6 +1359,9 @@ pub enum SemanticSyntaxErrorKind {
|
|||
|
||||
/// Represents a type annotation on a variable that's been declared nonlocal
|
||||
AnnotatedNonlocal(String),
|
||||
|
||||
/// Represents the use of `yield from` inside an asynchronous function.
|
||||
YieldFromInAsyncFunction,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
|
|
|
@ -465,7 +465,7 @@ impl<'ast> SourceOrderVisitor<'ast> for ValidateAstVisitor<'ast> {
|
|||
|
||||
enum Scope {
|
||||
Module,
|
||||
Function,
|
||||
Function { is_async: bool },
|
||||
Comprehension { is_async: bool },
|
||||
Class,
|
||||
}
|
||||
|
@ -528,7 +528,15 @@ impl SemanticSyntaxContext for SemanticSyntaxCheckerVisitor<'_> {
|
|||
}
|
||||
|
||||
fn in_async_context(&self) -> bool {
|
||||
true
|
||||
if let Some(scope) = self.scopes.iter().next_back() {
|
||||
match scope {
|
||||
Scope::Class | Scope::Module => false,
|
||||
Scope::Comprehension { is_async } => *is_async,
|
||||
Scope::Function { is_async } => *is_async,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn in_sync_comprehension(&self) -> bool {
|
||||
|
@ -589,8 +597,10 @@ impl Visitor<'_> for SemanticSyntaxCheckerVisitor<'_> {
|
|||
self.visit_body(body);
|
||||
self.scopes.pop().unwrap();
|
||||
}
|
||||
ast::Stmt::FunctionDef(ast::StmtFunctionDef { .. }) => {
|
||||
self.scopes.push(Scope::Function);
|
||||
ast::Stmt::FunctionDef(ast::StmtFunctionDef { is_async, .. }) => {
|
||||
self.scopes.push(Scope::Function {
|
||||
is_async: *is_async,
|
||||
});
|
||||
ast::visitor::walk_stmt(self, stmt);
|
||||
self.scopes.pop().unwrap();
|
||||
}
|
||||
|
@ -604,7 +614,7 @@ impl Visitor<'_> for SemanticSyntaxCheckerVisitor<'_> {
|
|||
self.with_semantic_checker(|semantic, context| semantic.visit_expr(expr, context));
|
||||
match expr {
|
||||
ast::Expr::Lambda(_) => {
|
||||
self.scopes.push(Scope::Function);
|
||||
self.scopes.push(Scope::Function { is_async: false });
|
||||
ast::visitor::walk_expr(self, expr);
|
||||
self.scopes.pop().unwrap();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/yield_from_in_async_function.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..28,
|
||||
body: [
|
||||
FunctionDef(
|
||||
StmtFunctionDef {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..27,
|
||||
is_async: true,
|
||||
decorator_list: [],
|
||||
name: Identifier {
|
||||
id: Name("f"),
|
||||
range: 10..11,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
type_params: None,
|
||||
parameters: Parameters {
|
||||
range: 11..13,
|
||||
node_index: NodeIndex(None),
|
||||
posonlyargs: [],
|
||||
args: [],
|
||||
vararg: None,
|
||||
kwonlyargs: [],
|
||||
kwarg: None,
|
||||
},
|
||||
returns: None,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 15..27,
|
||||
value: YieldFrom(
|
||||
ExprYieldFrom {
|
||||
node_index: NodeIndex(None),
|
||||
range: 15..27,
|
||||
value: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 26..27,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
## Semantic Syntax Errors
|
||||
|
||||
|
|
||||
1 | async def f(): yield from x
|
||||
| ^^^^^^^^^^^^ Syntax Error: `yield from` statement in async function; use `async for` instead
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue