[ty] Do not promote literals in contravariant positions of generic specializations (#21171)

## Summary

closes https://github.com/astral-sh/ty/issues/1284

supersedes https://github.com/astral-sh/ruff/pull/20950 by @ibraheemdev 

## Test Plan

New regression test
This commit is contained in:
David Peter 2025-10-31 17:48:34 +01:00 committed by GitHub
parent ff3a6a8fbd
commit 1734ddfb3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 50 additions and 2 deletions

View File

@ -1,5 +1,10 @@
# Literal promotion
```toml
[environment]
python-version = "3.12"
```
There are certain places where we promote literals to their common supertype:
```py
@ -30,3 +35,34 @@ def double_negation(callback: Callable[[Callable[[Literal[1]], None]], None]):
reveal_type([callback]) # revealed: list[Unknown | (((int, /) -> None, /) -> None)]
```
Literal promotion should also not apply recursively to type arguments in contravariant/invariant
position:
```py
class Bivariant[T]:
pass
class Covariant[T]:
def pop(self) -> T:
raise NotImplementedError
class Contravariant[T]:
def push(self, value: T) -> None:
pass
class Invariant[T]:
x: T
def _(
bivariant: Bivariant[Literal[1]],
covariant: Covariant[Literal[1]],
contravariant: Contravariant[Literal[1]],
invariant: Invariant[Literal[1]],
):
reveal_type([bivariant]) # revealed: list[Unknown | Bivariant[int]]
reveal_type([covariant]) # revealed: list[Unknown | Covariant[int]]
reveal_type([contravariant]) # revealed: list[Unknown | Contravariant[Literal[1]]]
reveal_type([invariant]) # revealed: list[Unknown | Invariant[Literal[1]]]
```

View File

@ -969,10 +969,15 @@ impl<'db> Specialization<'db> {
let types: Box<[_]> = self
.types(db)
.iter()
.zip(self.generic_context(db).variables(db))
.enumerate()
.map(|(i, ty)| {
.map(|(i, (ty, typevar))| {
let tcx = TypeContext::new(tcx.get(i).copied());
ty.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
if typevar.variance(db).is_covariant() {
ty.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
} else {
ty.apply_type_mapping_impl(db, &type_mapping.flip(), tcx, visitor)
}
})
.collect();

View File

@ -85,6 +85,13 @@ impl TypeVarVariance {
TypeVarVariance::Bivariant => TypeVarVariance::Bivariant,
}
}
pub(crate) const fn is_covariant(self) -> bool {
matches!(
self,
TypeVarVariance::Covariant | TypeVarVariance::Bivariant
)
}
}
impl std::iter::FromIterator<Self> for TypeVarVariance {