FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

'in' can narrow TypedDict unions by ikonst · Pull Request #13838 · python/mypy · GitHub

Repository navigation

'in' can narrow TypedDict unions - #13838

Merged
hauntsaninja merged 23 commits into
python:masterfrom
ikonst:typed-dict-in-type-narrowing
Dec 20, 2022
Merged

hauntsaninja merged 23 commits into
python:masterfrom
ikonst:typed-dict-in-type-narrowing

Conversation

ikonst commented Oct 8, 2022 •
edited
Loading

Copy link
Copy Markdown
Contributor

in could narrow unions of TypeDicts, e.g.

class A(TypedDict)
   foo: int

@final
class B(TypedDict):
   bar: int

union: Union[A, B] = ...
value: int
if 'foo' in union:
  # Cannot be a B as it is final and has no "foo" field, so must be an A
  value = union['foo']
else:
  # Cannot be an A as those went to the if branch
  value = union['bar']

ikonst force-pushed the typed-dict-in-type-narrowing branch from e714a52 to 4642a9c Compare October 8, 2022 04:03

ikonst commented Oct 8, 2022 •
edited
Loading

Copy link
Copy Markdown
Contributor Author

Once done™️ , will address #7981 (and #13802).

This comment has been minimized.

This comment has been minimized.

ikonst marked this pull request as draft October 10, 2022 03:58
ikonst force-pushed the typed-dict-in-type-narrowing branch 5 times, most recently from c6f793e to 4617bf8 Compare October 15, 2022 03:11
ikonst force-pushed the typed-dict-in-type-narrowing branch from 4617bf8 to ca87360 Compare October 15, 2022 03:12

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

ikonst commented Oct 15, 2022

Copy link
Copy Markdown
Contributor Author

@hauntsaninja I still need to actually respect @final but am I on the right track?

BTW, does it strictly require @final? This can be deemed as truthy even without @final

class D(TypedDict):
  foo: str

d: D
if 'foo' in d:
  assert_type(d, D)
else:
  assert_never()

It doesn't make a good case for ruling out unions, though:

class D1(TypedDict):
  foo: str

class D2(TypedDict):
  bar: str

d: D1 | D2
if 'foo' in d:
  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.

ikonst commented Oct 15, 2022 •
edited
Loading

Copy link
Copy Markdown
Contributor Author

Update: Also discerning on final-ity now

In the case of https://github.com/Rapptz/discord.py/blob/3bca40352ecfa5b219bae0527252ee5d296113e7/discord/client.py#L1755:
it seems that data is typed AppInfo instead of AppInfo | PartialAppInfo, and so we assume rpc_origins is always assigned (true for AppInfo which is total) and deem the branch unreachable.

This comment has been minimized.

ikonst commented Oct 24, 2022

Copy link
Copy Markdown
Contributor Author

@sobolevn can this be merged as is?

This comment has been minimized.

ikonst commented Nov 2, 2022

Copy link
Copy Markdown
Contributor Author

@hauntsaninja ping

This comment has been minimized.

hauntsaninja left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

this test case seems completely duplicated in testOperatorContainsNarrowsTypedDicts_total, let's remove this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I'll remove the relevant part from testOperatorContainsNarrowsTypedDicts_total, as to keep test names descriptive.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Comment thread test-data/unit/check-typeddict.test Outdated
Comment on lines +2066 to +2067
else:
assert_type(d_or_list, list[str])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality
Suggested change
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])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality


foo_or_invalid: Literal['foo', 'invalid']
if foo_or_invalid in d:
assert_type(d, D1)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

in theory this could narrow foo_or_invalid as well, want to add an assert_type for that behaviour too?

ikonst Nov 3, 2022 •
edited
Loading

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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?

ikonst Nov 4, 2022 •
edited
Loading

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Gotcha, done: fb564eb

Comment thread test-data/unit/check-typeddict.test Outdated
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I think this is because mypy processes these twice. Want to change the test to just do reveal_type(arg['foo'])?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This comment has been minimized.

This comment has been minimized.

github-actions Bot commented Nov 4, 2022

Copy link
Copy Markdown
Contributor

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]

Copy link
Copy Markdown
Contributor

Could the PR description be modified to reflect the change made in f799344 ? Perhaps to

class A(TypedDict)
   foo: int

@final
class B(TypedDict):
   bar: int

union: Union[A, B] = ...
value: int
if 'foo' in union:
  # Cannot be a B as it is final and has no "foo" field, so must be an A
  value = union['foo']
else:
  # Cannot be an A as those went to the if branch
  value = union['bar']

(Super excited to see this PR, thank you so much!)

ikonst commented Nov 11, 2022

Copy link
Copy Markdown
Contributor Author

@alicederyn thanks for spotting this — updated with your proposed sample code

hauntsaninja mentioned this pull request Dec 19, 2022
10 of 17 tasks

hauntsaninja left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Thanks for improving this! This is a nice feature

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants


Back | FazBrowse Home | New Git URL