Skip to content

Commit aa906b9

Browse files
[pylint] Ignore __slots__ with dynamic values (#11488)
## Summary Closes #11333.
1 parent 3476e2f commit aa906b9

File tree

2 files changed

+41
-6
lines changed

2 files changed

+41
-6
lines changed

crates/ruff_linter/resources/test/fixtures/pylint/non_slot_assignment.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,16 @@ def qux(self):
8282
@qux.setter
8383
def qux(self, value):
8484
self.bar = value / 2
85+
86+
87+
class StudentG:
88+
names = ("surname",)
89+
__slots__ = (*names, "a")
90+
91+
def __init__(self, name, surname):
92+
self.name = name
93+
self.surname = surname # [assigning-non-slot]
94+
self.setup()
95+
96+
def setup(self):
97+
pass

crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ impl Ranged for AttributeAssignment<'_> {
9898
}
9999

100100
/// Return a list of attributes that are assigned to but not included in `__slots__`.
101+
///
102+
/// If the `__slots__` attribute cannot be statically determined, returns an empty vector.
101103
fn is_attributes_not_in_slots(body: &[Stmt]) -> Vec<AttributeAssignment> {
102104
// First, collect all the attributes that are assigned to `__slots__`.
103105
let mut slots = FxHashSet::default();
@@ -110,7 +112,13 @@ fn is_attributes_not_in_slots(body: &[Stmt]) -> Vec<AttributeAssignment> {
110112
};
111113

112114
if id == "__slots__" {
113-
slots.extend(slots_attributes(value));
115+
for attribute in slots_attributes(value) {
116+
if let Some(attribute) = attribute {
117+
slots.insert(attribute);
118+
} else {
119+
return vec![];
120+
}
121+
}
114122
}
115123
}
116124

@@ -125,7 +133,13 @@ fn is_attributes_not_in_slots(body: &[Stmt]) -> Vec<AttributeAssignment> {
125133
};
126134

127135
if id == "__slots__" {
128-
slots.extend(slots_attributes(value));
136+
for attribute in slots_attributes(value) {
137+
if let Some(attribute) = attribute {
138+
slots.insert(attribute);
139+
} else {
140+
return vec![];
141+
}
142+
}
129143
}
130144
}
131145

@@ -136,7 +150,13 @@ fn is_attributes_not_in_slots(body: &[Stmt]) -> Vec<AttributeAssignment> {
136150
};
137151

138152
if id == "__slots__" {
139-
slots.extend(slots_attributes(value));
153+
for attribute in slots_attributes(value) {
154+
if let Some(attribute) = attribute {
155+
slots.insert(attribute);
156+
} else {
157+
return vec![];
158+
}
159+
}
140160
}
141161
}
142162
_ => {}
@@ -237,12 +257,14 @@ fn is_attributes_not_in_slots(body: &[Stmt]) -> Vec<AttributeAssignment> {
237257
}
238258

239259
/// Return an iterator over the attributes enumerated in the given `__slots__` value.
240-
fn slots_attributes(expr: &Expr) -> impl Iterator<Item = &str> {
260+
///
261+
/// If an attribute can't be statically determined, it will be `None`.
262+
fn slots_attributes(expr: &Expr) -> impl Iterator<Item = Option<&str>> {
241263
// Ex) `__slots__ = ("name",)`
242264
let elts_iter = match expr {
243265
Expr::Tuple(ast::ExprTuple { elts, .. })
244266
| Expr::List(ast::ExprList { elts, .. })
245-
| Expr::Set(ast::ExprSet { elts, .. }) => Some(elts.iter().filter_map(|elt| match elt {
267+
| Expr::Set(ast::ExprSet { elts, .. }) => Some(elts.iter().map(|elt| match elt {
246268
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => Some(value.to_str()),
247269
_ => None,
248270
})),
@@ -251,7 +273,7 @@ fn slots_attributes(expr: &Expr) -> impl Iterator<Item = &str> {
251273

252274
// Ex) `__slots__ = {"name": ...}`
253275
let keys_iter = match expr {
254-
Expr::Dict(dict) => Some(dict.iter_keys().filter_map(|key| match key {
276+
Expr::Dict(dict) => Some(dict.iter_keys().map(|key| match key {
255277
Some(Expr::StringLiteral(ast::ExprStringLiteral { value, .. })) => Some(value.to_str()),
256278
_ => None,
257279
})),

0 commit comments

Comments
 (0)