[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]))
```
```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
```py

View File

@ -2105,11 +2105,12 @@ impl<'db> Type<'db> {
})
.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());
target.to_instance(db).when_some_and(|other_instance| {
this_instance.has_relation_to_impl(
// TODO: The repetition here isn't great, but we need the fallthrough logic.
subclass_of
.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,
@ -2120,12 +2121,30 @@ impl<'db> Type<'db> {
})
}
(_, Type::SubclassOf(subclass_of)) if subclass_of.is_type_var() => {
let other_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
self.to_instance(db).when_some_and(|this_instance| {
(_, Type::SubclassOf(subclass_of))
if !subclass_of
.into_type_var()
.zip(self.to_instance(db))
.when_some_and(|(other_instance, this_instance)| {
this_instance.has_relation_to_impl(
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,
relation,
relation_visitor,
@ -2656,7 +2675,9 @@ impl<'db> Type<'db> {
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)
}
@ -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::SubclassOf(subclass_of), other) | (other, Type::SubclassOf(subclass_of))
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()
})) =>
if !subclass_of
.into_type_var()
.zip(other.to_instance(db))
.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());
let other_instance = match other {
// An unbounded typevar `U` may have instances of type `object` if specialized to
// an instance of `type`.
Type::TypeVar(typevar)
if typevar.typevar(db).bound_or_constraints(db).is_none() =>
{
Some(Type::object())
}
_ => other.to_instance(db),
};
other_instance.when_none_or(|other_instance| {
this_instance.is_disjoint_from_impl(
// TODO: The repetition here isn't great, but we need the fallthrough logic.
subclass_of
.into_type_var()
.zip(other.to_instance(db))
.when_none_or(|(this_instance, other_instance)| {
Type::TypeVar(this_instance).is_disjoint_from_impl(
db,
other_instance,
inferable,