Repository: python/mypy

Issue:
Improve representation of TypedDicts
`reveal_type()` used with a TypedDict generates a long output that is quite different from the current syntax for defining a TypedDict. Example:

```
TypedDict(x=builtins.int, y=builtins.int, z=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])
```

Not sure what's the best course action. Some ideas:
* Drop fallback if it's a `Mapping`, since it's at least mostly redundant.
* Use `{'x': builtins.int, ...}` instead of `x=builtins.int, ...`.
* Support the syntax `TypedDict('Name', x=int, ...)` for defining a TypedDict, so that the supported syntax is closer to `reveal_type`.
* Do something about required/non-required keys. The best approach probably depends on other choices.


Discussion:
It would also be nice to have a better representation for an empty TypedDict type. Maybe `TypedDict({})`?

Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -188,8 +188,10 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
             return self.accept(e.analyzed, self.type_context[-1])
         if isinstance(e.callee, NameExpr) and isinstance(e.callee.node, TypeInfo) and \
                 e.callee.node.typeddict_type is not None:
-            return self.check_typeddict_call(e.callee.node.typeddict_type,
-                                             e.arg_kinds, e.arg_names, e.args, e)
+            # Use named fallback for better error messages.
+            typeddict_type = e.callee.node.typeddict_type.copy_modified(
+                fallback=Instance(e.callee.node, []))
+            return self.check_typeddict_call(typeddict_type, e.arg_kinds, e.arg_names, e.args, e)
         if isinstance(e.callee, NameExpr) and e.callee.name in ('isinstance', 'issubclass'):
             for typ in mypy.checker.flatten(e.args[1]):
                 if isinstance(typ, NameExpr):
@@ -303,7 +305,6 @@ def check_typeddict_call_with_kwargs(self, callee: TypedDictType,
                 context=context)
             return AnyType()
 
-        items = OrderedDict()  # type: OrderedDict[str, Type]
         for (item_name, item_expected_type) in callee.items.items():
             if item_name in kwargs:
                 item_value = kwargs[item_name]
@@ -312,12 +313,8 @@ def check_typeddict_call_with_kwargs(self, callee: TypedDictType,
                     msg=messages.INCOMPATIBLE_TYPES,
                     lvalue_name='TypedDict item "{}"'.format(item_name),
                     rvalue_name='expression')
-            items[item_name] = item_expected_type
 
-        mapping_value_type = join.join_type_list(list(items.values()))
-        fallback = self.chk.named_generic_type('typing.Mapping',
-                                               [self.chk.str_type(), mapping_value_type])
-        return TypedDictType(items, set(callee.required_keys), fallback)
+        return callee
 
     # Types and methods that can be used to infer partial types.
     item_args = {'builtins.list': ['append'],
diff --git a/mypy/types.py b/mypy/types.py
--- a/mypy/types.py
+++ b/mypy/types.py
@@ -1513,19 +1513,22 @@ def visit_tuple_type(self, t: TupleType) -> str:
         return 'Tuple[{}]'.format(s)
 
     def visit_typeddict_type(self, t: TypedDictType) -> str:
-        s = self.keywords_str(t.items.items())
-        if t.required_keys == set(t.items):
-            keys_str = ''
-        elif t.required_keys == set():
-            keys_str = ', _total=False'
-        else:
-            keys_str = ', _required_keys=[{}]'.format(', '.join(sorted(t.required_keys)))
+        def item_str(name: str, typ: str) -> str:
+            if name in t.required_keys:
+                return '{!r}: {}'.format(name, typ)
+            else:
+                return '{!r}?: {}'.format(name, typ)
+
+        s = '{' + ', '.join(item_str(name, typ.accept(self))
+                            for name, typ in t.items.items()) + '}'
+        prefix = ''
+        suffix = ''
         if t.fallback and t.fallback.type:
-            if s == '':
-                return 'TypedDict(_fallback={}{})'.format(t.fallback.accept(self), keys_str)
+            if t.fallback.type.fullname() != 'typing.Mapping':
+                prefix = repr(t.fallback.type.fullname()) + ', '
             else:
-                return 'TypedDict({}, _fallback={}{})'.format(s, t.fallback.accept(self), keys_str)
-        return 'TypedDict({})'.format(s)
+                suffix = ', fallback={}'.format(t.fallback.accept(self))
+        return 'TypedDict({}{}{})'.format(prefix, s, suffix)
 
     def visit_star_type(self, t: StarType) -> str:
         s = t.type.accept(self)
@@ -1560,15 +1563,6 @@ def list_str(self, a: List[Type]) -> str:
                 res.append(str(t))
         return ', '.join(res)
 
-    def keywords_str(self, a: Iterable[Tuple[str, Type]]) -> str:
-        """Convert keywords to strings (pretty-print types)
-        and join the results with commas.
-        """
-        return ', '.join([
-            '{}={}'.format(name, t.accept(self))
-            for (name, t) in a
-        ])
-
 
 class TypeQuery(SyntheticTypeVisitor[T]):
     """Visitor for performing queries of types.


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -3350,8 +3350,8 @@ x: TD
 x1 = TD({'x': []})
 y: NM
 y1 = NM(x=[])
-reveal_type(x) # E: Revealed type is 'TypedDict(x=builtins.list[Any], _fallback=__main__.TD)'
-reveal_type(x1) # E: Revealed type is 'TypedDict(x=builtins.list[Any], _fallback=typing.Mapping[builtins.str, builtins.list[Any]])'
+reveal_type(x) # E: Revealed type is 'TypedDict('__main__.TD', {'x': builtins.list[Any]})'
+reveal_type(x1) # E: Revealed type is 'TypedDict('__main__.TD', {'x': builtins.list[Any]})'
 reveal_type(y) # E: Revealed type is 'Tuple[builtins.list[Any], fallback=__main__.NM]'
 reveal_type(y1) # E: Revealed type is 'Tuple[builtins.list[Any], fallback=__main__.NM]'
 [builtins fixtures/dict.pyi]
diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test
--- a/test-data/unit/check-incremental.test
+++ b/test-data/unit/check-incremental.test
@@ -1901,11 +1901,11 @@ A = TypedDict('A', {'x': int, 'y': str})
 x: A
 [builtins fixtures/dict.pyi]
 [out1]
-main:2: error: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=b.A)'
-main:4: error: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=b.A)'
+main:2: error: Revealed type is 'TypedDict('b.A', {'x': builtins.int, 'y': builtins.str})'
+main:4: error: Revealed type is 'TypedDict('b.A', {'x': builtins.int, 'y': builtins.str})'
 [out2]
-main:2: error: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=b.A)'
-main:4: error: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=b.A)'
+main:2: error: Revealed type is 'TypedDict('b.A', {'x': builtins.int, 'y': builtins.str})'
+main:4: error: Revealed type is 'TypedDict('b.A', {'x': builtins.int, 'y': builtins.str})'
 
 [case testSerializeMetaclass]
 import b
diff --git a/test-data/unit/check-serialize.test b/test-data/unit/check-serialize.test
--- a/test-data/unit/check-serialize.test
+++ b/test-data/unit/check-serialize.test
@@ -1018,12 +1018,12 @@ class C:
         self.c = A
 [builtins fixtures/dict.pyi]
 [out1]
-main:2: error: Revealed type is 'TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
-main:3: error: Revealed type is 'TypedDict(x=builtins.int, _fallback=ntcrash.C.A@4)'
+main:2: error: Revealed type is 'TypedDict('ntcrash.C.A@4', {'x': builtins.int})'
+main:3: error: Revealed type is 'TypedDict('ntcrash.C.A@4', {'x': builtins.int})'
 main:4: error: Revealed type is 'def () -> ntcrash.C.A@4'
 [out2]
-main:2: error: Revealed type is 'TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
-main:3: error: Revealed type is 'TypedDict(x=builtins.int, _fallback=ntcrash.C.A@4)'
+main:2: error: Revealed type is 'TypedDict('ntcrash.C.A@4', {'x': builtins.int})'
+main:3: error: Revealed type is 'TypedDict('ntcrash.C.A@4', {'x': builtins.int})'
 main:4: error: Revealed type is 'def () -> ntcrash.C.A@4'
 
 [case testSerializeNonTotalTypedDict]
@@ -1035,9 +1035,9 @@ D = TypedDict('D', {'x': int, 'y': str}, total=False)
 d: D
 [builtins fixtures/dict.pyi]
 [out1]
-main:2: error: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=m.D, _total=False)'
+main:2: error: Revealed type is 'TypedDict('m.D', {'x'?: builtins.int, 'y'?: builtins.str})'
 [out2]
-main:2: error: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=m.D, _total=False)'
+main:2: error: Revealed type is 'TypedDict('m.D', {'x'?: builtins.int, 'y'?: builtins.str})'
 
 --
 -- Modules
diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test
--- a/test-data/unit/check-typeddict.test
+++ b/test-data/unit/check-typeddict.test
@@ -4,29 +4,41 @@
 from mypy_extensions import TypedDict
 Point = TypedDict('Point', {'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])'
+reveal_type(p)  # E: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.int, 'y': builtins.int})'
+# Use values() to check fallback value type.
+reveal_type(p.values()) # E: Revealed type is 'typing.Iterable[builtins.int*]'
 [builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]
 
 [case testCanCreateTypedDictInstanceWithDictCall]
 from mypy_extensions import TypedDict
 Point = TypedDict('Point', {'x': int, 'y': int})
 p = Point(dict(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])'
+reveal_type(p)  # E: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.int, 'y': builtins.int})'
+# Use values() to check fallback value type.
+reveal_type(p.values()) # E: Revealed type is 'typing.Iterable[builtins.int*]'
 [builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]
 
 [case testCanCreateTypedDictInstanceWithDictLiteral]
 from mypy_extensions import TypedDict
 Point = TypedDict('Point', {'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])'
+reveal_type(p)  # E: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.int, 'y': builtins.int})'
+# Use values() to check fallback value type.
+reveal_type(p.values()) # E: Revealed type is 'typing.Iterable[builtins.int*]'
 [builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]
 
 [case testCanCreateTypedDictInstanceWithNoArguments]
+from typing import TypeVar, Union
 from mypy_extensions import TypedDict
 EmptyDict = TypedDict('EmptyDict', {})
 p = EmptyDict()
-reveal_type(p)  # E: Revealed type is 'TypedDict(_fallback=typing.Mapping[builtins.str, <nothing>])'
+reveal_type(p)  # E: Revealed type is 'TypedDict('__main__.EmptyDict', {})'
+reveal_type(p.values()) # E: Revealed type is 'typing.Iterable[<nothing>]'
 [builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]
 
 
 -- Create Instance (Errors)
@@ -47,13 +59,13 @@ p = Point({x: 42, 'y': 1337})  # E: Expected TypedDict key to be string literal
 [case testCannotCreateTypedDictInstanceWithExtraItems]
 from mypy_extensions import TypedDict
 Point = TypedDict('Point', {'x': int, 'y': int})
-p = Point(x=42, y=1337, z=666)  # E: Expected TypedDict keys ('x', 'y') but found keys ('x', 'y', 'z')
+p = Point(x=42, y=1337, z=666)  # E: Extra key 'z' for TypedDict "Point"
 [builtins fixtures/dict.pyi]
 
 [case testCannotCreateTypedDictInstanceWithMissingItems]
 from mypy_extensions import TypedDict
 Point = TypedDict('Point', {'x': int, 'y': int})
-p = Point(x=42)  # E: Expected TypedDict keys ('x', 'y') but found only key 'x'
+p = Point(x=42)  # E: Key 'y' missing for TypedDict "Point"
 [builtins fixtures/dict.pyi]
 
 [case testCannotCreateTypedDictInstanceWithIncompatibleItemType]
@@ -74,7 +86,7 @@ class Point(TypedDict):
     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])'
+reveal_type(p)  # E: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.int, 'y': builtins.int})'
 [builtins fixtures/dict.pyi]
 
 [case testCanCreateTypedDictWithSubclass]
@@ -87,8 +99,8 @@ class Point2D(Point1D):
     y: int
 r: Point1D
 p: Point2D
-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)'
+reveal_type(r)  # E: Revealed type is 'TypedDict('__main__.Point1D', {'x': builtins.int})'
+reveal_type(p)  # E: Revealed type is 'TypedDict('__main__.Point2D', {'x': builtins.int, 'y': builtins.int})'
 [builtins fixtures/dict.pyi]
 
 [case testCanCreateTypedDictWithSubclass2]
@@ -101,7 +113,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=__main__.Point2D)'
+reveal_type(p)  # E: Revealed type is 'TypedDict('__main__.Point2D', {'x': builtins.int, 'y': builtins.int})'
 [builtins fixtures/dict.pyi]
 
 [case testCanCreateTypedDictClassEmpty]
@@ -112,7 +124,7 @@ class EmptyDict(TypedDict):
     pass
 
 p = EmptyDict()
-reveal_type(p)  # E: Revealed type is 'TypedDict(_fallback=typing.Mapping[builtins.str, <nothing>])'
+reveal_type(p)  # E: Revealed type is 'TypedDict('__main__.EmptyDict', {})'
 [builtins fixtures/dict.pyi]
 
 
@@ -138,7 +150,7 @@ class Point2D(Point1D, A): # E: All bases of a new TypedDict must be TypedDict t
     y: int
 
 p: Point2D
-reveal_type(p)  # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=__main__.Point2D)'
+reveal_type(p)  # E: Revealed type is 'TypedDict('__main__.Point2D', {'x': builtins.int, 'y': builtins.int})'
 [builtins fixtures/dict.pyi]
 
 [case testCannotCreateTypedDictWithClassWithOtherStuff]
@@ -152,7 +164,7 @@ class Point(TypedDict):
     z = int # E: Invalid statement in TypedDict definition; expected "field_name: field_type"
 
 p = Point(x=42, y=1337, z='whatever')
-reveal_type(p)  # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, z=Any, _fallback=typing.Mapping[builtins.str, Any])'
+reveal_type(p)  # E: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.int, 'y': builtins.int, 'z': Any})'
 [builtins fixtures/dict.pyi]
 
 [case testCanCreateTypedDictTypeWithUnderscoreItemName]
@@ -169,7 +181,7 @@ class Point(TypedDict):
     _y: int
 
 p: Point
-reveal_type(p) # E: Revealed type is 'TypedDict(x=builtins.int, _y=builtins.int, _fallback=__main__.Point)'
+reveal_type(p) # E: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.int, '_y': builtins.int})'
 [builtins fixtures/dict.pyi]
 
 [case testCannotCreateTypedDictWithClassOverwriting]
@@ -181,7 +193,7 @@ class Bad(TypedDict):
     x: str # E: Duplicate TypedDict field "x"
 
 b: Bad
-reveal_type(b) # E: Revealed type is 'TypedDict(x=builtins.int, _fallback=__main__.Bad)'
+reveal_type(b) # E: Revealed type is 'TypedDict('__main__.Bad', {'x': builtins.int})'
 [builtins fixtures/dict.pyi]
 
 [case testCannotCreateTypedDictWithClassOverwriting2]
@@ -196,7 +208,7 @@ class Bad(Point1, Point2): # E: Cannot overwrite TypedDict field "x" while mergi
     pass
 
 b: Bad
-reveal_type(b) # E: Revealed type is 'TypedDict(x=builtins.int, _fallback=__main__.Bad)'
+reveal_type(b) # E: Revealed type is 'TypedDict('__main__.Bad', {'x': builtins.int})'
 [builtins fixtures/dict.pyi]
 
 [case testCannotCreateTypedDictWithClassOverwriting2]
@@ -209,7 +221,7 @@ 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)'
+reveal_type(p2) # E: Revealed type is 'TypedDict('__main__.Point2', {'x': builtins.int})'
 [builtins fixtures/dict.pyi]
 
 
@@ -292,7 +304,7 @@ from typing import Any, Mapping
 Point = TypedDict('Point', {'x': float, 'y': float})
 def create_point() -> Point:
     return Point(x=1, y=2)
-reveal_type(Point(x=1, y=2))  # E: Revealed type is 'TypedDict(x=builtins.float, y=builtins.float, _fallback=typing.Mapping[builtins.str, builtins.float])'
+reveal_type(Point(x=1, y=2))  # E: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.float, 'y': builtins.float})'
 [builtins fixtures/dict.pyi]
 
 [case testTypedDictDoesNotAcceptsFloatForInt]
@@ -321,7 +333,7 @@ def create_point(something: Any) -> Point:
 from mypy_extensions import TypedDict
 from typing import List
 D = TypedDict('D', {'x': List[int]})
-reveal_type(D(x=[]))  # E: Revealed type is 'TypedDict(x=builtins.list[builtins.int], _fallback=typing.Mapping[builtins.str, builtins.list[builtins.int]])'
+reveal_type(D(x=[]))  # E: Revealed type is 'TypedDict('__main__.D', {'x': builtins.list[builtins.int]})'
 [builtins fixtures/dict.pyi]
 
 -- TODO: Fix mypy stubs so that the following passes in the test suite
@@ -384,11 +396,12 @@ TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int})
 Point3D = TypedDict('Point3D', {'x': int, 'y': int, 'z': int})
 p1 = TaggedPoint(type='2d', x=0, y=0)
 p2 = Point3D(x=1, y=1, z=1)
-joined_points = [p1, p2]
-reveal_type(p1)             # E: Revealed type is 'TypedDict(type=builtins.str, x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.object])'
-reveal_type(p2)             # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, z=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
-reveal_type(joined_points)  # E: Revealed type is 'builtins.list[TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]'
+joined_points = [p1, p2][0]
+reveal_type(p1.values())   # E: Revealed type is 'typing.Iterable[builtins.object*]'
+reveal_type(p2.values())   # E: Revealed type is 'typing.Iterable[builtins.int*]'
+reveal_type(joined_points)  # E: Revealed type is 'TypedDict({'x': builtins.int, 'y': builtins.int}, fallback=typing.Mapping[builtins.str, builtins.int])'
 [builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]
 
 [case testJoinOfTypedDictRemovesNonequivalentKeys]
 from mypy_extensions import TypedDict
@@ -397,9 +410,9 @@ CellWithObject = TypedDict('CellWithObject', {'value': object, 'meta': object})
 c1 = CellWithInt(value=1, meta=42)
 c2 = CellWithObject(value=2, meta='turtle doves')
 joined_cells = [c1, c2]
-reveal_type(c1)             # E: Revealed type is 'TypedDict(value=builtins.object, meta=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.object])'
-reveal_type(c2)             # E: Revealed type is 'TypedDict(value=builtins.object, meta=builtins.object, _fallback=typing.Mapping[builtins.str, builtins.object])'
-reveal_type(joined_cells)   # E: Revealed type is 'builtins.list[TypedDict(value=builtins.object, _fallback=typing.Mapping[builtins.str, builtins.object])]'
+reveal_type(c1)             # E: Revealed type is 'TypedDict('__main__.CellWithInt', {'value': builtins.object, 'meta': builtins.int})'
+reveal_type(c2)             # E: Revealed type is 'TypedDict('__main__.CellWithObject', {'value': builtins.object, 'meta': builtins.object})'
+reveal_type(joined_cells)   # E: Revealed type is 'builtins.list[TypedDict({'value': builtins.object}, fallback=typing.Mapping[builtins.str, builtins.object])]'
 [builtins fixtures/dict.pyi]
 
 [case testJoinOfDisjointTypedDictsIsEmptyTypedDict]
@@ -409,9 +422,9 @@ Cell = TypedDict('Cell', {'value': object})
 d1 = Point(x=0, y=0)
 d2 = Cell(value='pear tree')
 joined_dicts = [d1, d2]
-reveal_type(d1)             # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
-reveal_type(d2)             # E: Revealed type is 'TypedDict(value=builtins.object, _fallback=typing.Mapping[builtins.str, builtins.object])'
-reveal_type(joined_dicts)   # E: Revealed type is 'builtins.list[TypedDict(_fallback=typing.Mapping[builtins.str, <nothing>])]'
+reveal_type(d1)             # E: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.int, 'y': builtins.int})'
+reveal_type(d2)             # E: Revealed type is 'TypedDict('__main__.Cell', {'value': builtins.object})'
+reveal_type(joined_dicts)   # E: Revealed type is 'builtins.list[TypedDict({}, fallback=typing.Mapping[builtins.str, <nothing>])]'
 [builtins fixtures/dict.pyi]
 
 [case testJoinOfTypedDictWithCompatibleMappingIsMapping]
@@ -474,7 +487,7 @@ YZ = TypedDict('YZ', {'y': int, 'z': int})
 T = TypeVar('T')
 def f(x: Callable[[T, T], None]) -> T: pass
 def g(x: XY, y: YZ) -> None: pass
-reveal_type(f(g))  # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, z=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
+reveal_type(f(g))  # E: Revealed type is 'TypedDict({'x': builtins.int, 'y': builtins.int, 'z': builtins.int}, fallback=typing.Mapping[builtins.str, builtins.int])'
 [builtins fixtures/dict.pyi]
 
 [case testMeetOfTypedDictsWithIncompatibleCommonKeysIsUninhabited]
@@ -497,7 +510,7 @@ Z = TypedDict('Z', {'z': int})
 T = TypeVar('T')
 def f(x: Callable[[T, T], None]) -> T: pass
 def g(x: X, y: Z) -> None: pass
-reveal_type(f(g))  # E: Revealed type is 'TypedDict(x=builtins.int, z=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
+reveal_type(f(g))  # E: Revealed type is 'TypedDict({'x': builtins.int, 'z': builtins.int}, fallback=typing.Mapping[builtins.str, builtins.int])'
 [builtins fixtures/dict.pyi]
 
 # TODO: It would be more accurate for the meet to be TypedDict instead.
@@ -546,7 +559,7 @@ YZ = TypedDict('YZ', {'y': int, 'z': int}, total=False)
 T = TypeVar('T')
 def f(x: Callable[[T, T], None]) -> T: pass
 def g(x: XY, y: YZ) -> None: pass
-reveal_type(f(g))  # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, z=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int], _total=False)'
+reveal_type(f(g))  # E: Revealed type is 'TypedDict({'x'?: builtins.int, 'y'?: builtins.int, 'z'?: builtins.int}, fallback=typing.Mapping[builtins.str, builtins.int])'
 [builtins fixtures/dict.pyi]
 
 [case testMeetOfTypedDictsWithNonTotalAndTotal]
@@ -557,7 +570,7 @@ YZ = TypedDict('YZ', {'y': int, 'z': int})
 T = TypeVar('T')
 def f(x: Callable[[T, T], None]) -> T: pass
 def g(x: XY, y: YZ) -> None: pass
-reveal_type(f(g))  # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, z=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int], _required_keys=[y, z])'
+reveal_type(f(g))  # E: Revealed type is 'TypedDict({'x'?: builtins.int, 'y': builtins.int, 'z': builtins.int}, fallback=typing.Mapping[builtins.str, builtins.int])'
 [builtins fixtures/dict.pyi]
 
 [case testMeetOfTypedDictsWithIncompatibleNonTotalAndTotal]
@@ -681,7 +694,7 @@ p['x'] = 'y'  # E: Argument 2 has incompatible type "str"; expected "int"
 from mypy_extensions import TypedDict
 TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int})
 p = TaggedPoint(type='2d', x=42, y=1337)
-p['z'] = 1  # E: 'z' is not a valid TypedDict key; expected one of ('type', 'x', 'y')
+p['z'] = 1  # E: TypedDict "TaggedPoint" has no key 'z'
 [builtins fixtures/dict.pyi]
 
 [case testCannotSetItemOfTypedDictWithNonLiteralKey]
@@ -749,15 +762,15 @@ e = E(a='')
 f = F(x=1)
 g = G(a=cast(Any, 1))  # Work around #2610
 
-reveal_type(u(d, d)) # E: Revealed type is 'TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
-reveal_type(u(c, d)) # E: Revealed type is 'TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
-reveal_type(u(d, c)) # E: Revealed type is 'TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
-reveal_type(u(c, e)) # E: Revealed type is 'Union[TypedDict(a=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.str]), TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]'
-reveal_type(u(e, c)) # E: Revealed type is 'Union[TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int]), TypedDict(a=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.str])]'
-reveal_type(u(c, f)) # E: Revealed type is 'Union[TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int]), TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]'
-reveal_type(u(f, c)) # E: Revealed type is 'Union[TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int]), TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]'
-reveal_type(u(c, g)) # E: Revealed type is 'Union[TypedDict(a=Any, _fallback=typing.Mapping[builtins.str, Any]), TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]'
-reveal_type(u(g, c)) # E: Revealed type is 'Union[TypedDict(a=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int]), TypedDict(a=Any, _fallback=typing.Mapping[builtins.str, Any])]'
+reveal_type(u(d, d)) # E: Revealed type is 'TypedDict('__main__.D', {'a': builtins.int, 'b': builtins.int})'
+reveal_type(u(c, d)) # E: Revealed type is 'TypedDict('__main__.C', {'a': builtins.int})'
+reveal_type(u(d, c)) # E: Revealed type is 'TypedDict('__main__.C', {'a': builtins.int})'
+reveal_type(u(c, e)) # E: Revealed type is 'Union[TypedDict('__main__.E', {'a': builtins.str}), TypedDict('__main__.C', {'a': builtins.int})]'
+reveal_type(u(e, c)) # E: Revealed type is 'Union[TypedDict('__main__.C', {'a': builtins.int}), TypedDict('__main__.E', {'a': builtins.str})]'
+reveal_type(u(c, f)) # E: Revealed type is 'Union[TypedDict('__main__.F', {'x': builtins.int}), TypedDict('__main__.C', {'a': builtins.int})]'
+reveal_type(u(f, c)) # E: Revealed type is 'Union[TypedDict('__main__.C', {'a': builtins.int}), TypedDict('__main__.F', {'x': builtins.int})]'
+reveal_type(u(c, g)) # E: Revealed type is 'Union[TypedDict('__main__.G', {'a': Any}), TypedDict('__main__.C', {'a': builtins.int})]'
+reveal_type(u(g, c)) # E: Revealed type is 'Union[TypedDict('__main__.C', {'a': builtins.int}), TypedDict('__main__.G', {'a': Any})]'
 [builtins fixtures/dict.pyi]
 
 [case testTypedDictUnionSimplification2]
@@ -778,9 +791,9 @@ m_s_a: Mapping[str, Any]
 
 reveal_type(u(c, m_s_i)) # E: Revealed type is 'typing.Mapping*[builtins.str, builtins.int]'
 reveal_type(u(m_s_i, c)) # E: Revealed type is 'typing.Mapping*[builtins.str, builtins.int]'
-reveal_type(u(c, m_s_s)) # E: Revealed type is 'Union[typing.Mapping*[builtins.str, builtins.str], TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]'
-reveal_type(u(c, m_i_i)) # E: Revealed type is 'Union[typing.Mapping*[builtins.int, builtins.int], TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]'
-reveal_type(u(c, m_s_a)) # E: Revealed type is 'Union[typing.Mapping*[builtins.str, Any], TypedDict(a=builtins.int, b=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]'
+reveal_type(u(c, m_s_s)) # E: Revealed type is 'Union[typing.Mapping*[builtins.str, builtins.str], TypedDict('__main__.C', {'a': builtins.int, 'b': builtins.int})]'
+reveal_type(u(c, m_i_i)) # E: Revealed type is 'Union[typing.Mapping*[builtins.int, builtins.int], TypedDict('__main__.C', {'a': builtins.int, 'b': builtins.int})]'
+reveal_type(u(c, m_s_a)) # E: Revealed type is 'Union[typing.Mapping*[builtins.str, Any], TypedDict('__main__.C', {'a': builtins.int, 'b': builtins.int})]'
 [builtins fixtures/dict.pyi]
 
 
@@ -817,7 +830,7 @@ p2: Point
 p2 = dict(x='bye')  # E: Key 'y' missing for TypedDict "Point"
 
 p3 = Point(x=1, y=2)
-p3 = {'x': 'hi'}  # E: Expected TypedDict keys ('x', 'y') but found only key 'x'
+p3 = {'x': 'hi'}  # E: Key 'y' missing for TypedDict "Point"
 
 p4: Point = {'x': 1, 'y': 2}
 
@@ -902,7 +915,7 @@ from mypy_extensions import TypedDict
 D = TypedDict('D', {'x': int, 'y': str})
 E = TypedDict('E', {'d': D})
 p = E(d=D(x=0, y=''))
-reveal_type(p.get('d', {'x': 1, 'y': ''})) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=__main__.D)'
+reveal_type(p.get('d', {'x': 1, 'y': ''})) # E: Revealed type is 'TypedDict('__main__.D', {'x': builtins.int, 'y': builtins.str})'
 [builtins fixtures/dict.pyi]
 [typing fixtures/typing-full.pyi]
 
@@ -921,9 +934,9 @@ C = TypedDict('C', {'a': int})
 D = TypedDict('D', {'x': C, 'y': str})
 d: D
 reveal_type(d.get('x', {})) \
-    # E: Revealed type is 'TypedDict(a=builtins.int, _fallback=__main__.C, _total=False)'
+    # E: Revealed type is 'TypedDict('__main__.C', {'a'?: builtins.int})'
 reveal_type(d.get('x', None)) \
-    # E: Revealed type is 'Union[TypedDict(a=builtins.int, _fallback=__main__.C), builtins.None]'
+    # E: Revealed type is 'Union[TypedDict('__main__.C', {'a': builtins.int}), builtins.None]'
 reveal_type(d.get('x', {}).get('a')) # E: Revealed type is 'Union[builtins.int, builtins.None]'
 reveal_type(d.get('x', {})['a']) # E: Revealed type is 'builtins.int'
 [builtins fixtures/dict.pyi]
@@ -937,7 +950,7 @@ from mypy_extensions import TypedDict
 D = TypedDict('D', {'x': int, 'y': str}, total=True)
 d: D
 reveal_type(d) \
-    # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=__main__.D)'
+    # E: Revealed type is 'TypedDict('__main__.D', {'x': builtins.int, 'y': builtins.str})'
 [builtins fixtures/dict.pyi]
 
 [case testTypedDictWithInvalidTotalArgument]
@@ -952,7 +965,7 @@ D = TypedDict('D', {'x': int}, False) # E: Unexpected arguments to TypedDict()
 from mypy_extensions import TypedDict
 D = TypedDict('D', {'x': int, 'y': str}, total=False)
 def f(d: D) -> None:
-    reveal_type(d) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=__main__.D, _total=False)'
+    reveal_type(d) # E: Revealed type is 'TypedDict('__main__.D', {'x'?: builtins.int, 'y'?: builtins.str})'
 f({})
 f({'x': 1})
 f({'y': ''})
@@ -965,11 +978,11 @@ f({'x': ''}) # E: Incompatible types (expression has type "str", TypedDict item
 from mypy_extensions import TypedDict
 D = TypedDict('D', {'x': int, 'y': str}, total=False)
 def f(d: D) -> None: pass
-reveal_type(D()) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.object], _total=False)'
-reveal_type(D(x=1)) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.object], _total=False)'
+reveal_type(D()) # E: Revealed type is 'TypedDict('__main__.D', {'x'?: builtins.int, 'y'?: builtins.str})'
+reveal_type(D(x=1)) # E: Revealed type is 'TypedDict('__main__.D', {'x'?: builtins.int, 'y'?: builtins.str})'
 f(D(y=''))
 f(D(x=1, y=''))
-f(D(x=1, z='')) # E: Expected TypedDict key 'x' but found keys ('x', 'z')
+f(D(x=1, z='')) # E: Extra key 'z' for TypedDict "D"
 f(D(x='')) # E: Incompatible types (expression has type "str", TypedDict item "x" has type "int")
 [builtins fixtures/dict.pyi]
 
@@ -1015,15 +1028,15 @@ a: A
 b: B
 c: C
 reveal_type(j(a, b)) \
-    # E: Revealed type is 'TypedDict(_fallback=typing.Mapping[builtins.str, <nothing>])'
+    # E: Revealed type is 'TypedDict({}, fallback=typing.Mapping[builtins.str, <nothing>])'
 reveal_type(j(b, b)) \
-    # E: Revealed type is 'TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int], _total=False)'
+    # E: Revealed type is 'TypedDict({'x'?: builtins.int}, fallback=typing.Mapping[builtins.str, builtins.int])'
 reveal_type(j(c, c)) \
-    # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=typing.Mapping[builtins.str, builtins.object], _total=False)'
+    # E: Revealed type is 'TypedDict({'x'?: builtins.int, 'y'?: builtins.str}, fallback=typing.Mapping[builtins.str, builtins.object])'
 reveal_type(j(b, c)) \
-    # E: Revealed type is 'TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int], _total=False)'
+    # E: Revealed type is 'TypedDict({'x'?: builtins.int}, fallback=typing.Mapping[builtins.str, builtins.int])'
 reveal_type(j(c, b)) \
-    # E: Revealed type is 'TypedDict(x=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int], _total=False)'
+    # E: Revealed type is 'TypedDict({'x'?: builtins.int}, fallback=typing.Mapping[builtins.str, builtins.int])'
 [builtins fixtures/dict.pyi]
 
 [case testTypedDictClassWithTotalArgument]
@@ -1032,7 +1045,7 @@ class D(TypedDict, total=False):
     x: int
     y: str
 d: D
-reveal_type(d) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=__main__.D, _total=False)'
+reveal_type(d) # E: Revealed type is 'TypedDict('__main__.D', {'x'?: builtins.int, 'y'?: builtins.str})'
 [builtins fixtures/dict.pyi]
 
 [case testTypedDictClassWithInvalidTotalArgument]
@@ -1055,7 +1068,7 @@ class B(TypedDict, A, total=False):
 class C(TypedDict, B, total=True):
     z: str
 c: C
-reveal_type(c) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, z=builtins.str, _fallback=__main__.C, _required_keys=[x, z])'
+reveal_type(c) # E: Revealed type is 'TypedDict('__main__.C', {'x': builtins.int, 'y'?: builtins.int, 'z': builtins.str})'
 [builtins fixtures/dict.pyi]
 
 [case testNonTotalTypedDictInErrorMessages]
@@ -1172,7 +1185,7 @@ def f(x: int) -> None: ...
 def f(x): pass
 
 a: A
-f(a)  # E: No overload variant of "f" matches argument types [TypedDict(x=builtins.int, _fallback=__main__.A)]
+f(a)  # E: No overload variant of "f" matches argument types [TypedDict('__main__.A', {'x': builtins.int})]
 [builtins fixtures/dict.pyi]
 [typing fixtures/typing-full.pyi]
 
@@ -1250,7 +1263,7 @@ X = TypedDict('X', {'b': 'B', 'c': 'C'})
 class B: pass
 class C(B): pass
 x: X
-reveal_type(x) # E: Revealed type is 'TypedDict(b=__main__.B, c=__main__.C, _fallback=__main__.X)'
+reveal_type(x) # E: Revealed type is 'TypedDict('__main__.X', {'b': __main__.B, 'c': __main__.C})'
 m1: Mapping[str, B] = x
 m2: Mapping[str, C] = x # E: Incompatible types in assignment (expression has type "X", variable has type Mapping[str, C])
 [builtins fixtures/dict.pyi]
@@ -1264,7 +1277,7 @@ class X(TypedDict):
 class B: pass
 class C(B): pass
 x: X
-reveal_type(x) # E: Revealed type is 'TypedDict(b=__main__.B, c=__main__.C, _fallback=__main__.X)'
+reveal_type(x) # E: Revealed type is 'TypedDict('__main__.X', {'b': __main__.B, 'c': __main__.C})'
 m1: Mapping[str, B] = x
 m2: Mapping[str, C] = x # E: Incompatible types in assignment (expression has type "X", variable has type Mapping[str, C])
 [builtins fixtures/dict.pyi]
@@ -1276,6 +1289,6 @@ from mypy_extensions import TypedDict
 X = TypedDict('X', {'a': 'A'}) # E: Invalid type "__main__.A"
 A = TypedDict('A', {'b': int})
 x: X
-reveal_type(x) # E: Revealed type is 'TypedDict(a=TypedDict(b=builtins.int, _fallback=__main__.A), _fallback=__main__.X)'
+reveal_type(x) # E: Revealed type is 'TypedDict('__main__.X', {'a': TypedDict('__main__.A', {'b': builtins.int})})'
 reveal_type(x['a']['b']) # E: Revealed type is 'builtins.int'
 [builtins fixtures/dict.pyi]