Skip to content

Commit dfe4291

Browse files
authored
Improve ruff_python_semantic::all::extract_all_names() (#11335)
1 parent 4541337 commit dfe4291

File tree

8 files changed

+110
-91
lines changed

8 files changed

+110
-91
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import builtins
2+
a = 1
3+
4+
__all__ = builtins.list(["a", "b"])

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ use ruff_python_ast::{helpers, str, visitor, PySourceType};
4949
use ruff_python_codegen::{Generator, Stylist};
5050
use ruff_python_index::Indexer;
5151
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
52-
use ruff_python_semantic::all::{extract_all_names, DunderAllDefinition, DunderAllFlags};
52+
use ruff_python_semantic::all::{DunderAllDefinition, DunderAllFlags};
5353
use ruff_python_semantic::analyze::{imports, typing};
5454
use ruff_python_semantic::{
5555
BindingFlags, BindingId, BindingKind, Exceptions, Export, FromImport, Globals, Import, Module,
@@ -1882,8 +1882,7 @@ impl<'a> Checker<'a> {
18821882
_ => false,
18831883
}
18841884
{
1885-
let (all_names, all_flags) =
1886-
extract_all_names(parent, |name| self.semantic.has_builtin_binding(name));
1885+
let (all_names, all_flags) = self.semantic.extract_dunder_all_names(parent);
18871886

18881887
if all_flags.intersects(DunderAllFlags::INVALID_OBJECT) {
18891888
flags |= BindingFlags::INVALID_ALL_OBJECT;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ mod tests {
161161
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))]
162162
#[test_case(Rule::UndefinedExport, Path::new("F822_0.pyi"))]
163163
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
164+
#[test_case(Rule::UndefinedExport, Path::new("F822_1b.py"))]
164165
#[test_case(Rule::UndefinedExport, Path::new("F822_2.py"))]
165166
#[test_case(Rule::UndefinedExport, Path::new("F822_3.py"))]
166167
#[test_case(Rule::UndefinedLocal, Path::new("F823.py"))]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
3+
---
4+
F822_1b.py:4:31: F822 Undefined name `b` in `__all__`
5+
|
6+
2 | a = 1
7+
3 |
8+
4 | __all__ = builtins.list(["a", "b"])
9+
| ^^^ F822
10+
|

crates/ruff_python_semantic/src/definition.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ use std::fmt::Debug;
55
use std::ops::Deref;
66
use std::path::Path;
77

8-
use crate::all::DunderAllName;
98
use ruff_index::{newtype_index, IndexSlice, IndexVec};
109
use ruff_python_ast::{self as ast, Stmt};
1110
use ruff_text_size::{Ranged, TextRange};
1211

1312
use crate::analyze::visibility::{
1413
class_visibility, function_visibility, method_visibility, module_visibility, Visibility,
1514
};
15+
use crate::model::all::DunderAllName;
1616

1717
/// Id uniquely identifying a definition in a program.
1818
#[newtype_index]

crates/ruff_python_semantic/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
pub mod all;
21
pub mod analyze;
32
mod binding;
43
mod branches;

crates/ruff_python_semantic/src/model.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
pub mod all;
2+
13
use std::path::Path;
24

35
use bitflags::bitflags;
Lines changed: 90 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
//! Utilities for semantic analysis of `__all__` definitions
2+
13
use bitflags::bitflags;
24

35
use ruff_python_ast::{self as ast, helpers::map_subscript, Expr, Stmt};
46
use ruff_text_size::{Ranged, TextRange};
57

8+
use crate::SemanticModel;
9+
610
bitflags! {
711
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
812
pub struct DunderAllFlags: u8 {
@@ -66,34 +70,79 @@ impl Ranged for DunderAllDefinition<'_> {
6670
}
6771
}
6872

69-
/// Extract the names bound to a given __all__ assignment.
70-
///
71-
/// Accepts a closure that determines whether a given name (e.g., `"list"`) is a Python builtin.
72-
pub fn extract_all_names<F>(stmt: &Stmt, is_builtin: F) -> (Vec<DunderAllName>, DunderAllFlags)
73-
where
74-
F: Fn(&str) -> bool,
75-
{
76-
fn add_to_names<'a>(
77-
elts: &'a [Expr],
78-
names: &mut Vec<DunderAllName<'a>>,
79-
flags: &mut DunderAllFlags,
80-
) {
81-
for elt in elts {
82-
if let Expr::StringLiteral(ast::ExprStringLiteral { value, range }) = elt {
83-
names.push(DunderAllName {
84-
name: value.to_str(),
85-
range: *range,
86-
});
73+
impl<'a> SemanticModel<'a> {
74+
/// Extract the names bound to a given __all__ assignment.
75+
pub fn extract_dunder_all_names<'expr>(
76+
&self,
77+
stmt: &'expr Stmt,
78+
) -> (Vec<DunderAllName<'expr>>, DunderAllFlags) {
79+
fn add_to_names<'expr>(
80+
elts: &'expr [Expr],
81+
names: &mut Vec<DunderAllName<'expr>>,
82+
flags: &mut DunderAllFlags,
83+
) {
84+
for elt in elts {
85+
if let Expr::StringLiteral(ast::ExprStringLiteral { value, range }) = elt {
86+
names.push(DunderAllName {
87+
name: value.to_str(),
88+
range: *range,
89+
});
90+
} else {
91+
*flags |= DunderAllFlags::INVALID_OBJECT;
92+
}
93+
}
94+
}
95+
96+
let mut names: Vec<DunderAllName> = vec![];
97+
let mut flags = DunderAllFlags::empty();
98+
99+
if let Some(value) = match stmt {
100+
Stmt::Assign(ast::StmtAssign { value, .. }) => Some(value),
101+
Stmt::AnnAssign(ast::StmtAnnAssign { value, .. }) => value.as_ref(),
102+
Stmt::AugAssign(ast::StmtAugAssign { value, .. }) => Some(value),
103+
_ => None,
104+
} {
105+
if let Expr::BinOp(ast::ExprBinOp { left, right, .. }) = value.as_ref() {
106+
let mut current_left = left;
107+
let mut current_right = right;
108+
loop {
109+
// Process the right side, which should be a "real" value.
110+
let (elts, new_flags) = self.extract_dunder_all_elts(current_right);
111+
flags |= new_flags;
112+
if let Some(elts) = elts {
113+
add_to_names(elts, &mut names, &mut flags);
114+
}
115+
116+
// Process the left side, which can be a "real" value or the "rest" of the
117+
// binary operation.
118+
if let Expr::BinOp(ast::ExprBinOp { left, right, .. }) = current_left.as_ref() {
119+
current_left = left;
120+
current_right = right;
121+
} else {
122+
let (elts, new_flags) = self.extract_dunder_all_elts(current_left);
123+
flags |= new_flags;
124+
if let Some(elts) = elts {
125+
add_to_names(elts, &mut names, &mut flags);
126+
}
127+
break;
128+
}
129+
}
87130
} else {
88-
*flags |= DunderAllFlags::INVALID_OBJECT;
131+
let (elts, new_flags) = self.extract_dunder_all_elts(value);
132+
flags |= new_flags;
133+
if let Some(elts) = elts {
134+
add_to_names(elts, &mut names, &mut flags);
135+
}
89136
}
90137
}
138+
139+
(names, flags)
91140
}
92141

93-
fn extract_elts<F>(expr: &Expr, is_builtin: F) -> (Option<&[Expr]>, DunderAllFlags)
94-
where
95-
F: Fn(&str) -> bool,
96-
{
142+
fn extract_dunder_all_elts<'expr>(
143+
&self,
144+
expr: &'expr Expr,
145+
) -> (Option<&'expr [Expr]>, DunderAllFlags) {
97146
match expr {
98147
Expr::List(ast::ExprList { elts, .. }) => {
99148
return (Some(elts), DunderAllFlags::empty());
@@ -122,80 +171,35 @@ where
122171
}) => {
123172
// Allow `tuple()`, `list()`, and their generic forms, like `list[int]()`.
124173
if arguments.keywords.is_empty() && arguments.args.len() <= 1 {
125-
if let Expr::Name(ast::ExprName { id, .. }) = map_subscript(func) {
126-
let id = id.as_str();
127-
if matches!(id, "tuple" | "list") && is_builtin(id) {
128-
let [arg] = arguments.args.as_ref() else {
174+
if self
175+
.resolve_builtin_symbol(map_subscript(func))
176+
.is_some_and(|symbol| matches!(symbol, "tuple" | "list"))
177+
{
178+
let [arg] = arguments.args.as_ref() else {
179+
return (None, DunderAllFlags::empty());
180+
};
181+
match arg {
182+
Expr::List(ast::ExprList { elts, .. })
183+
| Expr::Set(ast::ExprSet { elts, .. })
184+
| Expr::Tuple(ast::ExprTuple { elts, .. }) => {
185+
return (Some(elts), DunderAllFlags::empty());
186+
}
187+
_ => {
188+
// We can't analyze other expressions, but they must be
189+
// valid, since the `list` or `tuple` call will ultimately
190+
// evaluate to a list or tuple.
129191
return (None, DunderAllFlags::empty());
130-
};
131-
match arg {
132-
Expr::List(ast::ExprList { elts, .. })
133-
| Expr::Set(ast::ExprSet { elts, .. })
134-
| Expr::Tuple(ast::ExprTuple { elts, .. }) => {
135-
return (Some(elts), DunderAllFlags::empty());
136-
}
137-
_ => {
138-
// We can't analyze other expressions, but they must be
139-
// valid, since the `list` or `tuple` call will ultimately
140-
// evaluate to a list or tuple.
141-
return (None, DunderAllFlags::empty());
142-
}
143192
}
144193
}
145194
}
146195
}
147196
}
148197
Expr::Named(ast::ExprNamed { value, .. }) => {
149198
// Allow, e.g., `__all__ += (value := ["A", "B"])`.
150-
return extract_elts(value, is_builtin);
199+
return self.extract_dunder_all_elts(value);
151200
}
152201
_ => {}
153202
}
154203
(None, DunderAllFlags::INVALID_FORMAT)
155204
}
156-
157-
let mut names: Vec<DunderAllName> = vec![];
158-
let mut flags = DunderAllFlags::empty();
159-
160-
if let Some(value) = match stmt {
161-
Stmt::Assign(ast::StmtAssign { value, .. }) => Some(value),
162-
Stmt::AnnAssign(ast::StmtAnnAssign { value, .. }) => value.as_ref(),
163-
Stmt::AugAssign(ast::StmtAugAssign { value, .. }) => Some(value),
164-
_ => None,
165-
} {
166-
if let Expr::BinOp(ast::ExprBinOp { left, right, .. }) = value.as_ref() {
167-
let mut current_left = left;
168-
let mut current_right = right;
169-
loop {
170-
// Process the right side, which should be a "real" value.
171-
let (elts, new_flags) = extract_elts(current_right, |expr| is_builtin(expr));
172-
flags |= new_flags;
173-
if let Some(elts) = elts {
174-
add_to_names(elts, &mut names, &mut flags);
175-
}
176-
177-
// Process the left side, which can be a "real" value or the "rest" of the
178-
// binary operation.
179-
if let Expr::BinOp(ast::ExprBinOp { left, right, .. }) = current_left.as_ref() {
180-
current_left = left;
181-
current_right = right;
182-
} else {
183-
let (elts, new_flags) = extract_elts(current_left, |expr| is_builtin(expr));
184-
flags |= new_flags;
185-
if let Some(elts) = elts {
186-
add_to_names(elts, &mut names, &mut flags);
187-
}
188-
break;
189-
}
190-
}
191-
} else {
192-
let (elts, new_flags) = extract_elts(value, |expr| is_builtin(expr));
193-
flags |= new_flags;
194-
if let Some(elts) = elts {
195-
add_to_names(elts, &mut names, &mut flags);
196-
}
197-
}
198-
}
199-
200-
(names, flags)
201205
}

0 commit comments

Comments
 (0)