summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAshley Whetter <AWhetter@users.noreply.github.com>2019-05-26 00:29:11 -0700
committerClaudiu Popa <pcmanticore@gmail.com>2019-05-26 09:29:11 +0200
commit1669bc3868b198a1e97fae4fa60e0d6cfaed53d9 (patch)
tree2e0b905f00ad0af81543b22329058afc31214039
parenta9cb1c740a9f3d570bc90b42d1d3015aee95712c (diff)
downloadpylint-git-1669bc3868b198a1e97fae4fa60e0d6cfaed53d9.tar.gz
Fixed false positives for function stubs (#2927)
Close #1581
-rw-r--r--ChangeLog4
-rw-r--r--pylint/checkers/base.py13
-rw-r--r--pylint/checkers/utils.py12
-rw-r--r--pylint/checkers/variables.py79
-rw-r--r--pylint/test/functional/typing_use.py51
-rw-r--r--pylint/test/functional/typing_use.rc2
-rw-r--r--pylint/test/functional/typing_use.txt1
7 files changed, 127 insertions, 35 deletions
diff --git a/ChangeLog b/ChangeLog
index 9ad2a79b6..5afba3ebc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -147,6 +147,10 @@ Release date: TBA
Close #202
+* Fixed `unused-argument` and `function-redefined` getting raised for
+ functions decorated with `typing.overload`.
+
+ Close #1581
What's New in Pylint 2.3.0?
===========================
diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py
index e9aecb287..76ef09608 100644
--- a/pylint/checkers/base.py
+++ b/pylint/checkers/base.py
@@ -836,7 +836,15 @@ class BasicErrorChecker(_BasicChecker):
def _check_redefinition(self, redeftype, node):
"""check for redefinition of a function / method / class name"""
parent_frame = node.parent.frame()
- defined_self = parent_frame[node.name]
+ # Ignore function stubs created for type information
+ defined_self = next(
+ (
+ local
+ for local in parent_frame.locals[node.name]
+ if not utils.is_overload_stub(local)
+ ),
+ node,
+ )
if defined_self is not node and not astroid.are_exclusive(node, defined_self):
# Additional checks for methods which are not considered
@@ -847,6 +855,9 @@ class BasicErrorChecker(_BasicChecker):
):
return
+ if utils.is_overload_stub(node):
+ return
+
dummy_variables_rgx = lint_utils.get_global_option(
self, "dummy-variables-rgx", default=None
)
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index 2f7402892..826181845 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -1225,3 +1225,15 @@ def is_subclass_of(child: astroid.ClassDef, parent: astroid.ClassDef) -> bool:
except _NonDeducibleTypeHierarchy:
continue
return False
+
+
+@lru_cache(maxsize=1024)
+def is_overload_stub(node: astroid.node_classes.NodeNG) -> bool:
+ """Check if a node if is a function stub decorated with typing.overload.
+
+ :param node: Node to check.
+ :returns: True if node is an overload function stub. False otherwise.
+ """
+ return isinstance(node, astroid.FunctionDef) and decorated_with(
+ node, ["typing.overload"]
+ )
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 4ae13cadf..26cb94555 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -968,6 +968,45 @@ class VariablesChecker(BaseChecker):
regex = authorized_rgx
return regex and regex.match(name)
+ def _check_unused_arguments(self, name, node, stmt, argnames):
+ is_method = node.is_method()
+ klass = node.parent.frame()
+ if is_method and isinstance(klass, astroid.ClassDef):
+ confidence = (
+ INFERENCE if utils.has_known_bases(klass) else INFERENCE_FAILURE
+ )
+ else:
+ confidence = HIGH
+
+ if is_method:
+ # Don't warn for the first argument of a (non static) method
+ if node.type != "staticmethod" and name == argnames[0]:
+ return
+ # Don't warn for argument of an overridden method
+ overridden = overridden_method(klass, node.name)
+ if overridden is not None and name in overridden.argnames():
+ return
+ if node.name in utils.PYMETHODS and node.name not in (
+ "__init__",
+ "__new__",
+ ):
+ return
+ # Don't check callback arguments
+ if any(
+ node.name.startswith(cb) or node.name.endswith(cb)
+ for cb in self.config.callbacks
+ ):
+ return
+ # Don't check arguments of singledispatch.register function.
+ if utils.is_registered_in_singledispatch_function(node):
+ return
+
+ # Don't check function stubs created only for type information
+ if utils.is_overload_stub(node):
+ return
+
+ self.add_message("unused-argument", args=name, node=stmt, confidence=confidence)
+
def _check_is_unused(self, name, node, stmt, global_names, nonlocal_names):
# pylint: disable=too-many-branches
# Ignore some special names specified by user configuration.
@@ -991,42 +1030,9 @@ class VariablesChecker(BaseChecker):
argnames = list(
itertools.chain(node.argnames(), [arg.name for arg in node.args.kwonlyargs])
)
- is_method = node.is_method()
- klass = node.parent.frame()
- if is_method and isinstance(klass, astroid.ClassDef):
- confidence = (
- INFERENCE if utils.has_known_bases(klass) else INFERENCE_FAILURE
- )
- else:
- confidence = HIGH
-
# Care about functions with unknown argument (builtins)
if name in argnames:
- if is_method:
- # Don't warn for the first argument of a (non static) method
- if node.type != "staticmethod" and name == argnames[0]:
- return
- # Don't warn for argument of an overridden method
- overridden = overridden_method(klass, node.name)
- if overridden is not None and name in overridden.argnames():
- return
- if node.name in utils.PYMETHODS and node.name not in (
- "__init__",
- "__new__",
- ):
- return
- # Don't check callback arguments
- if any(
- node.name.startswith(cb) or node.name.endswith(cb)
- for cb in self.config.callbacks
- ):
- return
- # Don't check arguments of singledispatch.register function.
- if utils.is_registered_in_singledispatch_function(node):
- return
- self.add_message(
- "unused-argument", args=name, node=stmt, confidence=confidence
- )
+ self._check_unused_arguments(name, node, stmt, argnames)
else:
if stmt.parent and isinstance(
stmt.parent, (astroid.Assign, astroid.AnnAssign)
@@ -1069,6 +1075,11 @@ class VariablesChecker(BaseChecker):
self.add_message("unused-import", args=msg, node=stmt)
return
message_name = "unused-variable"
+
+ # Don't check function stubs created only for type information
+ if utils.is_overload_stub(node):
+ return
+
self.add_message(message_name, args=name, node=stmt)
def leave_functiondef(self, node):
diff --git a/pylint/test/functional/typing_use.py b/pylint/test/functional/typing_use.py
new file mode 100644
index 000000000..3e2c48ba0
--- /dev/null
+++ b/pylint/test/functional/typing_use.py
@@ -0,0 +1,51 @@
+# pylint: disable=missing-docstring
+
+import typing
+
+
+@typing.overload
+def double_with_docstring(arg: str) -> str:
+ """Return arg, concatenated with itself."""
+
+
+@typing.overload
+def double_with_docstring(arg: int) -> int:
+ """Return twice arg."""
+
+
+def double_with_docstring(arg):
+ """Return 2 * arg."""
+ return 2 * arg
+
+
+def double_with_docstring(arg): # [function-redefined]
+ """Redefined function implementation"""
+ return 2 * arg
+
+
+@typing.overload
+def double_with_ellipsis(arg: str) -> str:
+ ...
+
+
+@typing.overload
+def double_with_ellipsis(arg: int) -> int:
+ ...
+
+
+def double_with_ellipsis(arg):
+ return 2 * arg
+
+
+@typing.overload
+def double_with_pass(arg: str) -> str:
+ pass
+
+
+@typing.overload
+def double_with_pass(arg: int) -> int:
+ pass
+
+
+def double_with_pass(arg):
+ return 2 * arg
diff --git a/pylint/test/functional/typing_use.rc b/pylint/test/functional/typing_use.rc
new file mode 100644
index 000000000..0ba2b6333
--- /dev/null
+++ b/pylint/test/functional/typing_use.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.6
diff --git a/pylint/test/functional/typing_use.txt b/pylint/test/functional/typing_use.txt
new file mode 100644
index 000000000..1d0233c98
--- /dev/null
+++ b/pylint/test/functional/typing_use.txt
@@ -0,0 +1 @@
+function-redefined:21:double_with_docstring:"function already defined line 16"