Skip to content

Commit 807a8a7

Browse files
authored
[red-knot] Acknowledge that T & anything is assignable to T (#17413)
This reworks the assignability/subtyping relations a bit to handle typevars better: 1. For the most part, types are not assignable to typevars, since there's no guarantee what type the typevar will be specialized to. 2. An intersection is an exception, if it contains the typevar itself as one of the positive elements. This should fall out from the other clauses automatically, since a typevar is assignable to itself, and an intersection is assignable to something if any positive element is assignable to that something. 3. Constrained typevars are an exception, since they must be specialized to _exactly_ one of the constraints, not to a _subtype_ of a constraint. If a type is assignable to every constraint, then the type is also assignable to the constrained typevar. We already had a special case for (3), but the ordering of it relative to the intersection clauses meant we weren't catching (2) correctly. To fix this, we keep the special case for (3), but fall through to the other match arms for non-constrained typevars and if the special case isn't true for a constrained typevar. Closes #17364
1 parent 78dabc3 commit 807a8a7

File tree

2 files changed

+52
-34
lines changed

2 files changed

+52
-34
lines changed

crates/red_knot_python_semantic/resources/mdtest/generics/pep695.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,20 @@ def remove_constraint[T: (int, str, bool)](t: T) -> None:
507507
reveal_type(x) # revealed: T & Any
508508
```
509509

510+
The intersection of a typevar with any other type is assignable to (and if fully static, a subtype
511+
of) itself.
512+
513+
```py
514+
from knot_extensions import is_assignable_to, is_subtype_of, static_assert, Not
515+
516+
def intersection_is_assignable[T](t: T) -> None:
517+
static_assert(is_assignable_to(Intersection[T, None], T))
518+
static_assert(is_assignable_to(Intersection[T, Not[None]], T))
519+
520+
static_assert(is_subtype_of(Intersection[T, None], T))
521+
static_assert(is_subtype_of(Intersection[T, Not[None]], T))
522+
```
523+
510524
## Narrowing
511525

512526
We can use narrowing expressions to eliminate some of the possibilities of a constrained typevar:

crates/red_knot_python_semantic/src/types.rs

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -890,23 +890,18 @@ impl<'db> Type<'db> {
890890
.iter()
891891
.any(|&elem_ty| self.is_subtype_of(db, elem_ty)),
892892

893-
(_, Type::TypeVar(typevar)) => match typevar.bound_or_constraints(db) {
894-
// No types are a subtype of a bounded typevar, or of an unbounded unconstrained
895-
// typevar, since there's no guarantee what type the typevar will be specialized
896-
// to. If the typevar is bounded, it might be specialized to a smaller type than
897-
// the bound. (This is true even if the bound is a final class, since the typevar
898-
// can still be specialized to `Never`.)
899-
None => false,
900-
Some(TypeVarBoundOrConstraints::UpperBound(_)) => false,
901-
// If the typevar is constrained, there must be multiple constraints, and the
902-
// typevar might be specialized to any one of them. However, the constraints do not
903-
// have to be disjoint, which means an lhs type might be a subtype of all of the
904-
// constraints.
905-
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
906-
.elements(db)
907-
.iter()
908-
.all(|constraint| self.is_subtype_of(db, *constraint)),
909-
},
893+
// If the typevar is constrained, there must be multiple constraints, and the typevar
894+
// might be specialized to any one of them. However, the constraints do not have to be
895+
// disjoint, which means an lhs type might be a subtype of all of the constraints.
896+
(_, Type::TypeVar(typevar))
897+
if typevar.constraints(db).is_some_and(|constraints| {
898+
constraints
899+
.iter()
900+
.all(|constraint| self.is_subtype_of(db, *constraint))
901+
}) =>
902+
{
903+
true
904+
}
910905

911906
// If both sides are intersections we need to handle the right side first
912907
// (A & B & C) is a subtype of (A & B) because the left is a subtype of both A and B,
@@ -927,6 +922,13 @@ impl<'db> Type<'db> {
927922
.iter()
928923
.any(|&elem_ty| elem_ty.is_subtype_of(db, target)),
929924

925+
// Other than the special cases checked above, no other types are a subtype of a
926+
// typevar, since there's no guarantee what type the typevar will be specialized to.
927+
// (If the typevar is bounded, it might be specialized to a smaller type than the
928+
// bound. This is true even if the bound is a final class, since the typevar can still
929+
// be specialized to `Never`.)
930+
(_, Type::TypeVar(_)) => false,
931+
930932
// Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`.
931933
// If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively.
932934
(left, Type::AlwaysFalsy) => left.bool(db).is_always_false(),
@@ -1186,23 +1188,18 @@ impl<'db> Type<'db> {
11861188
.iter()
11871189
.any(|&elem_ty| ty.is_assignable_to(db, elem_ty)),
11881190

1189-
(_, Type::TypeVar(typevar)) => match typevar.bound_or_constraints(db) {
1190-
// No types are assignable to a bounded typevar, or to an unbounded unconstrained
1191-
// typevar, since there's no guarantee what type the typevar will be specialized
1192-
// to. If the typevar is bounded, it might be specialized to a smaller type than
1193-
// the bound. (This is true even if the bound is a final class, since the typevar
1194-
// can still be specialized to `Never`.)
1195-
None => false,
1196-
Some(TypeVarBoundOrConstraints::UpperBound(_)) => false,
1197-
// If the typevar is constrained, there must be multiple constraints, and the
1198-
// typevar might be specialized to any one of them. However, the constraints do not
1199-
// have to be disjoint, which means an lhs type might be assignable to all of the
1200-
// constraints.
1201-
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
1202-
.elements(db)
1203-
.iter()
1204-
.all(|constraint| self.is_assignable_to(db, *constraint)),
1205-
},
1191+
// If the typevar is constrained, there must be multiple constraints, and the typevar
1192+
// might be specialized to any one of them. However, the constraints do not have to be
1193+
// disjoint, which means an lhs type might be assignable to all of the constraints.
1194+
(_, Type::TypeVar(typevar))
1195+
if typevar.constraints(db).is_some_and(|constraints| {
1196+
constraints
1197+
.iter()
1198+
.all(|constraint| self.is_assignable_to(db, *constraint))
1199+
}) =>
1200+
{
1201+
true
1202+
}
12061203

12071204
// If both sides are intersections we need to handle the right side first
12081205
// (A & B & C) is assignable to (A & B) because the left is assignable to both A and B,
@@ -1230,6 +1227,13 @@ impl<'db> Type<'db> {
12301227
.iter()
12311228
.any(|&elem_ty| elem_ty.is_assignable_to(db, ty)),
12321229

1230+
// Other than the special cases checked above, no other types are assignable to a
1231+
// typevar, since there's no guarantee what type the typevar will be specialized to.
1232+
// (If the typevar is bounded, it might be specialized to a smaller type than the
1233+
// bound. This is true even if the bound is a final class, since the typevar can still
1234+
// be specialized to `Never`.)
1235+
(_, Type::TypeVar(_)) => false,
1236+
12331237
// A tuple type S is assignable to a tuple type T if their lengths are the same, and
12341238
// each element of S is assignable to the corresponding element of T.
12351239
(Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => {

0 commit comments

Comments
 (0)