diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/non_slot_assignment.py b/crates/ruff_linter/resources/test/fixtures/pylint/non_slot_assignment.py index cef5b6dcce..0c443e773c 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/non_slot_assignment.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/non_slot_assignment.py @@ -66,3 +66,19 @@ class StudentF(object): def setup(self): pass + + +# https://github.com/astral-sh/ruff/issues/11358 +class Foo: + __slots__ = ("bar",) + + def __init__(self): + self.qux = 2 + + @property + def qux(self): + return self.bar * 2 + + @qux.setter + def qux(self, value): + self.bar = value / 2 diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs b/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs index 1fa567ff4f..08260ed04c 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_slot_assignment.rs @@ -147,6 +147,28 @@ fn is_attributes_not_in_slots(body: &[Stmt]) -> Vec { return vec![]; } + // And, collect all the property name with setter. + for statement in body { + let Stmt::FunctionDef(ast::StmtFunctionDef { decorator_list, .. }) = statement else { + continue; + }; + + for decorator in decorator_list { + let Some(ast::ExprAttribute { value, attr, .. }) = + decorator.expression.as_attribute_expr() + else { + continue; + }; + + if attr == "setter" { + let Some(ast::ExprName { id, .. }) = value.as_name_expr() else { + continue; + }; + slots.insert(id.as_str()); + } + } + } + // Second, find any assignments that aren't included in `__slots__`. let mut assignments = vec![]; for statement in body {