diff --git a/doc/whatsnew/fragments/7467.bugfix b/doc/whatsnew/fragments/7467.bugfix new file mode 100644 index 00000000000..6f28e87960e --- /dev/null +++ b/doc/whatsnew/fragments/7467.bugfix @@ -0,0 +1,3 @@ +``invalid-class-object`` do not crash anymore when ``__class__`` is assigned alongside another variable. + +Closes #7467 diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 099b6897a3d..37e53d00a25 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1560,7 +1560,18 @@ def visit_assignattr(self, node: nodes.AssignAttr) -> None: def _check_invalid_class_object(self, node: nodes.AssignAttr) -> None: if not node.attrname == "__class__": return - inferred = safe_infer(node.parent.value) + if isinstance(node.parent, nodes.Tuple): + class_index = -1 + for i, elt in enumerate(node.parent.elts): + if hasattr(elt, "attrname") and elt.attrname == "__class__": + class_index = i + if class_index == -1: + # This should not happen because we checked that the node attrname + # is '__class__' earlier, but let's not be too confident here + return + inferred = safe_infer(node.parent.parent.value.elts[class_index]) + else: + inferred = safe_infer(node.parent.value) if ( isinstance(inferred, nodes.ClassDef) or inferred is astroid.Uninferable diff --git a/tests/functional/i/invalid/invalid_class_object.py b/tests/functional/i/invalid/invalid_class_object.py index 7c08ebae812..b7363e9c879 100644 --- a/tests/functional/i/invalid/invalid_class_object.py +++ b/tests/functional/i/invalid/invalid_class_object.py @@ -1,12 +1,15 @@ # pylint: disable=missing-docstring,too-few-public-methods,invalid-name from collections import defaultdict + class A: pass + class B: pass + A.__class__ = B A.__class__ = str A.__class__ = float @@ -30,3 +33,43 @@ def __deepcopy__(self, memo): obj = C() obj.__class__ = self.__class__ return obj + + +class AnotherClass: + ... + + +class Pylint7429Good: + """See https://github.com/PyCQA/pylint/issues/7467""" + + def class_defining_function_good(self): + self.__class__, myvar = AnotherClass, "myvalue" + print(myvar) + + def class_defining_function_bad(self): + self.__class__, myvar = 1, "myvalue" # [invalid-class-object] + print(myvar) + + def class_defining_function_good_inverted(self): + myvar, self.__class__ = "myvalue", AnotherClass + print(myvar) + + def class_defining_function_bad_inverted(self): + myvar, self.__class__ = "myvalue", 1 # [invalid-class-object] + print(myvar) + + def class_defining_function_complex_bad(self): + myvar, self.__class__, other = ( # [invalid-class-object] + "myvalue", + 1, + "othervalue", + ) + print(myvar, other) + + def class_defining_function_complex_good(self): + myvar, self.__class__, other = ( + "myvalue", + str, + "othervalue", + ) + print(myvar, other) diff --git a/tests/functional/i/invalid/invalid_class_object.txt b/tests/functional/i/invalid/invalid_class_object.txt index 221431b48b4..793a5de6916 100644 --- a/tests/functional/i/invalid/invalid_class_object.txt +++ b/tests/functional/i/invalid/invalid_class_object.txt @@ -1,2 +1,5 @@ -invalid-class-object:17:0:17:11::Invalid __class__ object:UNDEFINED -invalid-class-object:18:0:18:11::Invalid __class__ object:UNDEFINED +invalid-class-object:20:0:20:11::Invalid __class__ object:UNDEFINED +invalid-class-object:21:0:21:11::Invalid __class__ object:UNDEFINED +invalid-class-object:50:8:50:22:Pylint7429Good.class_defining_function_bad:Invalid __class__ object:UNDEFINED +invalid-class-object:58:15:58:29:Pylint7429Good.class_defining_function_bad_inverted:Invalid __class__ object:UNDEFINED +invalid-class-object:62:15:62:29:Pylint7429Good.class_defining_function_complex_bad:Invalid __class__ object:UNDEFINED