Skip to content

Commit ec7c5cd

Browse files
committed
tests and docs
1 parent 5758a32 commit ec7c5cd

File tree

3 files changed

+132
-4
lines changed

3 files changed

+132
-4
lines changed

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,75 @@ static_assert(is_assignable_to(Intersection[LiteralString, Not[Literal[""]]], No
721721
static_assert(is_assignable_to(Intersection[LiteralString, Not[Literal["", "a"]]], Not[AlwaysFalsy]))
722722
```
723723

724+
## Intersections with non-fully-static negated elements
725+
726+
A type can be _assignable_ to an intersection containing negated elements only if the _bottom_
727+
materialization of that type is disjoint from the _bottom_ materialization of all negated elements
728+
in the intersection. This differs from subtyping, which should do the disjointness check against the
729+
_top_ materialization of the negated elements.
730+
731+
```py
732+
from typing_extensions import Any, Never, Sequence
733+
from ty_extensions import Not, is_assignable_to, static_assert
734+
735+
# The bottom materialization of `tuple[Any]` is `tuple[Never]`,
736+
# which simplifies to `Never`, so `tuple[int]` and `tuple[()]` are
737+
# both assignable to `~tuple[Any]`
738+
static_assert(is_assignable_to(tuple[int], Not[tuple[Any]]))
739+
static_assert(is_assignable_to(tuple[()], Not[tuple[Any]]))
740+
741+
# But the bottom materialization of `tuple[Any, ...]` is `tuple[Never, ...]`,
742+
# which simplifies to `tuple[()]`, so `tuple[int]` is still assignable to
743+
# `~tuple[Any, ...]`, but `tuple[()]` is not
744+
static_assert(is_assignable_to(tuple[int], Not[tuple[Any, ...]]))
745+
static_assert(not is_assignable_to(tuple[()], Not[tuple[Any, ...]]))
746+
747+
# Similarly, the bottom materialization of `Sequence[Any]` is `Sequence[Never]`,
748+
# so `tuple[()]` is not assignable to `~Sequence[Any]`, and nor is `list[Never]`,
749+
# since both `tuple[()]` and `list[Never]` are subtypes of `Sequence[Never]`.
750+
# `tuple[int, ...]` is also not assignable to `~Sequence[Any]`, as although it is
751+
# not a subtype of `Sequence[Never]` it is also not disjoint from `Sequence[Never]`:
752+
# `tuple[()]` is a subtype of both `Sequence[Never]` and `tuple[int, ...]`, so
753+
# `tuple[int, ...]` and `Sequence[Never]` cannot be considered disjoint.
754+
#
755+
# Other `list` and `tuple` specializations *are* assignable to `~Sequence[Any]`,
756+
# however, since there are many fully static materializations of `Sequence[Any]`
757+
# that would be disjoint from a given `list` or `tuple` specialization.
758+
static_assert(not is_assignable_to(tuple[()], Not[Sequence[Any]]))
759+
static_assert(not is_assignable_to(list[Never], Not[Sequence[Any]]))
760+
static_assert(not is_assignable_to(tuple[int, ...], Not[Sequence[Any]]))
761+
762+
# TODO: should pass (`tuple[int]` should be considered disjoint from `Sequence[Never]`)
763+
static_assert(is_assignable_to(tuple[int], Not[Sequence[Any]])) # error: [static-assert-error]
764+
765+
# TODO: should pass (`list[int]` should be considered disjoint from `Sequence[Never]`)
766+
static_assert(is_assignable_to(list[int], Not[Sequence[Any]])) # error: [static-assert-error]
767+
768+
# If the left-hand side is also not fully static,
769+
# the left-hand side will be assignable to the right if the bottom materialization
770+
# of the left-hand side is disjoint from the bottom materialization of all negated
771+
# elements on the right-hand side
772+
773+
# `tuple[Any, ...]` cannot be assignable to `~tuple[Any, ...]`,
774+
# because the bottom materialization of `tuple[Any, ...]` is
775+
# `tuple[()]`, and `tuple[()]` is not disjoint from itself
776+
static_assert(not is_assignable_to(tuple[Any, ...], Not[tuple[Any, ...]]))
777+
778+
# but `tuple[Any]` is assignable to `~tuple[Any]`,
779+
# as the bottom materialization of `tuple[Any]` is `Never`,
780+
# and `Never` *is* disjoint from itself
781+
static_assert(is_assignable_to(tuple[Any], Not[tuple[Any]]))
782+
783+
# The same principle applies for non-fully-static `list` specializations.
784+
# TODO: this should pass (`Bottom[list[Any]]` should simplify to `Never`)
785+
static_assert(is_assignable_to(list[Any], Not[list[Any]])) # error: [static-assert-error]
786+
787+
# `Bottom[list[Any]]` is `Never`, which is disjoint from `Bottom[Sequence[Any]]`
788+
# (which is `Sequence[Never]`).
789+
# TODO: this should pass (`Bottom[list[Any]]` should simplify to `Never`)
790+
static_assert(is_assignable_to(list[Any], Not[Sequence[Any]])) # error: [static-assert-error]
791+
```
792+
724793
## General properties
725794

726795
See also: our property tests in `property_tests.rs`.

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,45 @@ static_assert(not is_subtype_of(int, Not[Literal[3]]))
533533
static_assert(not is_subtype_of(Literal[1], Intersection[int, Not[Literal[1]]]))
534534
```
535535

536+
## Intersections with non-fully-static negated elements
537+
538+
A type can be a _subtype_ of an intersection containing negated elements only if the _top_
539+
materialization of that type is disjoint from the _top_ materialization of all negated elements in
540+
the intersection. This differs from assignability, which should do the disjointness check against
541+
the _bottom_ materialization of the negated elements.
542+
543+
```py
544+
from typing_extensions import Any, Never, Sequence
545+
from ty_extensions import Not, is_subtype_of, static_assert
546+
547+
# The top materialization of `tuple[Any]` is `tuple[object]`,
548+
# which is disjoint from `tuple[()]` but not `tuple[int]`,
549+
# so `tuple[()]` is a subtype of `~tuple[Any]` but `tuple[int]`
550+
# is not.
551+
static_assert(is_subtype_of(tuple[()], Not[tuple[Any]]))
552+
static_assert(not is_subtype_of(tuple[int], Not[tuple[Any]]))
553+
static_assert(not is_subtype_of(tuple[Any], Not[tuple[Any]]))
554+
555+
# The top materialization of `tuple[Any, ...]` is `tuple[object, ...]`,
556+
# so no tuple type can be considered a subtype of `~tuple[Any, ...]`
557+
static_assert(not is_subtype_of(tuple[()], Not[tuple[Any, ...]]))
558+
static_assert(not is_subtype_of(tuple[int], Not[tuple[Any, ...]]))
559+
static_assert(not is_subtype_of(tuple[int, ...], Not[tuple[Any, ...]]))
560+
static_assert(not is_subtype_of(tuple[object, ...], Not[tuple[Any, ...]]))
561+
static_assert(not is_subtype_of(tuple[Any, ...], Not[tuple[Any, ...]]))
562+
563+
# Similarly, the top materialization of `Sequence[Any]` is `Sequence[object]`,
564+
# so no sequence type can be considered a subtype of `~Sequence[Any]`.
565+
static_assert(not is_subtype_of(tuple[()], Not[Sequence[Any]]))
566+
static_assert(not is_subtype_of(tuple[int], Not[Sequence[Any]]))
567+
static_assert(not is_subtype_of(tuple[int, ...], Not[Sequence[Any]]))
568+
static_assert(not is_subtype_of(tuple[object, ...], Not[Sequence[Any]]))
569+
static_assert(not is_subtype_of(tuple[Any, ...], Not[Sequence[Any]]))
570+
static_assert(not is_subtype_of(list[Never], Not[Sequence[Any]]))
571+
static_assert(not is_subtype_of(list[Any], Not[Sequence[Any]]))
572+
static_assert(not is_subtype_of(list[int], Not[Sequence[Any]]))
573+
```
574+
536575
## Special types
537576

538577
### `Never`

crates/ty_python_semantic/src/types.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,12 +1731,22 @@ impl<'db> Type<'db> {
17311731
)
17321732
})
17331733
.and(db, || {
1734+
// For subtyping/redundancy, we would want to check whether the *top materialization*
1735+
// of `self` is disjoint from the *top materialization* of `neg_ty`. As an
1736+
// optimization, however, we can avoid this explicit transformation here, since our
1737+
// `Type::is_disjoint_from` implementation already only returns true for
1738+
// `T.is_disjoint_from(U)` if the *top materialization* of `T` is disjoint from the
1739+
// *top materialization* of `U`.
1740+
let self_ty = match relation {
1741+
TypeRelation::Subtyping | TypeRelation::Redundancy => self,
1742+
TypeRelation::Assignability => self.bottom_materialization(db),
1743+
};
17341744
intersection.negative(db).iter().when_all(db, |&neg_ty| {
17351745
let neg_ty = match relation {
17361746
TypeRelation::Subtyping | TypeRelation::Redundancy => neg_ty,
17371747
TypeRelation::Assignability => neg_ty.bottom_materialization(db),
17381748
};
1739-
self.is_disjoint_from_impl(
1749+
self_ty.is_disjoint_from_impl(
17401750
db,
17411751
neg_ty,
17421752
disjointness_visitor,
@@ -2262,10 +2272,20 @@ impl<'db> Type<'db> {
22622272
}
22632273
}
22642274

2265-
/// Return true if this type and `other` have no common elements.
2275+
/// Return true if the top materialization of `self` has no overlap with the
2276+
/// top materialization of `other`.
2277+
///
2278+
/// In other words, return true if there are no possible materializations
2279+
/// of the pair of types that could result in any possible runtime value
2280+
/// simultaneously inhabiting both types.
2281+
///
2282+
/// For example, `list[int]` is disjoint from `list[str]`: the two types have
2283+
/// no overlap. But `list[Any]` is not disjoint from `list[str]`: there exists
2284+
/// a fully static materialization of `list[Any]` (`list[str]`) that is a
2285+
/// subtype of `list[str]`
22662286
///
2267-
/// Note: This function aims to have no false positives, but might return
2268-
/// wrong `false` answers in some cases.
2287+
/// This function aims to have no false positives, but might return wrong
2288+
/// `false` answers in some cases.
22692289
pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool {
22702290
self.when_disjoint_from(db, other).is_always_satisfied()
22712291
}

0 commit comments

Comments
 (0)