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 @@ -498,3 +498,50 @@ def possibly_unbound_with_invalid_type(flag: bool):
static_assert(is_disjoint_from(G, Callable[..., Any]))
static_assert(is_disjoint_from(Callable[..., Any], G))
```

A callable type is disjoint from special form types, except for callable special forms.

```py
from ty_extensions import is_disjoint_from, static_assert, TypeOf
from typing_extensions import Any, Callable, TypedDict
from typing import Literal, Union, Optional, Final, Type, ChainMap, Counter, OrderedDict, DefaultDict, Deque

# Most special forms are disjoint from callable types because they are
# type constructors/annotations that are subscripted, not called.
static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Literal]))
static_assert(is_disjoint_from(TypeOf[Literal], Callable[..., Any]))

static_assert(is_disjoint_from(Callable[[], None], TypeOf[Union]))
static_assert(is_disjoint_from(TypeOf[Union], Callable[[], None]))

static_assert(is_disjoint_from(Callable[[int], str], TypeOf[Optional]))
static_assert(is_disjoint_from(TypeOf[Optional], Callable[[int], str]))

static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Type]))
static_assert(is_disjoint_from(TypeOf[Type], Callable[..., Any]))

static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Final]))
static_assert(is_disjoint_from(TypeOf[Final], Callable[..., Any]))

static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Callable]))
static_assert(is_disjoint_from(TypeOf[Callable], Callable[..., Any]))

# However, some special forms are callable (TypedDict and collection constructors)
static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[TypedDict]))
static_assert(not is_disjoint_from(TypeOf[TypedDict], Callable[..., Any]))

static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[ChainMap]))
static_assert(not is_disjoint_from(TypeOf[ChainMap], Callable[..., Any]))

static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[Counter]))
static_assert(not is_disjoint_from(TypeOf[Counter], Callable[..., Any]))

static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[DefaultDict]))
static_assert(not is_disjoint_from(TypeOf[DefaultDict], Callable[..., Any]))

static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[Deque]))
static_assert(not is_disjoint_from(TypeOf[Deque], Callable[..., Any]))

static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[OrderedDict]))
static_assert(not is_disjoint_from(TypeOf[OrderedDict], Callable[..., Any]))
```
9 changes: 9 additions & 0 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1926,6 +1926,15 @@ impl<'db> Type<'db> {
true
}

(Type::Callable(_), Type::SpecialForm(special_form))
| (Type::SpecialForm(special_form), Type::Callable(_)) => {
// A callable type is disjoint from special form types, except for special forms
// that are callable (like TypedDict and collection constructors).
// Most special forms are type constructors/annotations (like `typing.Literal`,
// `typing.Union`, etc.) that are subscripted, not called.
!special_form.is_callable()
}

(
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
instance @ Type::NominalInstance(NominalInstanceType { class, .. }),
Expand Down
53 changes: 53 additions & 0 deletions crates/ty_python_semantic/src/types/special_form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,59 @@ impl SpecialFormType {
self.class().to_class_literal(db)
}

/// Return true if this special form is callable at runtime.
/// Most special forms are not callable (they are type constructors that are subscripted),
/// but some like `TypedDict` and collection constructors can be called.
pub(super) const fn is_callable(self) -> bool {
match self {
// TypedDict can be called as a constructor to create TypedDict types
Self::TypedDict
// Collection constructors are callable
// TODO actually implement support for calling them
| Self::ChainMap
| Self::Counter
| Self::DefaultDict
| Self::Deque
| Self::OrderedDict => true,

// All other special forms are not callable
Self::Annotated
| Self::Literal
| Self::LiteralString
| Self::Optional
| Self::Union
| Self::NoReturn
| Self::Never
| Self::Tuple
| Self::List
| Self::Dict
| Self::Set
| Self::FrozenSet
| Self::Type
| Self::Unknown
| Self::AlwaysTruthy
| Self::AlwaysFalsy
| Self::Not
| Self::Intersection
| Self::TypeOf
| Self::CallableTypeOf
| Self::Callable
| Self::TypingSelf
| Self::Final
| Self::ClassVar
| Self::Concatenate
| Self::Unpack
| Self::Required
| Self::NotRequired
| Self::TypeAlias
| Self::TypeGuard
| Self::TypeIs
| Self::ReadOnly
| Self::Protocol
| Self::Generic => false,
}
}

/// Return the repr of the symbol at runtime
pub(super) const fn repr(self) -> &'static str {
match self {
Expand Down
Loading