mirror of https://github.com/astral-sh/ruff
[ty] `type[T]` is assignable to an inferable typevar (#21766)
## Summary Resolves https://github.com/astral-sh/ty/issues/1712.
This commit is contained in:
parent
2250fa6f98
commit
7b0aab1696
|
|
@ -227,6 +227,17 @@ def _[T: (int | str, int)](_: T):
|
||||||
static_assert(not is_disjoint_from(type[int], type[T]))
|
static_assert(not is_disjoint_from(type[int], type[T]))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
class X[T]:
|
||||||
|
value: T
|
||||||
|
|
||||||
|
def get(self) -> T:
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def _[T](x: X[type[T]]):
|
||||||
|
reveal_type(x.get()) # revealed: type[T@_]
|
||||||
|
```
|
||||||
|
|
||||||
## Generic Type Inference
|
## Generic Type Inference
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
|
||||||
|
|
@ -2105,11 +2105,12 @@ impl<'db> Type<'db> {
|
||||||
})
|
})
|
||||||
.is_never_satisfied(db) =>
|
.is_never_satisfied(db) =>
|
||||||
{
|
{
|
||||||
// TODO: The repetition here isn't great, but we really need the fallthrough logic,
|
// TODO: The repetition here isn't great, but we need the fallthrough logic.
|
||||||
// where this arm only engages if it returns true.
|
subclass_of
|
||||||
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
.into_type_var()
|
||||||
target.to_instance(db).when_some_and(|other_instance| {
|
.zip(target.to_instance(db))
|
||||||
this_instance.has_relation_to_impl(
|
.when_some_and(|(this_instance, other_instance)| {
|
||||||
|
Type::TypeVar(this_instance).has_relation_to_impl(
|
||||||
db,
|
db,
|
||||||
other_instance,
|
other_instance,
|
||||||
inferable,
|
inferable,
|
||||||
|
|
@ -2120,12 +2121,30 @@ impl<'db> Type<'db> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
(_, Type::SubclassOf(subclass_of)) if subclass_of.is_type_var() => {
|
(_, Type::SubclassOf(subclass_of))
|
||||||
let other_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
if !subclass_of
|
||||||
self.to_instance(db).when_some_and(|this_instance| {
|
.into_type_var()
|
||||||
|
.zip(self.to_instance(db))
|
||||||
|
.when_some_and(|(other_instance, this_instance)| {
|
||||||
this_instance.has_relation_to_impl(
|
this_instance.has_relation_to_impl(
|
||||||
db,
|
db,
|
||||||
other_instance,
|
Type::TypeVar(other_instance),
|
||||||
|
inferable,
|
||||||
|
relation,
|
||||||
|
relation_visitor,
|
||||||
|
disjointness_visitor,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.is_never_satisfied(db) =>
|
||||||
|
{
|
||||||
|
// TODO: The repetition here isn't great, but we need the fallthrough logic.
|
||||||
|
subclass_of
|
||||||
|
.into_type_var()
|
||||||
|
.zip(self.to_instance(db))
|
||||||
|
.when_some_and(|(other_instance, this_instance)| {
|
||||||
|
this_instance.has_relation_to_impl(
|
||||||
|
db,
|
||||||
|
Type::TypeVar(other_instance),
|
||||||
inferable,
|
inferable,
|
||||||
relation,
|
relation,
|
||||||
relation_visitor,
|
relation_visitor,
|
||||||
|
|
@ -2656,7 +2675,9 @@ impl<'db> Type<'db> {
|
||||||
disjointness_visitor,
|
disjointness_visitor,
|
||||||
),
|
),
|
||||||
|
|
||||||
(Type::SubclassOf(subclass_of), _) if subclass_of.is_type_var() => {
|
(Type::SubclassOf(subclass_of), _) | (_, Type::SubclassOf(subclass_of))
|
||||||
|
if subclass_of.is_type_var() =>
|
||||||
|
{
|
||||||
ConstraintSet::from(false)
|
ConstraintSet::from(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3116,26 +3137,26 @@ 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
|
||||||
&& (other.to_instance(db).is_some()
|
.into_type_var()
|
||||||
|| other.as_typevar().is_some_and(|type_var| {
|
.zip(other.to_instance(db))
|
||||||
type_var.typevar(db).bound_or_constraints(db).is_none()
|
.when_none_or(|(this_instance, other_instance)| {
|
||||||
})) =>
|
Type::TypeVar(this_instance).is_disjoint_from_impl(
|
||||||
|
db,
|
||||||
|
other_instance,
|
||||||
|
inferable,
|
||||||
|
disjointness_visitor,
|
||||||
|
relation_visitor,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.is_always_satisfied(db) =>
|
||||||
{
|
{
|
||||||
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
// TODO: The repetition here isn't great, but we need the fallthrough logic.
|
||||||
let other_instance = match other {
|
subclass_of
|
||||||
// An unbounded typevar `U` may have instances of type `object` if specialized to
|
.into_type_var()
|
||||||
// an instance of `type`.
|
.zip(other.to_instance(db))
|
||||||
Type::TypeVar(typevar)
|
.when_none_or(|(this_instance, other_instance)| {
|
||||||
if typevar.typevar(db).bound_or_constraints(db).is_none() =>
|
Type::TypeVar(this_instance).is_disjoint_from_impl(
|
||||||
{
|
|
||||||
Some(Type::object())
|
|
||||||
}
|
|
||||||
_ => other.to_instance(db),
|
|
||||||
};
|
|
||||||
|
|
||||||
other_instance.when_none_or(|other_instance| {
|
|
||||||
this_instance.is_disjoint_from_impl(
|
|
||||||
db,
|
db,
|
||||||
other_instance,
|
other_instance,
|
||||||
inferable,
|
inferable,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue