summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--doc/whatsnew/2.13.rst7
-rw-r--r--pylint/checkers/variables.py19
-rw-r--r--tests/functional/u/unused/unused_argument_py3.py9
-rw-r--r--tests/functional/u/unused/unused_name_from_wildcard_import.py (renamed from tests/functional/u/unused/unused_name_from_wilcard_import.py)0
-rw-r--r--tests/functional/u/unused/unused_name_from_wildcard_import.txt (renamed from tests/functional/u/unused/unused_name_from_wilcard_import.txt)2
6 files changed, 38 insertions, 6 deletions
diff --git a/ChangeLog b/ChangeLog
index f32d51ddf..9656ea62c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -16,6 +16,7 @@ Release date: TBA
Closes #578
+
* Fix false negative for ``no-member`` when attempting to assign an instance
attribute to itself without any prior assignment.
@@ -41,6 +42,12 @@ Release date: TBA
Closes #2793
+* Fixed false positive for ``unused-argument`` when a ``nonlocal`` name is used
+ in a nested function that is returned without being called by its parent.
+
+ Closes #5187
+
+
What's New in Pylint 2.13.0?
============================
Release date: 2022-03-24
diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst
index 856553983..b0bfd1321 100644
--- a/doc/whatsnew/2.13.rst
+++ b/doc/whatsnew/2.13.rst
@@ -548,7 +548,12 @@ Other Changes
Closes #5950
+* Fixed false positive for ``unused-argument`` when a ``nonlocal`` name is used
+ in a nested function that is returned without being called by its parent.
+
+ Closes #5187
+
* Avoid emitting ``raising-bad-type`` when there is inference ambiguity on
the variable being raised.
- Closes #2793 \ No newline at end of file
+ Closes #2793
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 08acaefb8..15fa5008b 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -16,6 +16,8 @@ from typing import (
Any,
DefaultDict,
Dict,
+ Iterable,
+ Iterator,
List,
NamedTuple,
Optional,
@@ -343,7 +345,9 @@ def _import_name_is_global(stmt, global_names):
return False
-def _flattened_scope_names(iterator):
+def _flattened_scope_names(
+ iterator: Iterator[Union[nodes.Global, nodes.Nonlocal]]
+) -> Set[str]:
values = (set(stmt.names) for stmt in iterator)
return set(itertools.chain.from_iterable(values))
@@ -2271,7 +2275,9 @@ class VariablesChecker(BaseChecker):
if not elements:
self.add_message("undefined-loop-variable", args=node.name, node=node)
- def _check_is_unused(self, name, node, stmt, global_names, nonlocal_names):
+ def _check_is_unused(
+ self, name, node, stmt, global_names, nonlocal_names: Iterable[str]
+ ):
# Ignore some special names specified by user configuration.
if self._is_name_ignored(stmt, name):
return
@@ -2293,7 +2299,7 @@ class VariablesChecker(BaseChecker):
argnames = node.argnames()
# Care about functions with unknown argument (builtins)
if name in argnames:
- self._check_unused_arguments(name, node, stmt, argnames)
+ self._check_unused_arguments(name, node, stmt, argnames, nonlocal_names)
else:
if stmt.parent and isinstance(
stmt.parent, (nodes.Assign, nodes.AnnAssign, nodes.Tuple)
@@ -2360,7 +2366,9 @@ class VariablesChecker(BaseChecker):
regex = authorized_rgx
return regex and regex.match(name)
- def _check_unused_arguments(self, name, node, stmt, argnames):
+ def _check_unused_arguments(
+ self, name, node, stmt, argnames, nonlocal_names: Iterable[str]
+ ):
is_method = node.is_method()
klass = node.parent.frame(future=True)
if is_method and isinstance(klass, nodes.ClassDef):
@@ -2401,6 +2409,9 @@ class VariablesChecker(BaseChecker):
if utils.is_protocol_class(klass):
return
+ if name in nonlocal_names:
+ return
+
self.add_message("unused-argument", args=name, node=stmt, confidence=confidence)
def _check_late_binding_closure(self, node: nodes.Name) -> None:
diff --git a/tests/functional/u/unused/unused_argument_py3.py b/tests/functional/u/unused/unused_argument_py3.py
index 4d0fd9adc..2529e6caa 100644
--- a/tests/functional/u/unused/unused_argument_py3.py
+++ b/tests/functional/u/unused/unused_argument_py3.py
@@ -7,3 +7,12 @@ def func(first, *, second): # [unused-argument, unused-argument]
def only_raises(first, second=42): # [unused-argument]
if first == 24:
raise ValueError
+
+
+def increment_factory(initial):
+
+ def increment():
+ nonlocal initial
+ initial += 1
+
+ return increment
diff --git a/tests/functional/u/unused/unused_name_from_wilcard_import.py b/tests/functional/u/unused/unused_name_from_wildcard_import.py
index 54658b855..54658b855 100644
--- a/tests/functional/u/unused/unused_name_from_wilcard_import.py
+++ b/tests/functional/u/unused/unused_name_from_wildcard_import.py
diff --git a/tests/functional/u/unused/unused_name_from_wilcard_import.txt b/tests/functional/u/unused/unused_name_from_wildcard_import.txt
index c939b8260..0289a13fb 100644
--- a/tests/functional/u/unused/unused_name_from_wilcard_import.txt
+++ b/tests/functional/u/unused/unused_name_from_wildcard_import.txt
@@ -1,4 +1,4 @@
-unused-wildcard-import:3:0:3:34::Unused import(s) func and only_raises from wildcard import of unused_argument_py3:UNDEFINED
+unused-wildcard-import:3:0:3:34::Unused import(s) func, only_raises and increment_factory from wildcard import of unused_argument_py3:UNDEFINED
wildcard-import:3:0:3:34::Wildcard import unused_argument_py3:UNDEFINED
unused-wildcard-import:4:0:4:38::Unused import(s) VAR from wildcard import of unused_global_variable1:UNDEFINED
wildcard-import:4:0:4:38::Wildcard import unused_global_variable1:UNDEFINED