summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Byrne <31762852+mbyrnepr2@users.noreply.github.com>2021-12-05 13:07:14 +0100
committerGitHub <noreply@github.com>2021-12-05 13:07:14 +0100
commita7302dbe6b58209388f88fdfeae41736e0986136 (patch)
treee68e8cea95e33b5b67eb311b3a8621989dc34bf5
parent7873b774b3dfb3183b630406d37bdb7044280ca9 (diff)
downloadpylint-git-a7302dbe6b58209388f88fdfeae41736e0986136.tar.gz
Treat `typing.NamedTuple` self as a sequence (#5346)
* Treat `typing.NamedTuple` self as a sequence Closes #5312 * Support emmitting `unbalanced-tuple-unpacking` with `typing.NamedTuple` * Add typehint and default to `None` * Use Marc's suggested improvements
-rw-r--r--ChangeLog4
-rw-r--r--pylint/checkers/variables.py10
-rw-r--r--tests/functional/u/unbalanced_tuple_unpacking.py24
-rw-r--r--tests/functional/u/unbalanced_tuple_unpacking.txt12
-rw-r--r--tests/functional/u/unpacking_non_sequence.py10
-rw-r--r--tests/functional/u/unpacking_non_sequence.txt20
6 files changed, 63 insertions, 17 deletions
diff --git a/ChangeLog b/ChangeLog
index f77b7e870..8ddac4258 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -18,6 +18,10 @@ Release date: TBA
Closes #4716
+* Fix false positive - Allow unpacking of ``self`` in a subclass of ``typing.NamedTuple``.
+
+ Closes #5312
+
* Fix false negative for ``consider-iterating-dictionary`` during membership checks encapsulated in iterables
or ``not in`` checks
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index f5605260a..f2bba355c 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -2130,9 +2130,17 @@ class VariablesChecker(BaseChecker):
):
# Variable-length argument, we can't determine the length.
return
+
+ # Attempt to check unpacking is properly balanced
+ values: Optional[List] = None
if isinstance(inferred, (nodes.Tuple, nodes.List)):
- # attempt to check unpacking is properly balanced
values = inferred.itered()
+ elif isinstance(inferred, astroid.Instance) and any(
+ ancestor.qname() == "typing.NamedTuple" for ancestor in inferred.ancestors()
+ ):
+ values = [i for i in inferred.values() if isinstance(i, nodes.AssignName)]
+
+ if values:
if len(targets) != len(values):
# Check if we have starred nodes.
if any(isinstance(target, nodes.Starred) for target in targets):
diff --git a/tests/functional/u/unbalanced_tuple_unpacking.py b/tests/functional/u/unbalanced_tuple_unpacking.py
index ed807c0d7..4deb6ce37 100644
--- a/tests/functional/u/unbalanced_tuple_unpacking.py
+++ b/tests/functional/u/unbalanced_tuple_unpacking.py
@@ -1,8 +1,9 @@
"""Check possible unbalanced tuple unpacking """
from __future__ import absolute_import
+from typing import NamedTuple
from functional.u.unpacking import unpack
-# pylint: disable=using-constant-test, useless-object-inheritance,import-outside-toplevel
+# pylint: disable=missing-class-docstring, missing-function-docstring, using-constant-test, useless-object-inheritance,import-outside-toplevel
def do_stuff():
"""This is not right."""
@@ -106,3 +107,24 @@ def test_issue_559():
from ctypes import c_int
root_x, root_y, win_x, win_y = [c_int()] * 4
return root_x, root_y, win_x, win_y
+
+
+class MyClass(NamedTuple):
+ first: float
+ second: float
+ third: float = 1.0
+
+ def my_sum(self):
+ """Unpack 3 variables"""
+ first, second, third = self
+ return first + second + third
+
+ def sum_unpack_3_into_4(self):
+ """Attempt to unpack 3 variables into 4"""
+ first, second, third, fourth = self # [unbalanced-tuple-unpacking]
+ return first + second + third + fourth
+
+ def sum_unpack_3_into_2(self):
+ """Attempt to unpack 3 variables into 2"""
+ first, second = self # [unbalanced-tuple-unpacking]
+ return first + second
diff --git a/tests/functional/u/unbalanced_tuple_unpacking.txt b/tests/functional/u/unbalanced_tuple_unpacking.txt
index 20aa5f40f..c64c1c2ad 100644
--- a/tests/functional/u/unbalanced_tuple_unpacking.txt
+++ b/tests/functional/u/unbalanced_tuple_unpacking.txt
@@ -1,5 +1,7 @@
-unbalanced-tuple-unpacking:9:4:9:27:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED
-unbalanced-tuple-unpacking:14:4:14:29:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
-unbalanced-tuple-unpacking:19:4:19:29:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED
-unbalanced-tuple-unpacking:69:4:69:28:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
-unbalanced-tuple-unpacking:81:8:81:33:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
+unbalanced-tuple-unpacking:10:4:10:27:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED
+unbalanced-tuple-unpacking:15:4:15:29:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
+unbalanced-tuple-unpacking:20:4:20:29:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)":UNDEFINED
+unbalanced-tuple-unpacking:70:4:70:28:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
+unbalanced-tuple-unpacking:82:8:82:33:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.u.unpacking: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
+unbalanced-tuple-unpacking:124:8:124:43:MyClass.sum_unpack_3_into_4:"Possible unbalanced tuple unpacking with sequence defined at line 112: left side has 4 label(s), right side has 3 value(s)":UNDEFINED
+unbalanced-tuple-unpacking:129:8:129:28:MyClass.sum_unpack_3_into_2:"Possible unbalanced tuple unpacking with sequence defined at line 112: left side has 2 label(s), right side has 3 value(s)":UNDEFINED
diff --git a/tests/functional/u/unpacking_non_sequence.py b/tests/functional/u/unpacking_non_sequence.py
index b7ea2189c..e9c23b388 100644
--- a/tests/functional/u/unpacking_non_sequence.py
+++ b/tests/functional/u/unpacking_non_sequence.py
@@ -4,6 +4,7 @@
# pylint: disable=using-constant-test, no-init, missing-docstring, wrong-import-order,wrong-import-position,no-else-return, useless-object-inheritance
from os import rename as nonseq_func
from functional.u.unpacking import nonseq
+from typing import NamedTuple
__revision__ = 0
@@ -137,3 +138,12 @@ def flow_control_unpacking(var=None):
var0, var1 = var
return var0, var1
return None
+
+
+class MyClass(NamedTuple):
+ x: float
+ y: float
+
+ def sum(self):
+ x, y = self
+ return x + y
diff --git a/tests/functional/u/unpacking_non_sequence.txt b/tests/functional/u/unpacking_non_sequence.txt
index 7a8b8a85c..d657af177 100644
--- a/tests/functional/u/unpacking_non_sequence.txt
+++ b/tests/functional/u/unpacking_non_sequence.txt
@@ -1,10 +1,10 @@
-unpacking-non-sequence:77:0:77:15::Attempting to unpack a non-sequence defined at line 74:UNDEFINED
-unpacking-non-sequence:78:0:78:17::Attempting to unpack a non-sequence:UNDEFINED
-unpacking-non-sequence:79:0:79:11::Attempting to unpack a non-sequence None:UNDEFINED
-unpacking-non-sequence:80:0:80:8::Attempting to unpack a non-sequence 1:UNDEFINED
-unpacking-non-sequence:81:0:81:13::Attempting to unpack a non-sequence defined at line 9 of functional.u.unpacking:UNDEFINED
-unpacking-non-sequence:82:0:82:15::Attempting to unpack a non-sequence defined at line 11 of functional.u.unpacking:UNDEFINED
-unpacking-non-sequence:83:0:83:18::Attempting to unpack a non-sequence:UNDEFINED
-unpacking-non-sequence:98:8:98:33:ClassUnpacking.test:Attempting to unpack a non-sequence defined at line 74:UNDEFINED
-unpacking-non-sequence:99:8:99:35:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED
-unpacking-non-sequence:100:8:100:31:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED
+unpacking-non-sequence:78:0:78:15::Attempting to unpack a non-sequence defined at line 75:UNDEFINED
+unpacking-non-sequence:79:0:79:17::Attempting to unpack a non-sequence:UNDEFINED
+unpacking-non-sequence:80:0:80:11::Attempting to unpack a non-sequence None:UNDEFINED
+unpacking-non-sequence:81:0:81:8::Attempting to unpack a non-sequence 1:UNDEFINED
+unpacking-non-sequence:82:0:82:13::Attempting to unpack a non-sequence defined at line 9 of functional.u.unpacking:UNDEFINED
+unpacking-non-sequence:83:0:83:15::Attempting to unpack a non-sequence defined at line 11 of functional.u.unpacking:UNDEFINED
+unpacking-non-sequence:84:0:84:18::Attempting to unpack a non-sequence:UNDEFINED
+unpacking-non-sequence:99:8:99:33:ClassUnpacking.test:Attempting to unpack a non-sequence defined at line 75:UNDEFINED
+unpacking-non-sequence:100:8:100:35:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED
+unpacking-non-sequence:101:8:101:31:ClassUnpacking.test:Attempting to unpack a non-sequence:UNDEFINED