Skip to content

Commit 02fd481

Browse files
authored
[ty] Don't warn yield not in function when yield is in function (#18008)
1 parent d375921 commit 02fd481

File tree

7 files changed

+87
-17
lines changed

7 files changed

+87
-17
lines changed

crates/ruff_linter/resources/test/fixtures/pyflakes/F704.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,11 @@ class Foo:
99
yield 3
1010
yield from 3
1111
await f()
12+
13+
def _():
14+
# Invalid yield scopes; but not outside a function
15+
type X[T: (yield 1)] = int
16+
type Y = (yield 2)
17+
18+
# Valid yield scope
19+
yield 3

crates/ruff_linter/src/checkers/ast/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,17 @@ impl SemanticSyntaxContext for Checker<'_> {
681681
false
682682
}
683683

684+
fn in_yield_allowed_context(&self) -> bool {
685+
for scope in self.semantic.current_scopes() {
686+
match scope.kind {
687+
ScopeKind::Class(_) | ScopeKind::Generator { .. } => return false,
688+
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
689+
ScopeKind::Module | ScopeKind::Type => {}
690+
}
691+
}
692+
false
693+
}
694+
684695
fn in_sync_comprehension(&self) -> bool {
685696
for scope in self.semantic.current_scopes() {
686697
if let ScopeKind::Generator {

crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F704_F704.py.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---
22
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
3-
snapshot_kind: text
43
---
54
F704.py:6:5: F704 `yield` statement outside of a function
65
|
@@ -31,4 +30,6 @@ F704.py:11:1: F704 `await` statement outside of a function
3130
10 | yield from 3
3231
11 | await f()
3332
| ^^^^^^^^^ F704
33+
12 |
34+
13 | def _():
3435
|

crates/ruff_python_parser/src/semantic_errors.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -769,16 +769,21 @@ impl SemanticSyntaxChecker {
769769
// We are intentionally not inspecting the async status of the scope for now to mimic F704.
770770
// await-outside-async is PLE1142 instead, so we'll end up emitting both syntax errors for
771771
// cases that trigger F704
772+
773+
if ctx.in_function_scope() {
774+
return;
775+
}
776+
772777
if kind.is_await() {
773-
if ctx.in_await_allowed_context() {
774-
return;
775-
}
776778
// `await` is allowed at the top level of a Jupyter notebook.
777779
// See: https://ipython.readthedocs.io/en/stable/interactive/autoawait.html.
778780
if ctx.in_module_scope() && ctx.in_notebook() {
779781
return;
780782
}
781-
} else if ctx.in_function_scope() {
783+
if ctx.in_await_allowed_context() {
784+
return;
785+
}
786+
} else if ctx.in_yield_allowed_context() {
782787
return;
783788
}
784789

@@ -1719,6 +1724,35 @@ pub trait SemanticSyntaxContext {
17191724
/// See the trait-level documentation for more details.
17201725
fn in_await_allowed_context(&self) -> bool;
17211726

1727+
/// Returns `true` if the visitor is currently in a context where `yield` and `yield from`
1728+
/// expressions are allowed.
1729+
///
1730+
/// Yield expressions are allowed only in:
1731+
/// 1. Function definitions
1732+
/// 2. Lambda expressions
1733+
///
1734+
/// Unlike `await`, yield is not allowed in:
1735+
/// - Comprehensions (list, set, dict)
1736+
/// - Generator expressions
1737+
/// - Class definitions
1738+
///
1739+
/// This method should traverse parent scopes to check if the closest relevant scope
1740+
/// is a function or lambda, and that no disallowed context (class, comprehension, generator)
1741+
/// intervenes. For example:
1742+
///
1743+
/// ```python
1744+
/// def f():
1745+
/// yield 1 # okay, in a function
1746+
/// lambda: (yield 1) # okay, in a lambda
1747+
///
1748+
/// [(yield 1) for x in range(3)] # error, in a comprehension
1749+
/// ((yield 1) for x in range(3)) # error, in a generator expression
1750+
/// class C:
1751+
/// yield 1 # error, in a class within a function
1752+
/// ```
1753+
///
1754+
fn in_yield_allowed_context(&self) -> bool;
1755+
17221756
/// Returns `true` if the visitor is currently inside of a synchronous comprehension.
17231757
///
17241758
/// This method is necessary because `in_async_context` only checks for the nearest, enclosing

crates/ruff_python_parser/tests/fixtures.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,10 @@ impl SemanticSyntaxContext for SemanticSyntaxCheckerVisitor<'_> {
556556
true
557557
}
558558

559+
fn in_yield_allowed_context(&self) -> bool {
560+
true
561+
}
562+
559563
fn in_generator_scope(&self) -> bool {
560564
true
561565
}

crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -298,25 +298,25 @@ python-version = "3.12"
298298
```
299299

300300
```py
301-
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
302-
# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
303-
# error: [invalid-syntax] "`yield` statement outside of a function"
304-
type X[T: (yield 1)] = int
301+
def _():
302+
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
303+
# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
304+
type X[T: (yield 1)] = int
305305

306-
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
307-
# error: [invalid-syntax] "yield expression cannot be used within a type alias"
308-
# error: [invalid-syntax] "`yield` statement outside of a function"
309-
type Y = (yield 1)
306+
def _():
307+
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
308+
# error: [invalid-syntax] "yield expression cannot be used within a type alias"
309+
type Y = (yield 1)
310310

311311
# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
312312
# error: [invalid-syntax] "named expression cannot be used within a generic definition"
313313
def f[T](x: int) -> (y := 3):
314314
return x
315315

316-
# error: [invalid-syntax] "`yield from` statement outside of a function"
317-
# error: [invalid-syntax] "yield expression cannot be used within a generic definition"
318-
class C[T]((yield from [object])):
319-
pass
316+
def _():
317+
# error: [invalid-syntax] "yield expression cannot be used within a generic definition"
318+
class C[T]((yield from [object])):
319+
pass
320320
```
321321

322322
## `await` outside async function

crates/ty_python_semantic/src/semantic_index/builder.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,6 +2481,18 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_> {
24812481
false
24822482
}
24832483

2484+
fn in_yield_allowed_context(&self) -> bool {
2485+
for scope_info in self.scope_stack.iter().rev() {
2486+
let scope = &self.scopes[scope_info.file_scope_id];
2487+
match scope.kind() {
2488+
ScopeKind::Class | ScopeKind::Comprehension => return false,
2489+
ScopeKind::Function | ScopeKind::Lambda => return true,
2490+
ScopeKind::Module | ScopeKind::TypeAlias | ScopeKind::Annotation => {}
2491+
}
2492+
}
2493+
false
2494+
}
2495+
24842496
fn in_sync_comprehension(&self) -> bool {
24852497
for scope_info in self.scope_stack.iter().rev() {
24862498
let scope = &self.scopes[scope_info.file_scope_id];

0 commit comments

Comments
 (0)