Skip to content

Commit eefcdd4

Browse files
committed
Track ranges of names inside __all__ definitions
1 parent 4f06d59 commit eefcdd4

9 files changed

+41
-39
lines changed

crates/ruff_linter/src/checkers/ast/analyze/definitions.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ruff_python_ast::str::raw_contents_range;
1+
use ruff_python_ast::{str::raw_contents_range, ExprStringLiteral};
22
use ruff_text_size::{Ranged, TextRange};
33

44
use ruff_python_semantic::{
@@ -93,7 +93,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
9393
}
9494

9595
// Compute visibility of all definitions.
96-
let exports: Option<Vec<&str>> = {
96+
let exports: Option<Vec<&ExprStringLiteral>> = {
9797
checker
9898
.semantic
9999
.global_scope()

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

+9-13
Original file line numberDiff line numberDiff line change
@@ -2097,45 +2097,41 @@ impl<'a> Checker<'a> {
20972097
fn visit_exports(&mut self) {
20982098
let snapshot = self.semantic.snapshot();
20992099

2100-
let exports: Vec<(&str, TextRange)> = self
2100+
let exports: Vec<&ast::ExprStringLiteral> = self
21012101
.semantic
21022102
.global_scope()
21032103
.get_all("__all__")
21042104
.map(|binding_id| &self.semantic.bindings[binding_id])
21052105
.filter_map(|binding| match &binding.kind {
2106-
BindingKind::Export(Export { names }) => {
2107-
Some(names.iter().map(|name| (*name, binding.range())))
2108-
}
2106+
BindingKind::Export(Export { names }) => Some(names.iter().copied()),
21092107
_ => None,
21102108
})
21112109
.flatten()
21122110
.collect();
21132111

2114-
for (name, range) in exports {
2115-
if let Some(binding_id) = self.semantic.global_scope().get(name) {
2112+
for ast::ExprStringLiteral { value, range } in exports {
2113+
if let Some(binding_id) = self.semantic.global_scope().get(value.to_str()) {
21162114
// Mark anything referenced in `__all__` as used.
2117-
// TODO(charlie): `range` here should be the range of the name in `__all__`, not
2118-
// the range of `__all__` itself.
21192115
self.semantic
2120-
.add_global_reference(binding_id, ExprContext::Load, range);
2116+
.add_global_reference(binding_id, ExprContext::Load, *range);
21212117
} else {
21222118
if self.semantic.global_scope().uses_star_imports() {
21232119
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
21242120
self.diagnostics.push(Diagnostic::new(
21252121
pyflakes::rules::UndefinedLocalWithImportStarUsage {
2126-
name: (*name).to_string(),
2122+
name: value.to_string(),
21272123
},
2128-
range,
2124+
*range,
21292125
));
21302126
}
21312127
} else {
21322128
if self.enabled(Rule::UndefinedExport) {
21332129
if !self.path.ends_with("__init__.py") {
21342130
self.diagnostics.push(Diagnostic::new(
21352131
pyflakes::rules::UndefinedExport {
2136-
name: (*name).to_string(),
2132+
name: value.to_string(),
21372133
},
2138-
range,
2134+
*range,
21392135
));
21402136
}
21412137
}

crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F405_F405.py.snap

+2-4
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@ F405.py:5:11: F405 `name` may be undefined, or defined from star imports
88
| ^^^^ F405
99
|
1010

11-
F405.py:11:1: F405 `a` may be undefined, or defined from star imports
11+
F405.py:11:12: F405 `a` may be undefined, or defined from star imports
1212
|
1313
9 | print(name)
1414
10 |
1515
11 | __all__ = ['a']
16-
| ^^^^^^^ F405
16+
| ^^^ F405
1717
|
18-
19-
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
---
22
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
33
---
4-
F822_0.py:3:1: F822 Undefined name `b` in `__all__`
4+
F822_0.py:3:17: F822 Undefined name `b` in `__all__`
55
|
66
1 | a = 1
77
2 |
88
3 | __all__ = ["a", "b"]
9-
| ^^^^^^^ F822
9+
| ^^^ F822
1010
|
11-
12-
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
33
---
4-
F822_0.pyi:4:1: F822 Undefined name `c` in `__all__`
4+
F822_0.pyi:4:22: F822 Undefined name `c` in `__all__`
55
|
66
2 | b: int # Considered a binding in a `.pyi` stub file, not in a `.py` runtime file
77
3 |
88
4 | __all__ = ["a", "b", "c"] # c is flagged as missing; b is not
9-
| ^^^^^^^ F822
9+
| ^^^ F822
1010
|
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
---
22
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
33
---
4-
F822_1.py:3:1: F822 Undefined name `b` in `__all__`
4+
F822_1.py:3:22: F822 Undefined name `b` in `__all__`
55
|
66
1 | a = 1
77
2 |
88
3 | __all__ = list(["a", "b"])
9-
| ^^^^^^^ F822
9+
| ^^^ F822
1010
|
11-
12-

crates/ruff_python_ast/src/all.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use bitflags::bitflags;
22

33
use crate::helpers::map_subscript;
4-
use crate::{self as ast, Expr, Stmt};
4+
use crate::{self as ast, Expr, ExprStringLiteral, Stmt};
55

66
bitflags! {
77
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
@@ -18,14 +18,18 @@ bitflags! {
1818
/// Extract the names bound to a given __all__ assignment.
1919
///
2020
/// Accepts a closure that determines whether a given name (e.g., `"list"`) is a Python builtin.
21-
pub fn extract_all_names<F>(stmt: &Stmt, is_builtin: F) -> (Vec<&str>, DunderAllFlags)
21+
pub fn extract_all_names<F>(stmt: &Stmt, is_builtin: F) -> (Vec<&ExprStringLiteral>, DunderAllFlags)
2222
where
2323
F: Fn(&str) -> bool,
2424
{
25-
fn add_to_names<'a>(elts: &'a [Expr], names: &mut Vec<&'a str>, flags: &mut DunderAllFlags) {
25+
fn add_to_names<'a>(
26+
elts: &'a [Expr],
27+
names: &mut Vec<&'a ExprStringLiteral>,
28+
flags: &mut DunderAllFlags,
29+
) {
2630
for elt in elts {
27-
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = elt {
28-
names.push(value.to_str());
31+
if let Expr::StringLiteral(string_literal) = elt {
32+
names.push(string_literal);
2933
} else {
3034
*flags |= DunderAllFlags::INVALID_OBJECT;
3135
}
@@ -96,7 +100,7 @@ where
96100
(None, DunderAllFlags::INVALID_FORMAT)
97101
}
98102

99-
let mut names: Vec<&str> = vec![];
103+
let mut names: Vec<&ExprStringLiteral> = vec![];
100104
let mut flags = DunderAllFlags::empty();
101105

102106
if let Some(value) = match stmt {

crates/ruff_python_semantic/src/binding.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use bitflags::bitflags;
55

66
use ruff_index::{newtype_index, IndexSlice, IndexVec};
77
use ruff_python_ast::name::QualifiedName;
8+
use ruff_python_ast::ExprStringLiteral;
89
use ruff_python_ast::Stmt;
910
use ruff_source_file::Locator;
1011
use ruff_text_size::{Ranged, TextRange};
@@ -370,7 +371,7 @@ impl<'a> FromIterator<Binding<'a>> for Bindings<'a> {
370371
#[derive(Debug, Clone)]
371372
pub struct Export<'a> {
372373
/// The names of the bindings exported via `__all__`.
373-
pub names: Box<[&'a str]>,
374+
pub names: Box<[&'a ExprStringLiteral]>,
374375
}
375376

376377
/// A binding for an `import`, keyed on the name to which the import is bound.

crates/ruff_python_semantic/src/definition.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,10 @@ impl<'a> Definitions<'a> {
187187
}
188188

189189
/// Resolve the visibility of each definition in the collection.
190-
pub fn resolve(self, exports: Option<&[&str]>) -> ContextualizedDefinitions<'a> {
190+
pub fn resolve(
191+
self,
192+
exports: Option<&[&ast::ExprStringLiteral]>,
193+
) -> ContextualizedDefinitions<'a> {
191194
let mut definitions: IndexVec<DefinitionId, ContextualizedDefinition<'a>> =
192195
IndexVec::with_capacity(self.len());
193196

@@ -201,7 +204,9 @@ impl<'a> Definitions<'a> {
201204
MemberKind::Class(class) => {
202205
let parent = &definitions[member.parent];
203206
if parent.visibility.is_private()
204-
|| exports.is_some_and(|exports| !exports.contains(&member.name()))
207+
|| exports.is_some_and(|exports| {
208+
!exports.iter().any(|export| export.value == *member.name())
209+
})
205210
{
206211
Visibility::Private
207212
} else {
@@ -221,7 +226,9 @@ impl<'a> Definitions<'a> {
221226
MemberKind::Function(function) => {
222227
let parent = &definitions[member.parent];
223228
if parent.visibility.is_private()
224-
|| exports.is_some_and(|exports| !exports.contains(&member.name()))
229+
|| exports.is_some_and(|exports| {
230+
!exports.iter().any(|export| export.value == *member.name())
231+
})
225232
{
226233
Visibility::Private
227234
} else {

0 commit comments

Comments
 (0)