Skip to content

Commit e193aed

Browse files
authored
Fix for NamedTuple in method [WIP] (#2553)
Fixes #2535.
1 parent 0fe8670 commit e193aed

File tree

4 files changed

+183
-14
lines changed

4 files changed

+183
-14
lines changed

mypy/semanal.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,14 +1681,18 @@ def check_namedtuple(self, node: Expression, var_name: str = None) -> Optional[T
16811681
if not ok:
16821682
# Error. Construct dummy return value.
16831683
return self.build_namedtuple_typeinfo('namedtuple', [], [])
1684+
name = cast(StrExpr, call.args[0]).value
1685+
if name != var_name or self.is_func_scope():
1686+
# Give it a unique name derived from the line number.
1687+
name += '@' + str(call.line)
1688+
info = self.build_namedtuple_typeinfo(name, items, types)
1689+
# Store it as a global just in case it would remain anonymous.
1690+
# (Or in the nearest class if there is one.)
1691+
stnode = SymbolTableNode(GDEF, info, self.cur_mod_id)
1692+
if self.type:
1693+
self.type.names[name] = stnode
16841694
else:
1685-
name = cast(StrExpr, call.args[0]).value
1686-
if name != var_name:
1687-
# Give it a unique name derived from the line number.
1688-
name += '@' + str(call.line)
1689-
info = self.build_namedtuple_typeinfo(name, items, types)
1690-
# Store it as a global just in case it would remain anonymous.
1691-
self.globals[name] = SymbolTableNode(GDEF, info, self.cur_mod_id)
1695+
self.globals[name] = stnode
16921696
call.analyzed = NamedTupleExpr(info)
16931697
call.analyzed.set_line(call.line, call.column)
16941698
return info
@@ -1901,14 +1905,18 @@ def check_typeddict(self, node: Expression, var_name: str = None) -> Optional[Ty
19011905
if not ok:
19021906
# Error. Construct dummy return value.
19031907
return self.build_typeddict_typeinfo('TypedDict', [], [])
1908+
name = cast(StrExpr, call.args[0]).value
1909+
if name != var_name or self.is_func_scope():
1910+
# Give it a unique name derived from the line number.
1911+
name += '@' + str(call.line)
1912+
info = self.build_typeddict_typeinfo(name, items, types)
1913+
# Store it as a global just in case it would remain anonymous.
1914+
# (Or in the nearest class if there is one.)
1915+
stnode = SymbolTableNode(GDEF, info, self.cur_mod_id)
1916+
if self.type:
1917+
self.type.names[name] = stnode
19041918
else:
1905-
name = cast(StrExpr, call.args[0]).value
1906-
if name != var_name:
1907-
# Give it a unique name derived from the line number.
1908-
name += '@' + str(call.line)
1909-
info = self.build_typeddict_typeinfo(name, items, types)
1910-
# Store it as a global just in case it would remain anonymous.
1911-
self.globals[name] = SymbolTableNode(GDEF, info, self.cur_mod_id)
1919+
self.globals[name] = stnode
19121920
call.analyzed = TypedDictExpr(info)
19131921
call.analyzed.set_line(call.line, call.column)
19141922
return info

test-data/unit/check-incremental.test

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,3 +1640,129 @@ follow_imports = skip
16401640
main:3: error: Revealed type is 'builtins.int'
16411641
[out2]
16421642
main:3: error: Revealed type is 'Any'
1643+
1644+
[case testIncrementalNamedTupleInMethod]
1645+
from ntcrash import nope
1646+
[file ntcrash.py]
1647+
from typing import NamedTuple
1648+
class C:
1649+
def f(self) -> None:
1650+
A = NamedTuple('A', [('x', int), ('y', int)])
1651+
[out1]
1652+
main:1: error: Module 'ntcrash' has no attribute 'nope'
1653+
[out2]
1654+
main:1: error: Module 'ntcrash' has no attribute 'nope'
1655+
1656+
[case testIncrementalNamedTupleInMethod2]
1657+
from ntcrash import nope
1658+
[file ntcrash.py]
1659+
from typing import NamedTuple
1660+
class C:
1661+
class D:
1662+
def f(self) -> None:
1663+
A = NamedTuple('A', [('x', int), ('y', int)])
1664+
[out1]
1665+
main:1: error: Module 'ntcrash' has no attribute 'nope'
1666+
[out2]
1667+
main:1: error: Module 'ntcrash' has no attribute 'nope'
1668+
1669+
[case testIncrementalNamedTupleInMethod3]
1670+
from ntcrash import nope
1671+
[file ntcrash.py]
1672+
from typing import NamedTuple
1673+
class C:
1674+
def a(self):
1675+
class D:
1676+
def f(self) -> None:
1677+
A = NamedTuple('A', [('x', int), ('y', int)])
1678+
[out1]
1679+
main:1: error: Module 'ntcrash' has no attribute 'nope'
1680+
[out2]
1681+
main:1: error: Module 'ntcrash' has no attribute 'nope'
1682+
1683+
[case testIncrementalNamedTupleInMethod4]
1684+
from ntcrash import C
1685+
reveal_type(C().a)
1686+
reveal_type(C().b)
1687+
reveal_type(C().c)
1688+
[file ntcrash.py]
1689+
from typing import NamedTuple
1690+
class C:
1691+
def __init__(self) -> None:
1692+
A = NamedTuple('A', [('x', int)])
1693+
self.a = A(0)
1694+
self.b = A(0) # type: A
1695+
self.c = A
1696+
[out1]
1697+
main:2: error: Revealed type is 'Tuple[builtins.int, fallback=ntcrash.C.A@4]'
1698+
main:3: error: Revealed type is 'Tuple[builtins.int, fallback=ntcrash.C.A@4]'
1699+
main:4: error: Revealed type is 'def (x: builtins.int) -> Tuple[builtins.int, fallback=ntcrash.C.A@4]'
1700+
[out2]
1701+
main:2: error: Revealed type is 'Tuple[builtins.int, fallback=ntcrash.C.A@4]'
1702+
main:3: error: Revealed type is 'Tuple[builtins.int, fallback=ntcrash.C.A@4]'
1703+
main:4: error: Revealed type is 'def (x: builtins.int) -> Tuple[builtins.int, fallback=ntcrash.C.A@4]'
1704+
1705+
[case testIncrementalTypedDictInMethod]
1706+
from tdcrash import nope
1707+
[file tdcrash.py]
1708+
from mypy_extensions import TypedDict
1709+
class C:
1710+
def f(self) -> None:
1711+
A = TypedDict('A', {'x': int, 'y': int})
1712+
[builtins fixtures/dict.pyi]
1713+
[out1]
1714+
main:1: error: Module 'tdcrash' has no attribute 'nope'
1715+
[out2]
1716+
main:1: error: Module 'tdcrash' has no attribute 'nope'
1717+
1718+
[case testIncrementalTypedDictInMethod2]
1719+
from tdcrash import nope
1720+
[file tdcrash.py]
1721+
from mypy_extensions import TypedDict
1722+
class C:
1723+
class D:
1724+
def f(self) -> None:
1725+
A = TypedDict('A', {'x': int, 'y': int})
1726+
[builtins fixtures/dict.pyi]
1727+
[out1]
1728+
main:1: error: Module 'tdcrash' has no attribute 'nope'
1729+
[out2]
1730+
main:1: error: Module 'tdcrash' has no attribute 'nope'
1731+
1732+
[case testIncrementalTypedDictInMethod3]
1733+
from tdcrash import nope
1734+
[file tdcrash.py]
1735+
from mypy_extensions import TypedDict
1736+
class C:
1737+
def a(self):
1738+
class D:
1739+
def f(self) -> None:
1740+
A = TypedDict('A', {'x': int, 'y': int})
1741+
[builtins fixtures/dict.pyi]
1742+
[out1]
1743+
main:1: error: Module 'tdcrash' has no attribute 'nope'
1744+
[out2]
1745+
main:1: error: Module 'tdcrash' has no attribute 'nope'
1746+
1747+
[case testIncrementalTypedDictInMethod4]
1748+
from ntcrash import C
1749+
reveal_type(C().a)
1750+
reveal_type(C().b)
1751+
reveal_type(C().c)
1752+
[file ntcrash.py]
1753+
from mypy_extensions import TypedDict
1754+
class C:
1755+
def __init__(self) -> None:
1756+
A = TypedDict('A', {'x': int})
1757+
self.a = A(x=0)
1758+
self.b = A(x=0) # type: A
1759+
self.c = A
1760+
[builtins fixtures/dict.pyi]
1761+
[out1]
1762+
main:2: error: Revealed type is 'TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
1763+
main:3: error: Revealed type is 'TypedDict(x=builtins.int, _fallback=ntcrash.C.A@4)'
1764+
main:4: error: Revealed type is 'def () -> ntcrash.C.A@4'
1765+
[out2]
1766+
main:2: error: Revealed type is 'TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
1767+
main:3: error: Revealed type is 'TypedDict(x=builtins.int, _fallback=ntcrash.C.A@4)'
1768+
main:4: error: Revealed type is 'def () -> ntcrash.C.A@4'

test-data/unit/check-namedtuple.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,3 +411,19 @@ reveal_type(B._make([''])) # E: Revealed type is 'Tuple[builtins.str, fallback=
411411
b = B._make(['']) # type: B
412412

413413
[builtins fixtures/list.pyi]
414+
415+
[case testNamedTupleInClassNamespace]
416+
# https://github.com/python/mypy/pull/2553#issuecomment-266474341
417+
from typing import NamedTuple
418+
class C:
419+
def f(self):
420+
A = NamedTuple('A', [('x', int)])
421+
def g(self):
422+
A = NamedTuple('A', [('y', int)])
423+
C.A # E: "C" has no attribute "A"
424+
425+
[case testNamedTupleInFunction]
426+
from typing import NamedTuple
427+
def f() -> None:
428+
A = NamedTuple('A', [('x', int)])
429+
A # E: Name 'A' is not defined

test-data/unit/check-typeddict.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,22 @@ reveal_type(f(g)) # E: Revealed type is '<uninhabited>'
432432

433433
-- TODO: Implement support for this case.
434434
--[case testCannotIsInstanceTypedDictType]
435+
436+
-- scoping
437+
[case testTypedDictInClassNamespace]
438+
# https://github.com/python/mypy/pull/2553#issuecomment-266474341
439+
from mypy_extensions import TypedDict
440+
class C:
441+
def f(self):
442+
A = TypedDict('A', {'x': int})
443+
def g(self):
444+
A = TypedDict('A', {'y': int})
445+
C.A # E: "C" has no attribute "A"
446+
[builtins fixtures/dict.pyi]
447+
448+
[case testTypedDictInFunction]
449+
from mypy_extensions import TypedDict
450+
def f() -> None:
451+
A = TypedDict('A', {'x': int})
452+
A # E: Name 'A' is not defined
453+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)