mirror of
https://github.com/astral-sh/ruff
synced 2026-01-21 05:20:49 -05:00
[ty] improve typevar solving from constraint sets (#22411)
## Summary Fixes https://github.com/astral-sh/ty/issues/2292 When solving a bounded typevar, we preferred the upper bound over the actual type seen in the call. This change fixes that. ## Test Plan Added mdtest, existing tests pass.
This commit is contained in:
@@ -752,3 +752,32 @@ reveal_type(x) # revealed: list[Sub]
|
||||
y: list[Sub] = f2(Sub())
|
||||
reveal_type(y) # revealed: list[Sub]
|
||||
```
|
||||
|
||||
## Bounded TypeVar with callable parameter
|
||||
|
||||
When a bounded TypeVar appears in a `Callable` parameter's return type, the inferred type should be
|
||||
the actual type from the call, not the TypeVar's upper bound.
|
||||
|
||||
See: <https://github.com/astral-sh/ty/issues/2292>
|
||||
|
||||
```py
|
||||
from typing import Callable, TypeVar
|
||||
|
||||
class Base:
|
||||
pass
|
||||
|
||||
class Derived(Base):
|
||||
attr: int
|
||||
|
||||
T = TypeVar("T", bound=Base)
|
||||
|
||||
def takes_factory(factory: Callable[[], T]) -> T:
|
||||
return factory()
|
||||
|
||||
# Passing a class as a factory: should infer Derived, not Base
|
||||
result = takes_factory(Derived)
|
||||
reveal_type(result) # revealed: Derived
|
||||
|
||||
# Accessing an attribute that only exists on Derived should work
|
||||
print(result.attr) # No error
|
||||
```
|
||||
|
||||
@@ -1662,14 +1662,17 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
|
||||
for (bound_typevar, bounds) in mappings.drain() {
|
||||
let variance = formal.variance_of(self.db, bound_typevar);
|
||||
let upper = IntersectionType::from_elements(self.db, bounds.upper);
|
||||
if !upper.is_object() {
|
||||
self.add_type_mapping(bound_typevar, upper, variance, &mut f);
|
||||
continue;
|
||||
}
|
||||
// Prefer the lower bound (often the concrete actual type seen) over the
|
||||
// upper bound (which may include TypeVar bounds/constraints). The upper bound
|
||||
// should only be used as a fallback when no concrete type was inferred.
|
||||
let lower = UnionType::from_elements(self.db, bounds.lower);
|
||||
if !lower.is_never() {
|
||||
self.add_type_mapping(bound_typevar, lower, variance, &mut f);
|
||||
continue;
|
||||
}
|
||||
let upper = IntersectionType::from_elements(self.db, bounds.upper);
|
||||
if !upper.is_object() {
|
||||
self.add_type_mapping(bound_typevar, upper, variance, &mut f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user