Repository navigation
"Type of TypedDict is ambiguous" despite tag fields being distinct Literal types #8533
Description
Hi there,
Thanks for all the awesome work on mypy! It's brought incredible improvements to my workflow. :-)
I'm reporting what could be a bug with tagged unions, which were released in 0.770. As of today this is also reproducible against master.
Unfortunately this bug makes usage of tagged unions impractical. Last time I beta-tested it (a few days before the 0.770 release — perhaps around March 1st), it worked though.
Let me know if there's anything I can do to help resolve!
Reproduction case
# example.py
from typing import TypedDict, Literal, Union
# TypedDict interfaces with distinct tag fields.
A = TypedDict('A', {'@type': Literal['type-a'], 'value': str})
B = TypedDict('B', {'@type': Literal['type-b'], 'value': str})
# Tagged union of A and B.
C = Union[A, B]
c: C = {
'@type': 'type-a',
'value': 'test',
}Expected behavior
$ mypy example.py
# OK - no errorsActual behavior
$ mypy example.py
example.py:7:8: error: Type of TypedDict is ambiguous, could be any of ("A", "B")
example.py:7:8: error: Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "Union[A, B]")
Found 2 errors in 1 file (checked 1 source file)Version info
- Python 3.8.1
- Mypy 0.770
- Mypy flags: none (and no
mypy.iniin the current workdir).
Additional context
I notice an ambiguous case is tested here:
mypy/test-data/unit/check-typeddict.test
Lines 837 to 846 in 1180a9d
| [case testTypedDictUnionAmbiguousCase] | |
| from typing import Union, Mapping, Any, cast | |
| from typing_extensions import TypedDict, Literal | |
| A = TypedDict('A', {'@type': Literal['a-type'], 'a': str}) | |
| B = TypedDict('B', {'@type': Literal['a-type'], 'a': str}) | |
| c: Union[A, B] = {'@type': 'a-type', 'a': 'Test'} # E: Type of TypedDict is ambiguous, could be any of ("A", "B") \ | |
| # E: Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "Union[A, B]") | |
| [builtins fixtures/dict.pyi] |
But the snippet above is unambiguous: Literal['type-a'] and Literal['type-b'] don't overlap at all. Is the test above passing in master?
Activity
florimondmanca commentedon Mar 13, 2020
Ah, so I realize this might actually be a feature request… It seems we can't use a tagged union to declare variable, but only when declaring a function parameter, and then pattern-match inside the function based on whichever exact type is passed as a parameter, eg:
I think (?) it would make sense to allow declaring a variable typed after a tagged union, given that we can do it with regular unions, eg…
msullivan commentedon Mar 14, 2020
I think that I agree that this is a bug.
There is a pretty easy workaround, though:
works
florimondmanca commentedon Mar 14, 2020
Hi @msullivan, thanks :)
FWIW, my precise use case was yielding dicts that match a tagged union inside a generator function:
In that case your workaround works too, although pretty clunky:
I'm new to the mypy codebase, but if you've got any pointers on where this might come from I can try and investigate a bit more.
Michael0x2a commentedon Mar 14, 2020
The strategy I personally use is to grep for part of the error message. That usually points you to some location in messages.py, which is responsible for constructing and reporting error messages. Then, you can work backwards to figure out how exactly we ended up reaching that particular point in the code.
For example, grepping for "Type of TypedDict is ambiguous" should point you to typed_dict_ambiguous in messages.py. And from there, we can see this is only called by find_typeddict_context in checkexpr.py.
And finally, if we step through what this function is doing, we discover that mypy is comparing the TypedDicts against the dict expression only by their keys and bails out early if there are multiple potentially valid TypedDict contexts. (And the caller of find_typeddict_context is what's actually responsible for checking the values).
This is where the bug is coming from: this filtering strategy is probably too aggressive/the whole flow of logic here could probably do with a second look.
Add typing for the ASGI messages
Consider field value types when disambiguating a union of TypedDicts
Consider field value types when disambiguating a union of TypedDicts
Properly support union of TypedDicts as dict literal context (#14505)
Add typing for the ASGI messages