-
Notifications
You must be signed in to change notification settings - Fork 2k
[ty] Avoid inferring intersection types for call arguments #23933
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
Changes from all commits
46196b4
0c3a7a5
024d987
00a9891
483c20d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,13 +8,13 @@ when generics are involved, the type of an outer expression can sometimes be use | |
| inner expressions. Bidirectional type inference is a mechanism that propagates such "expected types" | ||
| to the inference of inner expressions. | ||
|
|
||
| ## Propagating target type annotation | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.12" | ||
| ``` | ||
|
|
||
| ## Propagating target type annotation | ||
|
|
||
| ```py | ||
| from typing import Literal | ||
|
|
||
|
|
@@ -80,11 +80,6 @@ def _() -> TD: | |
|
|
||
| ## Propagating return type annotation | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.12" | ||
| ``` | ||
|
|
||
| ```py | ||
| from typing import overload, Callable | ||
|
|
||
|
|
@@ -192,11 +187,6 @@ def f() -> list[Literal[1]]: | |
|
|
||
| ## Instance attributes | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.12" | ||
| ``` | ||
|
|
||
| Both meta and class/instance attribute annotations are used as type context: | ||
|
|
||
| ```py | ||
|
|
@@ -240,13 +230,110 @@ def _(xy: X | Y): | |
| xy.x = reveal_type([1]) # revealed: list[int] | ||
| ``` | ||
|
|
||
| ## Class constructor parameters | ||
| ## Overload evaluation | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.12" | ||
| The type context of all matching overloads are considered during argument inference: | ||
|
|
||
| ```py | ||
| from typing import overload, TypedDict | ||
|
|
||
| def int_or_str() -> int | str: | ||
| raise NotImplementedError | ||
|
|
||
| @overload | ||
| def f1(x: list[int | None], y: int) -> int: ... | ||
| @overload | ||
| def f1(x: list[int | str], y: str) -> str: ... | ||
| def f1(x, y) -> int | str: | ||
| raise NotImplementedError | ||
|
|
||
| # TODO: We should reveal `list[int]` here. | ||
| x1 = f1(reveal_type([1]), 1) # revealed: list[int] | ||
| reveal_type(x1) # revealed: int | ||
|
|
||
| x2 = f1(reveal_type([1]), int_or_str()) # revealed: list[int] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I notice you don't have a TODO comment here -- but
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the case that there is a single applicable type context (after overload resolution), we should reveal the type inferred with that type context. When there are multiple and we actually infer the type multiple times for each matching overload, it's less clear what to reveal here. It's closer to |
||
| reveal_type(x2) # revealed: int | str | ||
|
|
||
| @overload | ||
| def f2[T](x: T, y: int) -> T: ... | ||
| @overload | ||
| def f2(x: list[int | str], y: str) -> object: ... | ||
| def f2(x, y) -> object: ... | ||
|
|
||
| x3 = f2(reveal_type([1]), 1) # revealed: list[int] | ||
| reveal_type(x3) # revealed: list[int] | ||
|
|
||
| class TD(TypedDict): | ||
| x: list[int | str] | ||
|
|
||
| class TD2(TypedDict): | ||
| x: list[int | None] | ||
|
|
||
| @overload | ||
| def f3(x: TD, y: int) -> int: ... | ||
| @overload | ||
| def f3(x: TD2, y: str) -> str: ... | ||
| def f3(x, y) -> object: ... | ||
|
|
||
| # TODO: We should reveal `TD2` here. | ||
| x4 = f3(reveal_type({"x": [1]}), "1") # revealed: dict[str, list[int]] | ||
| reveal_type(x4) # revealed: str | ||
|
|
||
| x5 = f3(reveal_type({"x": [1]}), int_or_str()) # revealed: dict[str, list[int]] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here again it seems like a TODO comment is warranted, as much as it is in the previous case? |
||
| reveal_type(x5) # revealed: int | str | ||
|
|
||
| @overload | ||
| def f4[T](_: list[T]) -> list[T]: ... | ||
| @overload | ||
| def f4(_: list[str]) -> list[str]: ... | ||
| def f4(_: object): ... | ||
|
|
||
| x6 = f4(reveal_type([])) # revealed: list[Unknown] | ||
| reveal_type(x6) # revealed: list[Unknown] | ||
|
|
||
| @overload | ||
| def f5(_: list[int | str]) -> int: ... | ||
| @overload | ||
| def f5(_: set[int | str]) -> str: ... | ||
| def f5(_) -> object: | ||
| raise NotImplementedError | ||
|
|
||
| def list_or_set[T](x: T) -> list[T] | set[T]: | ||
| raise NotImplementedError | ||
|
|
||
| # TODO: We should reveal `list[int | str] | set[int | str]` here. | ||
| x7 = f5(reveal_type(list_or_set(1))) # revealed: list[int] | set[int] | ||
| reveal_type(x7) # revealed: int | str | ||
|
|
||
| @overload | ||
| def f6(_: list[int | None]) -> int: ... | ||
| @overload | ||
| def f6(_: set[int | str]) -> str: ... | ||
| def f6(_) -> object: | ||
| raise NotImplementedError | ||
|
|
||
| def list_or_set2[T, U](x: T, y: U) -> list[T] | set[U]: | ||
| raise NotImplementedError | ||
|
|
||
| # TODO: We should not error here. | ||
| # error: [no-matching-overload] | ||
| x8 = f6(reveal_type(list_or_set2(1, 1))) # revealed: list[int] | set[int] | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that pyright does not handle this case correctly either, and it seems very tricky to implement correctly (and also very unlikely to occur in practice). pyright is also not able to resolve |
||
| reveal_type(x8) # revealed: Unknown | ||
|
|
||
| @overload | ||
| def f7(y: list[int | str]) -> list[int | str]: ... | ||
| @overload | ||
| def f7[T](y: list[T]) -> list[T]: ... | ||
| def f7(y: object) -> object: | ||
| raise NotImplementedError | ||
|
|
||
| # TODO: We should reveal `list[int | str]` here. | ||
| x9 = f7(reveal_type(["Sheet1"])) # revealed: list[str] | ||
| reveal_type(x9) # revealed: list[int | str] | ||
| ``` | ||
|
|
||
| ## Class constructor parameters | ||
|
|
||
| The parameters of both `__init__` and `__new__` are used as type context sources for constructor | ||
| calls: | ||
|
|
||
|
|
@@ -269,11 +356,6 @@ A(f([])) | |
|
|
||
| ## Conditional expressions | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.12" | ||
| ``` | ||
|
|
||
| The type context is propagated through both branches of conditional expressions: | ||
|
|
||
| ```py | ||
|
|
@@ -290,11 +372,6 @@ def _(flag: bool): | |
|
|
||
| ## Dunder Calls | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.12" | ||
| ``` | ||
|
|
||
| The key and value parameters types are used as type context for `__setitem__` dunder calls: | ||
|
|
||
| ```py | ||
|
|
@@ -387,11 +464,6 @@ def _(x: Intersection[X, Y]): | |
|
|
||
| ## Multi-inference diagnostics | ||
|
|
||
| ```toml | ||
| [environment] | ||
| python-version = "3.12" | ||
| ``` | ||
|
|
||
| Diagnostics unrelated to the type-context are only reported once: | ||
|
|
||
| ```py | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main remaining limitation of this PR is that we do not "choose" the inference of the matching overload after overload evaluation, so hover types will show types inferred without type context. This is also the behavior on main -- I'll try to address it in a follow up (but it doesn't seem very high priority).