Skip to content

Commit

Permalink
Fix TypeVar upper bounds sometimes not being displayed in pretty call…
Browse files Browse the repository at this point in the history
…ables (#17802)

Fixes #17792. Related to #17791.

Currently,
[`pretty_callable`](https://github.com/python/mypy/blob/5dfc7d941253553ab77836e9845cb8fdfb9d23a9/mypy/messages.py#L2862)
only renders `TypeVar` upper bounds if they are of type `Instance`:

https://github.com/python/mypy/blob/5dfc7d941253553ab77836e9845cb8fdfb9d23a9/mypy/messages.py#L2943-L2949

However, there are some types that can appear as `TypeVar` upper bounds
which are not represented by `Instance`, such as `UnionType` and
`CallableType`.

This PR allows such non-`Instance` upper bounds to be rendered as well.

## Effect
Consider the below code. 
Playground link:
https://mypy-play.net/?mypy=1.11.2&python=3.12&enable-incomplete-feature=NewGenericSyntax&gist=ba30c820cc3668e0919dadf2f391ff4b
```python
from collections.abc import Callable
from typing import Any, overload

### No matching overloads

@overload
def f1[T: int](x: T) -> T: ...
@overload
def f1[T: Callable[..., None]](x: T) -> T: ...
@overload
def f1[T: tuple[int]](x: T) -> T: ...
@overload
def f1[T: None](x: T) -> T: ...
@overload
def f1[T: type[int]](x: T) -> T: ...
@overload
def f1[T: bytes | bytearray](x: T) -> T: ...
def f1(x): return x

f1(1.23)

### Mismatching conditional definitions

if input():
    def f2[T](x: T) -> T:
        return x
else:
    def f2[T: Callable[..., None]](x: T) -> T:
        return x
```

### Before
* In the first error on line 20, all overloads aside from the first one
are displayed as `def [T] f1(x: T) -> T` (upper bound missing).
Duplicate entries are suppressed.
* In the second error on line 28, the second definition is displayed as
`def [T] f2(x: T) -> T` (upper bound missing), and is removed as an
apparent duplicate of the first.
```none
main.py:20: error: No overload variant of "f1" matches argument type "float"  [call-overload]
main.py:20: note: Possible overload variants:
main.py:20: note:     def [T: int] f1(x: T) -> T
main.py:20: note:     def [T] f1(x: T) -> T
main.py:28: error: All conditional function variants must have identical signatures  [misc]
main.py:28: note: Original:
main.py:28: note:     def [T] f2(x: T) -> T
main.py:28: note: Redefinition:
Found 2 errors in 1 file (checked 1 source file)
```

### After
* All type var upper bounds are rendered.
```none
main.py:20: error: No overload variant of "f1" matches argument type "float"  [call-overload]
main.py:20: note: Possible overload variants:
main.py:20: note:     def [T: int] f1(x: T) -> T
main.py:20: note:     def [T: Callable[..., None]] f1(x: T) -> T
main.py:20: note:     def [T: tuple[int]] f1(x: T) -> T
main.py:20: note:     def [T: None] f1(x: T) -> T
main.py:20: note:     def [T: type[int]] f1(x: T) -> T
main.py:20: note:     def [T: bytes | bytearray] f1(x: T) -> T
main.py:28: error: All conditional function variants must have identical signatures  [misc]
main.py:28: note: Original:
main.py:28: note:     def [T] f2(x: T) -> T
main.py:28: note: Redefinition:
main.py:28: note:     def [T: Callable[..., None]] f2(x: T) -> T
Found 2 errors in 1 file (checked 1 source file)
```
  • Loading branch information
brianschubert authored Sep 21, 2024
1 parent 5dfc7d9 commit 94109aa
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 2 deletions.
4 changes: 2 additions & 2 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2942,9 +2942,9 @@ def [T <: int] f(self, x: int, y: T) -> None
for tvar in tp.variables:
if isinstance(tvar, TypeVarType):
upper_bound = get_proper_type(tvar.upper_bound)
if (
if not (
isinstance(upper_bound, Instance)
and upper_bound.type.fullname != "builtins.object"
and upper_bound.type.fullname == "builtins.object"
):
tvars.append(f"{tvar.name}: {format_type_bare(upper_bound, options)}")
elif tvar.values:
Expand Down
14 changes: 14 additions & 0 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1428,6 +1428,20 @@ else:
# N: Redefinition: \
# N: def f(x: int = ...) -> None

[case testIncompatibleConditionalFunctionDefinition4]
from typing import Any, Union, TypeVar
T1 = TypeVar('T1')
T2 = TypeVar('T2', bound=Union[int, str])
x = None # type: Any
if x:
def f(x: T1) -> T1: pass
else:
def f(x: T2) -> T2: pass # E: All conditional function variants must have identical signatures \
# N: Original: \
# N: def [T1] f(x: T1) -> T1 \
# N: Redefinition: \
# N: def [T2: Union[int, str]] f(x: T2) -> T2

[case testConditionalFunctionDefinitionUsingDecorator1]
from typing import Callable

Expand Down

0 comments on commit 94109aa

Please sign in to comment.