[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:
Ibraheem Ahmed 2025-12-02 18:25:09 -05:00 committed by GitHub
parent 2250fa6f98
commit 7b0aab1696
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 84 additions and 52 deletions

View File

@ -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

View File

@ -2105,33 +2105,52 @@ 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)| {
db, Type::TypeVar(this_instance).has_relation_to_impl(
other_instance, db,
inferable, other_instance,
relation, inferable,
relation_visitor, relation,
disjointness_visitor, relation_visitor,
) disjointness_visitor,
}) )
})
} }
(_, 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()
this_instance.has_relation_to_impl( .zip(self.to_instance(db))
db, .when_some_and(|(other_instance, this_instance)| {
other_instance, this_instance.has_relation_to_impl(
inferable, db,
relation, Type::TypeVar(other_instance),
relation_visitor, inferable,
disjointness_visitor, 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,
relation,
relation_visitor,
disjointness_visitor,
)
})
} }
// A fully static typevar is a subtype of its upper bound, and to something similar to // A fully static typevar is a subtype of its upper bound, and to something similar to
@ -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,33 +3137,33 @@ 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(
{ db,
Some(Type::object()) other_instance,
} inferable,
_ => other.to_instance(db), disjointness_visitor,
}; relation_visitor,
)
other_instance.when_none_or(|other_instance| { })
this_instance.is_disjoint_from_impl(
db,
other_instance,
inferable,
disjointness_visitor,
relation_visitor,
)
})
} }
// A typevar is never disjoint from itself, since all occurrences of the typevar must // A typevar is never disjoint from itself, since all occurrences of the typevar must