mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:35 +00:00
[ty] Don't warn yield
not in function when yield
is in function (#18008)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
This commit is contained in:
parent
d37592175f
commit
02fd48132c
7 changed files with 87 additions and 17 deletions
|
@ -9,3 +9,11 @@ class Foo:
|
||||||
yield 3
|
yield 3
|
||||||
yield from 3
|
yield from 3
|
||||||
await f()
|
await f()
|
||||||
|
|
||||||
|
def _():
|
||||||
|
# Invalid yield scopes; but not outside a function
|
||||||
|
type X[T: (yield 1)] = int
|
||||||
|
type Y = (yield 2)
|
||||||
|
|
||||||
|
# Valid yield scope
|
||||||
|
yield 3
|
||||||
|
|
|
@ -681,6 +681,17 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn in_yield_allowed_context(&self) -> bool {
|
||||||
|
for scope in self.semantic.current_scopes() {
|
||||||
|
match scope.kind {
|
||||||
|
ScopeKind::Class(_) | ScopeKind::Generator { .. } => return false,
|
||||||
|
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
||||||
|
ScopeKind::Module | ScopeKind::Type => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn in_sync_comprehension(&self) -> bool {
|
fn in_sync_comprehension(&self) -> bool {
|
||||||
for scope in self.semantic.current_scopes() {
|
for scope in self.semantic.current_scopes() {
|
||||||
if let ScopeKind::Generator {
|
if let ScopeKind::Generator {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
F704.py:6:5: F704 `yield` statement outside of a function
|
F704.py:6:5: F704 `yield` statement outside of a function
|
||||||
|
|
|
|
||||||
|
@ -31,4 +30,6 @@ F704.py:11:1: F704 `await` statement outside of a function
|
||||||
10 | yield from 3
|
10 | yield from 3
|
||||||
11 | await f()
|
11 | await f()
|
||||||
| ^^^^^^^^^ F704
|
| ^^^^^^^^^ F704
|
||||||
|
12 |
|
||||||
|
13 | def _():
|
||||||
|
|
|
|
||||||
|
|
|
@ -769,16 +769,21 @@ impl SemanticSyntaxChecker {
|
||||||
// We are intentionally not inspecting the async status of the scope for now to mimic F704.
|
// We are intentionally not inspecting the async status of the scope for now to mimic F704.
|
||||||
// await-outside-async is PLE1142 instead, so we'll end up emitting both syntax errors for
|
// await-outside-async is PLE1142 instead, so we'll end up emitting both syntax errors for
|
||||||
// cases that trigger F704
|
// cases that trigger F704
|
||||||
if kind.is_await() {
|
|
||||||
if ctx.in_await_allowed_context() {
|
if ctx.in_function_scope() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if kind.is_await() {
|
||||||
// `await` is allowed at the top level of a Jupyter notebook.
|
// `await` is allowed at the top level of a Jupyter notebook.
|
||||||
// See: https://ipython.readthedocs.io/en/stable/interactive/autoawait.html.
|
// See: https://ipython.readthedocs.io/en/stable/interactive/autoawait.html.
|
||||||
if ctx.in_module_scope() && ctx.in_notebook() {
|
if ctx.in_module_scope() && ctx.in_notebook() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if ctx.in_function_scope() {
|
if ctx.in_await_allowed_context() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if ctx.in_yield_allowed_context() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1719,6 +1724,35 @@ pub trait SemanticSyntaxContext {
|
||||||
/// See the trait-level documentation for more details.
|
/// See the trait-level documentation for more details.
|
||||||
fn in_await_allowed_context(&self) -> bool;
|
fn in_await_allowed_context(&self) -> bool;
|
||||||
|
|
||||||
|
/// Returns `true` if the visitor is currently in a context where `yield` and `yield from`
|
||||||
|
/// expressions are allowed.
|
||||||
|
///
|
||||||
|
/// Yield expressions are allowed only in:
|
||||||
|
/// 1. Function definitions
|
||||||
|
/// 2. Lambda expressions
|
||||||
|
///
|
||||||
|
/// Unlike `await`, yield is not allowed in:
|
||||||
|
/// - Comprehensions (list, set, dict)
|
||||||
|
/// - Generator expressions
|
||||||
|
/// - Class definitions
|
||||||
|
///
|
||||||
|
/// This method should traverse parent scopes to check if the closest relevant scope
|
||||||
|
/// is a function or lambda, and that no disallowed context (class, comprehension, generator)
|
||||||
|
/// intervenes. For example:
|
||||||
|
///
|
||||||
|
/// ```python
|
||||||
|
/// def f():
|
||||||
|
/// yield 1 # okay, in a function
|
||||||
|
/// lambda: (yield 1) # okay, in a lambda
|
||||||
|
///
|
||||||
|
/// [(yield 1) for x in range(3)] # error, in a comprehension
|
||||||
|
/// ((yield 1) for x in range(3)) # error, in a generator expression
|
||||||
|
/// class C:
|
||||||
|
/// yield 1 # error, in a class within a function
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
fn in_yield_allowed_context(&self) -> bool;
|
||||||
|
|
||||||
/// Returns `true` if the visitor is currently inside of a synchronous comprehension.
|
/// Returns `true` if the visitor is currently inside of a synchronous comprehension.
|
||||||
///
|
///
|
||||||
/// This method is necessary because `in_async_context` only checks for the nearest, enclosing
|
/// This method is necessary because `in_async_context` only checks for the nearest, enclosing
|
||||||
|
|
|
@ -556,6 +556,10 @@ impl SemanticSyntaxContext for SemanticSyntaxCheckerVisitor<'_> {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn in_yield_allowed_context(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn in_generator_scope(&self) -> bool {
|
fn in_generator_scope(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -298,24 +298,24 @@ python-version = "3.12"
|
||||||
```
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
def _():
|
||||||
# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
|
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||||
# error: [invalid-syntax] "`yield` statement outside of a function"
|
# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
|
||||||
type X[T: (yield 1)] = int
|
type X[T: (yield 1)] = int
|
||||||
|
|
||||||
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
def _():
|
||||||
# error: [invalid-syntax] "yield expression cannot be used within a type alias"
|
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||||
# error: [invalid-syntax] "`yield` statement outside of a function"
|
# error: [invalid-syntax] "yield expression cannot be used within a type alias"
|
||||||
type Y = (yield 1)
|
type Y = (yield 1)
|
||||||
|
|
||||||
# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
|
# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
|
||||||
# error: [invalid-syntax] "named expression cannot be used within a generic definition"
|
# error: [invalid-syntax] "named expression cannot be used within a generic definition"
|
||||||
def f[T](x: int) -> (y := 3):
|
def f[T](x: int) -> (y := 3):
|
||||||
return x
|
return x
|
||||||
|
|
||||||
# error: [invalid-syntax] "`yield from` statement outside of a function"
|
def _():
|
||||||
# error: [invalid-syntax] "yield expression cannot be used within a generic definition"
|
# error: [invalid-syntax] "yield expression cannot be used within a generic definition"
|
||||||
class C[T]((yield from [object])):
|
class C[T]((yield from [object])):
|
||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -2481,6 +2481,18 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn in_yield_allowed_context(&self) -> bool {
|
||||||
|
for scope_info in self.scope_stack.iter().rev() {
|
||||||
|
let scope = &self.scopes[scope_info.file_scope_id];
|
||||||
|
match scope.kind() {
|
||||||
|
ScopeKind::Class | ScopeKind::Comprehension => return false,
|
||||||
|
ScopeKind::Function | ScopeKind::Lambda => return true,
|
||||||
|
ScopeKind::Module | ScopeKind::TypeAlias | ScopeKind::Annotation => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn in_sync_comprehension(&self) -> bool {
|
fn in_sync_comprehension(&self) -> bool {
|
||||||
for scope_info in self.scope_stack.iter().rev() {
|
for scope_info in self.scope_stack.iter().rev() {
|
||||||
let scope = &self.scopes[scope_info.file_scope_id];
|
let scope = &self.scopes[scope_info.file_scope_id];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue