From 8e18fc012a5fcbd6cfe2b719e35ecd995da9f4bf Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 20 Jul 2024 10:41:01 -0400 Subject: [PATCH] Fix a crash when a subclass extends `__slots__` (#9817) --- doc/whatsnew/fragments/9814.bugfix | 3 +++ pylint/checkers/classes/class_checker.py | 20 ++++++++++++++++---- tests/functional/s/slots_checks.py | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 doc/whatsnew/fragments/9814.bugfix diff --git a/doc/whatsnew/fragments/9814.bugfix b/doc/whatsnew/fragments/9814.bugfix new file mode 100644 index 0000000000..0d635e6bfd --- /dev/null +++ b/doc/whatsnew/fragments/9814.bugfix @@ -0,0 +1,3 @@ +Fix a crash when a subclass extends ``__slots__``. + +Closes #9814 diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 9758d2450c..d7bd299105 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1537,7 +1537,11 @@ def _has_valid_slots(self, node: nodes.ClassDef) -> bool: if "__slots__" not in node.locals: return False - for slots in node.ilookup("__slots__"): + try: + inferred_slots = tuple(node.ilookup("__slots__")) + except astroid.InferenceError: + return False + for slots in inferred_slots: # check if __slots__ is a valid type if isinstance(slots, util.UninferableBase): return False @@ -1555,7 +1559,11 @@ def _check_slots(self, node: nodes.ClassDef) -> None: if "__slots__" not in node.locals: return - for slots in node.ilookup("__slots__"): + try: + inferred_slots = tuple(node.ilookup("__slots__")) + except astroid.InferenceError: + return + for slots in inferred_slots: # check if __slots__ is a valid type if isinstance(slots, util.UninferableBase): continue @@ -1586,8 +1594,12 @@ def _check_slots(self, node: nodes.ClassDef) -> None: def _get_classdef_slots_names(self, node: nodes.ClassDef) -> list[str]: - slots_names = [] - for slots in node.ilookup("__slots__"): + slots_names: list[str] = [] + try: + inferred_slots = tuple(node.ilookup("__slots__")) + except astroid.InferenceError: # pragma: no cover + return slots_names + for slots in inferred_slots: if isinstance(slots, nodes.Dict): values = [item[0] for item in slots.items] else: diff --git a/tests/functional/s/slots_checks.py b/tests/functional/s/slots_checks.py index 5a0bc0630c..e0c76dbe45 100644 --- a/tests/functional/s/slots_checks.py +++ b/tests/functional/s/slots_checks.py @@ -187,3 +187,17 @@ class ClassWithEmptySlotsAndAnnotation: __slots__ = () a: int + + +# https://github.com/pylint-dev/pylint/issues/9814 +class SlotsManipulationTest: + __slots__ = ["a", "b", "c"] + + +class TestChild(SlotsManipulationTest): + __slots__ += ["d", "e", "f"] # pylint: disable=undefined-variable + + +t = TestChild() + +print(t.__slots__)