diff options
Diffstat (limited to 'pylint')
| -rw-r--r-- | pylint/checkers/base.py | 3 | ||||
| -rw-r--r-- | pylint/checkers/utils.py | 35 | ||||
| -rw-r--r-- | pylint/checkers/variables.py | 4 | ||||
| -rw-r--r-- | pylint/test/functional/singledispatch_functions.py | 63 | ||||
| -rw-r--r-- | pylint/test/functional/singledispatch_functions.rc | 3 | ||||
| -rw-r--r-- | pylint/test/functional/singledispatch_functions.txt | 1 | ||||
| -rw-r--r-- | pylint/test/functional/singledispatch_functions_py3.py | 63 | ||||
| -rw-r--r-- | pylint/test/functional/singledispatch_functions_py3.rc | 3 | ||||
| -rw-r--r-- | pylint/test/functional/singledispatch_functions_py3.txt | 1 | ||||
| -rw-r--r-- | pylint/test/test_functional.py | 7 |
10 files changed, 177 insertions, 6 deletions
diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 835b07f9e..0db9addf0 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -384,7 +384,8 @@ class BasicErrorChecker(_BasicChecker): 'duplicate-argument-name', 'nonlocal-and-global') def visit_functiondef(self, node): self._check_nonlocal_and_global(node) - if not redefined_by_decorator(node): + if (not redefined_by_decorator(node) and + not utils.is_registered_in_singledispatch_function(node)): self._check_redefinition(node.is_method() and 'method' or 'function', node) # checks for max returns, branch, return in __init__ returns = node.nodes_of_class(astroid.Return, diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 589ed6841..e3f0e2f94 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -503,9 +503,12 @@ def decorated_with(func, qnames): """Determine if the `func` node has a decorator with the qualified name `qname`.""" decorators = func.decorators.nodes if func.decorators else [] for decorator_node in decorators: - dec = safe_infer(decorator_node) - if dec and dec.qname() in qnames: - return True + try: + if any(i is not None and i.qname() in qnames for i in decorator_node.infer()): + return True + except astroid.InferenceError: + continue + return False def unimplemented_abstract_methods(node, is_abstract_cb=None): @@ -803,3 +806,29 @@ def node_type(node): except astroid.InferenceError: return return types.pop() if types else None + + +def is_registered_in_singledispatch_function(node): + if not isinstance(node, astroid.FunctionDef): + return False + + decorators = node.decorators.nodes if node.decorators else [] + for decorator in decorators: + # func.register are function calls + if not isinstance(decorator, astroid.Call): + continue + + func = decorator.func + if not isinstance(func, astroid.Attribute) or func.attrname != 'register': + continue + + try: + func_def = next(func.expr.infer()) + except astroid.InferenceError: + continue + + singledispatch_qnames = ('functools.singledispatch', 'singledispatch.singledispatch') + if isinstance(func_def, astroid.FunctionDef): + return decorated_with(func_def, singledispatch_qnames) + + return False diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 0823864a0..c8b49f7b6 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -692,6 +692,10 @@ class VariablesChecker(BaseChecker): if is_method and node.is_abstract(): return + # Don't check arguments of singledispatch.register function. + if utils.is_registered_in_singledispatch_function(node): + return + global_names = _flattened_scope_names(node.nodes_of_class(astroid.Global)) nonlocal_names = _flattened_scope_names(node.nodes_of_class(astroid.Nonlocal)) diff --git a/pylint/test/functional/singledispatch_functions.py b/pylint/test/functional/singledispatch_functions.py new file mode 100644 index 000000000..f8b816a4e --- /dev/null +++ b/pylint/test/functional/singledispatch_functions.py @@ -0,0 +1,63 @@ +# pylint: disable=missing-docstring,import-error,unused-import,assignment-from-no-return +from __future__ import print_function +from UNINFERABLE import uninferable_decorator, uninferable_func + +try: + from functools import singledispatch +except ImportError: + from singledispatch import singledispatch + +my_single_dispatch = singledispatch # pylint: disable=invalid-name + + +@singledispatch +def func(arg): + return arg + + +@func.register(str) +def _(arg): + return 42 + + +@func.register(float) +@func.register(int) +def _(arg): + return 42 + + +@my_single_dispatch +def func2(arg): + return arg + + +@func2.register(int) +def _(arg): + return 42 + + +@singledispatch +def with_extra_arg(arg, verbose=False): + if verbose: + print(arg) + return arg + + +@with_extra_arg.register(str) +def _(arg, verbose=False): + return arg[::-1] + + +@uninferable_decorator +def uninferable(arg): + return 2*arg + + +@uninferable.register(str) +def bad_single_dispatch(arg): + return arg + + +@uninferable_func.register(str) +def test(arg): + return arg diff --git a/pylint/test/functional/singledispatch_functions.rc b/pylint/test/functional/singledispatch_functions.rc new file mode 100644 index 000000000..fc795dc6d --- /dev/null +++ b/pylint/test/functional/singledispatch_functions.rc @@ -0,0 +1,3 @@ +[testoptions] +;http://bugs.python.org/issue10445 +max_pyver=3.0 diff --git a/pylint/test/functional/singledispatch_functions.txt b/pylint/test/functional/singledispatch_functions.txt new file mode 100644 index 000000000..60dd49880 --- /dev/null +++ b/pylint/test/functional/singledispatch_functions.txt @@ -0,0 +1 @@ +unused-argument:51:uninferable:Unused argument 'arg' diff --git a/pylint/test/functional/singledispatch_functions_py3.py b/pylint/test/functional/singledispatch_functions_py3.py new file mode 100644 index 000000000..f8b816a4e --- /dev/null +++ b/pylint/test/functional/singledispatch_functions_py3.py @@ -0,0 +1,63 @@ +# pylint: disable=missing-docstring,import-error,unused-import,assignment-from-no-return +from __future__ import print_function +from UNINFERABLE import uninferable_decorator, uninferable_func + +try: + from functools import singledispatch +except ImportError: + from singledispatch import singledispatch + +my_single_dispatch = singledispatch # pylint: disable=invalid-name + + +@singledispatch +def func(arg): + return arg + + +@func.register(str) +def _(arg): + return 42 + + +@func.register(float) +@func.register(int) +def _(arg): + return 42 + + +@my_single_dispatch +def func2(arg): + return arg + + +@func2.register(int) +def _(arg): + return 42 + + +@singledispatch +def with_extra_arg(arg, verbose=False): + if verbose: + print(arg) + return arg + + +@with_extra_arg.register(str) +def _(arg, verbose=False): + return arg[::-1] + + +@uninferable_decorator +def uninferable(arg): + return 2*arg + + +@uninferable.register(str) +def bad_single_dispatch(arg): + return arg + + +@uninferable_func.register(str) +def test(arg): + return arg diff --git a/pylint/test/functional/singledispatch_functions_py3.rc b/pylint/test/functional/singledispatch_functions_py3.rc new file mode 100644 index 000000000..d43f6c339 --- /dev/null +++ b/pylint/test/functional/singledispatch_functions_py3.rc @@ -0,0 +1,3 @@ +[testoptions] +;http://bugs.python.org/issue10445 +min_pyver=3.4 diff --git a/pylint/test/functional/singledispatch_functions_py3.txt b/pylint/test/functional/singledispatch_functions_py3.txt new file mode 100644 index 000000000..60dd49880 --- /dev/null +++ b/pylint/test/functional/singledispatch_functions_py3.txt @@ -0,0 +1 @@ +unused-argument:51:uninferable:Unused argument 'arg' diff --git a/pylint/test/test_functional.py b/pylint/test/test_functional.py index cc591a4fe..a8ab1850c 100644 --- a/pylint/test/test_functional.py +++ b/pylint/test/test_functional.py @@ -238,8 +238,7 @@ class LintModuleTest(unittest.TestCase): self._test_file = test_file def setUp(self): - if (sys.version_info < self._test_file.options['min_pyver'] - or sys.version_info >= self._test_file.options['max_pyver']): + if self._should_be_skipped_due_to_version(): self.skipTest( 'Test cannot run with Python %s.' % (sys.version.split(' ')[0],)) missing = [] @@ -261,6 +260,10 @@ class LintModuleTest(unittest.TestCase): 'Test cannot run with Python implementation %r' % (implementation, )) + def _should_be_skipped_due_to_version(self): + return (sys.version_info < self._test_file.options['min_pyver'] or + sys.version_info > self._test_file.options['max_pyver']) + def __str__(self): return "%s (%s.%s)" % (self._test_file.base, self.__class__.__module__, self.__class__.__name__) |
