[ty] Avoid inferring intersection types for call arguments#23933
[ty] Avoid inferring intersection types for call arguments#23933ibraheemdev merged 5 commits intomainfrom
Conversation
|
|
||
| # TODO: We should not error here. | ||
| # error: [no-matching-overload] | ||
| x8 = f6(reveal_type(list_or_set2(1, 1))) # revealed: list[int] | set[int] |
There was a problem hiding this comment.
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 f5 above, which we are able to.
f8eb408 to
e6120a6
Compare
| raise NotImplementedError | ||
|
|
||
| # TODO: We should reveal `list[int]` here. | ||
| x1 = f1(reveal_type([1]), 1) # revealed: list[int] |
There was a problem hiding this comment.
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).
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 85.29%. The percentage of expected errors that received a diagnostic held steady at 78.13%. The number of fully passing files held steady at 64/132. |
Memory usage reportSummary
Significant changesClick to expand detailed breakdownflake8
trio
sphinx
prefect
|
e6120a6 to
cec3504
Compare
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-await |
40 | 0 | 0 |
invalid-assignment |
0 | 4 | 32 |
invalid-argument-type |
3 | 0 | 17 |
type-assertion-failure |
0 | 1 | 2 |
unresolved-attribute |
0 | 0 | 3 |
invalid-return-type |
1 | 0 | 1 |
unsupported-operator |
1 | 0 | 0 |
| Total | 45 | 5 | 55 |
Merging this PR will improve performance by 4.82%
Performance Changes
Comparing Footnotes
|
| Ignore, | ||
|
|
||
| /// Store the intersection of all types inferred for the expression. | ||
| Intersect, |
There was a problem hiding this comment.
We should be able to remove MultiInferenceState entirely now (see #23923 (comment)), but I'll save that for a follow up.
|
19ed6c3 to
31afd45
Compare
|
The ecosystem report looks all positive except for a regression with from typing import TypedDict, NotRequired
class Inner(TypedDict):
x: int
class Outer(TypedDict):
x: NotRequired[Inner]
def g(x: Outer):
# error[invalid-assignment]: Object of type `Inner | dict[str, int]` is not assignable to `Inner`
_: Inner = x.get("x", {"x": 1})
def f[T](x: Outer, y: T) -> Inner | T:
raise NotImplementedError
# error[invalid-assignment]: Object of type `Inner | dict[str, int]` is not assignable to `Inner`
_: Inner = f("x", {"x": 1})This one is arguably a limitation of the constraint solver. I added a failing test, I don't think it should block this PR. Interestingly, pyright errors on the second but the first, so it might be special casing typed dicts here. |
|
I can take this one in exchange for the one I just reassigned to you, @dcreager :) |
f6cf193 to
1b31b23
Compare
1b31b23 to
483c20d
Compare
| 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] |
There was a problem hiding this comment.
I notice you don't have a TODO comment here -- but list[int] seems just as wrong here as above. I think it should be list[int | None] | list[int | str]?
There was a problem hiding this comment.
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 list[int | None] & list[int | str] than list[int | None] | list[int | str] (but this PR is about removing the intersection types after all). If we actually did infer a union, overload resolution would fail here, so I'm not sure revealing the union makes sense. pyright also reveals the type-contextless inference. We could maybe reveal a ty internal Both[list[int | None], list[int | str]], but that also seems confusing?
| 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]] |
There was a problem hiding this comment.
Here again it seems like a TODO comment is warranted, as much as it is in the previous case?
* main: [ty] Filter out unsatisfiable inference attempts during generic call narrowing (#24025) [ty] Avoid inferring intersection types for call arguments (#23933) [ty] Pin mypy_primer in `setup_primer_project.py` (#24020) [`pycodestyle`] Recognize `pyrefly:` as a pragma comment (`E501`) (#24019) Add company AI policy to contributing guide (#24021) [ty] Remove the mypy_primer CI workflow (#24016) Update prek dependencies (#23980) [ty] Smarter semantic token classification for attribute access on union type (#23841) [ty] ecosystem-analyzer: Inline diffs and panic messages (#24015) [ty] Improve `.toml` support in the ty playground (#23476) PEP 639 license information (#19661)
We currently infer intersection types when call arguments have multiple applicable type contexts across call bindings. However, this can lead to incorrect and confusing types being inferred (see astral-sh/ty#3001 for details). Instead, we should store all inferred types separately, and access them based on the type context for the given binding being matched against.
Resolves the remaining part of astral-sh/ty#3001.