From 8f356012a1d031c98216600caac5edf01f3fd298 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 4 Feb 2017 19:36:07 +0100 Subject: [PATCH 1/5] Class syntax for TypedDict --- mypy/semanal.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 71a8323be292..cb892181f5ae 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -564,6 +564,8 @@ def check_function_signature(self, fdef: FuncItem) -> None: def visit_class_def(self, defn: ClassDef) -> None: self.clean_up_bases_and_infer_type_variables(defn) + if self.analyze_typeddict_classdef(defn): + return if self.analyze_namedtuple_classdef(defn): return self.setup_class_def_analysis(defn) @@ -944,6 +946,78 @@ def bind_class_type_variables_in_symbol_table( nodes.append(node) return nodes + def is_typeddict(self, expr: RefExpr) -> bool: + return isinstance(expr.node, TypeInfo) and \ + expr.node.typeddict_type is not None + + def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: + # special case for TypedDict + possible = False + for base_expr in defn.base_type_exprs: + if isinstance(base_expr, RefExpr): + base_expr.accept(self) + if base_expr.fullname == 'mypy_extensions.TypedDict' or self.is_typeddict(base_expr): + possible = True + if possible: + node = self.lookup(defn.name, defn) + if node is not None: + node.kind = GDEF # TODO in process_namedtuple_definition also applies here + if len(defn.base_type_exprs) and defn.base_type_exprs[0].fullname == 'mypy_extensions.TypedDict': + # Building a new TypedDict + fields, types = self.check_typeddict_classdef(defn) + node.node = self.build_typeddict_typeinfo(defn.name, fields, types) + return True + if any(expr.fullname != 'mypy_extensions.TypedDict' and not self.is_typeddict(expr) + for expr in defn.base_type_exprs): + self.fail("All bases of a new TypedDict must be TypedDict's", defn) + fields, types = self.check_typeddict_classdef(defn) + # Extending/merging existing TypedDicts + typeddict_bases = list(filter(self.is_typeddict, defn.base_type_exprs)) + newfields = [] + newtypes = [] + for base in typeddict_bases: + newfields.extend(base.node.typeddict_type.items.keys()) + newtypes.extend(base.node.typeddict_type.items.values()) + newfields.extend(fields) + newtypes.extend(types) + node.node = self.build_typeddict_typeinfo(defn.name, newfields, newtypes) + return True + return False + + def check_typeddict_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type]]: + TPDICT_CLASS_ERROR = ('Invalid statement in TypedDict definition; ' + 'expected "field_name: field_type"') + if self.options.python_version < (3, 6): + self.fail('TypedDict class syntax is only supported in Python 3.6', defn) + return [], [] + fields = [] # type: List[str] + types = [] # type: List[Type] + for stmt in defn.defs.body: + if not isinstance(stmt, AssignmentStmt): + # Still allow pass or ... (for empty TypedDict's). + if (not isinstance(stmt, PassStmt) and + not (isinstance(stmt, ExpressionStmt) and + isinstance(stmt.expr, EllipsisExpr))): + self.fail(TPDICT_CLASS_ERROR, stmt) + elif len(stmt.lvalues) > 1 or not isinstance(stmt.lvalues[0], NameExpr): + # An assignment, but an invalid one. + self.fail(TPDICT_CLASS_ERROR, stmt) + else: + # Append name and type in this case... + name = stmt.lvalues[0].name + fields.append(name) + types.append(AnyType() if stmt.type is None else self.anal_type(stmt.type)) + # ...despite possible minor failures that allow further analyzis. + if name.startswith('_'): + self.fail('TypedDict field name cannot start with an underscore: {}' + .format(name), stmt) + if stmt.type is None or hasattr(stmt, 'new_syntax') and not stmt.new_syntax: + self.fail(TPDICT_CLASS_ERROR, stmt) + elif not isinstance(stmt.rvalue, TempNode): + # x: int assigns rvalue to TempNode(AnyType()) + self.fail('Right hand side values are not supported in TypedDict', stmt) + return fields, types + def visit_import(self, i: Import) -> None: for id, as_id in i.ids: if as_id is not None: From 19044c1c5988fed72754fbe7131e94d6601c4f65 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 5 Feb 2017 01:36:24 +0100 Subject: [PATCH 2/5] Add tests; fix minor points --- mypy/semanal.py | 26 ++++--- test-data/unit/check-typeddict.test | 105 ++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index cb892181f5ae..a75907cc4911 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -946,9 +946,9 @@ def bind_class_type_variables_in_symbol_table( nodes.append(node) return nodes - def is_typeddict(self, expr: RefExpr) -> bool: - return isinstance(expr.node, TypeInfo) and \ - expr.node.typeddict_type is not None + def is_typeddict(self, expr: Expression) -> bool: + return (isinstance(expr, RefExpr) and isinstance(expr.node, TypeInfo) and + expr.node.typeddict_type is not None) def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: # special case for TypedDict @@ -956,28 +956,34 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: for base_expr in defn.base_type_exprs: if isinstance(base_expr, RefExpr): base_expr.accept(self) - if base_expr.fullname == 'mypy_extensions.TypedDict' or self.is_typeddict(base_expr): + if (base_expr.fullname == 'mypy_extensions.TypedDict' or + self.is_typeddict(base_expr)): possible = True if possible: node = self.lookup(defn.name, defn) if node is not None: node.kind = GDEF # TODO in process_namedtuple_definition also applies here - if len(defn.base_type_exprs) and defn.base_type_exprs[0].fullname == 'mypy_extensions.TypedDict': + if (len(defn.base_type_exprs) and isinstance(defn.base_type_exprs[0], RefExpr) and + defn.base_type_exprs[0].fullname == 'mypy_extensions.TypedDict'): # Building a new TypedDict fields, types = self.check_typeddict_classdef(defn) node.node = self.build_typeddict_typeinfo(defn.name, fields, types) return True - if any(expr.fullname != 'mypy_extensions.TypedDict' and not self.is_typeddict(expr) + if any(not isinstance(expr, RefExpr) or + expr.fullname != 'mypy_extensions.TypedDict' and not self.is_typeddict(expr) for expr in defn.base_type_exprs): self.fail("All bases of a new TypedDict must be TypedDict's", defn) fields, types = self.check_typeddict_classdef(defn) # Extending/merging existing TypedDicts typeddict_bases = list(filter(self.is_typeddict, defn.base_type_exprs)) - newfields = [] - newtypes = [] + newfields = [] # type: List[str] + newtypes = [] # type: List[Type] + tpdict = None # type: OrderedDict[str, Type] for base in typeddict_bases: - newfields.extend(base.node.typeddict_type.items.keys()) - newtypes.extend(base.node.typeddict_type.items.values()) + # mypy doesn't yet understand predicates like is_typeddict + tpdict = base.node.typeddict_type.items # type: ignore + newfields.extend(tpdict.keys()) + newtypes.extend(tpdict.values()) newfields.extend(fields) newtypes.extend(types) node.node = self.build_typeddict_typeinfo(defn.name, newfields, newtypes) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 424c8b2b84e0..64d61eff861d 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -63,6 +63,111 @@ p = Point(x='meaning_of_life', y=1337) # E: Incompatible types (expression has [builtins fixtures/dict.pyi] +-- Define TypedDict (Class syntax) + +[case testCanCreateTypedDictWithClass] +# flags: --python-version 3.6 +from mypy_extensions import TypedDict + +class Point(TypedDict): + x: int + y: int + +p = Point(x=42, y=1337) +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +[builtins fixtures/dict.pyi] + +[case testCanCreateTypedDictWithSubclass] +# flags: --python-version 3.6 +from mypy_extensions import TypedDict + +class Point1D(TypedDict): + x: int +class Point2D(Point1D): + y: int + +p: Point2D +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +[builtins fixtures/dict.pyi] + +[case testCanCreateTypedDictWithSubclass2] +# flags: --python-version 3.6 +from mypy_extensions import TypedDict + +class Point1D(TypedDict): + x: int +class Point2D(TypedDict, Point1D): # We also allow to include TypedDict in bases, it is simply ignored at runtime + y: int + +p: Point2D +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +[builtins fixtures/dict.pyi] + +[case testCanCreateTypedDictClassEmpty] +# flags: --python-version 3.6 +from mypy_extensions import TypedDict + +class EmptyDict(TypedDict): + pass + +p = EmptyDict() +reveal_type(p) # E: Revealed type is 'TypedDict(_fallback=typing.Mapping[builtins.str, builtins.None])' +[builtins fixtures/dict.pyi] + + +-- Define TypedDict (Class syntax errors) + +[case testCanCreateTypedDictWithClassOldVersion] +# flags: --python-version 3.5 +from mypy_extensions import TypedDict + +class Point(TypedDict): # E: TypedDict class syntax is only supported in Python 3.6 + x: int + y: int +[builtins fixtures/dict.pyi] + +[case testCannotCreateTypedDictWithClassOtherBases] +# flags: --python-version 3.6 +from mypy_extensions import TypedDict + +class A: pass + +class Point1D(TypedDict, A): # E: All bases of a new TypedDict must be TypedDict's + x: int +class Point2D(Point1D, A): # E: All bases of a new TypedDict must be TypedDict's + y: int + +p: Point2D +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +[builtins fixtures/dict.pyi] + +[case testCannotCreateTypedDictWithClassWithOtherStuff] +# flags: --python-version 3.6 +from mypy_extensions import TypedDict + +class Point(TypedDict): + x: int + y: int = 1 # E: Right hand side values are not supported in TypedDict + def f(): pass # E: Invalid statement in TypedDict definition; expected "field_name: field_type" + x = 5 # E: Invalid statement in TypedDict definition; expected "field_name: field_type" + +p = Point(x=42, y=1337) +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +[builtins fixtures/dict.pyi] + +[case testCannotCreateTypedDictWithClassUnderscores] +# flags: --python-version 3.6 +from mypy_extensions import TypedDict + +class Point(TypedDict): + x: int + _y: int # E: TypedDict field name cannot start with an underscore: _y + +p: Point +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, _y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +[builtins fixtures/dict.pyi] + + -- Subtyping [case testCanConvertTypedDictToItself] From 368684586ff36fd202d6763d64e70b88d47cd1bb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 5 Feb 2017 11:25:57 +0100 Subject: [PATCH 3/5] Fix tests; formatting --- mypy/semanal.py | 9 +++++---- test-data/unit/check-typeddict.test | 14 +++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index a75907cc4911..4c28947acf71 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -963,18 +963,19 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: node = self.lookup(defn.name, defn) if node is not None: node.kind = GDEF # TODO in process_namedtuple_definition also applies here - if (len(defn.base_type_exprs) and isinstance(defn.base_type_exprs[0], RefExpr) and + if (len(defn.base_type_exprs) == 1 and + isinstance(defn.base_type_exprs[0], RefExpr) and defn.base_type_exprs[0].fullname == 'mypy_extensions.TypedDict'): # Building a new TypedDict fields, types = self.check_typeddict_classdef(defn) node.node = self.build_typeddict_typeinfo(defn.name, fields, types) return True + # Extending/merging existing TypedDicts if any(not isinstance(expr, RefExpr) or - expr.fullname != 'mypy_extensions.TypedDict' and not self.is_typeddict(expr) - for expr in defn.base_type_exprs): + expr.fullname != 'mypy_extensions.TypedDict' and + not self.is_typeddict(expr) for expr in defn.base_type_exprs): self.fail("All bases of a new TypedDict must be TypedDict's", defn) fields, types = self.check_typeddict_classdef(defn) - # Extending/merging existing TypedDicts typeddict_bases = list(filter(self.is_typeddict, defn.base_type_exprs)) newfields = [] # type: List[str] newtypes = [] # type: List[Type] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 64d61eff861d..c09b3136665c 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -85,9 +85,10 @@ class Point1D(TypedDict): x: int class Point2D(Point1D): y: int - +r: Point1D p: Point2D -reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +reveal_type(r) # E: Revealed type is 'TypedDict(x=builtins.int, _fallback=__main__.Point1D)' +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=__main__.Point2D)' [builtins fixtures/dict.pyi] [case testCanCreateTypedDictWithSubclass2] @@ -100,7 +101,7 @@ class Point2D(TypedDict, Point1D): # We also allow to include TypedDict in bases y: int p: Point2D -reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=__main__.Point2D)' [builtins fixtures/dict.pyi] [case testCanCreateTypedDictClassEmpty] @@ -122,8 +123,7 @@ reveal_type(p) # E: Revealed type is 'TypedDict(_fallback=typing.Mapping[builti from mypy_extensions import TypedDict class Point(TypedDict): # E: TypedDict class syntax is only supported in Python 3.6 - x: int - y: int + pass [builtins fixtures/dict.pyi] [case testCannotCreateTypedDictWithClassOtherBases] @@ -138,7 +138,7 @@ class Point2D(Point1D, A): # E: All bases of a new TypedDict must be TypedDict's y: int p: Point2D -reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=__main__.Point2D)' [builtins fixtures/dict.pyi] [case testCannotCreateTypedDictWithClassWithOtherStuff] @@ -164,7 +164,7 @@ class Point(TypedDict): _y: int # E: TypedDict field name cannot start with an underscore: _y p: Point -reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, _y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, _y=builtins.int, _fallback=__main__.Point)' [builtins fixtures/dict.pyi] From dca9810f7e8e847e00399bd117a00536f447e387 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 5 Feb 2017 12:32:18 +0100 Subject: [PATCH 4/5] Prohibit overwriting fields on merging/extending --- mypy/semanal.py | 21 ++++++++++++++++----- test-data/unit/check-typeddict.test | 28 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 4c28947acf71..8c114adbda52 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -975,7 +975,6 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: expr.fullname != 'mypy_extensions.TypedDict' and not self.is_typeddict(expr) for expr in defn.base_type_exprs): self.fail("All bases of a new TypedDict must be TypedDict's", defn) - fields, types = self.check_typeddict_classdef(defn) typeddict_bases = list(filter(self.is_typeddict, defn.base_type_exprs)) newfields = [] # type: List[str] newtypes = [] # type: List[Type] @@ -983,15 +982,23 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: for base in typeddict_bases: # mypy doesn't yet understand predicates like is_typeddict tpdict = base.node.typeddict_type.items # type: ignore - newfields.extend(tpdict.keys()) - newtypes.extend(tpdict.values()) + newdict = tpdict.copy() + for key in tpdict: + if key in newfields: + self.fail('Cannot overwrite TypedDict field {} while merging' + .format(key), defn) + newdict.pop(key) + newfields.extend(newdict.keys()) + newtypes.extend(newdict.values()) + fields, types = self.check_typeddict_classdef(defn, newfields) newfields.extend(fields) newtypes.extend(types) node.node = self.build_typeddict_typeinfo(defn.name, newfields, newtypes) return True return False - def check_typeddict_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type]]: + def check_typeddict_classdef(self, defn: ClassDef, + oldfields: List[str] = None) -> Tuple[List[str], List[Type]]: TPDICT_CLASS_ERROR = ('Invalid statement in TypedDict definition; ' 'expected "field_name: field_type"') if self.options.python_version < (3, 6): @@ -1010,8 +1017,12 @@ def check_typeddict_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type # An assignment, but an invalid one. self.fail(TPDICT_CLASS_ERROR, stmt) else: - # Append name and type in this case... name = stmt.lvalues[0].name + if name in (oldfields or []): + self.fail('Cannot overwrite TypedDict field {} while extending' + .format(name), stmt) + continue + # Append name and type in this case... fields.append(name) types.append(AnyType() if stmt.type is None else self.anal_type(stmt.type)) # ...despite possible minor failures that allow further analyzis. diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index c09b3136665c..a63fadfb4875 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -167,6 +167,34 @@ p: Point reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, _y=builtins.int, _fallback=__main__.Point)' [builtins fixtures/dict.pyi] +[case testCannotCreateTypedDictWithClassOverwriting] +# flags: --python-version 3.6 +from mypy_extensions import TypedDict + +class Point1(TypedDict): + x: int +class Point2(TypedDict): + x: float +class Bad(Point1, Point2): # E: Cannot overwrite TypedDict field x while merging + pass + +b: Bad +reveal_type(b) # E: Revealed type is 'TypedDict(x=builtins.int, _fallback=__main__.Bad)' +[builtins fixtures/dict.pyi] + +[case testCannotCreateTypedDictWithClassOverwriting2] +# flags: --python-version 3.6 +from mypy_extensions import TypedDict + +class Point1(TypedDict): + x: int +class Point2(Point1): + x: float # E: Cannot overwrite TypedDict field x while extending + +p2: Point2 +reveal_type(p2) # E: Revealed type is 'TypedDict(x=builtins.int, _fallback=__main__.Point2)' +[builtins fixtures/dict.pyi] + -- Subtyping From ee08aa8eb202c0f3e2de570c9fb41161501c0c10 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 6 Feb 2017 21:37:21 +0100 Subject: [PATCH 5/5] Response to review comments --- mypy/semanal.py | 15 ++++++++++----- test-data/unit/check-typeddict.test | 26 +++++++++++++++++++------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8c114adbda52..5fa30fc55b1a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -974,18 +974,20 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: if any(not isinstance(expr, RefExpr) or expr.fullname != 'mypy_extensions.TypedDict' and not self.is_typeddict(expr) for expr in defn.base_type_exprs): - self.fail("All bases of a new TypedDict must be TypedDict's", defn) + self.fail("All bases of a new TypedDict must be TypedDict types", defn) typeddict_bases = list(filter(self.is_typeddict, defn.base_type_exprs)) newfields = [] # type: List[str] newtypes = [] # type: List[Type] tpdict = None # type: OrderedDict[str, Type] for base in typeddict_bases: - # mypy doesn't yet understand predicates like is_typeddict - tpdict = base.node.typeddict_type.items # type: ignore + assert isinstance(base, RefExpr) + assert isinstance(base.node, TypeInfo) + assert isinstance(base.node.typeddict_type, TypedDictType) + tpdict = base.node.typeddict_type.items newdict = tpdict.copy() for key in tpdict: if key in newfields: - self.fail('Cannot overwrite TypedDict field {} while merging' + self.fail('Cannot overwrite TypedDict field "{}" while merging' .format(key), defn) newdict.pop(key) newfields.extend(newdict.keys()) @@ -1019,9 +1021,12 @@ def check_typeddict_classdef(self, defn: ClassDef, else: name = stmt.lvalues[0].name if name in (oldfields or []): - self.fail('Cannot overwrite TypedDict field {} while extending' + self.fail('Cannot overwrite TypedDict field "{}" while extending' .format(name), stmt) continue + if name in fields: + self.fail('Duplicate TypedDict field "{}"'.format(name), stmt) + continue # Append name and type in this case... fields.append(name) types.append(AnyType() if stmt.type is None else self.anal_type(stmt.type)) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index a63fadfb4875..4f9c66c5b4fc 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -132,9 +132,9 @@ from mypy_extensions import TypedDict class A: pass -class Point1D(TypedDict, A): # E: All bases of a new TypedDict must be TypedDict's +class Point1D(TypedDict, A): # E: All bases of a new TypedDict must be TypedDict types x: int -class Point2D(Point1D, A): # E: All bases of a new TypedDict must be TypedDict's +class Point2D(Point1D, A): # E: All bases of a new TypedDict must be TypedDict types y: int p: Point2D @@ -149,10 +149,10 @@ class Point(TypedDict): x: int y: int = 1 # E: Right hand side values are not supported in TypedDict def f(): pass # E: Invalid statement in TypedDict definition; expected "field_name: field_type" - x = 5 # E: Invalid statement in TypedDict definition; expected "field_name: field_type" + z = int # E: Invalid statement in TypedDict definition; expected "field_name: field_type" -p = Point(x=42, y=1337) -reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])' +p = Point(x=42, y=1337, z='whatever') +reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, z=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.object])' [builtins fixtures/dict.pyi] [case testCannotCreateTypedDictWithClassUnderscores] @@ -171,11 +171,23 @@ reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, _y=builtins.int, # flags: --python-version 3.6 from mypy_extensions import TypedDict +class Bad(TypedDict): + x: int + x: str # E: Duplicate TypedDict field "x" + +b: Bad +reveal_type(b) # E: Revealed type is 'TypedDict(x=builtins.int, _fallback=__main__.Bad)' +[builtins fixtures/dict.pyi] + +[case testCannotCreateTypedDictWithClassOverwriting2] +# flags: --python-version 3.6 +from mypy_extensions import TypedDict + class Point1(TypedDict): x: int class Point2(TypedDict): x: float -class Bad(Point1, Point2): # E: Cannot overwrite TypedDict field x while merging +class Bad(Point1, Point2): # E: Cannot overwrite TypedDict field "x" while merging pass b: Bad @@ -189,7 +201,7 @@ from mypy_extensions import TypedDict class Point1(TypedDict): x: int class Point2(Point1): - x: float # E: Cannot overwrite TypedDict field x while extending + x: float # E: Cannot overwrite TypedDict field "x" while extending p2: Point2 reveal_type(p2) # E: Revealed type is 'TypedDict(x=builtins.int, _fallback=__main__.Point2)'