Skip to content

Commit e3f2659

Browse files
authored
Fix: Named types incorrectly raising UndefinedName inside annotated subscript (#609)
1 parent 43541ee commit e3f2659

File tree

2 files changed

+104
-12
lines changed

2 files changed

+104
-12
lines changed

pyflakes/checker.py

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1678,28 +1678,79 @@ def CALL(self, node):
16781678
):
16791679
self._handle_string_dot_format(node)
16801680

1681+
omit = []
1682+
annotated = []
1683+
not_annotated = []
1684+
16811685
if (
16821686
_is_typing(node.func, 'cast', self.scopeStack) and
1683-
len(node.args) >= 1 and
1684-
isinstance(node.args[0], ast.Str)
1687+
len(node.args) >= 1
16851688
):
1686-
with self._enter_annotation(AnnotationState.STRING):
1689+
with self._enter_annotation():
16871690
self.handleNode(node.args[0], node)
16881691

16891692
elif _is_typing(node.func, 'TypeVar', self.scopeStack):
1693+
16901694
# TypeVar("T", "int", "str")
1691-
for arg in node.args[1:]:
1692-
if isinstance(arg, ast.Str):
1693-
with self._enter_annotation():
1694-
self.handleNode(arg, node)
1695+
omit += ["args"]
1696+
annotated += [arg for arg in node.args[1:]]
16951697

16961698
# TypeVar("T", bound="str")
1697-
for keyword in node.keywords:
1698-
if keyword.arg == 'bound' and isinstance(keyword.value, ast.Str):
1699-
with self._enter_annotation():
1700-
self.handleNode(keyword.value, node)
1699+
omit += ["keywords"]
1700+
annotated += [k.value for k in node.keywords if k.arg == "bound"]
1701+
not_annotated += [
1702+
(k, ["value"] if k.arg == "bound" else None)
1703+
for k in node.keywords
1704+
]
1705+
1706+
elif _is_typing(node.func, "TypedDict", self.scopeStack):
1707+
# TypedDict("a", {"a": int})
1708+
if len(node.args) > 1 and isinstance(node.args[1], ast.Dict):
1709+
omit += ["args"]
1710+
annotated += node.args[1].values
1711+
not_annotated += [
1712+
(arg, ["values"] if i == 1 else None)
1713+
for i, arg in enumerate(node.args)
1714+
]
17011715

1702-
self.handleChildren(node)
1716+
# TypedDict("a", a=int)
1717+
omit += ["keywords"]
1718+
annotated += [k.value for k in node.keywords]
1719+
not_annotated += [(k, ["value"]) for k in node.keywords]
1720+
1721+
elif _is_typing(node.func, "NamedTuple", self.scopeStack):
1722+
# NamedTuple("a", [("a", int)])
1723+
if (
1724+
len(node.args) > 1 and
1725+
isinstance(node.args[1], (ast.Tuple, ast.List)) and
1726+
all(isinstance(x, (ast.Tuple, ast.List)) and
1727+
len(x.elts) == 2 for x in node.args[1].elts)
1728+
):
1729+
omit += ["args"]
1730+
annotated += [elt.elts[1] for elt in node.args[1].elts]
1731+
not_annotated += [(elt.elts[0], None) for elt in node.args[1].elts]
1732+
not_annotated += [
1733+
(arg, ["elts"] if i == 1 else None)
1734+
for i, arg in enumerate(node.args)
1735+
]
1736+
not_annotated += [(elt, "elts") for elt in node.args[1].elts]
1737+
1738+
# NamedTuple("a", a=int)
1739+
omit += ["keywords"]
1740+
annotated += [k.value for k in node.keywords]
1741+
not_annotated += [(k, ["value"]) for k in node.keywords]
1742+
1743+
if omit:
1744+
with self._enter_annotation(AnnotationState.NONE):
1745+
for na_node, na_omit in not_annotated:
1746+
self.handleChildren(na_node, omit=na_omit)
1747+
self.handleChildren(node, omit=omit)
1748+
1749+
with self._enter_annotation():
1750+
for annotated_node in annotated:
1751+
self.handleNode(annotated_node, node)
1752+
else:
1753+
self.handleChildren(node)
17031754

17041755
def _handle_percent_format(self, node):
17051756
try:

pyflakes/test/test_type_annotations.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,3 +695,44 @@ class C(Protocol):
695695
def f(): # type: () -> int
696696
pass
697697
""")
698+
699+
def test_typednames_correct_forward_ref(self):
700+
self.flakes("""
701+
from typing import TypedDict, List, NamedTuple
702+
703+
List[TypedDict("x", {})]
704+
List[TypedDict("x", x=int)]
705+
List[NamedTuple("a", a=int)]
706+
List[NamedTuple("a", [("a", int)])]
707+
""")
708+
self.flakes("""
709+
from typing import TypedDict, List, NamedTuple, TypeVar
710+
711+
List[TypedDict("x", {"x": "Y"})]
712+
List[TypedDict("x", x="Y")]
713+
List[NamedTuple("a", [("a", "Y")])]
714+
List[NamedTuple("a", a="Y")]
715+
List[TypedDict("x", {"x": List["a"]})]
716+
List[TypeVar("A", bound="C")]
717+
List[TypeVar("A", List["C"])]
718+
""", *[m.UndefinedName]*7)
719+
self.flakes("""
720+
from typing import NamedTuple, TypeVar, cast
721+
from t import A, B, C, D, E
722+
723+
NamedTuple("A", [("a", A["C"])])
724+
TypeVar("A", bound=A["B"])
725+
TypeVar("A", A["D"])
726+
cast(A["E"], [])
727+
""")
728+
729+
@skipIf(version_info < (3, 6), 'new in Python 3.6')
730+
def test_namedtypes_classes(self):
731+
self.flakes("""
732+
from typing import TypedDict, NamedTuple
733+
class X(TypedDict):
734+
y: TypedDict("z", {"zz":int})
735+
736+
class Y(NamedTuple):
737+
y: NamedTuple("v", [("vv", int)])
738+
""")

0 commit comments

Comments
 (0)