mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-19 11:05:24 +00:00
[ty] fix assigning a typevar to a union with itself (#17910)
Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
2ec0d7e072
commit
c6f4929cdc
2 changed files with 159 additions and 36 deletions
|
@ -997,12 +997,24 @@ impl<'db> Type<'db> {
|
|||
// Everything is a subtype of `object`.
|
||||
(_, Type::NominalInstance(instance)) if instance.class().is_object(db) => true,
|
||||
|
||||
// A fully static typevar is always a subtype of itself, and is never a subtype of any
|
||||
// other typevar, since there is no guarantee that they will be specialized to the same
|
||||
// type. (This is true even if both typevars are bounded by the same final class, since
|
||||
// you can specialize the typevars to `Never` in addition to that final class.)
|
||||
(Type::TypeVar(self_typevar), Type::TypeVar(other_typevar)) => {
|
||||
self_typevar == other_typevar
|
||||
// In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied:
|
||||
// 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`.
|
||||
// TypeVars without an explicit upper bound are treated as having an implicit upper bound of `object`.
|
||||
// 2. `T` is a constrained TypeVar and all of `T`'s constraints are subtypes of `S`.
|
||||
//
|
||||
// However, there is one exception to this general rule: for any given typevar `T`,
|
||||
// `T` will always be a subtype of any union containing `T`.
|
||||
// A similar rule applies in reverse to intersection types.
|
||||
(Type::TypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => true,
|
||||
(Type::Intersection(intersection), Type::TypeVar(_))
|
||||
if intersection.positive(db).contains(&target) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::Intersection(intersection), Type::TypeVar(_))
|
||||
if intersection.negative(db).contains(&target) =>
|
||||
{
|
||||
false
|
||||
}
|
||||
|
||||
// A fully static typevar is a subtype of its upper bound, and to something similar to
|
||||
|
@ -1021,16 +1033,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
(Type::Union(union), _) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.all(|&elem_ty| elem_ty.is_subtype_of(db, target)),
|
||||
|
||||
(_, Type::Union(union)) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|&elem_ty| self.is_subtype_of(db, elem_ty)),
|
||||
|
||||
// If the typevar is constrained, there must be multiple constraints, and the typevar
|
||||
// might be specialized to any one of them. However, the constraints do not have to be
|
||||
// disjoint, which means an lhs type might be a subtype of all of the constraints.
|
||||
|
@ -1044,6 +1046,16 @@ impl<'db> Type<'db> {
|
|||
true
|
||||
}
|
||||
|
||||
(Type::Union(union), _) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.all(|&elem_ty| elem_ty.is_subtype_of(db, target)),
|
||||
|
||||
(_, Type::Union(union)) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|&elem_ty| self.is_subtype_of(db, elem_ty)),
|
||||
|
||||
// If both sides are intersections we need to handle the right side first
|
||||
// (A & B & C) is a subtype of (A & B) because the left is a subtype of both A and B,
|
||||
// but none of A, B, or C is a subtype of (A & B).
|
||||
|
@ -1309,12 +1321,24 @@ impl<'db> Type<'db> {
|
|||
// TODO this special case might be removable once the below cases are comprehensive
|
||||
(_, Type::NominalInstance(instance)) if instance.class().is_object(db) => true,
|
||||
|
||||
// A typevar is always assignable to itself, and is never assignable to any other
|
||||
// typevar, since there is no guarantee that they will be specialized to the same
|
||||
// type. (This is true even if both typevars are bounded by the same final class, since
|
||||
// you can specialize the typevars to `Never` in addition to that final class.)
|
||||
(Type::TypeVar(self_typevar), Type::TypeVar(other_typevar)) => {
|
||||
self_typevar == other_typevar
|
||||
// In general, a TypeVar `T` is not assignable to a type `S` unless one of the two conditions is satisfied:
|
||||
// 1. `T` is a bound TypeVar and `T`'s upper bound is assignable to `S`.
|
||||
// TypeVars without an explicit upper bound are treated as having an implicit upper bound of `object`.
|
||||
// 2. `T` is a constrained TypeVar and all of `T`'s constraints are assignable to `S`.
|
||||
//
|
||||
// However, there is one exception to this general rule: for any given typevar `T`,
|
||||
// `T` will always be assignable to any union containing `T`.
|
||||
// A similar rule applies in reverse to intersection types.
|
||||
(Type::TypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => true,
|
||||
(Type::Intersection(intersection), Type::TypeVar(_))
|
||||
if intersection.positive(db).contains(&target) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::Intersection(intersection), Type::TypeVar(_))
|
||||
if intersection.negative(db).contains(&target) =>
|
||||
{
|
||||
false
|
||||
}
|
||||
|
||||
// A typevar is assignable to its upper bound, and to something similar to the union of
|
||||
|
@ -1333,18 +1357,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
// A union is assignable to a type T iff every element of the union is assignable to T.
|
||||
(Type::Union(union), ty) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.all(|&elem_ty| elem_ty.is_assignable_to(db, ty)),
|
||||
|
||||
// A type T is assignable to a union iff T is assignable to any element of the union.
|
||||
(ty, Type::Union(union)) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|&elem_ty| ty.is_assignable_to(db, elem_ty)),
|
||||
|
||||
// If the typevar is constrained, there must be multiple constraints, and the typevar
|
||||
// might be specialized to any one of them. However, the constraints do not have to be
|
||||
// disjoint, which means an lhs type might be assignable to all of the constraints.
|
||||
|
@ -1358,6 +1370,18 @@ impl<'db> Type<'db> {
|
|||
true
|
||||
}
|
||||
|
||||
// A union is assignable to a type T iff every element of the union is assignable to T.
|
||||
(Type::Union(union), ty) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.all(|&elem_ty| elem_ty.is_assignable_to(db, ty)),
|
||||
|
||||
// A type T is assignable to a union iff T is assignable to any element of the union.
|
||||
(ty, Type::Union(union)) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|&elem_ty| ty.is_assignable_to(db, elem_ty)),
|
||||
|
||||
// If both sides are intersections we need to handle the right side first
|
||||
// (A & B & C) is assignable to (A & B) because the left is assignable to both A and B,
|
||||
// but none of A, B, or C is assignable to (A & B).
|
||||
|
@ -1572,8 +1596,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
(Type::TypeVar(first), Type::TypeVar(second)) => first == second,
|
||||
|
||||
(Type::NominalInstance(first), Type::NominalInstance(second)) => {
|
||||
first.is_gradual_equivalent_to(db, second)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue