Skip to content

Commit f11d9cb

Browse files
authored
[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`
1 parent 549ab74 commit f11d9cb

File tree

2 files changed

+74
-4
lines changed

2 files changed

+74
-4
lines changed

crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,65 @@ static_assert(is_equivalent_to(int | Callable[[int | str], None], Callable[[str
256256

257257
### Overloads
258258

259-
TODO
259+
#### One overload
260+
261+
`overloaded.pyi`:
262+
263+
```pyi
264+
from typing import overload
265+
266+
class Grandparent: ...
267+
class Parent(Grandparent): ...
268+
class Child(Parent): ...
269+
270+
@overload
271+
def overloaded(a: Child) -> None: ...
272+
@overload
273+
def overloaded(a: Parent) -> None: ...
274+
@overload
275+
def overloaded(a: Grandparent) -> None: ...
276+
```
277+
278+
```py
279+
from knot_extensions import CallableTypeOf, is_equivalent_to, static_assert
280+
from overloaded import Grandparent, Parent, Child, overloaded
281+
282+
def grandparent(a: Grandparent) -> None: ...
283+
284+
static_assert(is_equivalent_to(CallableTypeOf[grandparent], CallableTypeOf[overloaded]))
285+
static_assert(is_equivalent_to(CallableTypeOf[overloaded], CallableTypeOf[grandparent]))
286+
```
287+
288+
#### Both overloads
289+
290+
`overloaded.pyi`:
291+
292+
```pyi
293+
from typing import overload
294+
295+
class Grandparent: ...
296+
class Parent(Grandparent): ...
297+
class Child(Parent): ...
298+
299+
@overload
300+
def pg(a: Parent) -> None: ...
301+
@overload
302+
def pg(a: Grandparent) -> None: ...
303+
304+
@overload
305+
def cpg(a: Child) -> None: ...
306+
@overload
307+
def cpg(a: Parent) -> None: ...
308+
@overload
309+
def cpg(a: Grandparent) -> None: ...
310+
```
311+
312+
```py
313+
from knot_extensions import CallableTypeOf, is_equivalent_to, static_assert
314+
from overloaded import pg, cpg
315+
316+
static_assert(is_equivalent_to(CallableTypeOf[pg], CallableTypeOf[cpg]))
317+
static_assert(is_equivalent_to(CallableTypeOf[cpg], CallableTypeOf[pg]))
318+
```
260319

261320
[the equivalence relation]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent

crates/red_knot_python_semantic/src/types.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6984,11 +6984,22 @@ impl<'db> CallableType<'db> {
69846984
fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
69856985
match (&**self.signatures(db), &**other.signatures(db)) {
69866986
([self_signature], [other_signature]) => {
6987+
// Common case: both callable types contain a single signature, use the custom
6988+
// equivalence check instead of delegating it to the subtype check.
69876989
self_signature.is_equivalent_to(db, other_signature)
69886990
}
6989-
_ => {
6990-
// TODO: overloads
6991-
false
6991+
(self_signatures, other_signatures) => {
6992+
if !self_signatures
6993+
.iter()
6994+
.chain(other_signatures.iter())
6995+
.all(|signature| signature.is_fully_static(db))
6996+
{
6997+
return false;
6998+
}
6999+
if self == other {
7000+
return true;
7001+
}
7002+
self.is_subtype_of(db, other) && other.is_subtype_of(db, self)
69927003
}
69937004
}
69947005
}

0 commit comments

Comments
 (0)