mirror of https://github.com/astral-sh/ruff
[ty] Only prefer declared types in non-covariant positions (#22068)
## Summary
The following snippet currently errors because we widen the inferred
type, even though `X` is covariant over `T`. If `T` was contravariant or
invariant, this would be fine, as it would lead to an assignability
error anyways.
```python
class X[T]:
def __init__(self: X[None]): ...
def pop(self) -> T:
raise NotImplementedError
# error: Argument to bound method `__init__` is incorrect: Expected `X[None]`, found `X[int | None]`
x: X[int | None] = X()
```
There are some cases where it is still helpful to prefer covariant
declared types, but this error seems hard to fix otherwise, and makes
our heuristics more consistent overall.
This commit is contained in:
parent
1a18ada931
commit
674d3902c6
|
|
@ -511,7 +511,7 @@ def f(self, dt: dict[str, Any], key: str):
|
|||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
python-version = "3.14"
|
||||
```
|
||||
|
||||
```py
|
||||
|
|
@ -550,7 +550,7 @@ g: list[Any] | dict[Any, Any] = f3(1)
|
|||
reveal_type(g) # revealed: list[int] | dict[int, int]
|
||||
```
|
||||
|
||||
We currently prefer the generic declared type regardless of its variance:
|
||||
We only prefer the declared type if it is in non-covariant position.
|
||||
|
||||
```py
|
||||
class Bivariant[T]:
|
||||
|
|
@ -594,12 +594,22 @@ x6: Covariant[Any] = covariant(1)
|
|||
x7: Contravariant[Any] = contravariant(1)
|
||||
x8: Invariant[Any] = invariant(1)
|
||||
|
||||
reveal_type(x5) # revealed: Bivariant[Any]
|
||||
reveal_type(x6) # revealed: Covariant[Any]
|
||||
reveal_type(x5) # revealed: Bivariant[Literal[1]]
|
||||
reveal_type(x6) # revealed: Covariant[Literal[1]]
|
||||
reveal_type(x7) # revealed: Contravariant[Any]
|
||||
reveal_type(x8) # revealed: Invariant[Any]
|
||||
```
|
||||
|
||||
```py
|
||||
class X[T]:
|
||||
def __init__(self: X[None]): ...
|
||||
def pop(self) -> T:
|
||||
raise NotImplementedError
|
||||
|
||||
x1: X[int | None] = X()
|
||||
reveal_type(x1) # revealed: X[None]
|
||||
```
|
||||
|
||||
## Narrow generic unions
|
||||
|
||||
```toml
|
||||
|
|
|
|||
|
|
@ -3016,7 +3016,19 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
|||
tcx.filter_union(self.db, |ty| ty.class_specialization(self.db).is_some())
|
||||
.class_specialization(self.db)?;
|
||||
|
||||
builder.infer(return_ty, tcx).ok()?;
|
||||
builder
|
||||
.infer_map(return_ty, tcx, |(_, variance, inferred_ty)| {
|
||||
// Avoid unnecessarily widening the return type based on a covariant
|
||||
// type parameter from the type context, as it can lead to argument
|
||||
// assignability errors if the type variable is constrained by a narrower
|
||||
// parameter type.
|
||||
if variance.is_covariant() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(inferred_ty)
|
||||
})
|
||||
.ok()?;
|
||||
Some(builder.type_mappings().clone())
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue