summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaura M?dioni <laura.medioni@logilab.fr>2015-10-30 13:20:27 +0100
committerLaura M?dioni <laura.medioni@logilab.fr>2015-10-30 13:20:27 +0100
commit48de7002126f9c0a73c299ebd8d494612d42a285 (patch)
tree1def60fde707fb28a69b741fa6227306a37ebf89
parent0c7dc2248c66da8d8884985ec5d96f2d243b1849 (diff)
downloadpylint-48de7002126f9c0a73c299ebd8d494612d42a285.tar.gz
check if the type of a variable is redefined (at a function, class or module scope)
related to issue #674
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--pylint/checkers/base.py88
-rw-r--r--pylint/test/functional/bad_continuation.py2
-rw-r--r--pylint/test/functional/member_checks.py2
-rw-r--r--pylint/test/functional/redefined_variable_type.py50
-rw-r--r--pylint/test/functional/redefined_variable_type.txt6
-rw-r--r--pylint/test/functional/undefined_variable.py2
-rw-r--r--pylint/test/input/func_typecheck_callfunc_assigment.py2
8 files changed, 149 insertions, 5 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index d912d37..a7ece12 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -76,4 +76,4 @@ Order doesn't matter (not that much, at least ;)
* Laura Medioni (Logilab, on behalf of the CNES): misplaced-comparison-constant,
no-classmethod-decorator, no-staticmethod-decorator, too-many-nested-blocks,
too-many-boolean-expressions, unneeded-not, wrong-import-order, ungrouped-imports,
- wrong-import-position \ No newline at end of file
+ wrong-import-position, redefined-variable-type
diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py
index 73e0315..12e4e17 100644
--- a/pylint/checkers/base.py
+++ b/pylint/checkers/base.py
@@ -1882,6 +1882,93 @@ class NotChecker(_BasicChecker):
args=(node.as_string(), suggestion))
+class MultipleTypesChecker(BaseChecker):
+ """Checks for variable type redefinitions (NoneType excepted)
+
+ At a function, method, class or module scope
+ """
+ __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._class_assigns = {}
+
+ @check_messages('redefined-variable-type')
+ def leave_classdef(self, _):
+ self._check_and_add_messages(self._class_assigns)
+
+ def visit_functiondef(self, _):
+ self._func_assigns = {}
+
+ @check_messages('redefined-variable-type')
+ def leave_functiondef(self, _):
+ self._check_and_add_messages(self._func_assigns)
+
+ def visit_module(self, _):
+ self._module_assigns = {}
+
+ @check_messages('redefined-variable-type')
+ def leave_module(self, _):
+ self._check_and_add_messages(self._module_assigns)
+
+ def _check_and_add_messages(self, assigns):
+ for name, args in assigns.iteritems():
+ 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:
+ orig_type = orig_type.replace('__builtin__.', '')
+ redef_type = redef_type.replace('__builtin__.', '')
+ self.add_message('redefined-variable-type', node=redef_node,
+ args=(name, orig_type, redef_type))
+ break
+
+ def visit_assign(self, node):
+ scope = node.scope()
+ if isinstance(scope, astroid.FunctionDef):
+ msgs = self._func_assigns
+ elif isinstance(scope, astroid.Module):
+ msgs = self._module_assigns
+ elif isinstance(scope, astroid.ClassDef):
+ msgs = self._class_assigns
+ else:
+ return
+ # we don't handle multiple assignment nor slice assignment
+ target = node.targets[0]
+ if isinstance(target, (astroid.Tuple, astroid.Subscript)):
+ return
+ # ignore NoneType
+ if node.value.as_string() == 'None':
+ return
+ # check there is only one possible type for the assign node. Else we
+ # don't handle it for now
+ types = set()
+ try:
+ for var_type in node.value.infer():
+ if var_type == astroid.YES or var_type.as_string() == 'None':
+ continue
+ var_type = var_type.pytype()
+ types.add(var_type)
+ if len(types) > 1:
+ print ('more than one possible type for node %s (%s, %s)'
+ % (node.as_string(), types.pop(), types.pop()))
+ return
+ except InferenceError:
+ return
+ if types:
+ msgs.setdefault(target.as_string(), []).append((node, types.pop()))
+
+
def register(linter):
"""required method to auto register this checker"""
linter.register_checker(BasicErrorChecker(linter))
@@ -1894,3 +1981,4 @@ def register(linter):
linter.register_checker(NotChecker(linter))
linter.register_checker(RecommandationChecker(linter))
linter.register_checker(ElifChecker(linter))
+ linter.register_checker(MultipleTypesChecker(linter))
diff --git a/pylint/test/functional/bad_continuation.py b/pylint/test/functional/bad_continuation.py
index 34292ab..c0918a6 100644
--- a/pylint/test/functional/bad_continuation.py
+++ b/pylint/test/functional/bad_continuation.py
@@ -1,5 +1,5 @@
"""Regression test case for bad-continuation."""
-# pylint: disable=print-statement,using-constant-test
+# pylint: disable=print-statement,using-constant-test, redefined-variable-type
# Various alignment for brackets
from __future__ import print_function
diff --git a/pylint/test/functional/member_checks.py b/pylint/test/functional/member_checks.py
index 50a0d97..9692e05 100644
--- a/pylint/test/functional/member_checks.py
+++ b/pylint/test/functional/member_checks.py
@@ -1,5 +1,5 @@
# pylint: disable=print-statement,missing-docstring,no-self-use,too-few-public-methods,bare-except,broad-except
-# pylint: disable=using-constant-test,expression-not-assigned
+# pylint: disable=using-constant-test,expression-not-assigned, redefined-variable-type
from __future__ import print_function
class Provider(object):
diff --git a/pylint/test/functional/redefined_variable_type.py b/pylint/test/functional/redefined_variable_type.py
new file mode 100644
index 0000000..9851702
--- /dev/null
+++ b/pylint/test/functional/redefined_variable_type.py
@@ -0,0 +1,50 @@
+"""Checks variable types aren't redefined within a method or a function"""
+
+# pylint: disable=too-few-public-methods, missing-docstring, unused-variable
+
+_OK = True
+
+def my_func():
+ var = 'foo'
+ var = 'bar'
+ var = var.split() # [redefined-variable-type]
+
+class MyClass(object):
+ def __init__(self):
+ self.var = True
+ self.var1 = 2
+ self.var2 = 1.
+ self.var1 = 2. # [redefined-variable-type]
+ self.a_str = "hello"
+ a_str = False
+ (a_str, b_str) = (1, 2)
+ a_str = 2.0 if self.var else 1.0
+
+ def _getter(self):
+ return self.a_str
+ def _setter(self, val):
+ self.a_str = val
+ var2 = property(_getter, _setter)
+
+ def some_method(self):
+ self.var = 1
+ test = 'foo'
+ myint = 2
+ myint = False # [redefined-variable-type]
+
+_OK = "This is OK" # [redefined-variable-type]
+
+SOME_FLOAT = 1.
+
+def dummy_function():
+ return 2
+
+def other_function():
+ instance = MyClass()
+ instance = True # [redefined-variable-type]
+
+SOME_FLOAT = dummy_function() # [redefined-variable-type]
+
+A_GLOB = None
+A_GLOB = [1, 2, 3]
+
diff --git a/pylint/test/functional/redefined_variable_type.txt b/pylint/test/functional/redefined_variable_type.txt
new file mode 100644
index 0000000..329d4d6
--- /dev/null
+++ b/pylint/test/functional/redefined_variable_type.txt
@@ -0,0 +1,6 @@
+redefined-variable-type:10:my_func:Redefinition of var type from str to list
+redefined-variable-type:17:MyClass.__init__:Redefinition of self.var1 type from int to float
+redefined-variable-type:33:MyClass.some_method:Redefinition of myint type from int to bool
+redefined-variable-type:35::Redefinition of _OK type from bool to str
+redefined-variable-type:44:other_function:Redefinition of instance type from functional.redefined_variable_type.MyClass to bool
+redefined-variable-type:46::Redefinition of SOME_FLOAT type from float to int
diff --git a/pylint/test/functional/undefined_variable.py b/pylint/test/functional/undefined_variable.py
index 44a0652..538cff1 100644
--- a/pylint/test/functional/undefined_variable.py
+++ b/pylint/test/functional/undefined_variable.py
@@ -1,4 +1,4 @@
-# pylint: disable=missing-docstring, multiple-statements
+# pylint: disable=missing-docstring, multiple-statements, redefined-variable-type
# pylint: disable=too-few-public-methods, no-init, no-self-use, old-style-class,bare-except,broad-except
from __future__ import print_function
DEFINED = 1
diff --git a/pylint/test/input/func_typecheck_callfunc_assigment.py b/pylint/test/input/func_typecheck_callfunc_assigment.py
index 6ca744b..927dcb5 100644
--- a/pylint/test/input/func_typecheck_callfunc_assigment.py
+++ b/pylint/test/input/func_typecheck_callfunc_assigment.py
@@ -11,7 +11,7 @@
"""
from __future__ import generators, print_function
-
+#pylint: disable=redefined-variable-type
def func_no_return():
"""function without return"""