From 3c062be3f28319b34150d3f1e7eb01887594cf56 Mon Sep 17 00:00:00 2001 From: AhmadF Date: Wed, 9 Oct 2019 06:34:01 +0330 Subject: [PATCH 1/4] Produce error when NamedTuple is assigned to attribute Fixes #7531 --- mypy/semanal.py | 5 ++++- test-data/unit/check-namedtuple.test | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 790c0c6d96d73..3dc5b6806110b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2120,7 +2120,7 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: """Check if s defines a namedtuple.""" if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, NamedTupleExpr): return True # This is a valid and analyzed named tuple definition, nothing to do here. - if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr): + if len(s.lvalues) != 1 or type(s.lvalues[0]) not in [NameExpr, MemberExpr]: return False lvalue = s.lvalues[0] name = lvalue.name @@ -2128,6 +2128,9 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: self.is_func_scope()) if not is_named_tuple: return False + if isinstance(s.lvalues[0], MemberExpr): + self.fail("NamedTuple type as an attribute is not supported", s.lvalues[0]) + return False # Yes, it's a valid namedtuple, but defer if it is not ready. if not info: self.mark_incomplete(name, lvalue, becomes_typeinfo=True) diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 6d8626456403a..646430388567c 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -853,3 +853,10 @@ b = (1, 2) if not b: ''() # E: "str" not callable [builtins fixtures/tuple.pyi] + +[case testNamedTupleAtRunTime] +from typing import NamedTuple + +class A: + def __init__(self) -> None: + self.b = NamedTuple('x', [('s', str), ('n', int)]) # E: NamedTuple type as an attribute is not supported From 91a477d7ddd9e8ce10558bcf211213c695eb0093 Mon Sep 17 00:00:00 2001 From: AhmadF Date: Thu, 10 Oct 2019 09:13:09 +0330 Subject: [PATCH 2/4] An idea to fix CI --- mypy/semanal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 3dc5b6806110b..2f1b2c329d79b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2120,7 +2120,8 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: """Check if s defines a namedtuple.""" if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, NamedTupleExpr): return True # This is a valid and analyzed named tuple definition, nothing to do here. - if len(s.lvalues) != 1 or type(s.lvalues[0]) not in [NameExpr, MemberExpr]: + if len(s.lvalues) != 1 or not (isinstance(s.lvalues[0], NameExpr) or + isinstance(s.lvalues[0], MemberExpr)): return False lvalue = s.lvalues[0] name = lvalue.name From 66514015df9fc42e3f684c6c4c8560e0e3e21414 Mon Sep 17 00:00:00 2001 From: AhmadF Date: Sat, 12 Oct 2019 10:28:58 +0330 Subject: [PATCH 3/4] Use isinstance with a tuple --- mypy/semanal.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 2f1b2c329d79b..e4d81466dae68 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2120,8 +2120,7 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: """Check if s defines a namedtuple.""" if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, NamedTupleExpr): return True # This is a valid and analyzed named tuple definition, nothing to do here. - if len(s.lvalues) != 1 or not (isinstance(s.lvalues[0], NameExpr) or - isinstance(s.lvalues[0], MemberExpr)): + if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)): return False lvalue = s.lvalues[0] name = lvalue.name From 4129bf91a33491d94ea2c6db37c72d2a4fb6068e Mon Sep 17 00:00:00 2001 From: AhmadF Date: Sat, 12 Oct 2019 10:52:56 +0330 Subject: [PATCH 4/4] Move tests to pythoneval --- mypy/semanal.py | 2 +- test-data/unit/check-namedtuple.test | 7 ------- test-data/unit/pythoneval.test | 12 ++++++++++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index e4d81466dae68..c27b43f01e7cc 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2129,7 +2129,7 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: if not is_named_tuple: return False if isinstance(s.lvalues[0], MemberExpr): - self.fail("NamedTuple type as an attribute is not supported", s.lvalues[0]) + self.fail("NamedTuple type as an attribute is not supported", lvalue) return False # Yes, it's a valid namedtuple, but defer if it is not ready. if not info: diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 646430388567c..6d8626456403a 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -853,10 +853,3 @@ b = (1, 2) if not b: ''() # E: "str" not callable [builtins fixtures/tuple.pyi] - -[case testNamedTupleAtRunTime] -from typing import NamedTuple - -class A: - def __init__(self) -> None: - self.b = NamedTuple('x', [('s', str), ('n', int)]) # E: NamedTuple type as an attribute is not supported diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 1a3c778b4604d..0341d96eaecef 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1453,3 +1453,15 @@ def f_suppresses() -> int: [out] _testUnreachableWithStdlibContextManagersNoStrictOptional.py:9: error: Statement is unreachable _testUnreachableWithStdlibContextManagersNoStrictOptional.py:15: error: Statement is unreachable + +[case testNamedTupleAtRunTime] +from typing import NamedTuple + +class A: + def __init__(self) -> None: + self.b = NamedTuple('x', [('s', str), ('n', int)]) + +reveal_type(A().b) +[out] +_testNamedTupleAtRunTime.py:5: error: NamedTuple type as an attribute is not supported +_testNamedTupleAtRunTime.py:7: note: Revealed type is 'Any'