mirror of https://github.com/astral-sh/ruff
[ty] Use `ParamSpec` without the attr for inferable check (#21934)
## Summary fixes: https://github.com/astral-sh/ty/issues/1820 ## Test Plan Add new mdtests. Ecosystem changes removes all false positives.
This commit is contained in:
parent
04f9949711
commit
ba47349c2e
|
|
@ -670,3 +670,59 @@ reveal_type(with_parameters(int_int, 1)) # revealed: Overload[(x: int) -> str,
|
||||||
# error: [invalid-argument-type]
|
# error: [invalid-argument-type]
|
||||||
reveal_type(with_parameters(int_int, "a")) # revealed: Overload[(x: int) -> str, (x: str) -> str]
|
reveal_type(with_parameters(int_int, "a")) # revealed: Overload[(x: int) -> str, (x: str) -> str]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## ParamSpec attribute assignability
|
||||||
|
|
||||||
|
When comparing signatures with `ParamSpec` attributes (`P.args` and `P.kwargs`), two different
|
||||||
|
inferable `ParamSpec` attributes with the same kind are assignable to each other. This enables
|
||||||
|
method overrides where both methods have their own `ParamSpec`.
|
||||||
|
|
||||||
|
### Same attribute kind, both inferable
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
class Parent:
|
||||||
|
def method[**P](self, callback: Callable[P, None]) -> Callable[P, None]:
|
||||||
|
return callback
|
||||||
|
|
||||||
|
class Child1(Parent):
|
||||||
|
# This is a valid override: Q.args matches P.args, Q.kwargs matches P.kwargs
|
||||||
|
def method[**Q](self, callback: Callable[Q, None]) -> Callable[Q, None]:
|
||||||
|
return callback
|
||||||
|
|
||||||
|
# Both signatures use ParamSpec, so they should be compatible
|
||||||
|
def outer[**P](f: Callable[P, int]) -> Callable[P, int]:
|
||||||
|
def inner[**Q](g: Callable[Q, int]) -> Callable[Q, int]:
|
||||||
|
return g
|
||||||
|
return inner(f)
|
||||||
|
```
|
||||||
|
|
||||||
|
We can explicitly mark it as an override using the `@override` decorator.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import override
|
||||||
|
|
||||||
|
class Child2(Parent):
|
||||||
|
@override
|
||||||
|
def method[**Q](self, callback: Callable[Q, None]) -> Callable[Q, None]:
|
||||||
|
return callback
|
||||||
|
```
|
||||||
|
|
||||||
|
### One `ParamSpec` not inferable
|
||||||
|
|
||||||
|
Here, `P` is in a non-inferable position while `Q` is inferable. So, they are not considered
|
||||||
|
assignable.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
class Container[**P]:
|
||||||
|
def method(self, f: Callable[P, None]) -> Callable[P, None]:
|
||||||
|
return f
|
||||||
|
|
||||||
|
def try_assign[**Q](self, f: Callable[Q, None]) -> Callable[Q, None]:
|
||||||
|
# error: [invalid-return-type] "Return type does not match returned value: expected `(**Q@try_assign) -> None`, found `(**P@Container) -> None`"
|
||||||
|
# error: [invalid-argument-type] "Argument to bound method `method` is incorrect: Expected `(**P@Container) -> None`, found `(**Q@try_assign) -> None`"
|
||||||
|
return self.method(f)
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -1072,6 +1072,29 @@ impl<'db> Signature<'db> {
|
||||||
let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
|
let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
|
||||||
let type1 = type1.unwrap_or(Type::unknown());
|
let type1 = type1.unwrap_or(Type::unknown());
|
||||||
let type2 = type2.unwrap_or(Type::unknown());
|
let type2 = type2.unwrap_or(Type::unknown());
|
||||||
|
|
||||||
|
match (type1, type2) {
|
||||||
|
// This is a special case where the _same_ components of two different `ParamSpec`
|
||||||
|
// type variables are assignable to each other when they're both in an inferable
|
||||||
|
// position.
|
||||||
|
//
|
||||||
|
// `ParamSpec` type variables can only occur in parameter lists so this special case
|
||||||
|
// is present here instead of in `Type::has_relation_to_impl`.
|
||||||
|
(Type::TypeVar(typevar1), Type::TypeVar(typevar2))
|
||||||
|
if typevar1.paramspec_attr(db).is_some()
|
||||||
|
&& typevar1.paramspec_attr(db) == typevar2.paramspec_attr(db)
|
||||||
|
&& typevar1
|
||||||
|
.without_paramspec_attr(db)
|
||||||
|
.is_inferable(db, inferable)
|
||||||
|
&& typevar2
|
||||||
|
.without_paramspec_attr(db)
|
||||||
|
.is_inferable(db, inferable) =>
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
!result
|
!result
|
||||||
.intersect(
|
.intersect(
|
||||||
db,
|
db,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue