[ty] Prefer exact matches when solving constrained type variables (#21165)

## Summary

The solver is currently order-dependent, and will choose a supertype
over the exact type if it appears earlier in the list of constraints. We
could be smarter and try to choose the most precise subtype, but I
imagine this is something the new constraint solver will fix anyways,
and this fixes the issue showing up on
https://github.com/astral-sh/ruff/pull/21070.
This commit is contained in:
Ibraheem Ahmed 2025-10-31 10:58:09 -04:00 committed by GitHub
parent cf4e82d4b0
commit 1d6ae8596a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 33 additions and 0 deletions

View file

@ -545,3 +545,28 @@ def f(x: T, y: Not[T]) -> T:
y = x # error: [invalid-assignment]
return x
```
## Prefer exact matches for constrained typevars
```py
from typing import TypeVar
class Base: ...
class Sub(Base): ...
# We solve to `Sub`, regardless of the order of constraints.
T = TypeVar("T", Base, Sub)
T2 = TypeVar("T2", Sub, Base)
def f(x: T) -> list[T]:
return [x]
def f2(x: T2) -> list[T2]:
return [x]
x: list[Sub] = f(Sub())
reveal_type(x) # revealed: list[Sub]
y: list[Sub] = f2(Sub())
reveal_type(y) # revealed: list[Sub]
```

View file

@ -1483,6 +1483,14 @@ impl<'db> SpecializationBuilder<'db> {
self.add_type_mapping(bound_typevar, ty);
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
// Prefer an exact match first.
for constraint in constraints.elements(self.db) {
if ty == *constraint {
self.add_type_mapping(bound_typevar, ty);
return Ok(());
}
}
for constraint in constraints.elements(self.db) {
if ty
.when_assignable_to(self.db, *constraint, self.inferable)