mirror of https://github.com/astral-sh/ruff
[ty] Fix subtyping with `type[T]` and unions (#21740)
## Summary Resolves https://github.com/astral-sh/ruff/pull/21685#issuecomment-3591695954.
This commit is contained in:
parent
edc6ed5077
commit
ec854c7199
|
|
@ -123,11 +123,11 @@ class A:
|
||||||
A class `A` is a subtype of `type[T]` if any instance of `A` is a subtype of `T`.
|
A class `A` is a subtype of `type[T]` if any instance of `A` is a subtype of `T`.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import Callable, Protocol
|
from typing import Any, Callable, Protocol
|
||||||
from ty_extensions import is_assignable_to, is_subtype_of, is_disjoint_from, static_assert
|
from ty_extensions import is_assignable_to, is_subtype_of, is_disjoint_from, static_assert
|
||||||
|
|
||||||
class IntCallback(Protocol):
|
class Callback[T](Protocol):
|
||||||
def __call__(self, *args, **kwargs) -> int: ...
|
def __call__(self, *args, **kwargs) -> T: ...
|
||||||
|
|
||||||
def _[T](_: T):
|
def _[T](_: T):
|
||||||
static_assert(not is_subtype_of(type[T], T))
|
static_assert(not is_subtype_of(type[T], T))
|
||||||
|
|
@ -141,8 +141,11 @@ def _[T](_: T):
|
||||||
static_assert(is_assignable_to(type[T], Callable[..., T]))
|
static_assert(is_assignable_to(type[T], Callable[..., T]))
|
||||||
static_assert(not is_disjoint_from(type[T], Callable[..., T]))
|
static_assert(not is_disjoint_from(type[T], Callable[..., T]))
|
||||||
|
|
||||||
static_assert(not is_assignable_to(type[T], IntCallback))
|
static_assert(is_assignable_to(type[T], Callable[..., T] | Callable[..., Any]))
|
||||||
static_assert(not is_disjoint_from(type[T], IntCallback))
|
static_assert(not is_disjoint_from(type[T], Callable[..., T] | Callable[..., Any]))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(type[T], Callback[int]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], Callback[int]))
|
||||||
|
|
||||||
def _[T: int](_: T):
|
def _[T: int](_: T):
|
||||||
static_assert(not is_subtype_of(type[T], T))
|
static_assert(not is_subtype_of(type[T], T))
|
||||||
|
|
@ -157,14 +160,23 @@ def _[T: int](_: T):
|
||||||
static_assert(is_subtype_of(type[T], type[int]))
|
static_assert(is_subtype_of(type[T], type[int]))
|
||||||
static_assert(not is_disjoint_from(type[T], type[int]))
|
static_assert(not is_disjoint_from(type[T], type[int]))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(type[T], type[int] | None))
|
||||||
|
static_assert(not is_disjoint_from(type[T], type[int] | None))
|
||||||
|
|
||||||
static_assert(is_subtype_of(type[T], type[T]))
|
static_assert(is_subtype_of(type[T], type[T]))
|
||||||
static_assert(not is_disjoint_from(type[T], type[T]))
|
static_assert(not is_disjoint_from(type[T], type[T]))
|
||||||
|
|
||||||
static_assert(is_assignable_to(type[T], Callable[..., T]))
|
static_assert(is_assignable_to(type[T], Callable[..., T]))
|
||||||
static_assert(not is_disjoint_from(type[T], Callable[..., T]))
|
static_assert(not is_disjoint_from(type[T], Callable[..., T]))
|
||||||
|
|
||||||
static_assert(is_assignable_to(type[T], IntCallback))
|
static_assert(is_assignable_to(type[T], Callable[..., T] | Callable[..., Any]))
|
||||||
static_assert(not is_disjoint_from(type[T], IntCallback))
|
static_assert(not is_disjoint_from(type[T], Callable[..., T] | Callable[..., Any]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(type[T], Callback[int]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], Callback[int]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(type[T], Callback[int] | Callback[Any]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], Callback[int] | Callback[Any]))
|
||||||
|
|
||||||
static_assert(is_subtype_of(type[T], type[T] | None))
|
static_assert(is_subtype_of(type[T], type[T] | None))
|
||||||
static_assert(not is_disjoint_from(type[T], type[T] | None))
|
static_assert(not is_disjoint_from(type[T], type[T] | None))
|
||||||
|
|
@ -183,8 +195,14 @@ def _[T: (int, str)](_: T):
|
||||||
static_assert(is_assignable_to(type[T], Callable[..., T]))
|
static_assert(is_assignable_to(type[T], Callable[..., T]))
|
||||||
static_assert(not is_disjoint_from(type[T], Callable[..., T]))
|
static_assert(not is_disjoint_from(type[T], Callable[..., T]))
|
||||||
|
|
||||||
static_assert(not is_assignable_to(type[T], IntCallback))
|
static_assert(is_assignable_to(type[T], Callable[..., T] | Callable[..., Any]))
|
||||||
static_assert(not is_disjoint_from(type[T], IntCallback))
|
static_assert(not is_disjoint_from(type[T], Callable[..., T] | Callable[..., Any]))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(type[T], Callback[int]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], Callback[int]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(type[T], Callback[int | str]))
|
||||||
|
static_assert(not is_disjoint_from(type[T], Callback[int] | Callback[str]))
|
||||||
|
|
||||||
static_assert(is_subtype_of(type[T], type[T] | None))
|
static_assert(is_subtype_of(type[T], type[T] | None))
|
||||||
static_assert(not is_disjoint_from(type[T], type[T] | None))
|
static_assert(not is_disjoint_from(type[T], type[T] | None))
|
||||||
|
|
|
||||||
|
|
@ -2089,18 +2089,25 @@ impl<'db> Type<'db> {
|
||||||
// `type[T]` is a subtype of the class object `A` if every instance of `T` is a subtype of an instance
|
// `type[T]` is a subtype of the class object `A` if every instance of `T` is a subtype of an instance
|
||||||
// of `A`, and vice versa.
|
// of `A`, and vice versa.
|
||||||
(Type::SubclassOf(subclass_of), _)
|
(Type::SubclassOf(subclass_of), _)
|
||||||
if subclass_of.is_type_var()
|
if !subclass_of
|
||||||
&& !matches!(target, Type::Callable(_) | Type::ProtocolInstance(_)) =>
|
.into_type_var()
|
||||||
|
.zip(target.to_instance(db))
|
||||||
|
.when_some_and(|(this_instance, other_instance)| {
|
||||||
|
Type::TypeVar(this_instance).has_relation_to_impl(
|
||||||
|
db,
|
||||||
|
other_instance,
|
||||||
|
inferable,
|
||||||
|
relation,
|
||||||
|
relation_visitor,
|
||||||
|
disjointness_visitor,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.is_never_satisfied(db) =>
|
||||||
{
|
{
|
||||||
|
// TODO: The repetition here isn't great, but we really need the fallthrough logic,
|
||||||
|
// where this arm only engages if it returns true.
|
||||||
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
||||||
let other_instance = match target {
|
target.to_instance(db).when_some_and(|other_instance| {
|
||||||
Type::Union(union) => Some(
|
|
||||||
union.map(db, |element| element.to_instance(db).unwrap_or(Type::Never)),
|
|
||||||
),
|
|
||||||
_ => target.to_instance(db),
|
|
||||||
};
|
|
||||||
|
|
||||||
other_instance.when_some_and(|other_instance| {
|
|
||||||
this_instance.has_relation_to_impl(
|
this_instance.has_relation_to_impl(
|
||||||
db,
|
db,
|
||||||
other_instance,
|
other_instance,
|
||||||
|
|
@ -2111,6 +2118,7 @@ impl<'db> Type<'db> {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
(_, Type::SubclassOf(subclass_of)) if subclass_of.is_type_var() => {
|
(_, Type::SubclassOf(subclass_of)) if subclass_of.is_type_var() => {
|
||||||
let other_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
let other_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
||||||
self.to_instance(db).when_some_and(|this_instance| {
|
self.to_instance(db).when_some_and(|this_instance| {
|
||||||
|
|
@ -2647,6 +2655,10 @@ impl<'db> Type<'db> {
|
||||||
disjointness_visitor,
|
disjointness_visitor,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
(Type::SubclassOf(subclass_of), _) if subclass_of.is_type_var() => {
|
||||||
|
ConstraintSet::from(false)
|
||||||
|
}
|
||||||
|
|
||||||
// `Literal[<class 'C'>]` is a subtype of `type[B]` if `C` is a subclass of `B`,
|
// `Literal[<class 'C'>]` is a subtype of `type[B]` if `C` is a subclass of `B`,
|
||||||
// since `type[B]` describes all possible runtime subclasses of the class object `B`.
|
// since `type[B]` describes all possible runtime subclasses of the class object `B`.
|
||||||
(Type::ClassLiteral(class), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty
|
(Type::ClassLiteral(class), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty
|
||||||
|
|
@ -3081,8 +3093,7 @@ impl<'db> Type<'db> {
|
||||||
ConstraintSet::from(false)
|
ConstraintSet::from(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// `type[T]` is disjoint from a callable or protocol instance if its upper bound or
|
// `type[T]` is disjoint from a callable or protocol instance if its upper bound or constraints are.
|
||||||
// constraints are.
|
|
||||||
(Type::SubclassOf(subclass_of), Type::Callable(_) | Type::ProtocolInstance(_))
|
(Type::SubclassOf(subclass_of), Type::Callable(_) | Type::ProtocolInstance(_))
|
||||||
| (Type::Callable(_) | Type::ProtocolInstance(_), Type::SubclassOf(subclass_of))
|
| (Type::Callable(_) | Type::ProtocolInstance(_), Type::SubclassOf(subclass_of))
|
||||||
if subclass_of.is_type_var() =>
|
if subclass_of.is_type_var() =>
|
||||||
|
|
@ -3104,13 +3115,14 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
// `type[T]` is disjoint from a class object `A` if every instance of `T` is disjoint from an instance of `A`.
|
// `type[T]` is disjoint from a class object `A` if every instance of `T` is disjoint from an instance of `A`.
|
||||||
(Type::SubclassOf(subclass_of), other) | (other, Type::SubclassOf(subclass_of))
|
(Type::SubclassOf(subclass_of), other) | (other, Type::SubclassOf(subclass_of))
|
||||||
if subclass_of.is_type_var() =>
|
if subclass_of.is_type_var()
|
||||||
|
&& (other.to_instance(db).is_some()
|
||||||
|
|| other.as_typevar().is_some_and(|type_var| {
|
||||||
|
type_var.typevar(db).bound_or_constraints(db).is_none()
|
||||||
|
})) =>
|
||||||
{
|
{
|
||||||
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
||||||
let other_instance = match other {
|
let other_instance = match other {
|
||||||
Type::Union(union) => Some(
|
|
||||||
union.map(db, |element| element.to_instance(db).unwrap_or(Type::Never)),
|
|
||||||
),
|
|
||||||
// An unbounded typevar `U` may have instances of type `object` if specialized to
|
// An unbounded typevar `U` may have instances of type `object` if specialized to
|
||||||
// an instance of `type`.
|
// an instance of `type`.
|
||||||
Type::TypeVar(typevar)
|
Type::TypeVar(typevar)
|
||||||
|
|
@ -3464,6 +3476,12 @@ impl<'db> Type<'db> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(Type::SubclassOf(subclass_of_ty), _) | (_, Type::SubclassOf(subclass_of_ty))
|
||||||
|
if subclass_of_ty.is_type_var() =>
|
||||||
|
{
|
||||||
|
ConstraintSet::from(true)
|
||||||
|
}
|
||||||
|
|
||||||
(Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b))
|
(Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b))
|
||||||
| (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => {
|
| (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => {
|
||||||
match subclass_of_ty.subclass_of() {
|
match subclass_of_ty.subclass_of() {
|
||||||
|
|
@ -3493,10 +3511,7 @@ impl<'db> Type<'db> {
|
||||||
// for `type[Any]`/`type[Unknown]`/`type[Todo]`, we know the type cannot be any larger than `type`,
|
// for `type[Any]`/`type[Unknown]`/`type[Todo]`, we know the type cannot be any larger than `type`,
|
||||||
// so although the type is dynamic we can still determine disjointedness in some situations
|
// so although the type is dynamic we can still determine disjointedness in some situations
|
||||||
(Type::SubclassOf(subclass_of_ty), other)
|
(Type::SubclassOf(subclass_of_ty), other)
|
||||||
| (other, Type::SubclassOf(subclass_of_ty))
|
| (other, Type::SubclassOf(subclass_of_ty)) => match subclass_of_ty.subclass_of() {
|
||||||
if !subclass_of_ty.is_type_var() =>
|
|
||||||
{
|
|
||||||
match subclass_of_ty.subclass_of() {
|
|
||||||
SubclassOfInner::Dynamic(_) => {
|
SubclassOfInner::Dynamic(_) => {
|
||||||
KnownClass::Type.to_instance(db).is_disjoint_from_impl(
|
KnownClass::Type.to_instance(db).is_disjoint_from_impl(
|
||||||
db,
|
db,
|
||||||
|
|
@ -3516,8 +3531,7 @@ impl<'db> Type<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SubclassOfInner::TypeVar(_) => unreachable!(),
|
SubclassOfInner::TypeVar(_) => unreachable!(),
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
|
||||||
(Type::SpecialForm(special_form), Type::NominalInstance(instance))
|
(Type::SpecialForm(special_form), Type::NominalInstance(instance))
|
||||||
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => {
|
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => {
|
||||||
|
|
@ -3779,11 +3793,6 @@ impl<'db> Type<'db> {
|
||||||
relation_visitor,
|
relation_visitor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::SubclassOf(_), _) | (_, Type::SubclassOf(_)) => {
|
|
||||||
// All cases should have been handled above.
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue