[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:
Bhuminjay Soni 2025-09-03 19:43:05 +05:30 committed by GitHub
parent 5d7c17c20a
commit 4c3e1930f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 140 additions and 33 deletions

View file

@ -0,0 +1 @@
async def f(): yield from x

View file

@ -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)]

View file

@ -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();
}

View file

@ -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
|