Skip to content

Commit

Permalink
Fix false positive super-init-not-called for inherited init (#…
Browse files Browse the repository at this point in the history
…5698)

And remove useless suppressed messages
  • Loading branch information
DanielNoord committed Jan 24, 2022
1 parent 6977e48 commit 600e9b3
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 9 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Expand Up @@ -223,6 +223,11 @@ Release date: TBA
Closes #4434
Closes #5370

* Fix false positive ``super-init-not-called`` for classes that inherit their ``init`` from
a parent.

Closes #4941

* ``encoding`` can now be supplied as a positional argument to calls that open
files without triggering ``unspecified-encoding``.

Expand Down
5 changes: 5 additions & 0 deletions doc/whatsnew/2.13.rst
Expand Up @@ -128,6 +128,11 @@ Other Changes

Closes #5483

* Fix false positive ``super-init-not-called`` for classes that inherit their ``init`` from
a parent.

Closes #4941

* Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry.

Closes #4716
Expand Down
12 changes: 10 additions & 2 deletions pylint/checkers/classes/class_checker.py
Expand Up @@ -52,7 +52,7 @@
"""Classes checker for Python code"""
import collections
from itertools import chain, zip_longest
from typing import Dict, List, Pattern
from typing import Dict, List, Pattern, Set

import astroid
from astroid import bases, nodes
Expand Down Expand Up @@ -1939,6 +1939,7 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No
return
to_call = _ancestors_to_call(klass_node)
not_called_yet = dict(to_call)
parents_with_called_inits: Set[bases.UnboundMethod] = set()
for stmt in node.nodes_of_class(nodes.Call):
expr = stmt.func
if not isinstance(expr, nodes.Attribute) or expr.attrname != "__init__":
Expand Down Expand Up @@ -1971,7 +1972,9 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No
if isinstance(klass, astroid.objects.Super):
return
try:
del not_called_yet[klass]
method = not_called_yet.pop(klass)
# 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:
self.add_message(
Expand All @@ -1980,6 +1983,11 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No
except astroid.InferenceError:
continue
for klass, method in not_called_yet.items():
# Check if the init of the class that defines this init has already
# been called.
if node_frame_class(method) in parents_with_called_inits:
return

# Return if klass is protocol
if klass.qname() in utils.TYPING_PROTOCOLS:
return
Expand Down
4 changes: 1 addition & 3 deletions pylint/checkers/imports.py
Expand Up @@ -427,9 +427,7 @@ class ImportsChecker(DeprecatedMixin, BaseChecker):
),
)

def __init__(
self, linter: Optional["PyLinter"] = None
): # pylint: disable=super-init-not-called # See https://github.com/PyCQA/pylint/issues/4941
def __init__(self, linter: Optional["PyLinter"] = None) -> None:
BaseChecker.__init__(self, linter)
self.import_graph: collections.defaultdict = collections.defaultdict(set)
self._imports_stack: List[Tuple[Any, Any]] = []
Expand Down
4 changes: 1 addition & 3 deletions pylint/checkers/stdlib.py
Expand Up @@ -463,9 +463,7 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
),
}

def __init__(
self, linter: Optional["PyLinter"] = None
): # pylint: disable=super-init-not-called # See https://github.com/PyCQA/pylint/issues/4941
def __init__(self, linter: Optional["PyLinter"] = None) -> None:
BaseChecker.__init__(self, linter)
self._deprecated_methods: Set[Any] = set()
self._deprecated_methods.update(DEPRECATED_METHODS[0])
Expand Down
30 changes: 29 additions & 1 deletion tests/functional/s/super/super_init_not_called.py
@@ -1,5 +1,5 @@
"""Tests for super-init-not-called."""
# pylint: disable=too-few-public-methods
# pylint: disable=too-few-public-methods, missing-class-docstring

import ctypes

Expand All @@ -20,3 +20,31 @@ class UninferableChild(UninferableParent): # [undefined-variable]

def __init__(self):
...


# Tests for not calling the init of a parent that does not define one
# but inherits it.
class GrandParentWithInit:
def __init__(self):
print(self)


class ParentWithoutInit(GrandParentWithInit):
pass


class ChildOne(ParentWithoutInit, GrandParentWithInit):
"""Since ParentWithoutInit calls GrandParentWithInit it doesn't need to be called."""

def __init__(self):
GrandParentWithInit.__init__(self)


class ChildTwo(ParentWithoutInit):
def __init__(self):
ParentWithoutInit.__init__(self)


class ChildThree(ParentWithoutInit):
def __init__(self): # [super-init-not-called]
...
1 change: 1 addition & 0 deletions tests/functional/s/super/super_init_not_called.txt
@@ -1 +1,2 @@
undefined-variable:18:23:18:40:UninferableChild:Undefined variable 'UninferableParent':UNDEFINED
super-init-not-called:49:4:50:11:ChildThree.__init__:__init__ method from base class 'ParentWithoutInit' is not called:INFERENCE

0 comments on commit 600e9b3

Please sign in to comment.