Skip to content

Commit 8ceb6bf

Browse files
committed
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
1 parent 2c42d7a commit 8ceb6bf

7 files changed

+459
-15
lines changed

crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,128 @@ def f(x: int | str) -> int | str:
1616

1717
f(b"foo") # error: [no-matching-overload]
1818
```
19+
20+
## Call to function with many unmatched overloads
21+
22+
Note that it would be fine to use `pow` here as an example of a routine with many overloads, but at
23+
time of writing (2025-05-14), ty doesn't support some of the type signatures of those overloads.
24+
Which in turn makes snapshotting a bit annoying, since the output can depend on how ty is compiled
25+
(because of how `Todo` types are dealt with when `debug_assertions` is enabled versus disabled).
26+
27+
```py
28+
from typing import overload
29+
30+
class Foo: ...
31+
32+
@overload
33+
def foo(a: int, b: int, c: int): ...
34+
@overload
35+
def foo(a: str, b: int, c: int): ...
36+
@overload
37+
def foo(a: int, b: str, c: int): ...
38+
@overload
39+
def foo(a: int, b: int, c: str): ...
40+
@overload
41+
def foo(a: str, b: str, c: int): ...
42+
@overload
43+
def foo(a: int, b: str, c: str): ...
44+
@overload
45+
def foo(a: str, b: str, c: str): ...
46+
@overload
47+
def foo(a: int, b: int, c: int): ...
48+
@overload
49+
def foo(a: float, b: int, c: int): ...
50+
@overload
51+
def foo(a: int, b: float, c: int): ...
52+
@overload
53+
def foo(a: int, b: int, c: float): ...
54+
@overload
55+
def foo(a: float, b: float, c: int): ...
56+
@overload
57+
def foo(a: int, b: float, c: float): ...
58+
@overload
59+
def foo(a: float, b: float, c: float): ...
60+
@overload
61+
def foo(a: str, b: str, c: str): ...
62+
@overload
63+
def foo(a: float, b: str, c: str): ...
64+
@overload
65+
def foo(a: str, b: float, c: str): ...
66+
@overload
67+
def foo(a: str, b: str, c: float): ...
68+
@overload
69+
def foo(a: float, b: float, c: str): ...
70+
@overload
71+
def foo(a: str, b: float, c: float): ...
72+
@overload
73+
def foo(a: float, b: float, c: float): ...
74+
def foo(a, b, c): ...
75+
76+
foo(Foo(), Foo()) # error: [no-matching-overload]
77+
```
78+
79+
## Calls to overloaded functions with lots of parameters
80+
81+
```py
82+
from typing import overload
83+
84+
@overload
85+
def f(
86+
lion: int,
87+
turtle: int,
88+
tortoise: int,
89+
goat: int,
90+
capybara: int,
91+
chicken: int,
92+
ostrich: int,
93+
gorilla: int,
94+
giraffe: int,
95+
condor: int,
96+
kangaroo: int,
97+
anaconda: int,
98+
tarantula: int,
99+
millipede: int,
100+
leopard: int,
101+
hyena: int,
102+
) -> int: ...
103+
@overload
104+
def f(
105+
lion: str,
106+
turtle: str,
107+
tortoise: str,
108+
goat: str,
109+
capybara: str,
110+
chicken: str,
111+
ostrich: str,
112+
gorilla: str,
113+
giraffe: str,
114+
condor: str,
115+
kangaroo: str,
116+
anaconda: str,
117+
tarantula: str,
118+
millipede: str,
119+
leopard: str,
120+
hyena: str,
121+
) -> str: ...
122+
def f(
123+
lion: int | str,
124+
turtle: int | str,
125+
tortoise: int | str,
126+
goat: int | str,
127+
capybara: int | str,
128+
chicken: int | str,
129+
ostrict: int | str,
130+
gorilla: int | str,
131+
giraffe: int | str,
132+
condor: int | str,
133+
kangaroo: int | str,
134+
anaconda: int | str,
135+
tarantula: int | str,
136+
millipede: int | str,
137+
leopard: int | str,
138+
hyena: int | str,
139+
) -> int | str:
140+
return 0
141+
142+
f(b"foo") # error: [no-matching-overload]
143+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
source: crates/ty_test/src/lib.rs
3+
expression: snapshot
4+
---
5+
---
6+
mdtest name: no_matching_overload.md - No matching overload diagnostics - Call to function with many unmatched overloads
7+
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_overload.md
8+
---
9+
10+
# Python source files
11+
12+
## mdtest_snippet.py
13+
14+
```
15+
1 | from typing import overload
16+
2 |
17+
3 | class Foo: ...
18+
4 |
19+
5 | @overload
20+
6 | def foo(a: int, b: int, c: int): ...
21+
7 | @overload
22+
8 | def foo(a: str, b: int, c: int): ...
23+
9 | @overload
24+
10 | def foo(a: int, b: str, c: int): ...
25+
11 | @overload
26+
12 | def foo(a: int, b: int, c: str): ...
27+
13 | @overload
28+
14 | def foo(a: str, b: str, c: int): ...
29+
15 | @overload
30+
16 | def foo(a: int, b: str, c: str): ...
31+
17 | @overload
32+
18 | def foo(a: str, b: str, c: str): ...
33+
19 | @overload
34+
20 | def foo(a: int, b: int, c: int): ...
35+
21 | @overload
36+
22 | def foo(a: float, b: int, c: int): ...
37+
23 | @overload
38+
24 | def foo(a: int, b: float, c: int): ...
39+
25 | @overload
40+
26 | def foo(a: int, b: int, c: float): ...
41+
27 | @overload
42+
28 | def foo(a: float, b: float, c: int): ...
43+
29 | @overload
44+
30 | def foo(a: int, b: float, c: float): ...
45+
31 | @overload
46+
32 | def foo(a: float, b: float, c: float): ...
47+
33 | @overload
48+
34 | def foo(a: str, b: str, c: str): ...
49+
35 | @overload
50+
36 | def foo(a: float, b: str, c: str): ...
51+
37 | @overload
52+
38 | def foo(a: str, b: float, c: str): ...
53+
39 | @overload
54+
40 | def foo(a: str, b: str, c: float): ...
55+
41 | @overload
56+
42 | def foo(a: float, b: float, c: str): ...
57+
43 | @overload
58+
44 | def foo(a: str, b: float, c: float): ...
59+
45 | @overload
60+
46 | def foo(a: float, b: float, c: float): ...
61+
47 | def foo(a, b, c): ...
62+
48 |
63+
49 | foo(Foo(), Foo()) # error: [no-matching-overload]
64+
```
65+
66+
# Diagnostics
67+
68+
```
69+
error[no-matching-overload]: No overload of function `foo` matches arguments
70+
--> src/mdtest_snippet.py:49:1
71+
|
72+
47 | def foo(a, b, c): ...
73+
48 |
74+
49 | foo(Foo(), Foo()) # error: [no-matching-overload]
75+
| ^^^^^^^^^^^^^^^^^
76+
|
77+
info: First overload defined here
78+
--> src/mdtest_snippet.py:6:8
79+
|
80+
5 | @overload
81+
6 | def foo(a: int, b: int, c: int): ...
82+
| ^^^^^^^^^^^^^^^^^^^^^^^^
83+
7 | @overload
84+
8 | def foo(a: str, b: int, c: int): ...
85+
|
86+
info: Possible overloads for function `foo`:
87+
info: (a: int, b: int, c: int) -> Unknown
88+
info: (a: str, b: int, c: int) -> Unknown
89+
info: (a: int, b: str, c: int) -> Unknown
90+
info: (a: int, b: int, c: str) -> Unknown
91+
info: (a: str, b: str, c: int) -> Unknown
92+
info: (a: int, b: str, c: str) -> Unknown
93+
info: (a: str, b: str, c: str) -> Unknown
94+
info: (a: int, b: int, c: int) -> Unknown
95+
info: (a: int | float, b: int, c: int) -> Unknown
96+
info: (a: int, b: int | float, c: int) -> Unknown
97+
info: (a: int, b: int, c: int | float) -> Unknown
98+
info: (a: int | float, b: int | float, c: int) -> Unknown
99+
info: (a: int, b: int | float, c: int | float) -> Unknown
100+
info: (a: int | float, b: int | float, c: int | float) -> Unknown
101+
info: (a: str, b: str, c: str) -> Unknown
102+
info: (a: int | float, b: str, c: str) -> Unknown
103+
info: (a: str, b: int | float, c: str) -> Unknown
104+
info: (a: str, b: str, c: int | float) -> Unknown
105+
info: (a: int | float, b: int | float, c: str) -> Unknown
106+
info: (a: str, b: int | float, c: int | float) -> Unknown
107+
info: (a: int | float, b: int | float, c: int | float) -> Unknown
108+
info: Overload implementation defined here
109+
--> src/mdtest_snippet.py:47:8
110+
|
111+
45 | @overload
112+
46 | def foo(a: float, b: float, c: float): ...
113+
47 | def foo(a, b, c): ...
114+
| ^^^^^^^^^
115+
48 |
116+
49 | foo(Foo(), Foo()) # error: [no-matching-overload]
117+
|
118+
info: rule `no-matching-overload` is enabled by default
119+
120+
```

crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload…_-_No_matching_overload…_-_Calls_to_overloaded_…_(3553d085684e16a0).snap

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,46 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/no_matching_
1616
2 |
1717
3 | @overload
1818
4 | def f(x: int) -> int: ...
19-
5 |
20-
6 | @overload
21-
7 | def f(x: str) -> str: ...
22-
8 |
23-
9 | def f(x: int | str) -> int | str:
24-
10 | return x
25-
11 |
26-
12 | f(b"foo") # error: [no-matching-overload]
19+
5 | @overload
20+
6 | def f(x: str) -> str: ...
21+
7 | def f(x: int | str) -> int | str:
22+
8 | return x
23+
9 |
24+
10 | f(b"foo") # error: [no-matching-overload]
2725
```
2826

2927
# Diagnostics
3028

3129
```
3230
error[no-matching-overload]: No overload of function `f` matches arguments
33-
--> src/mdtest_snippet.py:12:1
31+
--> src/mdtest_snippet.py:10:1
3432
|
35-
10 | return x
36-
11 |
37-
12 | f(b"foo") # error: [no-matching-overload]
33+
8 | return x
34+
9 |
35+
10 | f(b"foo") # error: [no-matching-overload]
3836
| ^^^^^^^^^
3937
|
38+
info: First overload defined here
39+
--> src/mdtest_snippet.py:4:6
40+
|
41+
3 | @overload
42+
4 | def f(x: int) -> int: ...
43+
| ^^^^^^^^
44+
5 | @overload
45+
6 | def f(x: str) -> str: ...
46+
|
47+
info: Possible overloads for function `f`:
48+
info: (x: int) -> int
49+
info: (x: str) -> str
50+
info: Overload implementation defined here
51+
--> src/mdtest_snippet.py:7:6
52+
|
53+
5 | @overload
54+
6 | def f(x: str) -> str: ...
55+
7 | def f(x: int | str) -> int | str:
56+
| ^^^^^^^^^^^^^^
57+
8 | return x
58+
|
4059
info: rule `no-matching-overload` is enabled by default
4160
4261
```

0 commit comments

Comments
 (0)