From cfec627ac2a4f43d10194025debf8e4c496dc51d Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 18 May 2023 16:30:47 -0400 Subject: [PATCH] Improve reference resolution for deferred-annotations-within-classes --- .../resources/test/fixtures/pyflakes/F401_12.py | 10 ++++++++++ .../resources/test/fixtures/pyflakes/F401_13.py | 12 ++++++++++++ .../resources/test/fixtures/pyflakes/F401_14.py | 8 ++++++++ crates/ruff/src/rules/pyflakes/mod.rs | 3 +++ ...ff__rules__pyflakes__tests__F401_F401_12.py.snap | 4 ++++ ...ff__rules__pyflakes__tests__F401_F401_13.py.snap | 4 ++++ ...ff__rules__pyflakes__tests__F401_F401_14.py.snap | 4 ++++ crates/ruff_python_semantic/src/context.rs | 13 ++++++++++++- 8 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 crates/ruff/resources/test/fixtures/pyflakes/F401_12.py create mode 100644 crates/ruff/resources/test/fixtures/pyflakes/F401_13.py create mode 100644 crates/ruff/resources/test/fixtures/pyflakes/F401_14.py create mode 100644 crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_12.py.snap create mode 100644 crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_13.py.snap create mode 100644 crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_14.py.snap diff --git a/crates/ruff/resources/test/fixtures/pyflakes/F401_12.py b/crates/ruff/resources/test/fixtures/pyflakes/F401_12.py new file mode 100644 index 0000000000000..f3ba7c360c777 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pyflakes/F401_12.py @@ -0,0 +1,10 @@ +"""Test: module bindings are preferred over local bindings, for deferred annotations.""" + +from __future__ import annotations + +import datetime +from typing import Optional + + +class Class: + datetime: Optional[datetime.datetime] diff --git a/crates/ruff/resources/test/fixtures/pyflakes/F401_13.py b/crates/ruff/resources/test/fixtures/pyflakes/F401_13.py new file mode 100644 index 0000000000000..49ba589ae7d75 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pyflakes/F401_13.py @@ -0,0 +1,12 @@ +"""Test: module bindings are preferred over local bindings, for deferred annotations.""" + +from __future__ import annotations + +from typing import TypeAlias, List + + +class Class: + List: TypeAlias = List + + def bar(self) -> List: + pass diff --git a/crates/ruff/resources/test/fixtures/pyflakes/F401_14.py b/crates/ruff/resources/test/fixtures/pyflakes/F401_14.py new file mode 100644 index 0000000000000..6e7bb3695cd84 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pyflakes/F401_14.py @@ -0,0 +1,8 @@ +"""Test: module bindings are preferred over local bindings, for deferred annotations.""" + +import datetime +from typing import Optional + + +class Class: + datetime: "Optional[datetime.datetime]" diff --git a/crates/ruff/src/rules/pyflakes/mod.rs b/crates/ruff/src/rules/pyflakes/mod.rs index 648f93ba118e0..89afaefdd70d7 100644 --- a/crates/ruff/src/rules/pyflakes/mod.rs +++ b/crates/ruff/src/rules/pyflakes/mod.rs @@ -36,6 +36,9 @@ mod tests { #[test_case(Rule::UnusedImport, Path::new("F401_9.py"); "F401_9")] #[test_case(Rule::UnusedImport, Path::new("F401_10.py"); "F401_10")] #[test_case(Rule::UnusedImport, Path::new("F401_11.py"); "F401_11")] + #[test_case(Rule::UnusedImport, Path::new("F401_12.py"); "F401_12")] + #[test_case(Rule::UnusedImport, Path::new("F401_13.py"); "F401_13")] + #[test_case(Rule::UnusedImport, Path::new("F401_14.py"); "F401_14")] #[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.py"); "F402")] #[test_case(Rule::UndefinedLocalWithImportStar, Path::new("F403.py"); "F403")] #[test_case(Rule::LateFutureImport, Path::new("F404.py"); "F404")] diff --git a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_12.py.snap b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_12.py.snap new file mode 100644 index 0000000000000..1976c4331d419 --- /dev/null +++ b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_12.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff/src/rules/pyflakes/mod.rs +--- + diff --git a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_13.py.snap b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_13.py.snap new file mode 100644 index 0000000000000..1976c4331d419 --- /dev/null +++ b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_13.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff/src/rules/pyflakes/mod.rs +--- + diff --git a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_14.py.snap b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_14.py.snap new file mode 100644 index 0000000000000..1976c4331d419 --- /dev/null +++ b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F401_F401_14.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff/src/rules/pyflakes/mod.rs +--- + diff --git a/crates/ruff_python_semantic/src/context.rs b/crates/ruff_python_semantic/src/context.rs index 55c2fbda6ed00..6be6dd0ddae21 100644 --- a/crates/ruff_python_semantic/src/context.rs +++ b/crates/ruff_python_semantic/src/context.rs @@ -116,6 +116,17 @@ impl<'a> Context<'a> { /// Resolve a reference to the given symbol. pub fn resolve_reference(&mut self, symbol: &str, range: TextRange) -> ResolvedReference { + // PEP 563 indicates that if a forward reference can be resolved in the module scope, we + // should prefer it over local resolutions. + if self.in_deferred_type_definition() { + let scope_id = ScopeId::global(); + if let Some(binding_id) = self.scopes[scope_id].get(symbol) { + let context = self.execution_context(); + self.bindings[*binding_id].mark_used(scope_id, range, context); + return ResolvedReference::Resolved(scope_id, *binding_id); + } + } + let mut first_iter = true; let mut import_starred = false; for scope_id in self.scopes.ancestor_ids(self.scope_id) { @@ -751,7 +762,7 @@ impl ContextFlags { } /// A snapshot of the [`Context`] at a given point in the AST traversal. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Snapshot { scope_id: ScopeId, stmt_id: Option,