Skip to content

Commit a2de81c

Browse files
carljmAlexWaygood
andauthored
[ty] implement disjointness of Callable vs SpecialForm (#18503)
## Summary Fixes astral-sh/ty#557 ## Test Plan Stable property tests succeed with a million iterations. Added mdtests. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent eb60bd6 commit a2de81c

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,3 +498,50 @@ def possibly_unbound_with_invalid_type(flag: bool):
498498
static_assert(is_disjoint_from(G, Callable[..., Any]))
499499
static_assert(is_disjoint_from(Callable[..., Any], G))
500500
```
501+
502+
A callable type is disjoint from special form types, except for callable special forms.
503+
504+
```py
505+
from ty_extensions import is_disjoint_from, static_assert, TypeOf
506+
from typing_extensions import Any, Callable, TypedDict
507+
from typing import Literal, Union, Optional, Final, Type, ChainMap, Counter, OrderedDict, DefaultDict, Deque
508+
509+
# Most special forms are disjoint from callable types because they are
510+
# type constructors/annotations that are subscripted, not called.
511+
static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Literal]))
512+
static_assert(is_disjoint_from(TypeOf[Literal], Callable[..., Any]))
513+
514+
static_assert(is_disjoint_from(Callable[[], None], TypeOf[Union]))
515+
static_assert(is_disjoint_from(TypeOf[Union], Callable[[], None]))
516+
517+
static_assert(is_disjoint_from(Callable[[int], str], TypeOf[Optional]))
518+
static_assert(is_disjoint_from(TypeOf[Optional], Callable[[int], str]))
519+
520+
static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Type]))
521+
static_assert(is_disjoint_from(TypeOf[Type], Callable[..., Any]))
522+
523+
static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Final]))
524+
static_assert(is_disjoint_from(TypeOf[Final], Callable[..., Any]))
525+
526+
static_assert(is_disjoint_from(Callable[..., Any], TypeOf[Callable]))
527+
static_assert(is_disjoint_from(TypeOf[Callable], Callable[..., Any]))
528+
529+
# However, some special forms are callable (TypedDict and collection constructors)
530+
static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[TypedDict]))
531+
static_assert(not is_disjoint_from(TypeOf[TypedDict], Callable[..., Any]))
532+
533+
static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[ChainMap]))
534+
static_assert(not is_disjoint_from(TypeOf[ChainMap], Callable[..., Any]))
535+
536+
static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[Counter]))
537+
static_assert(not is_disjoint_from(TypeOf[Counter], Callable[..., Any]))
538+
539+
static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[DefaultDict]))
540+
static_assert(not is_disjoint_from(TypeOf[DefaultDict], Callable[..., Any]))
541+
542+
static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[Deque]))
543+
static_assert(not is_disjoint_from(TypeOf[Deque], Callable[..., Any]))
544+
545+
static_assert(not is_disjoint_from(Callable[..., Any], TypeOf[OrderedDict]))
546+
static_assert(not is_disjoint_from(TypeOf[OrderedDict], Callable[..., Any]))
547+
```

crates/ty_python_semantic/src/types.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,6 +1926,15 @@ impl<'db> Type<'db> {
19261926
true
19271927
}
19281928

1929+
(Type::Callable(_), Type::SpecialForm(special_form))
1930+
| (Type::SpecialForm(special_form), Type::Callable(_)) => {
1931+
// A callable type is disjoint from special form types, except for special forms
1932+
// that are callable (like TypedDict and collection constructors).
1933+
// Most special forms are type constructors/annotations (like `typing.Literal`,
1934+
// `typing.Union`, etc.) that are subscripted, not called.
1935+
!special_form.is_callable()
1936+
}
1937+
19291938
(
19301939
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
19311940
instance @ Type::NominalInstance(NominalInstanceType { class, .. }),

crates/ty_python_semantic/src/types/special_form.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,59 @@ impl SpecialFormType {
247247
self.class().to_class_literal(db)
248248
}
249249

250+
/// Return true if this special form is callable at runtime.
251+
/// Most special forms are not callable (they are type constructors that are subscripted),
252+
/// but some like `TypedDict` and collection constructors can be called.
253+
pub(super) const fn is_callable(self) -> bool {
254+
match self {
255+
// TypedDict can be called as a constructor to create TypedDict types
256+
Self::TypedDict
257+
// Collection constructors are callable
258+
// TODO actually implement support for calling them
259+
| Self::ChainMap
260+
| Self::Counter
261+
| Self::DefaultDict
262+
| Self::Deque
263+
| Self::OrderedDict => true,
264+
265+
// All other special forms are not callable
266+
Self::Annotated
267+
| Self::Literal
268+
| Self::LiteralString
269+
| Self::Optional
270+
| Self::Union
271+
| Self::NoReturn
272+
| Self::Never
273+
| Self::Tuple
274+
| Self::List
275+
| Self::Dict
276+
| Self::Set
277+
| Self::FrozenSet
278+
| Self::Type
279+
| Self::Unknown
280+
| Self::AlwaysTruthy
281+
| Self::AlwaysFalsy
282+
| Self::Not
283+
| Self::Intersection
284+
| Self::TypeOf
285+
| Self::CallableTypeOf
286+
| Self::Callable
287+
| Self::TypingSelf
288+
| Self::Final
289+
| Self::ClassVar
290+
| Self::Concatenate
291+
| Self::Unpack
292+
| Self::Required
293+
| Self::NotRequired
294+
| Self::TypeAlias
295+
| Self::TypeGuard
296+
| Self::TypeIs
297+
| Self::ReadOnly
298+
| Self::Protocol
299+
| Self::Generic => false,
300+
}
301+
}
302+
250303
/// Return the repr of the symbol at runtime
251304
pub(super) const fn repr(self) -> &'static str {
252305
match self {

0 commit comments

Comments
 (0)