Skip to content

Don't emit super-init-not-called for abstract __init__ methods #7227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/3975.false_positive
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Don't report ``super-init-not-called`` for abstract ``__init__`` methods.

Closes #3975
10 changes: 5 additions & 5 deletions pylint/checkers/classes/class_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -2061,9 +2061,6 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No

if decorated_with(node, ["typing.overload"]):
continue
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lines below are no longer necessary now that we skip object.__init__ because of its abstractness.

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,
Expand Down Expand Up @@ -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
4 changes: 2 additions & 2 deletions tests/functional/i/init_not_called.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
1 change: 0 additions & 1 deletion tests/functional/i/init_not_called.txt
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion tests/functional/n/non/non_init_parent_called.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
1 change: 0 additions & 1 deletion tests/functional/n/non/non_init_parent_called.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
43 changes: 42 additions & 1 deletion tests/functional/s/super/super_init_not_called.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Tests for super-init-not-called."""
# pylint: disable=too-few-public-methods, missing-class-docstring

import abc
import ctypes


Expand Down Expand Up @@ -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]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a regression test for the coverage that dropped. We try to infer bases which fails here.

def __init__(self) -> None:
print("Called")


class DerivedFromUnknownGrandparent(DerivedFrom):
def __init__(self) -> None:
DerivedFrom.__init__(self)
4 changes: 4 additions & 0 deletions tests/functional/s/super/super_init_not_called.rc
Original file line number Diff line number Diff line change
@@ -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
7 changes: 4 additions & 3 deletions tests/functional/s/super/super_init_not_called.txt
Original file line number Diff line number Diff line change
@@ -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