[red-knot] Add FunctionType::to_overloaded#17585
Conversation
|
da7e00b to
dfdcef0
Compare
| // The semantic model records a use for each function on the name node. This is used here | ||
| // to get the previous function definition with the same name. |
There was a problem hiding this comment.
Does that mean that we call to_overloaded 4 times if a function has 3 overloads (and one impl). 4 times because:
to_overloadedon the implementation- which calls into
to_overloadedon the last overload - which, in turn, calls into `to_overloaded on the second overload
- which, in turn, calls into
to_overloadedon the first overload
Or is it at most two to_overloaded calls, one for the impl (root) which then calls into the to_overloaded for the first overload?
There was a problem hiding this comment.
Yes, the former is correct. It will call 4 times.
There was a problem hiding this comment.
This kind of gives me a thought as to whether this is being wasting resources in terms of memory because we don't really need to cache the calls on the function that isn't the implementation or the last overload (if implementation doesn't exists).
There was a problem hiding this comment.
Only caching the last result would be great. Not only reduces it overall memory consumption. It also removes the need to clone the overloads vec multiple times only to add one extra element.
Would it be possible to rewrite this logic as a loop where we store the current function type that we look up instead of a recursive function call?
There was a problem hiding this comment.
It should be possible to do this iteratively instead of recursively. It will be less efficient (will repeat work) if we did end up calling it on intermediate overloads too, but it should be somewhat more efficient if we only end up calling it on the last overload. Which I think should usually be the case?
The memory used here is tiny, and there's no cold regression shown; I doubt that overloads will ever be a significant part of our overall performance profile (cold check time or memory). There is a 1% incremental regression, probably due to the new memoized query results; an iterative approach might be able to bring that down if it reduces the number of memos?
I don't think we should spend more than an hour or two right now on reworking this.
There was a problem hiding this comment.
t should be possible to do this iteratively instead of recursively. It will be less efficient (will repeat work) if we did end up calling it on intermediate overloads too, but it should be somewhat more efficient if we only end up calling it on the last overload. Which I think should usually be the case?
Could we remove the query all-together, considering that they're rare and because to_overloaded is only called from signature which itself is cached?
There was a problem hiding this comment.
Yeah, I don't think we would face any regression if we don't cache the to_overloaded right now as most of the usages of overloads would come from signature which is cached.
Currently, there isn't any usages of to_overloaded but that will happen when I add the checks for valid overloads in a follow-up PR. I think I'd prefer to see if removing the cache would cause any regression after that and not now. What do you think?
There was a problem hiding this comment.
There will likely be another upcoming use for dataclass transforms that wouldn't be from within signature? But would also be within a different query.
There was a problem hiding this comment.
Yeah, for now I'll just follow-up with an iterative approach instead. I'll merge this PR after expanding the documentation for to_overloaded method.
2fafb6b to
53d594d
Compare
|
Merging this PR, I'll follow-up with an iterative approach (instead of the current recursive approach) to collect the overloads. Later, once we have a couple of usages of |
* main: [red-knot] fix collapsing literal and its negation to object (#17605) [red-knot] Add more tests for protocols (#17603) [red-knot] Ban direct instantiations of `Protocol` classes (#17597) [`pyupgrade`] Preserve parenthesis when fixing native literals containing newlines (`UP018`) (#17220) [`airflow`] fix typos (`AIR302`, `AIR312`) (#17574) [red-knot] Special case `@abstractmethod` for function type (#17591) [red-knot] Emit diagnostics for isinstance() and issubclass() calls where a non-runtime-checkable protocol is the second argument (#17561) [red-knot] Infer the members of a protocol class (#17556) [red-knot] Add `FunctionType::to_overloaded` (#17585) [red-knot] Add mdtests for `global` statement (#17563) [syntax-errors] Make duplicate parameter names a semantic error (#17131)
## Summary This PR updates the `to_overloaded` method to use an iterative approach instead of a recursive one. Refer to #17585 (comment) for context. The main benefit here is that it avoids calling the `to_overloaded` function in a recursive manner which is a salsa query. So, this is a bit hand wavy but we should also see less memory used because the cache will only contain a single entry which should be the entire overload chain. Previously, the recursive approach would mean that each of the function involved in an overload chain would have a cache entry. This reduce in memory shouldn't be too much and I haven't looked at the actual data for it. ## Test Plan Existing test cases should pass.
Summary
This PR adds a new method
FunctionType::to_overloadedwhich converts aFunctionTypeinto anOverloadedFunctionwhich contains all the@overload-edFunctionTypeand the implementationFunctionTypeif it exists.There's a big caveat here (it's the way overloads work) which is that this method can only "see" all the overloads that comes before itself. Consider the following example:
Here, when the
to_overloadedmethod is invoked on thefoodefinition, it would only contain a single overload which is itself and no implementation.foodefinition, it would contain both overloads and still no implementationfoodefinition, it would contain both overloads and the implementation which is itselfUsages
This method will be used in the logic for checking invalid overload usages. It can also be used for #17541.
Test Plan
Make sure that existing tests pass.