Repository: astral-sh/ruff
File: crates/ruff_python_parser/src/semantic_errors.rs (Rust)

Code before:
                        .as_name_expr()
                        .is_some_and(|name| name.id == *id)
                }) {
                    self.rebound_variables.push(*range);
                }
            }
        }
        walk_expr(self, expr);
    }
}

#[derive(Default)]
struct ReturnVisitor {
    return_range: Option<TextRange>,
    has_yield: bool,
}

impl StatementVisitor<'_> for ReturnVisitor {
    fn visit_stmt(&mut self, stmt: &Stmt) {
        match stmt {
            Stmt::Expr(ast::StmtExpr { value, .. }) => match **value {
                Expr::Yield(_) | Expr::YieldFrom(_) => {
                    self.has_yield = true;
                }
                _ => {}
            },
            // Do not recurse into nested functions; they're evaluated separately.
            Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {}
            Stmt::Return(ast::StmtReturn {
                value: Some(_),
                range,
                ..
            }) => {
                self.return_range = Some(*range);
            }
            _ => statement_visitor::walk_stmt(self, stmt),
        }
    }
}

struct MatchPatternVisitor<'a, Ctx> {
    names: FxHashSet<&'a ast::name::Name>,
    ctx: &'a Ctx,
}

impl<'a, Ctx: SemanticSyntaxContext> MatchPatternVisitor<'a, Ctx> {
    fn visit_pattern(&mut self, pattern: &'a Pattern) {
        // test_ok class_keyword_in_case_pattern
        // match 2:
        //     case Class(x=x): ...


Review comment (bug):
I noticed this while reviewing a different PR (https://github.com/astral-sh/ruff/pull/21177#discussion_r2539702728), but I think this logic is actually incorrect here and in B901 itself. This only catches `yield` and `yield from` in standalone statements, but the syntax error applies even when these expressions are nested within other statements:

```pycon
>>> async def foo():
...     return (yield 1)
...
  File "<python-input-1>", line 2
    return (yield 1)
    ^^^^^^^^^^^^^^^^
SyntaxError: 'return' with value in async generator
```

That's one compact example that won't be flagged here.

The fix is to implement both `visit_stmt` and `visit_expr` and to perform the `yield` checks in `visit_expr`, as in PLR1708:

https://github.com/astral-sh/ruff/blob/0e651b50b7d0c2614c5958e78525213e759e5b41/crates/ruff_linter/src/rules/pylint/rules/stop_iteration_return.rs#L99-L100

We also have to call `walk_stmt` in the `Return` branch, or this case will still not be caught.

Code after:
            InvalidExpressionPosition::TypeVarDefault => "TypeVar default",
            InvalidExpressionPosition::TypeVarTupleDefault => "TypeVarTuple default",
            InvalidExpressionPosition::ParamSpecDefault => "ParamSpec default",
            InvalidExpressionPosition::TypeAnnotation => "type annotation",
            InvalidExpressionPosition::GenericDefinition => "generic definition",
            InvalidExpressionPosition::TypeAlias => "type alias",
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
pub enum InvalidExpressionKind {
    Yield,
    NamedExpr,
    Await,
}

impl Display for InvalidExpressionKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            InvalidExpressionKind::Yield => "yield expression",
            InvalidExpressionKind::NamedExpr => "named expression",
            InvalidExpressionKind::Await => "await expression",
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
pub enum WriteToDebugKind {
    Store,
    Delete(PythonVersion),
}

/// Searches for the first named expression (`x := y`) rebinding one of the `iteration_variables` in
/// a comprehension or generator expression.
struct ReboundComprehensionVisitor<'a> {
    comprehensions: &'a [ast::Comprehension],
    rebound_variables: Vec<TextRange>,
}

impl Visitor<'_> for ReboundComprehensionVisitor<'_> {
    fn visit_expr(&mut self, expr: &Expr) {
        if let Expr::Named(ast::ExprNamed { target, .. }) = expr {
            if let Expr::Name(ast::ExprName { id, range, .. }) = &**target {
                if self.comprehensions.iter().any(|comp| {
                    comp.target
                        .as_name_expr()
                        .is_some_and(|name| name.id == *id)
                }) {
                    self.rebound_variables.push(*range);
                }