Skip to content

Commit 7b0aab1

Browse files
authored
[ty] type[T] is assignable to an inferable typevar (#21766)
## Summary Resolves astral-sh/ty#1712.
1 parent 2250fa6 commit 7b0aab1

File tree

2 files changed

+84
-52
lines changed

2 files changed

+84
-52
lines changed

crates/ty_python_semantic/resources/mdtest/type_of/generics.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,17 @@ def _[T: (int | str, int)](_: T):
227227
static_assert(not is_disjoint_from(type[int], type[T]))
228228
```
229229

230+
```py
231+
class X[T]:
232+
value: T
233+
234+
def get(self) -> T:
235+
return self.value
236+
237+
def _[T](x: X[type[T]]):
238+
reveal_type(x.get()) # revealed: type[T@_]
239+
```
240+
230241
## Generic Type Inference
231242

232243
```py

crates/ty_python_semantic/src/types.rs

Lines changed: 73 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2105,33 +2105,52 @@ impl<'db> Type<'db> {
21052105
})
21062106
.is_never_satisfied(db) =>
21072107
{
2108-
// TODO: The repetition here isn't great, but we really need the fallthrough logic,
2109-
// where this arm only engages if it returns true.
2110-
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
2111-
target.to_instance(db).when_some_and(|other_instance| {
2112-
this_instance.has_relation_to_impl(
2113-
db,
2114-
other_instance,
2115-
inferable,
2116-
relation,
2117-
relation_visitor,
2118-
disjointness_visitor,
2119-
)
2120-
})
2108+
// TODO: The repetition here isn't great, but we need the fallthrough logic.
2109+
subclass_of
2110+
.into_type_var()
2111+
.zip(target.to_instance(db))
2112+
.when_some_and(|(this_instance, other_instance)| {
2113+
Type::TypeVar(this_instance).has_relation_to_impl(
2114+
db,
2115+
other_instance,
2116+
inferable,
2117+
relation,
2118+
relation_visitor,
2119+
disjointness_visitor,
2120+
)
2121+
})
21212122
}
21222123

2123-
(_, Type::SubclassOf(subclass_of)) if subclass_of.is_type_var() => {
2124-
let other_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
2125-
self.to_instance(db).when_some_and(|this_instance| {
2126-
this_instance.has_relation_to_impl(
2127-
db,
2128-
other_instance,
2129-
inferable,
2130-
relation,
2131-
relation_visitor,
2132-
disjointness_visitor,
2133-
)
2134-
})
2124+
(_, Type::SubclassOf(subclass_of))
2125+
if !subclass_of
2126+
.into_type_var()
2127+
.zip(self.to_instance(db))
2128+
.when_some_and(|(other_instance, this_instance)| {
2129+
this_instance.has_relation_to_impl(
2130+
db,
2131+
Type::TypeVar(other_instance),
2132+
inferable,
2133+
relation,
2134+
relation_visitor,
2135+
disjointness_visitor,
2136+
)
2137+
})
2138+
.is_never_satisfied(db) =>
2139+
{
2140+
// TODO: The repetition here isn't great, but we need the fallthrough logic.
2141+
subclass_of
2142+
.into_type_var()
2143+
.zip(self.to_instance(db))
2144+
.when_some_and(|(other_instance, this_instance)| {
2145+
this_instance.has_relation_to_impl(
2146+
db,
2147+
Type::TypeVar(other_instance),
2148+
inferable,
2149+
relation,
2150+
relation_visitor,
2151+
disjointness_visitor,
2152+
)
2153+
})
21352154
}
21362155

21372156
// A fully static typevar is a subtype of its upper bound, and to something similar to
@@ -2656,7 +2675,9 @@ impl<'db> Type<'db> {
26562675
disjointness_visitor,
26572676
),
26582677

2659-
(Type::SubclassOf(subclass_of), _) if subclass_of.is_type_var() => {
2678+
(Type::SubclassOf(subclass_of), _) | (_, Type::SubclassOf(subclass_of))
2679+
if subclass_of.is_type_var() =>
2680+
{
26602681
ConstraintSet::from(false)
26612682
}
26622683

@@ -3116,33 +3137,33 @@ impl<'db> Type<'db> {
31163137

31173138
// `type[T]` is disjoint from a class object `A` if every instance of `T` is disjoint from an instance of `A`.
31183139
(Type::SubclassOf(subclass_of), other) | (other, Type::SubclassOf(subclass_of))
3119-
if subclass_of.is_type_var()
3120-
&& (other.to_instance(db).is_some()
3121-
|| other.as_typevar().is_some_and(|type_var| {
3122-
type_var.typevar(db).bound_or_constraints(db).is_none()
3123-
})) =>
3140+
if !subclass_of
3141+
.into_type_var()
3142+
.zip(other.to_instance(db))
3143+
.when_none_or(|(this_instance, other_instance)| {
3144+
Type::TypeVar(this_instance).is_disjoint_from_impl(
3145+
db,
3146+
other_instance,
3147+
inferable,
3148+
disjointness_visitor,
3149+
relation_visitor,
3150+
)
3151+
})
3152+
.is_always_satisfied(db) =>
31243153
{
3125-
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
3126-
let other_instance = match other {
3127-
// An unbounded typevar `U` may have instances of type `object` if specialized to
3128-
// an instance of `type`.
3129-
Type::TypeVar(typevar)
3130-
if typevar.typevar(db).bound_or_constraints(db).is_none() =>
3131-
{
3132-
Some(Type::object())
3133-
}
3134-
_ => other.to_instance(db),
3135-
};
3136-
3137-
other_instance.when_none_or(|other_instance| {
3138-
this_instance.is_disjoint_from_impl(
3139-
db,
3140-
other_instance,
3141-
inferable,
3142-
disjointness_visitor,
3143-
relation_visitor,
3144-
)
3145-
})
3154+
// TODO: The repetition here isn't great, but we need the fallthrough logic.
3155+
subclass_of
3156+
.into_type_var()
3157+
.zip(other.to_instance(db))
3158+
.when_none_or(|(this_instance, other_instance)| {
3159+
Type::TypeVar(this_instance).is_disjoint_from_impl(
3160+
db,
3161+
other_instance,
3162+
inferable,
3163+
disjointness_visitor,
3164+
relation_visitor,
3165+
)
3166+
})
31463167
}
31473168

31483169
// A typevar is never disjoint from itself, since all occurrences of the typevar must

0 commit comments

Comments
 (0)