Uh oh!
There was an error while loading. Please reload this page.
'in' can narrow TypedDict unions - #13838
Conversation
e714a52 to
4642a9cCompare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
c6f793e to
4617bf8Compare4617bf8 to
ca87360Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
ikonst
commented
Oct 15, 2022
@hauntsaninja I still need to actually respect BTW, does it strictly require classD(TypedDict):
foo: strd: Dif'foo'ind:
assert_type(d, D)
else:
assert_never()It doesn't make a good case for ruling out unions, though: classD1(TypedDict):
foo: strclassD2(TypedDict):
bar: strd: D1|D2if'foo'ind:
assert_type(d, Union[D1, D2])
else:
assert_type(d, D2)Might seem a bit complex, but we already have to deal with it for totality. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Update: Also discerning on final-ity now In the case of https://github.com/Rapptz/discord.py/blob/3bca40352ecfa5b219bae0527252ee5d296113e7/discord/client.py#L1755: |
This comment has been minimized.
This comment has been minimized.
ikonst
commented
Oct 24, 2022
@sobolevn can this be merged as is? |
This comment has been minimized.
This comment has been minimized.
ikonst
commented
Nov 2, 2022
@hauntsaninja ping |
This comment has been minimized.
This comment has been minimized.
hauntsaninja
left a comment
There was a problem hiding this comment.
Took a look at the tests and left some comments. I'll review the actual code too, but might take me a little bit to get to it. Will make sure to review before 1.0 though
| [builtins fixtures/dict.pyi] | ||
| [typing fixtures/typing-typeddict.pyi] | ||
| [case testOperatorContainsNarrowsTypedDicts_unionWithList] |
There was a problem hiding this comment.
this test case seems completely duplicated in testOperatorContainsNarrowsTypedDicts_total, let's remove this?
There was a problem hiding this comment.
I'll remove the relevant part from testOperatorContainsNarrowsTypedDicts_total, as to keep test names descriptive.
| else: | ||
| assert_type(d_or_list, list[str]) |
There was a problem hiding this comment.
| else: | |
| assert_type(d_or_list, list[str]) | |
| elif 'bar' in d_or_list: | |
| assert_type(d_or_list, list[str]) | |
| else: | |
| assert_type(d_or_list, list[str]) |
| foo_or_invalid: Literal['foo', 'invalid'] | ||
| if foo_or_invalid in d: | ||
| assert_type(d, D1) |
There was a problem hiding this comment.
in theory this could narrow foo_or_invalid as well, want to add an assert_type for that behaviour too?
There was a problem hiding this comment.
That'd have to be implemented. And I think it could be pretty neat, but would require a rework: I'd have to pass in the left expression, and return type maps (not "if_type" and "else_type"). Maybe in a follow-up?
There was a problem hiding this comment.
My current implementation is to make a form of "tagged TypeDicts" work, but I suspect a more generalized form of type narrowing should be possible. However, before we tackle the more contrived x in y case, we should probably do it for x == y, so that this example would see narrowing applied.
(I might be naive, though, and this might've been tried before and proven impossible.)
P.S. PyRight is similarly limited in this
There was a problem hiding this comment.
Oh sorry, to be clear my suggestion wasn't to implement this / I don't think it's a terribly important feature. I was just saying it's worth adding an assert_type in this test to make this (lack of) behaviour clear to the reader.
| value: int | ||
| if 'foo' in arg: | ||
| assert_type(d, Union[D1, D2]) # strangely enough it's seen as a union | ||
| value = arg['foo'] # but acts here as D1 |
There was a problem hiding this comment.
I think this is because mypy processes these twice. Want to change the test to just do reveal_type(arg['foo'])?
…nst/mypy into typed-dict-in-type-narrowing
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Diff from mypy_primer, showing the effect of this PR on open source code: discord.py (https://github.com/Rapptz/discord.py)
- discord/client.py:1755: error: Value of "rpc_origins" has incompatible type "None"; expected "List[str]" [typeddict-item] |
alicederyn
commented
Nov 11, 2022
Could the PR description be modified to reflect the change made in f799344 ? Perhaps to classA(TypedDict)
foo: int@finalclassB(TypedDict):
bar: intunion: Union[A, B] = ...
value: intif'foo'inunion:
# Cannot be a B as it is final and has no "foo" field, so must be an Avalue=union['foo']
else:
# Cannot be an A as those went to the if branchvalue=union['bar'](Super excited to see this PR, thank you so much!) |
ikonst
commented
Nov 11, 2022
@alicederyn thanks for spotting this — updated with your proposed sample code |
hauntsaninja
left a comment
There was a problem hiding this comment.
Thanks for improving this! This is a nice feature
incould narrow unions of TypeDicts, e.g.