You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix type inference for index expression with bounded TypeVar (#11434)
Closes#8231
When type of index expression (e.g. `foo[bar]`) is analyzed and left
expression (i.e. `foo`) has generic type (`TypeVar`) with upper bound,
for some upper bound types this type inference yields wrong result.
For example, if upper bound type is instance of `TypeDict`, mypy
considers return type of such index expression as `object`:
```
from typing import TypedDict, TypeVar
class Data(TypedDict):
x: int
T = TypeVar("T", bound=Data)
def f(data: T) -> int:
# line below leads to mypy error:
# 'Unsupported operand types for + ("object" and "int")'
return data["x"] + 1
```
The root cause of this issue was in `visit_index_with_type` method
from `checkexpr.py` which does type analysis for index expressions. For
`TypeVar` left expression code flow goes via default branch which just
returns return type of upper bound's `__getitem__`. For some types this
return type inference logic operates on a fallback type. For example,
for `TypedDict` fallback type is `typing._TypedDict` with `__getitem__`
returning just `object`.
To fix the issue we added special case to `visit_index_with_type` for
`TypeVar` left expression which recursively calls
`visit_index_with_type` with `TypeVar` upper bound parameter. This way
we always handle upper bounds requiring special treatment correctly.
Corner case -- recursive TypeVar `T` with upper bound having
`__getitem__` method with `self` having type `T` and returning `T`.
In this case when we call `visit_index_with_type` recursively, `TypeVar`
types of upper bound `__getitem__` method are erased and
`check_method_call_by_name` returns type of upper bound, not `T`. So we
don't do recursive `visit_index_with_type` call when upper bound has
`__getitem__` method and handle this case in `else` branch. which
handles it as expected -- it return `T` as return type.
0 commit comments