Skip to content

conformance: Fix TypeIs Any intersection case#2236

Open
JelleZijlstra wants to merge 1 commit intopython:mainfrom
JelleZijlstra:typeis-awaitable
Open

conformance: Fix TypeIs Any intersection case#2236
JelleZijlstra wants to merge 1 commit intopython:mainfrom
JelleZijlstra:typeis-awaitable

Conversation

@JelleZijlstra
Copy link
Copy Markdown
Member

The assert_type(val, int) here is incorrect in the negative case
if a type checker is sufficiently precise in its implementation of
intersections.

ty currently infers int & ~Awaitable[object], apparently because it
has chosen to always use the top materialization of TypeIs. A change
I am about to make to pycroscope will have it infer int | Awaitable[int],
because it drops the negated part of Not types.

The `assert_type(val, int)` here is incorrect in the negative case
if a type checker is sufficiently precise in its implementation of
intersections.

ty currently infers int & ~Awaitable[object], apparently because it
has chosen to always use the top materialization of TypeIs. A change
I am about to make to pycroscope will have it infer `int | Awaitable[int]`,
because it drops the negated part of Not types.
# (int | Awaitable[int]) & ~Awaitable[Any], but conformant implementations
# may simplify this.
# But it should definitely remain assignable to `int | Awaitable[int]`.
y: int | Awaitable[int] = val
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.

I feel like this isn't testing all that much anymore. Not sure how to improve it, though ^^;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, unfortunately I can't think of a better way here. Using theoretically correct intersection semantics is not always practical. For reference here are the tests I added to pycroscope while looking at this: https://github.com/JelleZijlstra/pycroscope/pull/444/changes#diff-17d47f7718e5c338fb6076c1e2971a2ca798555e661e881cd1653cf5e54d99fe

I tried both TypeIs[Awaitable[Any]] and TypeIs[Awaitable[object]].

Among the current type checkers, two type checkers actually have explicit intersections, but neither produces the full theoretically correct types. Ty treats the Awaitable[Any] TypeIs as if it's Awaitable[object]. That behaves better in some cases but worse in others; in this check it means that y: int = await val is rejected because val might also be int & Awaitable[object]. Pycroscope (unreleased version) doesn't do that but it drops negation types, so the else branch types are simpler.

Three of the other type checkers (mypy, zuban, pyright) narrow to Awaitable[int] and int for both the Any and object guards; pyrefly narrows to Awaitable[Any] for the TypeIs[Awaitable[Any]]. This is unsound but perhaps more practical than the precise intersection behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants