|
60 | 60 | FuncExpr, MDEF, FuncBase, Decorator, SetExpr, TypeVarExpr, NewTypeExpr,
|
61 | 61 | StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr,
|
62 | 62 | ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, MroError, type_aliases,
|
63 |
| - YieldFromExpr, NamedTupleExpr, NonlocalDecl, SymbolNode, |
| 63 | + YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SymbolNode, |
64 | 64 | SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr,
|
65 | 65 | YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr,
|
66 | 66 | IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr,
|
@@ -1126,6 +1126,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
|
1126 | 1126 | self.process_newtype_declaration(s)
|
1127 | 1127 | self.process_typevar_declaration(s)
|
1128 | 1128 | self.process_namedtuple_definition(s)
|
| 1129 | + self.process_typeddict_definition(s) |
1129 | 1130 |
|
1130 | 1131 | if (len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr) and
|
1131 | 1132 | s.lvalues[0].name == '__all__' and s.lvalues[0].kind == GDEF and
|
@@ -1578,10 +1579,9 @@ def process_namedtuple_definition(self, s: AssignmentStmt) -> None:
|
1578 | 1579 | # Yes, it's a valid namedtuple definition. Add it to the symbol table.
|
1579 | 1580 | node = self.lookup(name, s)
|
1580 | 1581 | node.kind = GDEF # TODO locally defined namedtuple
|
1581 |
| - # TODO call.analyzed |
1582 | 1582 | node.node = named_tuple
|
1583 | 1583 |
|
1584 |
| - def check_namedtuple(self, node: Expression, var_name: str = None) -> TypeInfo: |
| 1584 | + def check_namedtuple(self, node: Expression, var_name: str = None) -> Optional[TypeInfo]: |
1585 | 1585 | """Check if a call defines a namedtuple.
|
1586 | 1586 |
|
1587 | 1587 | The optional var_name argument is the name of the variable to
|
@@ -1776,6 +1776,113 @@ def analyze_types(self, items: List[Expression]) -> List[Type]:
|
1776 | 1776 | result.append(AnyType())
|
1777 | 1777 | return result
|
1778 | 1778 |
|
| 1779 | + def process_typeddict_definition(self, s: AssignmentStmt) -> None: |
| 1780 | + """Check if s defines a TypedDict; if yes, store the definition in symbol table.""" |
| 1781 | + if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr): |
| 1782 | + return |
| 1783 | + lvalue = s.lvalues[0] |
| 1784 | + name = lvalue.name |
| 1785 | + typed_dict = self.check_typeddict(s.rvalue, name) |
| 1786 | + if typed_dict is None: |
| 1787 | + return |
| 1788 | + # Yes, it's a valid TypedDict definition. Add it to the symbol table. |
| 1789 | + node = self.lookup(name, s) |
| 1790 | + node.kind = GDEF # TODO locally defined TypedDict |
| 1791 | + node.node = typed_dict |
| 1792 | + |
| 1793 | + def check_typeddict(self, node: Expression, var_name: str = None) -> Optional[TypeInfo]: |
| 1794 | + """Check if a call defines a TypedDict. |
| 1795 | +
|
| 1796 | + The optional var_name argument is the name of the variable to |
| 1797 | + which this is assigned, if any. |
| 1798 | +
|
| 1799 | + If it does, return the corresponding TypeInfo. Return None otherwise. |
| 1800 | +
|
| 1801 | + If the definition is invalid but looks like a TypedDict, |
| 1802 | + report errors but return (some) TypeInfo. |
| 1803 | + """ |
| 1804 | + if not isinstance(node, CallExpr): |
| 1805 | + return None |
| 1806 | + call = node |
| 1807 | + if not isinstance(call.callee, RefExpr): |
| 1808 | + return None |
| 1809 | + callee = call.callee |
| 1810 | + fullname = callee.fullname |
| 1811 | + if fullname != 'mypy.typing.TypedDict': |
| 1812 | + return None |
| 1813 | + items, types, ok = self.parse_typeddict_args(call, fullname) |
| 1814 | + if not ok: |
| 1815 | + # Error. Construct dummy return value. |
| 1816 | + return self.build_typeddict_typeinfo('TypedDict', [], []) |
| 1817 | + else: |
| 1818 | + # Give it a unique name derived from the line number. |
| 1819 | + name = cast(StrExpr, call.args[0]).value |
| 1820 | + if name != var_name: |
| 1821 | + name += '@' + str(call.line) |
| 1822 | + info = self.build_typeddict_typeinfo(name, items, types) |
| 1823 | + # Store it as a global just in case it would remain anonymous. |
| 1824 | + self.globals[name] = SymbolTableNode(GDEF, info, self.cur_mod_id) |
| 1825 | + call.analyzed = TypedDictExpr(info) |
| 1826 | + call.analyzed.set_line(call.line, call.column) |
| 1827 | + return info |
| 1828 | + |
| 1829 | + def parse_typeddict_args(self, call: CallExpr, |
| 1830 | + fullname: str) -> Tuple[List[str], List[Type], bool]: |
| 1831 | + # TODO Share code with check_argument_count in checkexpr.py? |
| 1832 | + args = call.args |
| 1833 | + if len(args) < 2: |
| 1834 | + return self.fail_typeddict_arg("Too few arguments for TypedDict()", call) |
| 1835 | + if len(args) > 2: |
| 1836 | + return self.fail_typeddict_arg("Too many arguments for TypedDict()", call) |
| 1837 | + if call.arg_kinds != [ARG_POS, ARG_POS]: |
| 1838 | + return self.fail_typeddict_arg("Unexpected arguments to TypedDict()", call) |
| 1839 | + if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)): |
| 1840 | + return self.fail_typeddict_arg( |
| 1841 | + "TypedDict() expects a string literal as the first argument", call) |
| 1842 | + if not isinstance(args[1], DictExpr): |
| 1843 | + return self.fail_typeddict_arg( |
| 1844 | + "TypedDict() expects a dictionary literal as the second argument", call) |
| 1845 | + dictexpr = args[1] |
| 1846 | + items, types, ok = self.parse_typeddict_fields_with_types(dictexpr.items, call) |
| 1847 | + return items, types, ok |
| 1848 | + |
| 1849 | + def parse_typeddict_fields_with_types(self, dict_items: List[Tuple[Expression, Expression]], |
| 1850 | + context: Context) -> Tuple[List[str], List[Type], bool]: |
| 1851 | + items = [] # type: List[str] |
| 1852 | + types = [] # type: List[Type] |
| 1853 | + for (field_name_expr, field_type_expr) in dict_items: |
| 1854 | + if isinstance(field_name_expr, (StrExpr, BytesExpr, UnicodeExpr)): |
| 1855 | + items.append(field_name_expr.value) |
| 1856 | + else: |
| 1857 | + return self.fail_typeddict_arg("Invalid TypedDict() field name", field_name_expr) |
| 1858 | + try: |
| 1859 | + type = expr_to_unanalyzed_type(field_type_expr) |
| 1860 | + except TypeTranslationError: |
| 1861 | + return self.fail_typeddict_arg('Invalid field type', field_type_expr) |
| 1862 | + types.append(self.anal_type(type)) |
| 1863 | + return items, types, True |
| 1864 | + |
| 1865 | + def fail_typeddict_arg(self, message: str, |
| 1866 | + context: Context) -> Tuple[List[str], List[Type], bool]: |
| 1867 | + self.fail(message, context) |
| 1868 | + return [], [], False |
| 1869 | + |
| 1870 | + def build_typeddict_typeinfo(self, name: str, items: List[str], |
| 1871 | + types: List[Type]) -> TypeInfo: |
| 1872 | + strtype = self.named_type('__builtins__.str') # type: Type |
| 1873 | + dictype = (self.named_type_or_none('builtins.dict', [strtype, AnyType()]) |
| 1874 | + or self.object_type()) |
| 1875 | + fallback = dictype |
| 1876 | + |
| 1877 | + info = self.basic_new_typeinfo(name, fallback) |
| 1878 | + info.is_typed_dict = True |
| 1879 | + |
| 1880 | + # (TODO: Store {items, types} inside "info" somewhere for use later. |
| 1881 | + # Probably inside a new "info.keys" field which |
| 1882 | + # would be analogous to "info.names".) |
| 1883 | + |
| 1884 | + return info |
| 1885 | + |
1779 | 1886 | def visit_decorator(self, dec: Decorator) -> None:
|
1780 | 1887 | for d in dec.decorators:
|
1781 | 1888 | d.accept(self)
|
|
0 commit comments