Skip to content

Change reveal_type() representation of TypedDicts #3598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/source/kinds_of_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,11 @@ value of type ``Awaitable[T]``:
my_coroutine = countdown_1("Millennium Falcon", 5)
reveal_type(my_coroutine) # has type 'Awaitable[str]'

.. note::

:ref:`reveal_type() <reveal-type>` displays the inferred static type of
an expression.

If you want to use coroutines in older versions of Python that do not support
the ``async def`` syntax, you can instead use the ``@asyncio.coroutine``
decorator to convert a generator into a coroutine.
Expand Down Expand Up @@ -1284,6 +1289,14 @@ just need to be careful with it, as it could result in a ``KeyError``.
Requiring ``get()`` everywhere would be too cumbersome. (Note that you
are free to use ``get()`` with total TypedDicts as well.)

Keys that aren't required are shown with a ``?`` in error messages:

.. code-block:: python

# Revealed type is 'TypedDict('GuiOptions', {'language'?: builtins.str,
# 'color'?: builtins.str})'
reveal_type(options)

Totality also affects structural compatibility. You can't use a partial
TypedDict when a total one is expected. Also, a total typed dict is not
valid when a partial one is expected.
Expand Down
13 changes: 5 additions & 8 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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]
Expand All @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you do not create a new TypedDictType anymore?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, is that in order to preserve the original TypedDict name?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Also, the deleted code no longer serves its original purpose because of changes to TypedDict type inference.


# Types and methods that can be used to infer partial types.
item_args = {'builtins.list': ['append'],
Expand Down
34 changes: 14 additions & 20 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
8 changes: 4 additions & 4 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions test-data/unit/check-serialize.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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})'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think while the ?-syntax for non-required keys has its downsides, it works well in this case.
We need to be concise and I think ? is pretty intuitive.
It might be worth mentioning it documentation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good point. I'll update the docs tomorrow to mention ?.

[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
Expand Down
Loading