diff --git a/ChangeLog b/ChangeLog index 6e3a338e12..aabd4a78ee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -215,6 +215,9 @@ Release date: TBA Closes #3793 +* Fixed a false positive for ``invalid-class-object`` when the object + being assigned to the ``__class__`` attribute is uninferable. + * Fixed false positive for ``used-before-assignment`` with self-referential type annotation in conditional statements within class methods. diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 1aa94d4aff..7854efbe5b 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -283,6 +283,9 @@ Other Changes Closes #3793 +* Fixed a false positive for ``invalid-class-object`` when the object + being assigned to the ``__class__`` attribute is uninferable. + * Added a ``testutil`` extra require to the packaging, as ``gitpython`` should not be a dependency all the time but is still required to use the primer helper code in ``pylint.testutil``. You can install it with ``pip install pylint[testutil]``. diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index dcde9231b0..8dbb58fd4a 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -1482,8 +1482,12 @@ def _check_invalid_class_object(self, node: nodes.AssignAttr) -> None: if not node.attrname == "__class__": return inferred = safe_infer(node.parent.value) - if isinstance(inferred, nodes.ClassDef) or inferred is astroid.Uninferable: - # If is uninferrable, we allow it to prevent false positives + if ( + isinstance(inferred, nodes.ClassDef) + or inferred is astroid.Uninferable + or inferred is None + ): + # If is uninferable, we allow it to prevent false positives return self.add_message("invalid-class-object", node=node) diff --git a/tests/functional/i/invalid/invalid_class_object.py b/tests/functional/i/invalid/invalid_class_object.py index 367756eaa8..7c08ebae81 100644 --- a/tests/functional/i/invalid/invalid_class_object.py +++ b/tests/functional/i/invalid/invalid_class_object.py @@ -16,3 +16,17 @@ class B: A.__class__ = defaultdict A.__class__ = defaultdict(str) # [invalid-class-object] A.__class__ = 1 # [invalid-class-object] + + +# Here, ambiguity is found when inferring self.__class__ +class C: + @classmethod + def _new_instance(cls): + obj = C() + obj.__class__ = cls + return obj + + def __deepcopy__(self, memo): + obj = C() + obj.__class__ = self.__class__ + return obj