-
Notifications
You must be signed in to change notification settings - Fork 1.8k
[red-knot] Update call binding to return all matching overloads #17618
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
Conversation
|
afd5e15 to
bb269c6
Compare
|
I'm marking this as ready for review mainly to ask for feedback on the changes and the effect it has on the ecosystem changes. The PR description has my ecosystem analysis and it does reveal that this change would remove ~300 false positives by "hiding" it via intersection or |
87d916f to
6e80436
Compare
carljm
left a comment
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.
Thank you, that's a super-thorough analysis of the ecosystem impacts!
My main conclusion from looking at it is that it's hard to evaluate at this point. In particular, there are a lot of overloads in typeshed defined with protocol arguments, and so we are evaluating a lot of overloads as matching that really shouldn't be.
But I am seeing enough cases that would give counter-intuitive or wrong behavior (due to people relying on being able to silence an overlapping-overload error and count on the type checker picking the first matching overload) that I'm pretty convinced we will need to implement the full specced overload matching algorithm, in order to achieve sufficiently compatible behavior with the ecosystem. So I don't think we should merge this as-is.
I do think that in order to implement the full matching algorithm, we will still need to check all overloads and preserve all bindings, not short-circuit, and it seems like that was the bulk of the code changes here? So I think we should modify the return_type method (where the intersection is currently implemented) to instead return the return type of the first matching overload, with a TODO to implement proper overload matching, and then land this?
This reverts commit 97d3b584e02a3f93504de84443211a5a4b26f262.
6e80436 to
9c5e96d
Compare
(Archived) Ecosystem analysis for an earlier version of this PR:
Ecosystem analysisOverall statsAfter filtering out the diagnostics where it’s only the messages that have been updated due to the fact that the return type will now include additional types because of intersections, we get the following stats: NotesThe most common false positives which have been removed are around the usages of Removed false positives: import urllib
import webbrowser
# Red knot matches against both overloads - `Literal[b""] & @Todo`
url = urllib.parse.urlunparse(("file", "", "", "", "", ""))
# The following false positive is removed
# red-knot: Argument to this function is incorrect: Expected `str`, found `Literal[b""]` [lint:invalid-argument-type]
webbrowser.open(url)Diagnostic messages are getting verbose because of this change because the return type would be an intersection of all the return types from the matched overloads: - Attribute `name` on type `Binance | None` is possibly unbound
+ Attribute `name` on type `(Binance & Bitpanda & Bitcoinde & Bitfinex & Bitmex & Bitstamp & Bybit & Coinbase & Coinbaseprime & Gemini & Htx & Iconomi & Independentreserve & Kraken & Okx & Poloniex & Woo & Kucoin) | None` is possibly unboundSimilarly, the return type of fd = open("file.text", "rb")
# revealed: `TextIOWrapper & BufferedRandom & BufferedWriter & BufferedReader & @Todo(specialized non-generic class)`
# revealed on main: `TextIOWrapper`
reveal_type(fd)The revealed type for dictionary’s data = {}
value = data.get("foo", None)
# revealed on main: `@Todo(Support for `typing.TypeVar` instances in type expressions) | None`
# revealed: `@Todo(Support for `typing.TypeVar` instances in type expressions) | (None & @Todo(Support for `typing.TypeVar` instances in type expressions))`
reveal_type(value)False negativesFalse negative when from typing import reveal_type
class Foo:
pass
value = getattr(Foo, "foo", None)
if value is None:
# red-knot does not report that `value` is not callable
# revealed: `Any & None`
# revealed on main: `(Any & None) | None`
value()
else:
value()
# OR
def _(attr: str) -> int:
# red-knot (main): Return type does not match returned value: Expected `int`, found `Any | None` [lint:invalid-return-type]
# No error when using intersection of callables
return getattr(Foo, attr, None)Another false negative for import subprocess
def _(cmd: str) -> str:
out = subprocess.check_output(cmd)
# revealed: bytes & Any
# revealed on main: bytes
reveal_type(out)
# This raises `invalid-return-type` diagnostic on main
return outFalse positivesThe values = []
out = sum(values)
# revealed: `(int & @Todo(Support for `typing.TypeVar` instances in type expressions)) | Literal[0]`
# revealed on main: `int`
reveal_type(out)
# false positive: division-by-zero
1 / out
|
carljm
left a comment
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.
Looks good!
* main: [red-knot] Preliminary `NamedTuple` support (#17738) [red-knot] Add tests for classes that have incompatible `__new__` and `__init__` methods (#17747) Update dependency vite to v6.2.7 (#17746) [red-knot] Update call binding to return all matching overloads (#17618) [`airflow`] apply Replacement::AutoImport to `AIR312` (#17570) [`ruff`] Add fix safety section (`RUF028`) (#17722) [syntax-errors] Detect single starred expression assignment `x = *y` (#17624) py-fuzzer: fix minimization logic when `--only-new-bugs` is passed (#17739) Fix example syntax for pydocstyle ignore_var_parameters option (#17740) [red-knot] Update salsa to prevent panic in custom panic-handler (#17742) [red-knot] Ban direct instantiation of generic protocols as well as non-generic ones (#17741) [red-knot] Lookup of `__new__` (#17733) [red-knot] Check decorator consistency on overloads (#17684) [`flake8-use-pathlib`] Avoid suggesting `Path.iterdir()` for `os.listdir` with file descriptor (`PTH208`) (#17715) [red-knot] Check overloads without an implementation (#17681) Expand Semantic Syntax Coverage (#17725) [red-knot] Check for invalid overload usages (#17609)
Summary
This PR updates the existing overload matching methods to return an iterator of all the matched overloads instead.
This would be useful once the overload call evaluation algorithm is implemented which should provide an accurate picture of all the matched overloads. The return type would then be picked from either the only matched overload or the first overload from the ones that are matched.
In an earlier version of this PR, it tried to check if using an intersection of return types from the matched overload would help reduce the false positives but that's not enough. This comment keep the ecosystem analysis for that change for prosperity.
Note
The best way to review this PR is by hiding the whitespace changes because there are two instances where a large match expression is indented to be inside a loop over matching overlods
Test Plan
Make sure existing test cases are unaffected and no ecosystem changes.