summaryrefslogtreecommitdiff
path: root/pylint
diff options
context:
space:
mode:
Diffstat (limited to 'pylint')
-rw-r--r--pylint/checkers/base.py3
-rw-r--r--pylint/checkers/utils.py35
-rw-r--r--pylint/checkers/variables.py4
-rw-r--r--pylint/test/functional/singledispatch_functions.py63
-rw-r--r--pylint/test/functional/singledispatch_functions.rc3
-rw-r--r--pylint/test/functional/singledispatch_functions.txt1
-rw-r--r--pylint/test/functional/singledispatch_functions_py3.py63
-rw-r--r--pylint/test/functional/singledispatch_functions_py3.rc3
-rw-r--r--pylint/test/functional/singledispatch_functions_py3.txt1
-rw-r--r--pylint/test/test_functional.py7
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__)