Skip to content

Commit

Permalink
Fix crash on NamedTuple as attribute (#15404)
Browse files Browse the repository at this point in the history
Fixes #15380

Note I also update the `NamedTuple` fixture to be much closer to real
definition in `typing.pyi` in typeshed.
  • Loading branch information
ilevkivskyi authored and jhance committed Jun 12, 2023
1 parent 9caf095 commit 5e6e488
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 8 deletions.
9 changes: 6 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3067,6 +3067,12 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
return False
lvalue = s.lvalues[0]
if isinstance(lvalue, MemberExpr):
if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.callee, RefExpr):
fullname = s.rvalue.callee.fullname
if fullname == "collections.namedtuple" or fullname in TYPED_NAMEDTUPLE_NAMES:
self.fail("NamedTuple type as an attribute is not supported", lvalue)
return False
name = lvalue.name
namespace = self.qualified_name(name)
with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
Expand All @@ -3075,9 +3081,6 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
)
if internal_name is None:
return False
if isinstance(lvalue, MemberExpr):
self.fail("NamedTuple type as an attribute is not supported", lvalue)
return False
if internal_name != name:
self.fail(
'First argument to namedtuple() should be "{}", not "{}"'.format(
Expand Down
14 changes: 12 additions & 2 deletions test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -938,8 +938,9 @@ class A:
def __init__(self) -> None:
self.b = NamedTuple('x', [('s', str), ('n', int)]) # E: NamedTuple type as an attribute is not supported

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

[case testNamedTupleWrongfile]
from typing import NamedTuple
Expand Down Expand Up @@ -983,7 +984,7 @@ class Both2(Other, Bar): ...
class Both3(Biz, Other): ...

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

b1: Bar
b2: Baz
Expand Down Expand Up @@ -1337,3 +1338,12 @@ class SNT(NT[int]): ...
reveal_type(SNT("test", 42).meth()) # N: Revealed type is "Tuple[builtins.str, builtins.int, fallback=__main__.SNT]"
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-namedtuple.pyi]

[case testNoCrashUnsupportedNamedTuple]
from typing import NamedTuple
class Test:
def __init__(self, field) -> None:
self.Item = NamedTuple("x", [(field, str)]) # E: NamedTuple type as an attribute is not supported
self.item: self.Item # E: Name "self.Item" is not defined
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-namedtuple.pyi]
11 changes: 8 additions & 3 deletions test-data/unit/fixtures/typing-namedtuple.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Type = 0
Literal = 0
Optional = 0
Self = 0
Tuple = 0
ClassVar = 0

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

class Tuple(Sequence): pass
class NamedTuple(Tuple):
name: str
class NamedTuple(tuple[Any, ...]):
_fields: ClassVar[tuple[str, ...]]
@overload
def __init__(self, typename: str, fields: Iterable[tuple[str, Any]] = ...) -> None: ...
@overload
def __init__(self, typename: str, fields: None = None, **kwargs: Any) -> None: ...

0 comments on commit 5e6e488

Please sign in to comment.