mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
Allow bindings to be created and referenced within annotations (#7885)
## Summary Given: ```python baz: Annotated[ str, [qux for qux in foo], ] ``` We treat `baz` as `BindingKind::Annotation`, to ensure that references to `baz` are marked as unbound. However, we were _also_ treating `qux` as `BindingKind::Annotation`, which meant that the load in the comprehension _also_ errored. Closes https://github.com/astral-sh/ruff/issues/7879.
This commit is contained in:
parent
ec7395ba69
commit
a3e8e77172
6 changed files with 67 additions and 6 deletions
19
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_18.py
vendored
Normal file
19
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_18.py
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
"""Test bindings created within annotations."""
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
foo = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
# OK: Allow list comprehensions in annotations (i.e., treat `qux` as a valid
|
||||||
|
# load in the scope of the annotation).
|
||||||
|
baz: Annotated[
|
||||||
|
str,
|
||||||
|
[qux for qux in foo],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# OK: Allow named expressions in annotations.
|
||||||
|
x: (y := 1)
|
||||||
|
print(y)
|
21
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_19.py
vendored
Normal file
21
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_19.py
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
"""Test bindings created within annotations under `__future__` annotations."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
foo = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
# OK: Allow list comprehensions in annotations (i.e., treat `qux` as a valid
|
||||||
|
# load in the scope of the annotation).
|
||||||
|
baz: Annotated[
|
||||||
|
str,
|
||||||
|
[qux for qux in foo],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Error: `y` is not defined.
|
||||||
|
x: (y := 1)
|
||||||
|
print(y)
|
|
@ -1168,13 +1168,14 @@ where
|
||||||
range: _,
|
range: _,
|
||||||
}) = slice.as_ref()
|
}) = slice.as_ref()
|
||||||
{
|
{
|
||||||
if let Some(expr) = elts.first() {
|
let mut iter = elts.iter();
|
||||||
|
if let Some(expr) = iter.next() {
|
||||||
self.visit_expr(expr);
|
self.visit_expr(expr);
|
||||||
for expr in elts.iter().skip(1) {
|
|
||||||
self.visit_non_type_definition(expr);
|
|
||||||
}
|
|
||||||
self.visit_expr_context(ctx);
|
|
||||||
}
|
}
|
||||||
|
for expr in iter {
|
||||||
|
self.visit_non_type_definition(expr);
|
||||||
|
}
|
||||||
|
self.visit_expr_context(ctx);
|
||||||
} else {
|
} else {
|
||||||
debug!("Found non-Expr::Tuple argument to PEP 593 Annotation.");
|
debug!("Found non-Expr::Tuple argument to PEP 593 Annotation.");
|
||||||
}
|
}
|
||||||
|
@ -1618,10 +1619,12 @@ impl<'a> Checker<'a> {
|
||||||
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
||||||
let parent = self.semantic.current_statement();
|
let parent = self.semantic.current_statement();
|
||||||
|
|
||||||
|
// Match the left-hand side of an annotated assignment, like `x` in `x: int`.
|
||||||
if matches!(
|
if matches!(
|
||||||
parent,
|
parent,
|
||||||
Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. })
|
Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. })
|
||||||
) {
|
) && !self.semantic.in_annotation()
|
||||||
|
{
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
id,
|
id,
|
||||||
expr.range(),
|
expr.range(),
|
||||||
|
|
|
@ -133,6 +133,8 @@ mod tests {
|
||||||
#[test_case(Rule::UndefinedName, Path::new("F821_15.py"))]
|
#[test_case(Rule::UndefinedName, Path::new("F821_15.py"))]
|
||||||
#[test_case(Rule::UndefinedName, Path::new("F821_16.py"))]
|
#[test_case(Rule::UndefinedName, Path::new("F821_16.py"))]
|
||||||
#[test_case(Rule::UndefinedName, Path::new("F821_17.py"))]
|
#[test_case(Rule::UndefinedName, Path::new("F821_17.py"))]
|
||||||
|
#[test_case(Rule::UndefinedName, Path::new("F821_18.py"))]
|
||||||
|
#[test_case(Rule::UndefinedName, Path::new("F821_19.py"))]
|
||||||
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))]
|
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))]
|
||||||
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
|
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
|
||||||
#[test_case(Rule::UndefinedExport, Path::new("F822_2.py"))]
|
#[test_case(Rule::UndefinedExport, Path::new("F822_2.py"))]
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||||
|
---
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||||
|
---
|
||||||
|
F821_19.py:21:7: F821 Undefined name `y`
|
||||||
|
|
|
||||||
|
19 | # Error: `y` is not defined.
|
||||||
|
20 | x: (y := 1)
|
||||||
|
21 | print(y)
|
||||||
|
| ^ F821
|
||||||
|
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue