Skip to content

Commit 3cedd27

Browse files
authored
Fix crash on NamedTuple as attribute (#15404)
Fixes #15380 Note I also update the `NamedTuple` fixture to be much closer to real definition in `typing.pyi` in typeshed.
1 parent 2c66cba commit 3cedd27

File tree

3 files changed

+26
-8
lines changed

3 files changed

+26
-8
lines changed

mypy/semanal.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -3069,6 +3069,12 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
30693069
if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
30703070
return False
30713071
lvalue = s.lvalues[0]
3072+
if isinstance(lvalue, MemberExpr):
3073+
if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.callee, RefExpr):
3074+
fullname = s.rvalue.callee.fullname
3075+
if fullname == "collections.namedtuple" or fullname in TYPED_NAMEDTUPLE_NAMES:
3076+
self.fail("NamedTuple type as an attribute is not supported", lvalue)
3077+
return False
30723078
name = lvalue.name
30733079
namespace = self.qualified_name(name)
30743080
with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
@@ -3077,9 +3083,6 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
30773083
)
30783084
if internal_name is None:
30793085
return False
3080-
if isinstance(lvalue, MemberExpr):
3081-
self.fail("NamedTuple type as an attribute is not supported", lvalue)
3082-
return False
30833086
if internal_name != name:
30843087
self.fail(
30853088
'First argument to namedtuple() should be "{}", not "{}"'.format(

test-data/unit/check-namedtuple.test

+12-2
Original file line numberDiff line numberDiff line change
@@ -938,8 +938,9 @@ class A:
938938
def __init__(self) -> None:
939939
self.b = NamedTuple('x', [('s', str), ('n', int)]) # E: NamedTuple type as an attribute is not supported
940940

941-
reveal_type(A().b) # N: Revealed type is "Any"
941+
reveal_type(A().b) # N: Revealed type is "typing.NamedTuple"
942942
[builtins fixtures/tuple.pyi]
943+
[typing fixtures/typing-namedtuple.pyi]
943944

944945
[case testNamedTupleWrongfile]
945946
from typing import NamedTuple
@@ -983,7 +984,7 @@ class Both2(Other, Bar): ...
983984
class Both3(Biz, Other): ...
984985

985986
def print_namedtuple(obj: NamedTuple) -> None:
986-
reveal_type(obj.name) # N: Revealed type is "builtins.str"
987+
reveal_type(obj._fields) # N: Revealed type is "builtins.tuple[builtins.str, ...]"
987988

988989
b1: Bar
989990
b2: Baz
@@ -1337,3 +1338,12 @@ class SNT(NT[int]): ...
13371338
reveal_type(SNT("test", 42).meth()) # N: Revealed type is "Tuple[builtins.str, builtins.int, fallback=__main__.SNT]"
13381339
[builtins fixtures/tuple.pyi]
13391340
[typing fixtures/typing-namedtuple.pyi]
1341+
1342+
[case testNoCrashUnsupportedNamedTuple]
1343+
from typing import NamedTuple
1344+
class Test:
1345+
def __init__(self, field) -> None:
1346+
self.Item = NamedTuple("x", [(field, str)]) # E: NamedTuple type as an attribute is not supported
1347+
self.item: self.Item # E: Name "self.Item" is not defined
1348+
[builtins fixtures/tuple.pyi]
1349+
[typing fixtures/typing-namedtuple.pyi]

test-data/unit/fixtures/typing-namedtuple.pyi

+8-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Type = 0
66
Literal = 0
77
Optional = 0
88
Self = 0
9+
Tuple = 0
10+
ClassVar = 0
911

1012
T = TypeVar('T')
1113
T_co = TypeVar('T_co', covariant=True)
@@ -18,6 +20,9 @@ class Mapping(Iterable[KT], Generic[KT, T_co]):
1820
def keys(self) -> Iterable[T]: pass # Approximate return type
1921
def __getitem__(self, key: T) -> T_co: pass
2022

21-
class Tuple(Sequence): pass
22-
class NamedTuple(Tuple):
23-
name: str
23+
class NamedTuple(tuple[Any, ...]):
24+
_fields: ClassVar[tuple[str, ...]]
25+
@overload
26+
def __init__(self, typename: str, fields: Iterable[tuple[str, Any]] = ...) -> None: ...
27+
@overload
28+
def __init__(self, typename: str, fields: None = None, **kwargs: Any) -> None: ...

0 commit comments

Comments
 (0)