diff --git a/crates/ruff/resources/test/fixtures/pyflakes/F404.py b/crates/ruff/resources/test/fixtures/pyflakes/F404.py index 99783081d8..08d8d73064 100644 --- a/crates/ruff/resources/test/fixtures/pyflakes/F404.py +++ b/crates/ruff/resources/test/fixtures/pyflakes/F404.py @@ -4,3 +4,5 @@ from __future__ import absolute_import from collections import namedtuple from __future__ import print_function + +import __future__ diff --git a/crates/ruff/src/checkers/ast.rs b/crates/ruff/src/checkers/ast.rs index ec8d0a46c4..2889a7ff58 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -900,7 +900,38 @@ where } for alias in names { - if alias.node.name.contains('.') && alias.node.asname.is_none() { + if alias.node.name == "__future__" { + let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name); + self.add_binding( + name, + Binding { + kind: BindingKind::FutureImportation, + runtime_usage: None, + // Always mark `__future__` imports as used. + synthetic_usage: Some(( + self.scopes[*(self + .scope_stack + .last() + .expect("No current scope found"))] + .id, + Range::from_located(alias), + )), + typing_usage: None, + range: Range::from_located(alias), + source: Some(self.current_stmt().clone()), + context: self.execution_context(), + }, + ); + + if self.settings.rules.enabled(&Rule::LateFutureImport) + && !self.futures_allowed + { + self.diagnostics.push(Diagnostic::new( + pyflakes::rules::LateFutureImport, + Range::from_located(stmt), + )); + } + } else if alias.node.name.contains('.') && alias.node.asname.is_none() { // Given `import foo.bar`, `name` would be "foo", and `full_name` would be // "foo.bar". let name = alias.node.name.split('.').next().unwrap(); @@ -918,10 +949,6 @@ where }, ); } else { - if let Some(asname) = &alias.node.asname { - self.check_builtin_shadowing(asname, stmt, false); - } - // Given `import foo`, `name` and `full_name` would both be `foo`. // Given `import foo as bar`, `name` would be `bar` and `full_name` would // be `foo`. @@ -957,6 +984,10 @@ where context: self.execution_context(), }, ); + + if let Some(asname) = &alias.node.asname { + self.check_builtin_shadowing(asname, stmt, false); + } } // flake8-debugger diff --git a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F404_F404.py.snap b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F404_F404.py.snap index fcbe88c0df..305376bdc5 100644 --- a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F404_F404.py.snap +++ b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F404_F404.py.snap @@ -1,5 +1,5 @@ --- -source: src/rules/pyflakes/mod.rs +source: crates/ruff/src/rules/pyflakes/mod.rs expression: diagnostics --- - kind: @@ -12,4 +12,14 @@ expression: diagnostics column: 37 fix: ~ parent: ~ +- kind: + LateFutureImport: ~ + location: + row: 8 + column: 0 + end_location: + row: 8 + column: 17 + fix: ~ + parent: ~