Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions crates/ty_python_semantic/resources/mdtest/call/builtins.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
```
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 45 additions & 2 deletions crates/ty_python_semantic/resources/mdtest/call/overloads.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,28 @@ 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
(non-overloaded) function call. This means that any diagnostics resulted during type checking that
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`:
Expand Down Expand Up @@ -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.
Comment on lines +409 to +412
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case was missing in the original implementation so just wanted to make sure that we still follow the algorithm correctly with the logic in this PR.


`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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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]
```
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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)
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading
Loading