mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
[ty] Induct into instances and subclasses when finding and applying generics (#18052)
We were not inducting into instance types and subclass-of types when looking for legacy typevars, nor when apply specializations. This addresses https://github.com/astral-sh/ruff/pull/17832#discussion_r2081502056 ```py from __future__ import annotations from typing import TypeVar, Any, reveal_type S = TypeVar("S") class Foo[T]: def method(self, other: Foo[S]) -> Foo[T | S]: ... # type: ignore[invalid-return-type] def f(x: Foo[Any], y: Foo[Any]): reveal_type(x.method(y)) # revealed: `Foo[Any | S]`, but should be `Foo[Any]` ``` We were not detecting that `S` made `method` generic, since we were not finding it when searching the function signature for legacy typevars.
This commit is contained in:
parent
7e9b0df18a
commit
f301931159
9 changed files with 269 additions and 68 deletions
|
@ -66,18 +66,76 @@ reveal_type(f("string")) # revealed: Literal["string"]
|
|||
## Inferring “deep” generic parameter types
|
||||
|
||||
The matching up of call arguments and discovery of constraints on typevars can be a recursive
|
||||
process for arbitrarily-nested generic types in parameters.
|
||||
process for arbitrarily-nested generic classes and protocols in parameters.
|
||||
|
||||
TODO: Note that we can currently only infer a specialization for a generic protocol when the
|
||||
argument _explicitly_ implements the protocol by listing it as a base class.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
from typing import Protocol, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def f(x: list[T]) -> T:
|
||||
class CanIndex(Protocol[T]):
|
||||
def __getitem__(self, index: int) -> T: ...
|
||||
|
||||
class ExplicitlyImplements(CanIndex[T]): ...
|
||||
|
||||
def takes_in_list(x: list[T]) -> list[T]:
|
||||
return x
|
||||
|
||||
def takes_in_protocol(x: CanIndex[T]) -> T:
|
||||
return x[0]
|
||||
|
||||
# TODO: revealed: float
|
||||
reveal_type(f([1.0, 2.0])) # revealed: Unknown
|
||||
def deep_list(x: list[str]) -> None:
|
||||
# TODO: revealed: list[str]
|
||||
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def deeper_list(x: list[set[str]]) -> None:
|
||||
# TODO: revealed: list[set[str]]
|
||||
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
|
||||
# TODO: revealed: set[str]
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def deep_explicit(x: ExplicitlyImplements[str]) -> None:
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
|
||||
# TODO: revealed: set[str]
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def takes_in_type(x: type[T]) -> type[T]:
|
||||
return x
|
||||
|
||||
reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form)
|
||||
```
|
||||
|
||||
This also works when passing in arguments that are subclasses of the parameter type.
|
||||
|
||||
```py
|
||||
class Sub(list[int]): ...
|
||||
class GenericSub(list[T]): ...
|
||||
|
||||
# TODO: revealed: list[int]
|
||||
reveal_type(takes_in_list(Sub())) # revealed: list[Unknown]
|
||||
# TODO: revealed: int
|
||||
reveal_type(takes_in_protocol(Sub())) # revealed: Unknown
|
||||
|
||||
# TODO: revealed: list[str]
|
||||
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[Unknown]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown
|
||||
|
||||
class ExplicitSub(ExplicitlyImplements[int]): ...
|
||||
class ExplicitGenericSub(ExplicitlyImplements[T]): ...
|
||||
|
||||
# TODO: revealed: int
|
||||
reveal_type(takes_in_protocol(ExplicitSub())) # revealed: Unknown
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inferring a bound typevar
|
||||
|
|
|
@ -61,14 +61,76 @@ reveal_type(f("string")) # revealed: Literal["string"]
|
|||
## Inferring “deep” generic parameter types
|
||||
|
||||
The matching up of call arguments and discovery of constraints on typevars can be a recursive
|
||||
process for arbitrarily-nested generic types in parameters.
|
||||
process for arbitrarily-nested generic classes and protocols in parameters.
|
||||
|
||||
TODO: Note that we can currently only infer a specialization for a generic protocol when the
|
||||
argument _explicitly_ implements the protocol by listing it as a base class.
|
||||
|
||||
```py
|
||||
def f[T](x: list[T]) -> T:
|
||||
from typing import Protocol, TypeVar
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
class CanIndex(Protocol[S]):
|
||||
def __getitem__(self, index: int) -> S: ...
|
||||
|
||||
class ExplicitlyImplements[T](CanIndex[T]): ...
|
||||
|
||||
def takes_in_list[T](x: list[T]) -> list[T]:
|
||||
return x
|
||||
|
||||
def takes_in_protocol[T](x: CanIndex[T]) -> T:
|
||||
return x[0]
|
||||
|
||||
# TODO: revealed: float
|
||||
reveal_type(f([1.0, 2.0])) # revealed: Unknown
|
||||
def deep_list(x: list[str]) -> None:
|
||||
# TODO: revealed: list[str]
|
||||
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def deeper_list(x: list[set[str]]) -> None:
|
||||
# TODO: revealed: list[set[str]]
|
||||
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
|
||||
# TODO: revealed: set[str]
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def deep_explicit(x: ExplicitlyImplements[str]) -> None:
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
|
||||
# TODO: revealed: set[str]
|
||||
reveal_type(takes_in_protocol(x)) # revealed: Unknown
|
||||
|
||||
def takes_in_type[T](x: type[T]) -> type[T]:
|
||||
return x
|
||||
|
||||
reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form)
|
||||
```
|
||||
|
||||
This also works when passing in arguments that are subclasses of the parameter type.
|
||||
|
||||
```py
|
||||
class Sub(list[int]): ...
|
||||
class GenericSub[T](list[T]): ...
|
||||
|
||||
# TODO: revealed: list[int]
|
||||
reveal_type(takes_in_list(Sub())) # revealed: list[Unknown]
|
||||
# TODO: revealed: int
|
||||
reveal_type(takes_in_protocol(Sub())) # revealed: Unknown
|
||||
|
||||
# TODO: revealed: list[str]
|
||||
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[Unknown]
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown
|
||||
|
||||
class ExplicitSub(ExplicitlyImplements[int]): ...
|
||||
class ExplicitGenericSub[T](ExplicitlyImplements[T]): ...
|
||||
|
||||
# TODO: revealed: int
|
||||
reveal_type(takes_in_protocol(ExplicitSub())) # revealed: Unknown
|
||||
# TODO: revealed: str
|
||||
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inferring a bound typevar
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue