diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md index 9745fdca21..2bbe85b5ec 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md @@ -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] +``` diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 59216ca607..8485931ff2 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -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)