third_party.pylibs.pylint.src/pylint/extensions/check_reassign.py
Claudiu Popa 40a9d665dd Move redefined-variable-type into an extension
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.
2016-02-16 17:48:14 +00:00

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))