[ty] Avoid unnecessary argument type expansion#19999
Conversation
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
| reveal_type( | ||
| # error: [no-matching-overload] | ||
| # revealed: Unknown | ||
| f( | ||
| C(), |
There was a problem hiding this comment.
Considering the same example, but pass in A() as the first argument and use a=None instead of a=1 for the parameter above, this would still continue with the expansion. The reason being that A() matches parameter of at least one overload which means we cannot skip argument type expansion.
I'm trying to think through what kind of heuristic to have that would avoid the expansion in this case as well. This would still lead to no-matching-overload because even though Unknown is assignable to int, the spec says that all argument lists resulting from an expansion should evaluate successfully and there's no such expansion as expanding Unknown | None leads to argument lists where at least one would have None which isn't assignable to int.
Regardless, I think it'd be best to set a higher limit on the expansion.
There was a problem hiding this comment.
Yes, I think there will be cases where we can't avoid actually trying all the expansions and see if any of them work -- best we can do there is optimize the expansions as best we can (e.g. iterator instead of eagerly materialized?) and then set a reasonable limit.
|
EDIT: Nevermind, it is because ty doesn't support
I wasn't expecting any mypy-primer diff but it seems that we have new diagnostics :) I think this might be due to the fact that ty doesn't support Screen.Recording.2025-08-20.at.15.46.35.mov |
| reveal_type( | ||
| # error: [no-matching-overload] | ||
| # revealed: Unknown | ||
| f( | ||
| C(), |
There was a problem hiding this comment.
Yes, I think there will be cases where we can't avoid actually trying all the expansions and see if any of them work -- best we can do there is optimize the expansions as best we can (e.g. iterator instead of eagerly materialized?) and then set a reasonable limit.
| // for evaluating the expanded argument lists. | ||
| snapshotter.restore(self, pre_evaluation_snapshot); | ||
|
|
||
| // At this point, there's at least one argument that can be expanded. |
There was a problem hiding this comment.
Would it make sense to first explicitly check if there are any expandable arguments (using the new method you added), then do this check, then actually expand the arguments? This way even if the heuristic applies, we've already generated the expansion, just haven't used the expansions yet.
But maybe generating the expansions is very cheap relative to actually trying the call with new argument types, so this doesn't matter?
And I guess we'd slow down the fast path slightly if we first check all arguments for expandability, then separately expand them?
There was a problem hiding this comment.
Yeah, I don't think this would really matter in practice mainly because there are only a few differences between expand_type and is_type_expandable which are (1) collecting the enum members from the metadata (both method generates the metadata) (2) performing multi-cartesian product of tuple elements and (3) allocation. I guess it wouldn't hurt to avoid allocating when it's easy to do so. Let me try it, I think I'll make this change.
There was a problem hiding this comment.
Ah right, so the main reason I did it at this location is so that the bindings state is the one before the type checking step, so that we only skip the overloads that have been filtered by the arity check.
So, we'd either have to pay the cost of either
- Allocation for the first expansion, taking the bindings snapshot. We'd skip the snapshot if there are no expansion in this case.
- Always take the binding snapshot, check whether expansion needs to happen using the logic in this PR, skip allocating for the first expansion if there's no need
I think going with the latter sounds reasonable i.e., instead of always allocating the first expansion, we'd always allocate the bindings snapshot.
There was a problem hiding this comment.
Hmm, but then there's complication on restoring the snapshots at the correct places. I think I'll leave this as is for now.
## Summary Part of: astral-sh/ty#868 This PR adds a heuristic to avoid argument type expansion if it's going to eventually lead to no matching overload. This is done by checking whether the non-expandable argument types are assignable to the corresponding annotated parameter type. If one of them is not assignable to all of the remaining overloads, then argument type expansion isn't going to help. ## Test Plan Add mdtest that would otherwise take a long time because of the number of arguments that it would need to expand (30).
Summary
Part of: astral-sh/ty#868
This PR adds a heuristic to avoid argument type expansion if it's going to eventually lead to no matching overload.
This is done by checking whether the non-expandable argument types are assignable to the corresponding annotated parameter type. If one of them is not assignable to all of the remaining overloads, then argument type expansion isn't going to help.
Test Plan
Add mdtest that would otherwise take a long time because of the number of arguments that it would need to expand (30).