[ty] Surface matched overload diagnostic directly (#18452)
Some checks are pending
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo clippy (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

## Summary

This PR resolves the way diagnostics are reported for an invalid call to
an overloaded function.

If any of the steps in the overload call evaluation algorithm yields a
matching overload but it's type checking that failed, the
`no-matching-overload` diagnostic is incorrect because there is a
matching overload, it's the arguments passed that are invalid as per the
signature. So, this PR improves that by surfacing the diagnostics on the
matching overload directly.

It also provides additional context, specifically the matching overload
where this error occurred and other non-matching overloads. Consider the
following example:

```py
from typing import overload


@overload
def f() -> None: ...
@overload
def f(x: int) -> int: ...
@overload
def f(x: int, y: int) -> int: ...
def f(x: int | None = None, y: int | None = None) -> int | None:
    return None


f("a")
```

We get:

<img width="857" alt="Screenshot 2025-06-18 at 11 07 10"
src="https://github.com/user-attachments/assets/8dbcaf13-2a74-4661-aa94-1225c9402ea6"
/>


## Test Plan

Update test cases, resolve existing todos and validate the updated
snapshots.
This commit is contained in:
Dhruv Manilawala 2025-06-20 08:36:49 +05:30 committed by GitHub
parent 20d73dd41c
commit 22177e6915
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 856 additions and 137 deletions

View file

@ -48,13 +48,13 @@ The following calls are also invalid, due to incorrect argument types:
```py
class Base: ...
# error: [no-matching-overload] "No overload of class `type` matches arguments"
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `Literal[b"Foo"]`"
type(b"Foo", (), {})
# error: [no-matching-overload] "No overload of class `type` matches arguments"
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `<class 'Base'>`"
type("Foo", Base, {})
# error: [no-matching-overload] "No overload of class `type` matches arguments"
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `tuple[type, ...]`, found `tuple[Literal[1], Literal[2]]`"
type("Foo", (1, 2), {})
# TODO: this should be an error
@ -90,12 +90,18 @@ str(errors="replace")
### Invalid calls
```py
str(1, 2) # error: [no-matching-overload]
str(o=1) # error: [no-matching-overload]
# error: [invalid-argument-type] "Argument to class `str` is incorrect: Expected `bytes | bytearray`, found `Literal[1]`"
# error: [invalid-argument-type] "Argument to class `str` is incorrect: Expected `str`, found `Literal[2]`"
str(1, 2)
# error: [no-matching-overload]
str(o=1)
# First argument is not a bytes-like object:
str("Müsli", "utf-8") # error: [no-matching-overload]
# error: [invalid-argument-type] "Argument to class `str` is incorrect: Expected `bytes | bytearray`, found `Literal["Müsli"]`"
str("Müsli", "utf-8")
# Second argument is not a valid encoding:
str(b"M\xc3\xbcsli", b"utf-8") # error: [no-matching-overload]
# error: [invalid-argument-type] "Argument to class `str` is incorrect: Expected `str`, found `Literal[b"utf-8"]`"
str(b"M\xc3\xbcsli", b"utf-8")
```

View file

@ -235,7 +235,7 @@ method_wrapper(C(), None)
method_wrapper(None, C)
# Passing `None` without an `owner` argument is an
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
# error: [invalid-argument-type] "Argument to method wrapper `__get__` of function `f` is incorrect: Expected `~None`, found `None`"
method_wrapper(None)
# Passing something that is not assignable to `type` as the `owner` argument is an

View file

@ -74,6 +74,8 @@ from typing import overload
def f() -> None: ...
@overload
def f(x: int) -> int: ...
@overload
def f(x: int, y: int) -> int: ...
```
If the arity check only matches a single overload, it should be evaluated as a regular
@ -81,15 +83,19 @@ If the arity check only matches a single overload, it should be evaluated as a r
call should be reported directly and not as a `no-matching-overload` error.
```py
from typing_extensions import reveal_type
from overloaded import f
reveal_type(f()) # revealed: None
# TODO: This should be `invalid-argument-type` instead
# error: [no-matching-overload]
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["a"]`"
reveal_type(f("a")) # revealed: Unknown
```
More examples of this diagnostic can be found in the
[single_matching_overload.md](../diagnostics/single_matching_overload.md) document.
### Multiple matches
`overloaded.pyi`:
@ -400,6 +406,43 @@ def _(x: SomeEnum):
reveal_type(f(x)) # revealed: A
```
### No matching overloads
> If argument expansion has been applied to all arguments and one or more of the expanded argument
> lists cannot be evaluated successfully, generate an error and stop.
`overloaded.pyi`:
```pyi
from typing import overload
class A: ...
class B: ...
class C: ...
class D: ...
@overload
def f(x: A) -> A: ...
@overload
def f(x: B) -> B: ...
```
```py
from overloaded import A, B, C, D, f
def _(ab: A | B, ac: A | C, cd: C | D):
reveal_type(f(ab)) # revealed: A | B
# The `[A | C]` argument list is expanded to `[A], [C]` where the first list matches the first
# overload while the second list doesn't match any of the overloads, so we generate an
# error: [no-matching-overload] "No overload of function `f` matches arguments"
reveal_type(f(ac)) # revealed: Unknown
# None of the expanded argument lists (`[C], [D]`) match any of the overloads, so we generate an
# error: [no-matching-overload] "No overload of function `f` matches arguments"
reveal_type(f(cd)) # revealed: Unknown
```
## Filtering overloads with variadic arguments and parameters
TODO

View file

@ -607,7 +607,7 @@ wrapper_descriptor()
wrapper_descriptor(f)
# Calling it without the `owner` argument if `instance` is not `None` is an
# error: [no-matching-overload] "No overload of wrapper descriptor `FunctionType.__get__` matches arguments"
# error: [invalid-argument-type] "Argument to wrapper descriptor `FunctionType.__get__` is incorrect: Expected `~None`, found `None`"
wrapper_descriptor(f, None)
# But calling it with an instance is fine (in this case, the `owner` argument is optional):

View file

@ -0,0 +1,168 @@
# Single matching overload
<!-- snapshot-diagnostics -->
## Limited number of overloads
`overloaded.pyi`:
```pyi
from typing import overload
@overload
def f() -> None: ...
@overload
def f(x: int) -> int: ...
@overload
def f(x: int, y: int) -> int: ...
```
```py
from overloaded import f
f("a") # error: [invalid-argument-type]
```
## Call to function with too many unmatched overloads
This 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.
`overloaded.pyi`:
```pyi
from typing import overload
@overload
def foo(a: int): ...
@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): ...
```
```py
from typing import overload
from overloaded import foo
foo("foo") # error: [invalid-argument-type]
```

View file

@ -59,6 +59,7 @@ just ensuring that we get test coverage for each of the possible diagnostic mess
```py
from inspect import getattr_static
from typing import overload
def f1() -> int:
return 0
@ -72,11 +73,19 @@ def f3(a: int, b: int) -> int:
def f4[T: str](x: T) -> int:
return 0
class OverloadExample:
def f(self, x: str) -> int:
return 0
@overload
def f5() -> None: ...
@overload
def f5(x: str) -> str: ...
def f5(x: str | None = None) -> str | None:
return x
f5 = getattr_static(OverloadExample, "f").__get__
@overload
def f6() -> None: ...
@overload
def f6(x: str, y: str) -> str: ...
def f6(x: str | None = None, y: str | None = None) -> str | None:
return x + y if x and y else None
def _(n: int):
class PossiblyNotCallable:
@ -96,14 +105,17 @@ def _(n: int):
f = 5
elif n == 5:
f = f5
elif n == 6:
f = f6
else:
f = PossiblyNotCallable()
# error: [too-many-positional-arguments]
# error: [invalid-argument-type] "Argument to function `f2` is incorrect: Expected `str`, found `Literal[3]`"
# error: [missing-argument]
# error: [invalid-argument-type] "Argument to function `f4` is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`"
# error: [invalid-argument-type] "Argument to function `f5` is incorrect: Expected `str`, found `Literal[3]`"
# error: [no-matching-overload] "No overload of function `f6` matches arguments"
# error: [call-non-callable] "Object of type `Literal[5]` is not callable"
# error: [no-matching-overload]
# error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
x = f(3)
```

View file

@ -78,7 +78,7 @@ No narrowing should occur if `type` is used to dynamically create a class:
def _(x: str | int):
# The following diagnostic is valid, since the three-argument form of `type`
# can only be called with `str` as the first argument.
# error: [no-matching-overload] "No overload of class `type` matches arguments"
# error: [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `str | int`"
if type(x, (), {}) is str:
reveal_type(x) # revealed: str | int
else:

View file

@ -0,0 +1,226 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: single_matching_overload.md - Single matching overload - Call to function with too many unmatched overloads
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/single_matching_overload.md
---
# Python source files
## overloaded.pyi
```
1 | from typing import overload
2 |
3 | @overload
4 | def foo(a: int): ...
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): ...
```
## mdtest_snippet.py
```
1 | from typing import overload
2 |
3 | from overloaded import foo
4 |
5 | foo("foo") # error: [invalid-argument-type]
```
# Diagnostics
```
error[invalid-argument-type]: Argument to function `foo` is incorrect
--> src/mdtest_snippet.py:5:5
|
3 | from overloaded import foo
4 |
5 | foo("foo") # error: [invalid-argument-type]
| ^^^^^ Expected `int`, found `Literal["foo"]`
|
info: Matching overload defined here
--> src/overloaded.pyi:4:5
|
3 | @overload
4 | def foo(a: int): ...
| ^^^ ------ Parameter declared here
5 | @overload
6 | def foo(a: int, b: int, c: int): ...
|
info: Non-matching 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: ... omitted 12 overloads
info: rule `invalid-argument-type` is enabled by default
```

View file

@ -0,0 +1,59 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: single_matching_overload.md - Single matching overload - Limited number of overloads
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/single_matching_overload.md
---
# Python source files
## overloaded.pyi
```
1 | from typing import overload
2 |
3 | @overload
4 | def f() -> None: ...
5 | @overload
6 | def f(x: int) -> int: ...
7 | @overload
8 | def f(x: int, y: int) -> int: ...
```
## mdtest_snippet.py
```
1 | from overloaded import f
2 |
3 | f("a") # error: [invalid-argument-type]
```
# Diagnostics
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:3:3
|
1 | from overloaded import f
2 |
3 | f("a") # error: [invalid-argument-type]
| ^^^ Expected `int`, found `Literal["a"]`
|
info: Matching overload defined here
--> src/overloaded.pyi:6:5
|
4 | def f() -> None: ...
5 | @overload
6 | def f(x: int) -> int: ...
| ^ ------ Parameter declared here
7 | @overload
8 | def f(x: int, y: int) -> int: ...
|
info: Non-matching overloads for function `f`:
info: () -> None
info: (x: int, y: int) -> int
info: rule `invalid-argument-type` is enabled by default
```

View file

@ -13,176 +13,236 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m
```
1 | from inspect import getattr_static
2 |
3 | def f1() -> int:
4 | return 0
5 |
6 | def f2(name: str) -> int:
7 | return 0
8 |
9 | def f3(a: int, b: int) -> int:
10 | return 0
11 |
12 | def f4[T: str](x: T) -> int:
13 | return 0
14 |
15 | class OverloadExample:
16 | def f(self, x: str) -> int:
17 | return 0
18 |
19 | f5 = getattr_static(OverloadExample, "f").__get__
20 |
21 | def _(n: int):
22 | class PossiblyNotCallable:
23 | if n == 0:
24 | def __call__(self) -> int:
25 | return 0
26 |
27 | if n == 0:
28 | f = f1
29 | elif n == 1:
30 | f = f2
31 | elif n == 2:
32 | f = f3
33 | elif n == 3:
34 | f = f4
35 | elif n == 4:
36 | f = 5
37 | elif n == 5:
38 | f = f5
39 | else:
40 | f = PossiblyNotCallable()
41 | # error: [too-many-positional-arguments]
42 | # error: [invalid-argument-type] "Argument to function `f2` is incorrect: Expected `str`, found `Literal[3]`"
43 | # error: [missing-argument]
44 | # error: [invalid-argument-type] "Argument to function `f4` is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`"
45 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
2 | from typing import overload
3 |
4 | def f1() -> int:
5 | return 0
6 |
7 | def f2(name: str) -> int:
8 | return 0
9 |
10 | def f3(a: int, b: int) -> int:
11 | return 0
12 |
13 | def f4[T: str](x: T) -> int:
14 | return 0
15 |
16 | @overload
17 | def f5() -> None: ...
18 | @overload
19 | def f5(x: str) -> str: ...
20 | def f5(x: str | None = None) -> str | None:
21 | return x
22 |
23 | @overload
24 | def f6() -> None: ...
25 | @overload
26 | def f6(x: str, y: str) -> str: ...
27 | def f6(x: str | None = None, y: str | None = None) -> str | None:
28 | return x + y if x and y else None
29 |
30 | def _(n: int):
31 | class PossiblyNotCallable:
32 | if n == 0:
33 | def __call__(self) -> int:
34 | return 0
35 |
36 | if n == 0:
37 | f = f1
38 | elif n == 1:
39 | f = f2
40 | elif n == 2:
41 | f = f3
42 | elif n == 3:
43 | f = f4
44 | elif n == 4:
45 | f = 5
46 | elif n == 5:
47 | f = f5
48 | elif n == 6:
49 | f = f6
50 | else:
51 | f = PossiblyNotCallable()
52 | # error: [too-many-positional-arguments]
53 | # error: [invalid-argument-type] "Argument to function `f2` is incorrect: Expected `str`, found `Literal[3]`"
54 | # error: [missing-argument]
55 | # error: [invalid-argument-type] "Argument to function `f4` is incorrect: Argument type `Literal[3]` does not satisfy upper bound of type variable `T`"
56 | # error: [invalid-argument-type] "Argument to function `f5` is incorrect: Expected `str`, found `Literal[3]`"
57 | # error: [no-matching-overload] "No overload of function `f6` matches arguments"
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
60 | x = f(3)
```
# Diagnostics
```
error[call-non-callable]: Object of type `Literal[5]` is not callable
--> src/mdtest_snippet.py:48:9
--> src/mdtest_snippet.py:60:9
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
60 | x = f(3)
| ^^^^
|
info: Union variant `Literal[5]` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | (<method-wrapper `__get__` of `f`>) | PossiblyNotCallable`
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `call-non-callable` is enabled by default
```
```
error[call-non-callable]: Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)
--> src/mdtest_snippet.py:48:9
--> src/mdtest_snippet.py:60:9
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
60 | x = f(3)
| ^^^^
|
info: Union variant `PossiblyNotCallable` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | (<method-wrapper `__get__` of `f`>) | PossiblyNotCallable`
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `call-non-callable` is enabled by default
```
```
error[missing-argument]: No argument provided for required parameter `b` of function `f3`
--> src/mdtest_snippet.py:48:9
--> src/mdtest_snippet.py:60:9
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
60 | x = f(3)
| ^^^^
|
info: Union variant `def f3(a: int, b: int) -> int` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | (<method-wrapper `__get__` of `f`>) | PossiblyNotCallable`
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `missing-argument` is enabled by default
```
```
error[no-matching-overload]: No overload of method wrapper `__get__` of function `f` matches arguments
--> src/mdtest_snippet.py:48:9
error[no-matching-overload]: No overload of function `f6` matches arguments
--> src/mdtest_snippet.py:60:9
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
60 | x = f(3)
| ^^^^
|
info: Union variant `<method-wrapper `__get__` of `f`>` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | (<method-wrapper `__get__` of `f`>) | PossiblyNotCallable`
info: First overload defined here
--> src/mdtest_snippet.py:24:5
|
23 | @overload
24 | def f6() -> None: ...
| ^^^^^^^^^^^^
25 | @overload
26 | def f6(x: str, y: str) -> str: ...
|
info: Possible overloads for function `f6`:
info: () -> None
info: (x: str, y: str) -> str
info: Overload implementation defined here
--> src/mdtest_snippet.py:27:5
|
25 | @overload
26 | def f6(x: str, y: str) -> str: ...
27 | def f6(x: str | None = None, y: str | None = None) -> str | None:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28 | return x + y if x and y else None
|
info: Union variant `Overload[() -> None, (x: str, y: str) -> str]` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `no-matching-overload` is enabled by default
```
```
error[invalid-argument-type]: Argument to function `f2` is incorrect
--> src/mdtest_snippet.py:48:11
--> src/mdtest_snippet.py:60:11
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
60 | x = f(3)
| ^ Expected `str`, found `Literal[3]`
|
info: Function defined here
--> src/mdtest_snippet.py:6:5
--> src/mdtest_snippet.py:7:5
|
4 | return 0
5 |
6 | def f2(name: str) -> int:
5 | return 0
6 |
7 | def f2(name: str) -> int:
| ^^ --------- Parameter declared here
7 | return 0
8 | return 0
|
info: Union variant `def f2(name: str) -> int` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | (<method-wrapper `__get__` of `f`>) | PossiblyNotCallable`
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `invalid-argument-type` is enabled by default
```
```
error[invalid-argument-type]: Argument to function `f4` is incorrect
--> src/mdtest_snippet.py:48:11
--> src/mdtest_snippet.py:60:11
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
60 | x = f(3)
| ^ Argument type `Literal[3]` does not satisfy upper bound of type variable `T`
|
info: Type variable defined here
--> src/mdtest_snippet.py:12:8
--> src/mdtest_snippet.py:13:8
|
10 | return 0
11 |
12 | def f4[T: str](x: T) -> int:
11 | return 0
12 |
13 | def f4[T: str](x: T) -> int:
| ^^^^^^
13 | return 0
14 | return 0
|
info: Union variant `def f4(x: T) -> int` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | (<method-wrapper `__get__` of `f`>) | PossiblyNotCallable`
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `invalid-argument-type` is enabled by default
```
```
error[invalid-argument-type]: Argument to function `f5` is incorrect
--> src/mdtest_snippet.py:60:11
|
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
60 | x = f(3)
| ^ Expected `str`, found `Literal[3]`
|
info: Matching overload defined here
--> src/mdtest_snippet.py:19:5
|
17 | def f5() -> None: ...
18 | @overload
19 | def f5(x: str) -> str: ...
| ^^ ------ Parameter declared here
20 | def f5(x: str | None = None) -> str | None:
21 | return x
|
info: Non-matching overloads for function `f5`:
info: () -> None
info: Union variant `Overload[() -> None, (x: str) -> str]` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `invalid-argument-type` is enabled by default
```
```
error[too-many-positional-arguments]: Too many positional arguments to function `f1`: expected 0, got 1
--> src/mdtest_snippet.py:48:11
--> src/mdtest_snippet.py:60:11
|
46 | # error: [no-matching-overload]
47 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
48 | x = f(3)
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
60 | x = f(3)
| ^
|
info: Union variant `def f1() -> int` is incompatible with this call site
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | Unknown | (<method-wrapper `__get__` of `f`>) | PossiblyNotCallable`
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4(x: T) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
info: rule `too-many-positional-arguments` is enabled by default
```