Skip to content

Commit 6be0a50

Browse files
authored
[red-knot] Disjointness for callable types (#17094)
## Summary Part of #15382, this PR adds support for disjointness between two callable types. They are never disjoint because there exists a callable type that's a subtype of all other callable types: ```py (*args: object, **kwargs: object) -> Never ``` The `Never` is a subtype of every fully static type thus a callable type that has the return type of `Never` means that it is a subtype of every return type. ## Test Plan Add test cases related to mixed parameter kinds, gradual form (`...`) and `Never` type.
1 parent d6dcc37 commit 6be0a50

File tree

3 files changed

+55
-6
lines changed

3 files changed

+55
-6
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Tuple
2+
3+
## `Never`
4+
5+
If a tuple type contains a `Never` element, then it is eagerly simplified to `Never` which means
6+
that a tuple type containing `Never` is disjoint from any other tuple type.
7+
8+
```py
9+
from typing_extensions import Never
10+
11+
def _(x: tuple[Never], y: tuple[int, Never], z: tuple[Never, int]):
12+
reveal_type(x) # revealed: Never
13+
reveal_type(y) # revealed: Never
14+
reveal_type(z) # revealed: Never
15+
```

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ static_assert(is_disjoint_from(B2, FinalSubclass))
6161
## Tuple types
6262

6363
```py
64-
from typing_extensions import Literal
64+
from typing_extensions import Literal, Never
6565
from knot_extensions import TypeOf, is_disjoint_from, static_assert
6666

6767
static_assert(is_disjoint_from(tuple[()], TypeOf[object]))
@@ -353,3 +353,29 @@ class UsesMeta2(metaclass=Meta2): ...
353353

354354
static_assert(is_disjoint_from(type[UsesMeta1], type[UsesMeta2]))
355355
```
356+
357+
## Callables
358+
359+
No two callable types are disjoint because there exists a non-empty callable type
360+
`(*args: object, **kwargs: object) -> Never` that is a subtype of all fully static callable types.
361+
As such, for any two callable types, it is possible to conceive of a runtime callable object that
362+
would inhabit both types simultaneously.
363+
364+
```py
365+
from knot_extensions import CallableTypeOf, is_disjoint_from, static_assert
366+
from typing_extensions import Callable, Literal, Never
367+
368+
def mixed(a: int, /, b: str, *args: int, c: int = 2, **kwargs: int) -> None: ...
369+
370+
static_assert(not is_disjoint_from(Callable[[], Never], CallableTypeOf[mixed]))
371+
static_assert(not is_disjoint_from(Callable[[int, str], float], CallableTypeOf[mixed]))
372+
373+
# Using gradual form
374+
static_assert(not is_disjoint_from(Callable[..., None], Callable[[], None]))
375+
static_assert(not is_disjoint_from(Callable[..., None], Callable[..., None]))
376+
static_assert(not is_disjoint_from(Callable[..., None], Callable[[Literal[1]], None]))
377+
378+
# Using `Never`
379+
static_assert(not is_disjoint_from(Callable[[], Never], Callable[[], Never]))
380+
static_assert(not is_disjoint_from(Callable[[Never], str], Callable[[Never], int]))
381+
```

crates/red_knot_python_semantic/src/types.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,19 @@ impl<'db> Type<'db> {
13431343
.to_instance(db)
13441344
.is_disjoint_from(db, other),
13451345

1346+
(Type::Callable(_) | Type::FunctionLiteral(_), Type::Callable(_))
1347+
| (Type::Callable(_), Type::FunctionLiteral(_)) => {
1348+
// No two callable types are ever disjoint because
1349+
// `(*args: object, **kwargs: object) -> Never` is a subtype of all fully static
1350+
// callable types.
1351+
false
1352+
}
1353+
1354+
(Type::Callable(_), _) | (_, Type::Callable(_)) => {
1355+
// TODO: Implement disjointness for general callable type with other types
1356+
false
1357+
}
1358+
13461359
(Type::ModuleLiteral(..), other @ Type::Instance(..))
13471360
| (other @ Type::Instance(..), Type::ModuleLiteral(..)) => {
13481361
// Modules *can* actually be instances of `ModuleType` subclasses
@@ -1379,11 +1392,6 @@ impl<'db> Type<'db> {
13791392
// TODO: add checks for the above cases once we support them
13801393
instance.is_disjoint_from(db, KnownClass::Tuple.to_instance(db))
13811394
}
1382-
1383-
(Type::Callable(_), _) | (_, Type::Callable(_)) => {
1384-
// TODO: Implement disjointedness for callable types
1385-
false
1386-
}
13871395
}
13881396
}
13891397

0 commit comments

Comments
 (0)