mirror of https://github.com/astral-sh/ruff
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
|
|
@ -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)
|
||||||
|
|
@ -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) {
|
}
|
||||||
|
for expr in iter {
|
||||||
self.visit_non_type_definition(expr);
|
self.visit_non_type_definition(expr);
|
||||||
}
|
}
|
||||||
self.visit_expr_context(ctx);
|
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…
Reference in New Issue