Skip to content

Commit caa1450

Browse files
Don't treat annotations as redefinitions in .pyi files (#10512)
## Summary In #10341, we fixed some false positives in `.pyi` files, but introduced others. This PR effectively reverts the change in #10341 and fixes it in a slightly different way. Instead of changing the _bindings_ we generate in the semantic model in `.pyi` files, we instead change how we _resolve_ them. Closes #10509.
1 parent 60fd98e commit caa1450

File tree

5 files changed

+40
-8
lines changed

5 files changed

+40
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Regression test for: https://github.com/astral-sh/ruff/issues/10509"""
2+
3+
from foo import Bar as Bar
4+
5+
class Eggs:
6+
Bar: int # OK
7+
8+
Bar = 1 # F811

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1836,7 +1836,7 @@ impl<'a> Checker<'a> {
18361836
if matches!(
18371837
parent,
18381838
Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. })
1839-
) && !(self.semantic.in_annotation() || self.source_type.is_stub())
1839+
) && !self.semantic.in_annotation()
18401840
{
18411841
self.add_binding(id, expr.range(), BindingKind::Annotation, flags);
18421842
return;

crates/ruff_linter/src/rules/pyflakes/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ mod tests {
125125
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_26.py"))]
126126
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_27.py"))]
127127
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_28.py"))]
128+
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_29.pyi"))]
128129
#[test_case(Rule::UndefinedName, Path::new("F821_0.py"))]
129130
#[test_case(Rule::UndefinedName, Path::new("F821_1.py"))]
130131
#[test_case(Rule::UndefinedName, Path::new("F821_2.py"))]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
3+
---
4+
F811_29.pyi:8:1: F811 Redefinition of unused `Bar` from line 3
5+
|
6+
6 | Bar: int # OK
7+
7 |
8+
8 | Bar = 1 # F811
9+
| ^^^ F811
10+
|
11+
= help: Remove definition: `Bar`

crates/ruff_python_semantic/src/model.rs

+19-7
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,10 @@ impl<'a> SemanticModel<'a> {
397397
//
398398
// The `name` in `print(name)` should be treated as unresolved, but the `name` in
399399
// `name: str` should be treated as used.
400-
BindingKind::Annotation => continue,
400+
//
401+
// Stub files are an exception. In a stub file, it _is_ considered valid to
402+
// resolve to a type annotation.
403+
BindingKind::Annotation if !self.in_stub_file() => continue,
401404

402405
// If it's a deletion, don't treat it as resolved, since the name is now
403406
// unbound. For example, given:
@@ -1570,6 +1573,11 @@ impl<'a> SemanticModel<'a> {
15701573
.intersects(SemanticModelFlags::FUTURE_ANNOTATIONS)
15711574
}
15721575

1576+
/// Return `true` if the model is in a stub file (i.e., a file with a `.pyi` extension).
1577+
pub const fn in_stub_file(&self) -> bool {
1578+
self.flags.intersects(SemanticModelFlags::STUB_FILE)
1579+
}
1580+
15731581
/// Return `true` if the model is in a named expression assignment (e.g., `x := 1`).
15741582
pub const fn in_named_expression_assignment(&self) -> bool {
15751583
self.flags
@@ -1675,7 +1683,7 @@ bitflags! {
16751683
/// Flags indicating the current model state.
16761684
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
16771685
pub struct SemanticModelFlags: u32 {
1678-
/// The model is in a type annotation that will only be evaluated when running a type
1686+
/// The model is in a type annotation that will only be evaluated when running a type
16791687
/// checker.
16801688
///
16811689
/// For example, the model could be visiting `int` in:
@@ -1875,6 +1883,9 @@ bitflags! {
18751883
/// ```
18761884
const FUTURE_ANNOTATIONS = 1 << 15;
18771885

1886+
/// The model is in a Python stub file (i.e., a `.pyi` file).
1887+
const STUB_FILE = 1 << 16;
1888+
18781889
/// The model has traversed past the module docstring.
18791890
///
18801891
/// For example, the model could be visiting `x` in:
@@ -1883,7 +1894,7 @@ bitflags! {
18831894
///
18841895
/// x: int = 1
18851896
/// ```
1886-
const MODULE_DOCSTRING_BOUNDARY = 1 << 16;
1897+
const MODULE_DOCSTRING_BOUNDARY = 1 << 17;
18871898

18881899
/// The model is in a type parameter definition.
18891900
///
@@ -1893,23 +1904,23 @@ bitflags! {
18931904
///
18941905
/// Record = TypeVar("Record")
18951906
///
1896-
const TYPE_PARAM_DEFINITION = 1 << 17;
1907+
const TYPE_PARAM_DEFINITION = 1 << 18;
18971908

18981909
/// The model is in a named expression assignment.
18991910
///
19001911
/// For example, the model could be visiting `x` in:
19011912
/// ```python
19021913
/// if (x := 1): ...
19031914
/// ```
1904-
const NAMED_EXPRESSION_ASSIGNMENT = 1 << 18;
1915+
const NAMED_EXPRESSION_ASSIGNMENT = 1 << 19;
19051916

19061917
/// The model is in a comprehension variable assignment.
19071918
///
19081919
/// For example, the model could be visiting `x` in:
19091920
/// ```python
19101921
/// [_ for x in range(10)]
19111922
/// ```
1912-
const COMPREHENSION_ASSIGNMENT = 1 << 19;
1923+
const COMPREHENSION_ASSIGNMENT = 1 << 20;
19131924

19141925
/// The model is in a module / class / function docstring.
19151926
///
@@ -1928,7 +1939,7 @@ bitflags! {
19281939
/// """Function docstring."""
19291940
/// pass
19301941
/// ```
1931-
const DOCSTRING = 1 << 20;
1942+
const DOCSTRING = 1 << 21;
19321943

19331944
/// The context is in any type annotation.
19341945
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();
@@ -1953,6 +1964,7 @@ impl SemanticModelFlags {
19531964
pub fn new(path: &Path) -> Self {
19541965
let mut flags = Self::default();
19551966
if is_python_stub_file(path) {
1967+
flags |= Self::STUB_FILE;
19561968
flags |= Self::FUTURE_ANNOTATIONS;
19571969
}
19581970
flags

0 commit comments

Comments
 (0)