|
| 1 | +//! Utilities for semantic analysis of `__all__` definitions |
| 2 | +
|
1 | 3 | use bitflags::bitflags;
|
2 | 4 |
|
3 | 5 | use ruff_python_ast::{self as ast, helpers::map_subscript, Expr, Stmt};
|
4 | 6 | use ruff_text_size::{Ranged, TextRange};
|
5 | 7 |
|
| 8 | +use crate::SemanticModel; |
| 9 | + |
6 | 10 | bitflags! {
|
7 | 11 | #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
|
8 | 12 | pub struct DunderAllFlags: u8 {
|
@@ -66,34 +70,79 @@ impl Ranged for DunderAllDefinition<'_> {
|
66 | 70 | }
|
67 | 71 | }
|
68 | 72 |
|
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 | + } |
87 | 130 | } 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 | + } |
89 | 136 | }
|
90 | 137 | }
|
| 138 | + |
| 139 | + (names, flags) |
91 | 140 | }
|
92 | 141 |
|
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) { |
97 | 146 | match expr {
|
98 | 147 | Expr::List(ast::ExprList { elts, .. }) => {
|
99 | 148 | return (Some(elts), DunderAllFlags::empty());
|
@@ -122,80 +171,35 @@ where
|
122 | 171 | }) => {
|
123 | 172 | // Allow `tuple()`, `list()`, and their generic forms, like `list[int]()`.
|
124 | 173 | 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. |
129 | 191 | 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 |
| - } |
143 | 192 | }
|
144 | 193 | }
|
145 | 194 | }
|
146 | 195 | }
|
147 | 196 | }
|
148 | 197 | Expr::Named(ast::ExprNamed { value, .. }) => {
|
149 | 198 | // Allow, e.g., `__all__ += (value := ["A", "B"])`.
|
150 |
| - return extract_elts(value, is_builtin); |
| 199 | + return self.extract_dunder_all_elts(value); |
151 | 200 | }
|
152 | 201 | _ => {}
|
153 | 202 | }
|
154 | 203 | (None, DunderAllFlags::INVALID_FORMAT)
|
155 | 204 | }
|
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) |
201 | 205 | }
|
0 commit comments