diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_18.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_18.py new file mode 100644 index 0000000000000..8576a1d403d38 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_18.py @@ -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) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_19.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_19.py new file mode 100644 index 0000000000000..660f75ec1c336 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_19.py @@ -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) diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index dbfa15f65655a..bbf245a1b3eb9 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -1168,13 +1168,14 @@ where range: _, }) = slice.as_ref() { - if let Some(expr) = elts.first() { + let mut iter = elts.iter(); + if let Some(expr) = iter.next() { 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 { 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) { let parent = self.semantic.current_statement(); + // Match the left-hand side of an annotated assignment, like `x` in `x: int`. if matches!( parent, Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. }) - ) { + ) && !self.semantic.in_annotation() + { self.add_binding( id, expr.range(), diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 1ce5133933c91..172de5a243bba 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -133,6 +133,8 @@ mod tests { #[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_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_1.py"))] #[test_case(Rule::UndefinedExport, Path::new("F822_2.py"))] diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_18.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_18.py.snap new file mode 100644 index 0000000000000..d0b409f39ee0b --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_18.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_19.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_19.py.snap new file mode 100644 index 0000000000000..0461eeea1b703 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_19.py.snap @@ -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 + | + +