mirror of
https://fuchsia.googlesource.com/third_party/github.com/pylint-dev/pylint
synced 2024-09-21 16:19:21 +00:00
40a9d665dd
We got a lot of complaints about redefined-variable-type having multiple false positives and not being reliable enough for a core checker. The fact that it doesn't take into account duck typing makes it way to noisy when dealing with large code bases. Since it would take some time until these concerns will be fixed, the checker becomes an extension and can be later upgraded to a core one.
100 lines
3.6 KiB
Python
100 lines
3.6 KiB
Python
import six
|
|
|
|
import astroid
|
|
from pylint.checkers import BaseChecker
|
|
from pylint.checkers.utils import check_messages
|
|
from pylint.checkers.base import _is_none, _node_type
|
|
from pylint.interfaces import IAstroidChecker
|
|
|
|
|
|
BUILTINS = six.moves.builtins.__name__
|
|
|
|
|
|
class MultipleTypesChecker(BaseChecker):
|
|
"""Checks for variable type redefinitions (NoneType excepted)
|
|
|
|
At a function, method, class or module scope
|
|
|
|
This rule could be improved:
|
|
- Currently, if an attribute is set to different types in 2 methods of a
|
|
same class, it won't be detected (see functional test)
|
|
- One could improve the support for inference on assignment with tuples,
|
|
ifexpr, etc. Also it would be great to have support for inference on
|
|
str.split()
|
|
"""
|
|
__implements__ = IAstroidChecker
|
|
|
|
name = 'multiple_types'
|
|
msgs = {'R0204': ('Redefinition of %s type from %s to %s',
|
|
'redefined-variable-type',
|
|
'Used when the type of a variable changes inside a '
|
|
'method or a function.'
|
|
),
|
|
}
|
|
|
|
def visit_classdef(self, _):
|
|
self._assigns.append({})
|
|
|
|
@check_messages('redefined-variable-type')
|
|
def leave_classdef(self, _):
|
|
self._check_and_add_messages()
|
|
|
|
visit_functiondef = visit_classdef
|
|
leave_functiondef = leave_module = leave_classdef
|
|
|
|
def visit_module(self, _):
|
|
self._assigns = [{}]
|
|
|
|
def _check_and_add_messages(self):
|
|
assigns = self._assigns.pop()
|
|
for name, args in assigns.items():
|
|
if len(args) <= 1:
|
|
continue
|
|
orig_node, orig_type = args[0]
|
|
# Check if there is a type in the following nodes that would be
|
|
# different from orig_type.
|
|
for redef_node, redef_type in args[1:]:
|
|
if redef_type == orig_type:
|
|
continue
|
|
# if a variable is defined to several types in a if node,
|
|
# this is not actually redefining.
|
|
orig_parent = orig_node.parent
|
|
redef_parent = redef_node.parent
|
|
if isinstance(orig_parent, astroid.If):
|
|
if orig_parent == redef_parent:
|
|
if (redef_node in orig_parent.orelse and
|
|
orig_node not in orig_parent.orelse):
|
|
orig_node, orig_type = redef_node, redef_type
|
|
continue
|
|
elif (isinstance(redef_parent, astroid.If) and
|
|
redef_parent in orig_parent.nodes_of_class(astroid.If)):
|
|
orig_node, orig_type = redef_node, redef_type
|
|
continue
|
|
orig_type = orig_type.replace(BUILTINS + ".", '')
|
|
redef_type = redef_type.replace(BUILTINS + ".", '')
|
|
self.add_message('redefined-variable-type', node=redef_node,
|
|
args=(name, orig_type, redef_type))
|
|
break
|
|
|
|
def visit_assign(self, node):
|
|
# we don't handle multiple assignment nor slice assignment
|
|
target = node.targets[0]
|
|
if isinstance(target, (astroid.Tuple, astroid.Subscript)):
|
|
return
|
|
# ignore NoneType
|
|
if _is_none(node):
|
|
return
|
|
_type = _node_type(node.value)
|
|
if _type:
|
|
self._assigns[-1].setdefault(target.as_string(), []).append(
|
|
(node, _type.pytype()))
|
|
|
|
|
|
def register(linter):
|
|
"""Required method to auto register this checker.
|
|
|
|
:param linter: Main interface object for Pylint plugins
|
|
:type linter: Pylint object
|
|
"""
|
|
linter.register_checker(MultipleTypesChecker(linter))
|