mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-25 22:29:02 +00:00
ty_python_semantic: improve failed overloaded function call
The diagnostic now includes a pointer to the implementation definition along with each possible overload. This doesn't include information about *why* each overload failed. But given the emphasis on concise output (since there can be *many* unmatched overloads), it's not totally clear how to include that additional information. Fixes #274
This commit is contained in:
parent
451c5db7a3
commit
faf54c0181
8 changed files with 921 additions and 79 deletions
|
|
@ -16,3 +16,265 @@ def f(x: int | str) -> int | str:
|
|||
|
||||
f(b"foo") # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
## Call to function with many unmatched overloads
|
||||
|
||||
Note that it would be fine to use `pow` here as an example of a routine with many overloads, but at
|
||||
time of writing (2025-05-14), ty doesn't support some of the type signatures of those overloads.
|
||||
Which in turn makes snapshotting a bit annoying, since the output can depend on how ty is compiled
|
||||
(because of how `Todo` types are dealt with when `debug_assertions` is enabled versus disabled).
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
class Foo: ...
|
||||
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: str, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: str, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: float, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: float, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: float, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: float, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: float): ...
|
||||
def foo(a, b, c): ...
|
||||
|
||||
foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
## Call to function with too many unmatched overloads
|
||||
|
||||
This is like the above example, but has an excessive number of overloads to the point that ty will
|
||||
cut off the list in the diagnostic and emit a message stating the number of omitted overloads.
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
class Foo: ...
|
||||
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: str, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: str, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: float, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: float, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: float, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: float, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: float): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[int], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[int], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[str], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[int], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[str], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[str], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[str], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[int], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[int], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[float], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[int], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[float], c: list[int]): ...
|
||||
@overload
|
||||
def foo(a: list[int], b: list[float], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[float], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[str], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[str], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[float], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[str], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[float], c: list[str]): ...
|
||||
@overload
|
||||
def foo(a: list[str], b: list[float], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: list[float], b: list[float], c: list[float]): ...
|
||||
@overload
|
||||
def foo(a: bool, b: bool, c: bool): ...
|
||||
@overload
|
||||
def foo(a: str, b: bool, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: str, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: bool, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: bool, b: int, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: bool, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: int, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: bool, c: int): ...
|
||||
@overload
|
||||
def foo(a: int, b: bool, c: bool): ...
|
||||
@overload
|
||||
def foo(a: str, b: str, c: str): ...
|
||||
@overload
|
||||
def foo(a: float, b: bool, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: float, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: bool, c: float): ...
|
||||
@overload
|
||||
def foo(a: float, b: float, c: bool): ...
|
||||
@overload
|
||||
def foo(a: bool, b: float, c: float): ...
|
||||
def foo(a, b, c): ...
|
||||
|
||||
foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
## Calls to overloaded functions with lots of parameters
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(
|
||||
lion: int,
|
||||
turtle: int,
|
||||
tortoise: int,
|
||||
goat: int,
|
||||
capybara: int,
|
||||
chicken: int,
|
||||
ostrich: int,
|
||||
gorilla: int,
|
||||
giraffe: int,
|
||||
condor: int,
|
||||
kangaroo: int,
|
||||
anaconda: int,
|
||||
tarantula: int,
|
||||
millipede: int,
|
||||
leopard: int,
|
||||
hyena: int,
|
||||
) -> int: ...
|
||||
@overload
|
||||
def f(
|
||||
lion: str,
|
||||
turtle: str,
|
||||
tortoise: str,
|
||||
goat: str,
|
||||
capybara: str,
|
||||
chicken: str,
|
||||
ostrich: str,
|
||||
gorilla: str,
|
||||
giraffe: str,
|
||||
condor: str,
|
||||
kangaroo: str,
|
||||
anaconda: str,
|
||||
tarantula: str,
|
||||
millipede: str,
|
||||
leopard: str,
|
||||
hyena: str,
|
||||
) -> str: ...
|
||||
def f(
|
||||
lion: int | str,
|
||||
turtle: int | str,
|
||||
tortoise: int | str,
|
||||
goat: int | str,
|
||||
capybara: int | str,
|
||||
chicken: int | str,
|
||||
ostrict: int | str,
|
||||
gorilla: int | str,
|
||||
giraffe: int | str,
|
||||
condor: int | str,
|
||||
kangaroo: int | str,
|
||||
anaconda: int | str,
|
||||
tarantula: int | str,
|
||||
millipede: int | str,
|
||||
leopard: int | str,
|
||||
hyena: int | str,
|
||||
) -> int | str:
|
||||
return 0
|
||||
|
||||
f(b"foo") # error: [no-matching-overload]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: no_matching_overload.md - No matching overload diagnostics - Call to function with many unmatched overloads
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import overload
|
||||
2 |
|
||||
3 | class Foo: ...
|
||||
4 |
|
||||
5 | @overload
|
||||
6 | def foo(a: int, b: int, c: int): ...
|
||||
7 | @overload
|
||||
8 | def foo(a: str, b: int, c: int): ...
|
||||
9 | @overload
|
||||
10 | def foo(a: int, b: str, c: int): ...
|
||||
11 | @overload
|
||||
12 | def foo(a: int, b: int, c: str): ...
|
||||
13 | @overload
|
||||
14 | def foo(a: str, b: str, c: int): ...
|
||||
15 | @overload
|
||||
16 | def foo(a: int, b: str, c: str): ...
|
||||
17 | @overload
|
||||
18 | def foo(a: str, b: str, c: str): ...
|
||||
19 | @overload
|
||||
20 | def foo(a: int, b: int, c: int): ...
|
||||
21 | @overload
|
||||
22 | def foo(a: float, b: int, c: int): ...
|
||||
23 | @overload
|
||||
24 | def foo(a: int, b: float, c: int): ...
|
||||
25 | @overload
|
||||
26 | def foo(a: int, b: int, c: float): ...
|
||||
27 | @overload
|
||||
28 | def foo(a: float, b: float, c: int): ...
|
||||
29 | @overload
|
||||
30 | def foo(a: int, b: float, c: float): ...
|
||||
31 | @overload
|
||||
32 | def foo(a: float, b: float, c: float): ...
|
||||
33 | @overload
|
||||
34 | def foo(a: str, b: str, c: str): ...
|
||||
35 | @overload
|
||||
36 | def foo(a: float, b: str, c: str): ...
|
||||
37 | @overload
|
||||
38 | def foo(a: str, b: float, c: str): ...
|
||||
39 | @overload
|
||||
40 | def foo(a: str, b: str, c: float): ...
|
||||
41 | @overload
|
||||
42 | def foo(a: float, b: float, c: str): ...
|
||||
43 | @overload
|
||||
44 | def foo(a: str, b: float, c: float): ...
|
||||
45 | @overload
|
||||
46 | def foo(a: float, b: float, c: float): ...
|
||||
47 | def foo(a, b, c): ...
|
||||
48 |
|
||||
49 | foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[no-matching-overload]: No overload of function `foo` matches arguments
|
||||
--> src/mdtest_snippet.py:49:1
|
||||
|
|
||||
47 | def foo(a, b, c): ...
|
||||
48 |
|
||||
49 | foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: First overload defined here
|
||||
--> src/mdtest_snippet.py:6:5
|
||||
|
|
||||
5 | @overload
|
||||
6 | def foo(a: int, b: int, c: int): ...
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | @overload
|
||||
8 | def foo(a: str, b: int, c: int): ...
|
||||
|
|
||||
info: Possible overloads for function `foo`:
|
||||
info: (a: int, b: int, c: int) -> Unknown
|
||||
info: (a: str, b: int, c: int) -> Unknown
|
||||
info: (a: int, b: str, c: int) -> Unknown
|
||||
info: (a: int, b: int, c: str) -> Unknown
|
||||
info: (a: str, b: str, c: int) -> Unknown
|
||||
info: (a: int, b: str, c: str) -> Unknown
|
||||
info: (a: str, b: str, c: str) -> Unknown
|
||||
info: (a: int, b: int, c: int) -> Unknown
|
||||
info: (a: int | float, b: int, c: int) -> Unknown
|
||||
info: (a: int, b: int | float, c: int) -> Unknown
|
||||
info: (a: int, b: int, c: int | float) -> Unknown
|
||||
info: (a: int | float, b: int | float, c: int) -> Unknown
|
||||
info: (a: int, b: int | float, c: int | float) -> Unknown
|
||||
info: (a: int | float, b: int | float, c: int | float) -> Unknown
|
||||
info: (a: str, b: str, c: str) -> Unknown
|
||||
info: (a: int | float, b: str, c: str) -> Unknown
|
||||
info: (a: str, b: int | float, c: str) -> Unknown
|
||||
info: (a: str, b: str, c: int | float) -> Unknown
|
||||
info: (a: int | float, b: int | float, c: str) -> Unknown
|
||||
info: (a: str, b: int | float, c: int | float) -> Unknown
|
||||
info: (a: int | float, b: int | float, c: int | float) -> Unknown
|
||||
info: Overload implementation defined here
|
||||
--> src/mdtest_snippet.py:47:5
|
||||
|
|
||||
45 | @overload
|
||||
46 | def foo(a: float, b: float, c: float): ...
|
||||
47 | def foo(a, b, c): ...
|
||||
| ^^^^^^^^^^^^
|
||||
48 |
|
||||
49 | foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
|
|
||||
info: rule `no-matching-overload` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: no_matching_overload.md - No matching overload diagnostics - Call to function with too many unmatched overloads
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import overload
|
||||
2 |
|
||||
3 | class Foo: ...
|
||||
4 |
|
||||
5 | @overload
|
||||
6 | def foo(a: int, b: int, c: int): ...
|
||||
7 | @overload
|
||||
8 | def foo(a: str, b: int, c: int): ...
|
||||
9 | @overload
|
||||
10 | def foo(a: int, b: str, c: int): ...
|
||||
11 | @overload
|
||||
12 | def foo(a: int, b: int, c: str): ...
|
||||
13 | @overload
|
||||
14 | def foo(a: str, b: str, c: int): ...
|
||||
15 | @overload
|
||||
16 | def foo(a: int, b: str, c: str): ...
|
||||
17 | @overload
|
||||
18 | def foo(a: str, b: str, c: str): ...
|
||||
19 | @overload
|
||||
20 | def foo(a: int, b: int, c: int): ...
|
||||
21 | @overload
|
||||
22 | def foo(a: float, b: int, c: int): ...
|
||||
23 | @overload
|
||||
24 | def foo(a: int, b: float, c: int): ...
|
||||
25 | @overload
|
||||
26 | def foo(a: int, b: int, c: float): ...
|
||||
27 | @overload
|
||||
28 | def foo(a: float, b: float, c: int): ...
|
||||
29 | @overload
|
||||
30 | def foo(a: int, b: float, c: float): ...
|
||||
31 | @overload
|
||||
32 | def foo(a: float, b: float, c: float): ...
|
||||
33 | @overload
|
||||
34 | def foo(a: str, b: str, c: str): ...
|
||||
35 | @overload
|
||||
36 | def foo(a: float, b: str, c: str): ...
|
||||
37 | @overload
|
||||
38 | def foo(a: str, b: float, c: str): ...
|
||||
39 | @overload
|
||||
40 | def foo(a: str, b: str, c: float): ...
|
||||
41 | @overload
|
||||
42 | def foo(a: float, b: float, c: str): ...
|
||||
43 | @overload
|
||||
44 | def foo(a: str, b: float, c: float): ...
|
||||
45 | @overload
|
||||
46 | def foo(a: float, b: float, c: float): ...
|
||||
47 | @overload
|
||||
48 | def foo(a: list[int], b: list[int], c: list[int]): ...
|
||||
49 | @overload
|
||||
50 | def foo(a: list[str], b: list[int], c: list[int]): ...
|
||||
51 | @overload
|
||||
52 | def foo(a: list[int], b: list[str], c: list[int]): ...
|
||||
53 | @overload
|
||||
54 | def foo(a: list[int], b: list[int], c: list[str]): ...
|
||||
55 | @overload
|
||||
56 | def foo(a: list[str], b: list[str], c: list[int]): ...
|
||||
57 | @overload
|
||||
58 | def foo(a: list[int], b: list[str], c: list[str]): ...
|
||||
59 | @overload
|
||||
60 | def foo(a: list[str], b: list[str], c: list[str]): ...
|
||||
61 | @overload
|
||||
62 | def foo(a: list[int], b: list[int], c: list[int]): ...
|
||||
63 | @overload
|
||||
64 | def foo(a: list[float], b: list[int], c: list[int]): ...
|
||||
65 | @overload
|
||||
66 | def foo(a: list[int], b: list[float], c: list[int]): ...
|
||||
67 | @overload
|
||||
68 | def foo(a: list[int], b: list[int], c: list[float]): ...
|
||||
69 | @overload
|
||||
70 | def foo(a: list[float], b: list[float], c: list[int]): ...
|
||||
71 | @overload
|
||||
72 | def foo(a: list[int], b: list[float], c: list[float]): ...
|
||||
73 | @overload
|
||||
74 | def foo(a: list[float], b: list[float], c: list[float]): ...
|
||||
75 | @overload
|
||||
76 | def foo(a: list[str], b: list[str], c: list[str]): ...
|
||||
77 | @overload
|
||||
78 | def foo(a: list[float], b: list[str], c: list[str]): ...
|
||||
79 | @overload
|
||||
80 | def foo(a: list[str], b: list[float], c: list[str]): ...
|
||||
81 | @overload
|
||||
82 | def foo(a: list[str], b: list[str], c: list[float]): ...
|
||||
83 | @overload
|
||||
84 | def foo(a: list[float], b: list[float], c: list[str]): ...
|
||||
85 | @overload
|
||||
86 | def foo(a: list[str], b: list[float], c: list[float]): ...
|
||||
87 | @overload
|
||||
88 | def foo(a: list[float], b: list[float], c: list[float]): ...
|
||||
89 | @overload
|
||||
90 | def foo(a: bool, b: bool, c: bool): ...
|
||||
91 | @overload
|
||||
92 | def foo(a: str, b: bool, c: bool): ...
|
||||
93 | @overload
|
||||
94 | def foo(a: bool, b: str, c: bool): ...
|
||||
95 | @overload
|
||||
96 | def foo(a: bool, b: bool, c: str): ...
|
||||
97 | @overload
|
||||
98 | def foo(a: str, b: str, c: bool): ...
|
||||
99 | @overload
|
||||
100 | def foo(a: bool, b: str, c: str): ...
|
||||
101 | @overload
|
||||
102 | def foo(a: str, b: str, c: str): ...
|
||||
103 | @overload
|
||||
104 | def foo(a: int, b: int, c: int): ...
|
||||
105 | @overload
|
||||
106 | def foo(a: bool, b: int, c: int): ...
|
||||
107 | @overload
|
||||
108 | def foo(a: int, b: bool, c: int): ...
|
||||
109 | @overload
|
||||
110 | def foo(a: int, b: int, c: bool): ...
|
||||
111 | @overload
|
||||
112 | def foo(a: bool, b: bool, c: int): ...
|
||||
113 | @overload
|
||||
114 | def foo(a: int, b: bool, c: bool): ...
|
||||
115 | @overload
|
||||
116 | def foo(a: str, b: str, c: str): ...
|
||||
117 | @overload
|
||||
118 | def foo(a: float, b: bool, c: bool): ...
|
||||
119 | @overload
|
||||
120 | def foo(a: bool, b: float, c: bool): ...
|
||||
121 | @overload
|
||||
122 | def foo(a: bool, b: bool, c: float): ...
|
||||
123 | @overload
|
||||
124 | def foo(a: float, b: float, c: bool): ...
|
||||
125 | @overload
|
||||
126 | def foo(a: bool, b: float, c: float): ...
|
||||
127 | def foo(a, b, c): ...
|
||||
128 |
|
||||
129 | foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[no-matching-overload]: No overload of function `foo` matches arguments
|
||||
--> src/mdtest_snippet.py:129:1
|
||||
|
|
||||
127 | def foo(a, b, c): ...
|
||||
128 |
|
||||
129 | foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: First overload defined here
|
||||
--> src/mdtest_snippet.py:6:5
|
||||
|
|
||||
5 | @overload
|
||||
6 | def foo(a: int, b: int, c: int): ...
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | @overload
|
||||
8 | def foo(a: str, b: int, c: int): ...
|
||||
|
|
||||
info: Possible overloads for function `foo`:
|
||||
info: (a: int, b: int, c: int) -> Unknown
|
||||
info: (a: str, b: int, c: int) -> Unknown
|
||||
info: (a: int, b: str, c: int) -> Unknown
|
||||
info: (a: int, b: int, c: str) -> Unknown
|
||||
info: (a: str, b: str, c: int) -> Unknown
|
||||
info: (a: int, b: str, c: str) -> Unknown
|
||||
info: (a: str, b: str, c: str) -> Unknown
|
||||
info: (a: int, b: int, c: int) -> Unknown
|
||||
info: (a: int | float, b: int, c: int) -> Unknown
|
||||
info: (a: int, b: int | float, c: int) -> Unknown
|
||||
info: (a: int, b: int, c: int | float) -> Unknown
|
||||
info: (a: int | float, b: int | float, c: int) -> Unknown
|
||||
info: (a: int, b: int | float, c: int | float) -> Unknown
|
||||
info: (a: int | float, b: int | float, c: int | float) -> Unknown
|
||||
info: (a: str, b: str, c: str) -> Unknown
|
||||
info: (a: int | float, b: str, c: str) -> Unknown
|
||||
info: (a: str, b: int | float, c: str) -> Unknown
|
||||
info: (a: str, b: str, c: int | float) -> Unknown
|
||||
info: (a: int | float, b: int | float, c: str) -> Unknown
|
||||
info: (a: str, b: int | float, c: int | float) -> Unknown
|
||||
info: (a: int | float, b: int | float, c: int | float) -> Unknown
|
||||
info: (a: list[int], b: list[int], c: list[int]) -> Unknown
|
||||
info: (a: list[str], b: list[int], c: list[int]) -> Unknown
|
||||
info: (a: list[int], b: list[str], c: list[int]) -> Unknown
|
||||
info: (a: list[int], b: list[int], c: list[str]) -> Unknown
|
||||
info: (a: list[str], b: list[str], c: list[int]) -> Unknown
|
||||
info: (a: list[int], b: list[str], c: list[str]) -> Unknown
|
||||
info: (a: list[str], b: list[str], c: list[str]) -> Unknown
|
||||
info: (a: list[int], b: list[int], c: list[int]) -> Unknown
|
||||
info: (a: list[int | float], b: list[int], c: list[int]) -> Unknown
|
||||
info: (a: list[int], b: list[int | float], c: list[int]) -> Unknown
|
||||
info: (a: list[int], b: list[int], c: list[int | float]) -> Unknown
|
||||
info: (a: list[int | float], b: list[int | float], c: list[int]) -> Unknown
|
||||
info: (a: list[int], b: list[int | float], c: list[int | float]) -> Unknown
|
||||
info: (a: list[int | float], b: list[int | float], c: list[int | float]) -> Unknown
|
||||
info: (a: list[str], b: list[str], c: list[str]) -> Unknown
|
||||
info: (a: list[int | float], b: list[str], c: list[str]) -> Unknown
|
||||
info: (a: list[str], b: list[int | float], c: list[str]) -> Unknown
|
||||
info: (a: list[str], b: list[str], c: list[int | float]) -> Unknown
|
||||
info: (a: list[int | float], b: list[int | float], c: list[str]) -> Unknown
|
||||
info: (a: list[str], b: list[int | float], c: list[int | float]) -> Unknown
|
||||
info: (a: list[int | float], b: list[int | float], c: list[int | float]) -> Unknown
|
||||
info: (a: bool, b: bool, c: bool) -> Unknown
|
||||
info: (a: str, b: bool, c: bool) -> Unknown
|
||||
info: (a: bool, b: str, c: bool) -> Unknown
|
||||
info: (a: bool, b: bool, c: str) -> Unknown
|
||||
info: (a: str, b: str, c: bool) -> Unknown
|
||||
info: (a: bool, b: str, c: str) -> Unknown
|
||||
info: (a: str, b: str, c: str) -> Unknown
|
||||
info: (a: int, b: int, c: int) -> Unknown
|
||||
info: ... omitted 11 overloads
|
||||
info: Overload implementation defined here
|
||||
--> src/mdtest_snippet.py:127:5
|
||||
|
|
||||
125 | @overload
|
||||
126 | def foo(a: bool, b: float, c: float): ...
|
||||
127 | def foo(a, b, c): ...
|
||||
| ^^^^^^^^^^^^
|
||||
128 |
|
||||
129 | foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
|
|
||||
info: rule `no-matching-overload` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -16,27 +16,46 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_
|
|||
2 |
|
||||
3 | @overload
|
||||
4 | def f(x: int) -> int: ...
|
||||
5 |
|
||||
6 | @overload
|
||||
7 | def f(x: str) -> str: ...
|
||||
8 |
|
||||
9 | def f(x: int | str) -> int | str:
|
||||
10 | return x
|
||||
11 |
|
||||
12 | f(b"foo") # error: [no-matching-overload]
|
||||
5 | @overload
|
||||
6 | def f(x: str) -> str: ...
|
||||
7 | def f(x: int | str) -> int | str:
|
||||
8 | return x
|
||||
9 |
|
||||
10 | f(b"foo") # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[no-matching-overload]: No overload of function `f` matches arguments
|
||||
--> src/mdtest_snippet.py:12:1
|
||||
--> src/mdtest_snippet.py:10:1
|
||||
|
|
||||
10 | return x
|
||||
11 |
|
||||
12 | f(b"foo") # error: [no-matching-overload]
|
||||
8 | return x
|
||||
9 |
|
||||
10 | f(b"foo") # error: [no-matching-overload]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
info: First overload defined here
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | @overload
|
||||
4 | def f(x: int) -> int: ...
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
5 | @overload
|
||||
6 | def f(x: str) -> str: ...
|
||||
|
|
||||
info: Possible overloads for function `f`:
|
||||
info: (x: int) -> int
|
||||
info: (x: str) -> str
|
||||
info: Overload implementation defined here
|
||||
--> src/mdtest_snippet.py:7:5
|
||||
|
|
||||
5 | @overload
|
||||
6 | def f(x: str) -> str: ...
|
||||
7 | def f(x: int | str) -> int | str:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | return x
|
||||
|
|
||||
info: rule `no-matching-overload` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: no_matching_overload.md - No matching overload diagnostics - Calls to overloaded functions with lots of parameters
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import overload
|
||||
2 |
|
||||
3 | @overload
|
||||
4 | def f(
|
||||
5 | lion: int,
|
||||
6 | turtle: int,
|
||||
7 | tortoise: int,
|
||||
8 | goat: int,
|
||||
9 | capybara: int,
|
||||
10 | chicken: int,
|
||||
11 | ostrich: int,
|
||||
12 | gorilla: int,
|
||||
13 | giraffe: int,
|
||||
14 | condor: int,
|
||||
15 | kangaroo: int,
|
||||
16 | anaconda: int,
|
||||
17 | tarantula: int,
|
||||
18 | millipede: int,
|
||||
19 | leopard: int,
|
||||
20 | hyena: int,
|
||||
21 | ) -> int: ...
|
||||
22 | @overload
|
||||
23 | def f(
|
||||
24 | lion: str,
|
||||
25 | turtle: str,
|
||||
26 | tortoise: str,
|
||||
27 | goat: str,
|
||||
28 | capybara: str,
|
||||
29 | chicken: str,
|
||||
30 | ostrich: str,
|
||||
31 | gorilla: str,
|
||||
32 | giraffe: str,
|
||||
33 | condor: str,
|
||||
34 | kangaroo: str,
|
||||
35 | anaconda: str,
|
||||
36 | tarantula: str,
|
||||
37 | millipede: str,
|
||||
38 | leopard: str,
|
||||
39 | hyena: str,
|
||||
40 | ) -> str: ...
|
||||
41 | def f(
|
||||
42 | lion: int | str,
|
||||
43 | turtle: int | str,
|
||||
44 | tortoise: int | str,
|
||||
45 | goat: int | str,
|
||||
46 | capybara: int | str,
|
||||
47 | chicken: int | str,
|
||||
48 | ostrict: int | str,
|
||||
49 | gorilla: int | str,
|
||||
50 | giraffe: int | str,
|
||||
51 | condor: int | str,
|
||||
52 | kangaroo: int | str,
|
||||
53 | anaconda: int | str,
|
||||
54 | tarantula: int | str,
|
||||
55 | millipede: int | str,
|
||||
56 | leopard: int | str,
|
||||
57 | hyena: int | str,
|
||||
58 | ) -> int | str:
|
||||
59 | return 0
|
||||
60 |
|
||||
61 | f(b"foo") # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[no-matching-overload]: No overload of function `f` matches arguments
|
||||
--> src/mdtest_snippet.py:61:1
|
||||
|
|
||||
59 | return 0
|
||||
60 |
|
||||
61 | f(b"foo") # error: [no-matching-overload]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
info: First overload defined here
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | @overload
|
||||
4 | def f(
|
||||
| _____^
|
||||
5 | | lion: int,
|
||||
6 | | turtle: int,
|
||||
7 | | tortoise: int,
|
||||
8 | | goat: int,
|
||||
9 | | capybara: int,
|
||||
10 | | chicken: int,
|
||||
11 | | ostrich: int,
|
||||
12 | | gorilla: int,
|
||||
13 | | giraffe: int,
|
||||
14 | | condor: int,
|
||||
15 | | kangaroo: int,
|
||||
16 | | anaconda: int,
|
||||
17 | | tarantula: int,
|
||||
18 | | millipede: int,
|
||||
19 | | leopard: int,
|
||||
20 | | hyena: int,
|
||||
21 | | ) -> int: ...
|
||||
| |________^
|
||||
22 | @overload
|
||||
23 | def f(
|
||||
|
|
||||
info: Possible overloads for function `f`:
|
||||
info: (lion: int, turtle: int, tortoise: int, goat: int, capybara: int, chicken: int, ostrich: int, gorilla: int, giraffe: int, condor: int, kangaroo: int, anaconda: int, tarantula: int, millipede: int, leopard: int, hyena: int) -> int
|
||||
info: (lion: str, turtle: str, tortoise: str, goat: str, capybara: str, chicken: str, ostrich: str, gorilla: str, giraffe: str, condor: str, kangaroo: str, anaconda: str, tarantula: str, millipede: str, leopard: str, hyena: str) -> str
|
||||
info: Overload implementation defined here
|
||||
--> src/mdtest_snippet.py:41:5
|
||||
|
|
||||
39 | hyena: str,
|
||||
40 | ) -> str: ...
|
||||
41 | def f(
|
||||
| _____^
|
||||
42 | | lion: int | str,
|
||||
43 | | turtle: int | str,
|
||||
44 | | tortoise: int | str,
|
||||
45 | | goat: int | str,
|
||||
46 | | capybara: int | str,
|
||||
47 | | chicken: int | str,
|
||||
48 | | ostrict: int | str,
|
||||
49 | | gorilla: int | str,
|
||||
50 | | giraffe: int | str,
|
||||
51 | | condor: int | str,
|
||||
52 | | kangaroo: int | str,
|
||||
53 | | anaconda: int | str,
|
||||
54 | | tarantula: int | str,
|
||||
55 | | millipede: int | str,
|
||||
56 | | leopard: int | str,
|
||||
57 | | hyena: int | str,
|
||||
58 | | ) -> int | str:
|
||||
| |______________^
|
||||
59 | return 0
|
||||
|
|
||||
info: rule `no-matching-overload` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -5350,36 +5350,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a tuple of two spans. The first is
|
||||
/// the span for the identifier of the function
|
||||
/// definition for `self`. The second is
|
||||
/// the span for the return type in the function
|
||||
/// definition for `self`.
|
||||
///
|
||||
/// If there are no meaningful spans, then this
|
||||
/// returns `None`. For example, when this type
|
||||
/// isn't callable or if the function has no
|
||||
/// declared return type.
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// Note that this may introduce cross-module
|
||||
/// dependencies. This can have an impact on
|
||||
/// the effectiveness of incremental caching
|
||||
/// and should therefore be used judiciously.
|
||||
///
|
||||
/// An example of a good use case is to improve
|
||||
/// a diagnostic.
|
||||
fn return_type_span(&self, db: &'db dyn Db) -> Option<(Span, Span)> {
|
||||
match *self {
|
||||
Type::FunctionLiteral(function) => function.return_type_span(db),
|
||||
Type::BoundMethod(bound_method) => {
|
||||
Type::FunctionLiteral(bound_method.function(db)).return_type_span(db)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a tuple of two spans. The first is
|
||||
/// the span for the identifier of the function
|
||||
/// definition for `self`. The second is
|
||||
|
|
@ -5410,9 +5380,34 @@ impl<'db> Type<'db> {
|
|||
) -> Option<(Span, Span)> {
|
||||
match *self {
|
||||
Type::FunctionLiteral(function) => function.parameter_span(db, parameter_index),
|
||||
Type::BoundMethod(bound_method) => {
|
||||
Type::FunctionLiteral(bound_method.function(db)).parameter_span(db, parameter_index)
|
||||
}
|
||||
Type::BoundMethod(bound_method) => bound_method
|
||||
.function(db)
|
||||
.parameter_span(db, parameter_index),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a collection of useful spans for a
|
||||
/// function signature. These are useful for
|
||||
/// creating annotations on diagnostics.
|
||||
///
|
||||
/// If there are no meaningful spans, then this
|
||||
/// returns `None`. For example, when this type
|
||||
/// isn't callable.
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// Note that this may introduce cross-module
|
||||
/// dependencies. This can have an impact on
|
||||
/// the effectiveness of incremental caching
|
||||
/// and should therefore be used judiciously.
|
||||
///
|
||||
/// An example of a good use case is to improve
|
||||
/// a diagnostic.
|
||||
fn function_spans(&self, db: &'db dyn Db) -> Option<FunctionSpans> {
|
||||
match *self {
|
||||
Type::FunctionLiteral(function) => function.spans(db),
|
||||
Type::BoundMethod(bound_method) => bound_method.function(db).spans(db),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -6297,7 +6292,8 @@ impl<'db> BoolError<'db> {
|
|||
.member(context.db(), "__bool__")
|
||||
.into_lookup_result()
|
||||
.ok()
|
||||
.and_then(|quals| quals.inner_type().return_type_span(context.db()))
|
||||
.and_then(|quals| quals.inner_type().function_spans(context.db()))
|
||||
.and_then(|spans| Some((spans.name, spans.return_type?)))
|
||||
{
|
||||
sub.annotate(
|
||||
Annotation::primary(return_type_span).message("Incorrect return type"),
|
||||
|
|
@ -6894,37 +6890,6 @@ impl<'db> FunctionType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a tuple of two spans. The first is
|
||||
/// the span for the identifier of the function
|
||||
/// definition for `self`. The second is
|
||||
/// the span for the return type in the function
|
||||
/// definition for `self`.
|
||||
///
|
||||
/// If there are no meaningful spans, then this
|
||||
/// returns `None`. For example, when this type
|
||||
/// isn't callable or if the function has no
|
||||
/// declared return type.
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// Note that this may introduce cross-module
|
||||
/// dependencies. This can have an impact on
|
||||
/// the effectiveness of incremental caching
|
||||
/// and should therefore be used judiciously.
|
||||
///
|
||||
/// An example of a good use case is to improve
|
||||
/// a diagnostic.
|
||||
fn return_type_span(&self, db: &'db dyn Db) -> Option<(Span, Span)> {
|
||||
let function_scope = self.body_scope(db);
|
||||
let span = Span::from(function_scope.file(db));
|
||||
let node = function_scope.node(db);
|
||||
let func_def = node.as_function()?;
|
||||
let return_type_range = func_def.returns.as_ref()?.range();
|
||||
let name_span = span.clone().with_range(func_def.name.range);
|
||||
let return_type_span = span.with_range(return_type_range);
|
||||
Some((name_span, return_type_span))
|
||||
}
|
||||
|
||||
/// Returns a tuple of two spans. The first is
|
||||
/// the span for the identifier of the function
|
||||
/// definition for `self`. The second is
|
||||
|
|
@ -6949,7 +6914,7 @@ impl<'db> FunctionType<'db> {
|
|||
/// An example of a good use case is to improve
|
||||
/// a diagnostic.
|
||||
fn parameter_span(
|
||||
&self,
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
parameter_index: Option<usize>,
|
||||
) -> Option<(Span, Span)> {
|
||||
|
|
@ -6970,6 +6935,55 @@ impl<'db> FunctionType<'db> {
|
|||
let parameter_span = span.with_range(range);
|
||||
Some((name_span, parameter_span))
|
||||
}
|
||||
|
||||
/// Returns a collection of useful spans for a
|
||||
/// function signature. These are useful for
|
||||
/// creating annotations on diagnostics.
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// Note that this may introduce cross-module
|
||||
/// dependencies. This can have an impact on
|
||||
/// the effectiveness of incremental caching
|
||||
/// and should therefore be used judiciously.
|
||||
///
|
||||
/// An example of a good use case is to improve
|
||||
/// a diagnostic.
|
||||
fn spans(self, db: &'db dyn Db) -> Option<FunctionSpans> {
|
||||
let function_scope = self.body_scope(db);
|
||||
let span = Span::from(function_scope.file(db));
|
||||
let node = function_scope.node(db);
|
||||
let func_def = node.as_function()?;
|
||||
let return_type_range = func_def.returns.as_ref().map(|returns| returns.range());
|
||||
let mut signature = func_def.name.range.cover(func_def.parameters.range);
|
||||
if let Some(return_type_range) = return_type_range {
|
||||
signature = signature.cover(return_type_range);
|
||||
}
|
||||
Some(FunctionSpans {
|
||||
signature: span.clone().with_range(signature),
|
||||
name: span.clone().with_range(func_def.name.range),
|
||||
parameters: span.clone().with_range(func_def.parameters.range),
|
||||
return_type: return_type_range.map(|range| span.clone().with_range(range)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of useful spans for annotating functions.
|
||||
///
|
||||
/// This can be retrieved via `FunctionType::spans` or
|
||||
/// `Type::function_spans`.
|
||||
struct FunctionSpans {
|
||||
/// The span of the entire function "signature." This includes
|
||||
/// the name, parameter list and return type (if present).
|
||||
signature: Span,
|
||||
/// The span of the function name. i.e., `foo` in `def foo(): ...`.
|
||||
name: Span,
|
||||
/// The span of the parameter list, including the opening and
|
||||
/// closing parentheses.
|
||||
#[expect(dead_code)]
|
||||
parameters: Span,
|
||||
/// The span of the annotated return type, if present.
|
||||
return_type: Option<Span>,
|
||||
}
|
||||
|
||||
fn signature_cycle_recover<'db>(
|
||||
|
|
|
|||
|
|
@ -1100,6 +1100,13 @@ impl<'db> CallableBinding<'db> {
|
|||
);
|
||||
}
|
||||
_overloads => {
|
||||
// When the number of unmatched overloads exceeds this number, we stop
|
||||
// printing them to avoid excessive output.
|
||||
//
|
||||
// An example of a routine with many many overloads:
|
||||
// https://github.com/henribru/google-api-python-client-stubs/blob/master/googleapiclient-stubs/discovery.pyi
|
||||
const MAXIMUM_OVERLOADS: usize = 50;
|
||||
|
||||
let Some(builder) = context.report_lint(&NO_MATCHING_OVERLOAD, node) else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -1113,6 +1120,48 @@ impl<'db> CallableBinding<'db> {
|
|||
String::new()
|
||||
}
|
||||
));
|
||||
if let Some(function) = self.signature_type.into_function_literal() {
|
||||
if let Some(overloaded_function) = function.to_overloaded(context.db()) {
|
||||
if let Some(spans) = overloaded_function
|
||||
.overloads
|
||||
.first()
|
||||
.and_then(|overload| overload.spans(context.db()))
|
||||
{
|
||||
let mut sub =
|
||||
SubDiagnostic::new(Severity::Info, "First overload defined here");
|
||||
sub.annotate(Annotation::primary(spans.signature));
|
||||
diag.sub(sub);
|
||||
}
|
||||
|
||||
diag.info(format_args!(
|
||||
"Possible overloads for function `{}`:",
|
||||
function.name(context.db())
|
||||
));
|
||||
|
||||
let overloads = &function.signature(context.db()).overloads.overloads;
|
||||
for overload in overloads.iter().take(MAXIMUM_OVERLOADS) {
|
||||
diag.info(format_args!(" {}", overload.display(context.db())));
|
||||
}
|
||||
if overloads.len() > MAXIMUM_OVERLOADS {
|
||||
diag.info(format_args!(
|
||||
"... omitted {remaining} overloads",
|
||||
remaining = overloads.len() - MAXIMUM_OVERLOADS
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(spans) = overloaded_function
|
||||
.implementation
|
||||
.and_then(|function| function.spans(context.db()))
|
||||
{
|
||||
let mut sub = SubDiagnostic::new(
|
||||
Severity::Info,
|
||||
"Overload implementation defined here",
|
||||
);
|
||||
sub.annotate(Annotation::primary(spans.signature));
|
||||
diag.sub(sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(union_diag) = union_diag {
|
||||
union_diag.add_union_context(context.db(), &mut diag);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ pub(crate) struct CallableSignature<'db> {
|
|||
///
|
||||
/// By using `SmallVec`, we avoid an extra heap allocation for the common case of a
|
||||
/// non-overloaded callable.
|
||||
overloads: SmallVec<[Signature<'db>; 1]>,
|
||||
pub(crate) overloads: SmallVec<[Signature<'db>; 1]>,
|
||||
}
|
||||
|
||||
impl<'db> CallableSignature<'db> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue