diff --git a/doc/whatsnew/fragments/3975.false_positive b/doc/whatsnew/fragments/3975.false_positive new file mode 100644 index 0000000000..f6c7f1f9d2 --- /dev/null +++ b/doc/whatsnew/fragments/3975.false_positive @@ -0,0 +1,3 @@ +Don't report ``super-init-not-called`` for abstract ``__init__`` methods. + +Closes #3975 diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index c345f41b59..fb329c6ee5 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -2034,7 +2034,7 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No # Record that the class' init has been called parents_with_called_inits.add(node_frame_class(method)) except KeyError: - if klass not in to_call: + if klass not in klass_node.ancestors(recurs=False): self.add_message( "non-parent-init-called", node=expr, args=klass.name ) @@ -2061,9 +2061,6 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No if decorated_with(node, ["typing.overload"]): continue - cls = node_frame_class(method) - if klass.name == "object" or (cls and cls.name == "object"): - continue self.add_message( "super-init-not-called", args=klass.name, @@ -2196,7 +2193,10 @@ def _ancestors_to_call( to_call: dict[nodes.ClassDef, bases.UnboundMethod] = {} for base_node in klass_node.ancestors(recurs=False): try: - to_call[base_node] = next(base_node.igetattr(method)) + init_node: bases.UnboundMethod = next(base_node.igetattr(method)) + if init_node.is_abstract(): + continue + to_call[base_node] = init_node except astroid.InferenceError: continue return to_call diff --git a/tests/functional/i/init_not_called.py b/tests/functional/i/init_not_called.py index a95efadf6c..ac4baac6ed 100644 --- a/tests/functional/i/init_not_called.py +++ b/tests/functional/i/init_not_called.py @@ -59,9 +59,9 @@ def xx_init(self): class AssignedInit(NewStyleC): - """No init called.""" + """No init called, but abstract so that is fine.""" - def __init__(self): # [super-init-not-called] + def __init__(self): self.arg = 0 diff --git a/tests/functional/i/init_not_called.txt b/tests/functional/i/init_not_called.txt index 9015d1e27b..b944b5585d 100644 --- a/tests/functional/i/init_not_called.txt +++ b/tests/functional/i/init_not_called.txt @@ -1,2 +1 @@ super-init-not-called:32:4:32:16:ZZZZ.__init__:__init__ method from base class 'BBBB' is not called:INFERENCE -super-init-not-called:64:4:64:16:AssignedInit.__init__:__init__ method from base class 'NewStyleC' is not called:INFERENCE diff --git a/tests/functional/n/non/non_init_parent_called.py b/tests/functional/n/non/non_init_parent_called.py index 7ad3f1932e..0721703b29 100644 --- a/tests/functional/n/non/non_init_parent_called.py +++ b/tests/functional/n/non/non_init_parent_called.py @@ -46,6 +46,6 @@ class Super2(dict): """ Using the same idiom as Super, but without calling the __init__ method. """ - def __init__(self): # [super-init-not-called] + def __init__(self): base = super() base.__woohoo__() # [no-member] diff --git a/tests/functional/n/non/non_init_parent_called.txt b/tests/functional/n/non/non_init_parent_called.txt index 06de9a2443..0d9e227c24 100644 --- a/tests/functional/n/non/non_init_parent_called.txt +++ b/tests/functional/n/non/non_init_parent_called.txt @@ -2,5 +2,4 @@ import-error:7:0:7:18::Unable to import 'nonexistant':UNDEFINED non-parent-init-called:15:8:15:26:AAAA.__init__:__init__ method from a non direct base class 'BBBBMixin' is called:UNDEFINED no-member:23:50:23:77:CCC:Module 'functional.n.non.non_init_parent_called' has no 'BBBB' member:INFERENCE no-member:28:8:28:35:CCC.__init__:Module 'functional.n.non.non_init_parent_called' has no 'BBBB' member:INFERENCE -super-init-not-called:49:4:49:16:Super2.__init__:__init__ method from base class 'dict' is not called:INFERENCE no-member:51:8:51:23:Super2.__init__:Super of 'Super2' has no '__woohoo__' member:INFERENCE diff --git a/tests/functional/s/super/super_init_not_called.py b/tests/functional/s/super/super_init_not_called.py index 90a884b0b0..f0bfe03290 100644 --- a/tests/functional/s/super/super_init_not_called.py +++ b/tests/functional/s/super/super_init_not_called.py @@ -1,6 +1,7 @@ """Tests for super-init-not-called.""" # pylint: disable=too-few-public-methods, missing-class-docstring +import abc import ctypes @@ -53,5 +54,45 @@ def __init__(self): # [super-init-not-called] # Regression test as reported in # https://github.com/PyCQA/pylint/issues/6027 class MyUnion(ctypes.Union): - def __init__(self): # [super-init-not-called] + def __init__(self): pass + + +# Should not be called on abstract __init__ methods +# https://github.com/PyCQA/pylint/issues/3975 +class Base: + def __init__(self, param: int, param_two: str) -> None: + raise NotImplementedError() + + +class Derived(Base): + def __init__(self, param: int, param_two: str) -> None: + self.param = param + 1 + self.param_two = param_two[::-1] + + +class AbstractBase(abc.ABC): + def __init__(self, param: int) -> None: + self.param = param + 1 + + def abstract_method(self) -> str: + """This needs to be implemented.""" + raise NotImplementedError() + + +class DerivedFromAbstract(AbstractBase): + def __init__(self, param: int) -> None: # [super-init-not-called] + print("Called") + + def abstract_method(self) -> str: + return "Implemented" + + +class DerivedFrom(UnknownParent): # [undefined-variable] + def __init__(self) -> None: + print("Called") + + +class DerivedFromUnknownGrandparent(DerivedFrom): + def __init__(self) -> None: + DerivedFrom.__init__(self) diff --git a/tests/functional/s/super/super_init_not_called.rc b/tests/functional/s/super/super_init_not_called.rc new file mode 100644 index 0000000000..b8621ee577 --- /dev/null +++ b/tests/functional/s/super/super_init_not_called.rc @@ -0,0 +1,4 @@ +[testoptions] +# ctypes has a different implementation in PyPy and does have an inferable +# __init__ method for ctypes.Union. +except_implementations=PyPy diff --git a/tests/functional/s/super/super_init_not_called.txt b/tests/functional/s/super/super_init_not_called.txt index aafaa2023c..002db0d762 100644 --- a/tests/functional/s/super/super_init_not_called.txt +++ b/tests/functional/s/super/super_init_not_called.txt @@ -1,3 +1,4 @@ -undefined-variable:18:23:18:40:UninferableChild:Undefined variable 'UninferableParent':UNDEFINED -super-init-not-called:49:4:49:16:ChildThree.__init__:__init__ method from base class 'ParentWithoutInit' is not called:INFERENCE -super-init-not-called:56:4:56:16:MyUnion.__init__:__init__ method from base class 'Union' is not called:INFERENCE +undefined-variable:19:23:19:40:UninferableChild:Undefined variable 'UninferableParent':UNDEFINED +super-init-not-called:50:4:50:16:ChildThree.__init__:__init__ method from base class 'ParentWithoutInit' is not called:INFERENCE +super-init-not-called:84:4:84:16:DerivedFromAbstract.__init__:__init__ method from base class 'AbstractBase' is not called:INFERENCE +undefined-variable:91:18:91:31:DerivedFrom:Undefined variable 'UnknownParent':UNDEFINED