mirror of https://github.com/astral-sh/ruff
[red-knot] Support overloads for callable equivalence (#17698)
## Summary Part of #15383, this PR adds `is_equivalent_to` support for overloaded callables. This is mainly done by delegating it to the subtyping check in that two types A and B are considered equivalent if A is a subtype of B and B is a subtype of A. ## Test Plan Add test cases for overloaded callables in `is_equivalent_to.md`
This commit is contained in:
parent
549ab74bd6
commit
f11d9cb509
|
|
@ -256,6 +256,65 @@ static_assert(is_equivalent_to(int | Callable[[int | str], None], Callable[[str
|
|||
|
||||
### Overloads
|
||||
|
||||
TODO
|
||||
#### One overload
|
||||
|
||||
`overloaded.pyi`:
|
||||
|
||||
```pyi
|
||||
from typing import overload
|
||||
|
||||
class Grandparent: ...
|
||||
class Parent(Grandparent): ...
|
||||
class Child(Parent): ...
|
||||
|
||||
@overload
|
||||
def overloaded(a: Child) -> None: ...
|
||||
@overload
|
||||
def overloaded(a: Parent) -> None: ...
|
||||
@overload
|
||||
def overloaded(a: Grandparent) -> None: ...
|
||||
```
|
||||
|
||||
```py
|
||||
from knot_extensions import CallableTypeOf, is_equivalent_to, static_assert
|
||||
from overloaded import Grandparent, Parent, Child, overloaded
|
||||
|
||||
def grandparent(a: Grandparent) -> None: ...
|
||||
|
||||
static_assert(is_equivalent_to(CallableTypeOf[grandparent], CallableTypeOf[overloaded]))
|
||||
static_assert(is_equivalent_to(CallableTypeOf[overloaded], CallableTypeOf[grandparent]))
|
||||
```
|
||||
|
||||
#### Both overloads
|
||||
|
||||
`overloaded.pyi`:
|
||||
|
||||
```pyi
|
||||
from typing import overload
|
||||
|
||||
class Grandparent: ...
|
||||
class Parent(Grandparent): ...
|
||||
class Child(Parent): ...
|
||||
|
||||
@overload
|
||||
def pg(a: Parent) -> None: ...
|
||||
@overload
|
||||
def pg(a: Grandparent) -> None: ...
|
||||
|
||||
@overload
|
||||
def cpg(a: Child) -> None: ...
|
||||
@overload
|
||||
def cpg(a: Parent) -> None: ...
|
||||
@overload
|
||||
def cpg(a: Grandparent) -> None: ...
|
||||
```
|
||||
|
||||
```py
|
||||
from knot_extensions import CallableTypeOf, is_equivalent_to, static_assert
|
||||
from overloaded import pg, cpg
|
||||
|
||||
static_assert(is_equivalent_to(CallableTypeOf[pg], CallableTypeOf[cpg]))
|
||||
static_assert(is_equivalent_to(CallableTypeOf[cpg], CallableTypeOf[pg]))
|
||||
```
|
||||
|
||||
[the equivalence relation]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent
|
||||
|
|
|
|||
|
|
@ -6984,11 +6984,22 @@ impl<'db> CallableType<'db> {
|
|||
fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
match (&**self.signatures(db), &**other.signatures(db)) {
|
||||
([self_signature], [other_signature]) => {
|
||||
// Common case: both callable types contain a single signature, use the custom
|
||||
// equivalence check instead of delegating it to the subtype check.
|
||||
self_signature.is_equivalent_to(db, other_signature)
|
||||
}
|
||||
_ => {
|
||||
// TODO: overloads
|
||||
false
|
||||
(self_signatures, other_signatures) => {
|
||||
if !self_signatures
|
||||
.iter()
|
||||
.chain(other_signatures.iter())
|
||||
.all(|signature| signature.is_fully_static(db))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
self.is_subtype_of(db, other) && other.is_subtype_of(db, self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue