From 48de7002126f9c0a73c299ebd8d494612d42a285 Mon Sep 17 00:00:00 2001 From: Laura M?dioni Date: Fri, 30 Oct 2015 13:20:27 +0100 Subject: check if the type of a variable is redefined (at a function, class or module scope) related to issue #674 --- CONTRIBUTORS.txt | 2 +- pylint/checkers/base.py | 88 ++++++++++++++++++++++ pylint/test/functional/bad_continuation.py | 2 +- pylint/test/functional/member_checks.py | 2 +- pylint/test/functional/redefined_variable_type.py | 50 ++++++++++++ pylint/test/functional/redefined_variable_type.txt | 6 ++ pylint/test/functional/undefined_variable.py | 2 +- .../input/func_typecheck_callfunc_assigment.py | 2 +- 8 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 pylint/test/functional/redefined_variable_type.py create mode 100644 pylint/test/functional/redefined_variable_type.txt 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""" -- cgit v1.2.1