summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2018-04-25 11:18:56 +0200
committerClaudiu Popa <pcmanticore@gmail.com>2018-04-25 11:18:56 +0200
commitb93cdf863896556c95b064f8405d648c69953ffa (patch)
tree4d76d4b6308ad0632c997bf7e2941665abaee67f
parent815b4e3c2abbb07e832bfad35444eef78f083c55 (diff)
downloadpylint-git-b93cdf863896556c95b064f8405d648c69953ffa.tar.gz
`undefined-loop-variable` takes in consideration non-empty iterred objects before emitting
Close #2039
-rw-r--r--ChangeLog4
-rw-r--r--doc/whatsnew/2.0.rst6
-rw-r--r--pylint/checkers/variables.py37
-rw-r--r--pylint/test/functional/undefined_loop_variable.py24
4 files changed, 66 insertions, 5 deletions
diff --git a/ChangeLog b/ChangeLog
index 4c734505c..807aa2dd8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,10 @@ Pylint's ChangeLog
What's New in Pylint 2.0?
=========================
+ * `undefined-loop-variable` takes in consideration non-empty iterred objects before emitting
+
+ Close #2039
+
* Add support for nupmydoc optional return value names.
Close #2030
diff --git a/doc/whatsnew/2.0.rst b/doc/whatsnew/2.0.rst
index f5fa63b0c..98e1dc3aa 100644
--- a/doc/whatsnew/2.0.rst
+++ b/doc/whatsnew/2.0.rst
@@ -125,3 +125,9 @@ Other Changes
* Suppress false-positive ``not-callable`` messages from certain staticmethod descriptors
* `singleton-comparison` will suggest better boolean conditions for negative conditions.
+
+* `undefined-loop-variable` takes in consideration non-empty iterred objects before emitting.
+
+ For instance, if the loop iterable is not empty, this check will no longer be emitted.
+
+
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 22468a997..1b960910f 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -38,6 +38,7 @@ import re
import astroid
from astroid import decorators
from astroid import modutils
+from astroid import objects
from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH
from pylint.utils import get_global_option
from pylint.checkers import BaseChecker
@@ -930,11 +931,37 @@ class VariablesChecker(BaseChecker):
continue
_astmts.append(stmt)
astmts = _astmts
- if len(astmts) == 1:
- assign = astmts[0].assign_type()
- if (isinstance(assign, (astroid.For, astroid.Comprehension,
- astroid.GeneratorExp))
- and assign.statement() is not node.statement()):
+ if len(astmts) != 1:
+ return
+
+ assign = astmts[0].assign_type()
+ if not (isinstance(assign, (astroid.For, astroid.Comprehension, astroid.GeneratorExp))
+ and assign.statement() is not node.statement()):
+ return
+
+ # For functions we can do more by inferring the length of the iterred object
+ if not isinstance(assign, astroid.For):
+ self.add_message('undefined-loop-variable', args=name, node=node)
+ return
+
+ try:
+ inferred = next(assign.iter.infer())
+ except astroid.InferenceError:
+ self.add_message('undefined-loop-variable', args=name, node=node)
+ else:
+ sequences = (
+ astroid.List,
+ astroid.Tuple,
+ astroid.Dict,
+ astroid.Set,
+ objects.FrozenSet,
+ )
+ if not isinstance(inferred, sequences):
+ self.add_message('undefined-loop-variable', args=name, node=node)
+ return
+
+ elements = getattr(inferred, 'elts', getattr(inferred, 'items', []))
+ if not elements:
self.add_message('undefined-loop-variable', args=name, node=node)
def _should_ignore_redefined_builtin(self, stmt):
diff --git a/pylint/test/functional/undefined_loop_variable.py b/pylint/test/functional/undefined_loop_variable.py
index 6c28a4088..3840003b5 100644
--- a/pylint/test/functional/undefined_loop_variable.py
+++ b/pylint/test/functional/undefined_loop_variable.py
@@ -35,3 +35,27 @@ for x in []:
pass
for x in range(3):
VAR5 = (lambda: x)()
+
+
+def do_stuff_with_a_list():
+ for var in [1, 2, 3]:
+ pass
+ return var
+
+
+def do_stuff_with_a_set():
+ for var in {1, 2, 3}:
+ pass
+ return var
+
+
+def do_stuff_with_a_dict():
+ for var in {1: 2, 3: 4}:
+ pass
+ return var
+
+
+def do_stuff_with_a_tuple():
+ for var in (1, 2, 3):
+ pass
+ return var