Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,15 @@ static_assert(not is_equivalent_to(CallableTypeOf[f12], CallableTypeOf[f13]))
static_assert(not is_equivalent_to(CallableTypeOf[f13], CallableTypeOf[f12]))
```

### Unions containing `Callable`s containing unions

Differently ordered unions inside `Callable`s inside unions can still be equivalent:

```py
from typing import Callable
from knot_extensions import is_equivalent_to, static_assert

static_assert(is_equivalent_to(int | Callable[[int | str], None], Callable[[str | int], None] | int))
```

[the equivalence relation]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-equivalent
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
A type is single-valued iff it is not empty and all inhabitants of it compare equal.

```py
import types
from typing_extensions import Any, Literal, LiteralString, Never, Callable
from knot_extensions import is_single_valued, static_assert
from knot_extensions import is_single_valued, static_assert, TypeOf

static_assert(is_single_valued(None))
static_assert(is_single_valued(Literal[True]))
Expand All @@ -25,4 +26,11 @@ static_assert(not is_single_valued(tuple[None, int]))

static_assert(not is_single_valued(Callable[..., None]))
static_assert(not is_single_valued(Callable[[int, str], None]))

class A:
def method(self): ...

static_assert(is_single_valued(TypeOf[A().method]))
static_assert(is_single_valued(TypeOf[types.FunctionType.__get__]))
static_assert(is_single_valued(TypeOf[A.method.__get__]))
```
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,29 @@ from knot_extensions import static_assert, is_singleton
reveal_type(types.NotImplementedType) # revealed: Unknown | Literal[_NotImplementedType]
static_assert(not is_singleton(types.NotImplementedType))
```

### Callables

We currently treat the type of `types.FunctionType.__get__` as a singleton type that has its own
dedicated variant in the `Type` enum. That variant should be understood as a singleton type, but the
similar variants `Type::BoundMethod` and `Type::MethodWrapperDunderGet` should not be; nor should
`Type::Callable` types.

If we refactor `Type` in the future to get rid of some or all of these `Type` variants, the
assertion that the type of `types.FunctionType.__get__` is a singleton type does not necessarily
have to hold true; it's more of a unit test for our current implementation.

```py
import types
from typing import Callable
from knot_extensions import static_assert, is_singleton, TypeOf

class A:
def method(self): ...

static_assert(is_singleton(TypeOf[types.FunctionType.__get__]))

static_assert(not is_singleton(Callable[[], None]))
static_assert(not is_singleton(TypeOf[A().method]))
static_assert(not is_singleton(TypeOf[A.method.__get__]))
```
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,26 @@ static_assert(is_subtype_of(typing.TypeAliasType, AlwaysTruthy))
static_assert(is_subtype_of(types.MethodWrapperType, AlwaysTruthy))
static_assert(is_subtype_of(types.WrapperDescriptorType, AlwaysTruthy))
```

### `Callable` types always have ambiguous truthiness

```py
from typing import Callable

def f(x: Callable, y: Callable[[int], str]):
reveal_type(bool(x)) # revealed: bool
reveal_type(bool(y)) # revealed: bool
```

But certain callable single-valued types are known to be always truthy:

```py
from types import FunctionType

class A:
def method(self): ...

reveal_type(bool(A().method)) # revealed: Literal[True]
reveal_type(bool(f.__get__)) # revealed: Literal[True]
reveal_type(bool(FunctionType.__get__)) # revealed: Literal[True]
```
Loading
Loading