Skip to content

Commit ad90749

Browse files
committed
Fix subtyping of callable with gradual parameters.
1 parent a0ea7d3 commit ad90749

File tree

2 files changed

+39
-3
lines changed

2 files changed

+39
-3
lines changed

crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,27 @@ static_assert(is_subtype_of(TypeOf[C.foo], object))
12191219
static_assert(not is_subtype_of(object, TypeOf[C.foo]))
12201220
```
12211221

1222+
#### Gradual form
1223+
1224+
A callable type with `...` parameters can be considered a supertype of a callable type that accepts
1225+
any arguments of any type, but otherwise is not a subtype or supertype of any callable type.
1226+
1227+
```py
1228+
from typing import Callable, Never
1229+
from ty_extensions import CallableTypeOf, is_subtype_of, static_assert
1230+
1231+
def bottom(*args: object, **kwargs: object) -> Never:
1232+
raise Exception()
1233+
1234+
type BottomCallable = CallableTypeOf[bottom]
1235+
1236+
static_assert(is_subtype_of(BottomCallable, Callable[..., Never]))
1237+
static_assert(is_subtype_of(BottomCallable, Callable[..., int]))
1238+
1239+
static_assert(not is_subtype_of(Callable[[], object], Callable[..., object]))
1240+
static_assert(not is_subtype_of(Callable[..., object], Callable[[], object]))
1241+
```
1242+
12221243
### Classes with `__call__`
12231244

12241245
```py

crates/ty_python_semantic/src/types/signatures.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -562,12 +562,27 @@ impl<'db> Signature<'db> {
562562
return false;
563563
}
564564

565-
if self.parameters.is_gradual() || other.parameters.is_gradual() {
566-
// If either of the parameter lists contains a gradual form (`...`), then it is
567-
// assignable / subtype to and from any other callable type.
565+
// A gradual parameter list is a supertype of the "bottom" parameter list (*args: object,
566+
// **kwargs: object).
567+
if other.parameters.is_gradual()
568+
&& self
569+
.parameters
570+
.variadic()
571+
.is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object(db)))
572+
&& self
573+
.parameters
574+
.keyword_variadic()
575+
.is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object(db)))
576+
{
568577
return true;
569578
}
570579

580+
// If either of the parameter lists is gradual (`...`), then it is assignable to and from
581+
// any other parameter list, but not a subtype or supertype of any other parameter list.
582+
if self.parameters.is_gradual() || other.parameters.is_gradual() {
583+
return relation.is_assignability();
584+
}
585+
571586
let mut parameters = ParametersZip {
572587
current_self: None,
573588
current_other: None,

0 commit comments

Comments
 (0)