mirror of https://github.com/astral-sh/ruff
[ty] Do not promote literals in contravariant position (#21164)
## Summary closes https://github.com/astral-sh/ty/issues/1463 ## Test Plan Regression tests
This commit is contained in:
parent
1d6ae8596a
commit
0c2cf75869
|
|
@ -0,0 +1,32 @@
|
|||
# Literal promotion
|
||||
|
||||
There are certain places where we promote literals to their common supertype:
|
||||
|
||||
```py
|
||||
reveal_type([1, 2, 3]) # revealed: list[Unknown | int]
|
||||
reveal_type({"a", "b", "c"}) # revealed: set[Unknown | str]
|
||||
```
|
||||
|
||||
This promotion should not take place if the literal type appears in contravariant position:
|
||||
|
||||
```py
|
||||
from typing import Callable, Literal
|
||||
|
||||
def in_negated_position(non_zero_number: int):
|
||||
if non_zero_number == 0:
|
||||
raise ValueError()
|
||||
|
||||
reveal_type(non_zero_number) # revealed: int & ~Literal[0]
|
||||
|
||||
reveal_type([non_zero_number]) # revealed: list[Unknown | (int & ~Literal[0])]
|
||||
|
||||
def in_parameter_position(callback: Callable[[Literal[1]], None]):
|
||||
reveal_type(callback) # revealed: (Literal[1], /) -> None
|
||||
|
||||
reveal_type([callback]) # revealed: list[Unknown | ((Literal[1], /) -> None)]
|
||||
|
||||
def double_negation(callback: Callable[[Callable[[Literal[1]], None]], None]):
|
||||
reveal_type(callback) # revealed: ((Literal[1], /) -> None, /) -> None
|
||||
|
||||
reveal_type([callback]) # revealed: list[Unknown | (((int, /) -> None, /) -> None)]
|
||||
```
|
||||
|
|
@ -1270,7 +1270,11 @@ impl<'db> Type<'db> {
|
|||
///
|
||||
/// It also avoids literal promotion if a literal type annotation was provided as type context.
|
||||
pub(crate) fn promote_literals(self, db: &'db dyn Db, tcx: TypeContext<'db>) -> Type<'db> {
|
||||
self.apply_type_mapping(db, &TypeMapping::PromoteLiterals, tcx)
|
||||
self.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::PromoteLiterals(PromoteLiteralsMode::On),
|
||||
tcx,
|
||||
)
|
||||
}
|
||||
|
||||
/// Like [`Type::promote_literals`], but does not recurse into nested types.
|
||||
|
|
@ -6765,7 +6769,7 @@ impl<'db> Type<'db> {
|
|||
self
|
||||
}
|
||||
}
|
||||
TypeMapping::PromoteLiterals
|
||||
TypeMapping::PromoteLiterals(_)
|
||||
| TypeMapping::ReplaceParameterDefaults
|
||||
| TypeMapping::BindLegacyTypevars(_) => self,
|
||||
TypeMapping::Materialize(materialization_kind) => {
|
||||
|
|
@ -6779,7 +6783,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
TypeMapping::Specialization(_) |
|
||||
TypeMapping::PartialSpecialization(_) |
|
||||
TypeMapping::PromoteLiterals |
|
||||
TypeMapping::PromoteLiterals(_) |
|
||||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::Materialize(_) |
|
||||
|
|
@ -6790,7 +6794,7 @@ impl<'db> Type<'db> {
|
|||
let function = Type::FunctionLiteral(function.apply_type_mapping_impl(db, type_mapping, tcx, visitor));
|
||||
|
||||
match type_mapping {
|
||||
TypeMapping::PromoteLiterals => function.promote_literals_impl(db, tcx),
|
||||
TypeMapping::PromoteLiterals(PromoteLiteralsMode::On) => function.promote_literals_impl(db, tcx),
|
||||
_ => function
|
||||
}
|
||||
}
|
||||
|
|
@ -6867,13 +6871,9 @@ impl<'db> Type<'db> {
|
|||
builder =
|
||||
builder.add_positive(positive.apply_type_mapping_impl(db, type_mapping, tcx, visitor));
|
||||
}
|
||||
let flipped_mapping = match type_mapping {
|
||||
TypeMapping::Materialize(materialization_kind) => &TypeMapping::Materialize(materialization_kind.flip()),
|
||||
_ => type_mapping,
|
||||
};
|
||||
for negative in intersection.negative(db) {
|
||||
builder =
|
||||
builder.add_negative(negative.apply_type_mapping_impl(db, flipped_mapping, tcx, visitor));
|
||||
builder.add_negative(negative.apply_type_mapping_impl(db, &type_mapping.flip(), tcx, visitor));
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
|
@ -6902,8 +6902,9 @@ impl<'db> Type<'db> {
|
|||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::Materialize(_) |
|
||||
TypeMapping::ReplaceParameterDefaults => self,
|
||||
TypeMapping::PromoteLiterals => self.promote_literals_impl(db, tcx)
|
||||
TypeMapping::ReplaceParameterDefaults |
|
||||
TypeMapping::PromoteLiterals(PromoteLiteralsMode::Off) => self,
|
||||
TypeMapping::PromoteLiterals(PromoteLiteralsMode::On) => self.promote_literals_impl(db, tcx)
|
||||
}
|
||||
|
||||
Type::Dynamic(_) => match type_mapping {
|
||||
|
|
@ -6912,7 +6913,7 @@ impl<'db> Type<'db> {
|
|||
TypeMapping::BindLegacyTypevars(_) |
|
||||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::PromoteLiterals |
|
||||
TypeMapping::PromoteLiterals(_) |
|
||||
TypeMapping::ReplaceParameterDefaults => self,
|
||||
TypeMapping::Materialize(materialization_kind) => match materialization_kind {
|
||||
MaterializationKind::Top => Type::object(),
|
||||
|
|
@ -7456,6 +7457,21 @@ fn apply_specialization_cycle_initial<'db>(
|
|||
Type::Never
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
|
||||
pub enum PromoteLiteralsMode {
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
|
||||
impl PromoteLiteralsMode {
|
||||
const fn flip(self) -> Self {
|
||||
match self {
|
||||
PromoteLiteralsMode::On => PromoteLiteralsMode::Off,
|
||||
PromoteLiteralsMode::Off => PromoteLiteralsMode::On,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A mapping that can be applied to a type, producing another type. This is applied inductively to
|
||||
/// the components of complex types.
|
||||
///
|
||||
|
|
@ -7470,7 +7486,7 @@ pub enum TypeMapping<'a, 'db> {
|
|||
PartialSpecialization(PartialSpecialization<'a, 'db>),
|
||||
/// Replaces any literal types with their corresponding promoted type form (e.g. `Literal["string"]`
|
||||
/// to `str`, or `def _() -> int` to `Callable[[], int]`).
|
||||
PromoteLiterals,
|
||||
PromoteLiterals(PromoteLiteralsMode),
|
||||
/// Binds a legacy typevar with the generic context (class, function, type alias) that it is
|
||||
/// being used in.
|
||||
BindLegacyTypevars(BindingContext<'db>),
|
||||
|
|
@ -7495,7 +7511,7 @@ impl<'db> TypeMapping<'_, 'db> {
|
|||
match self {
|
||||
TypeMapping::Specialization(_)
|
||||
| TypeMapping::PartialSpecialization(_)
|
||||
| TypeMapping::PromoteLiterals
|
||||
| TypeMapping::PromoteLiterals(_)
|
||||
| TypeMapping::BindLegacyTypevars(_)
|
||||
| TypeMapping::Materialize(_)
|
||||
| TypeMapping::ReplaceParameterDefaults => context,
|
||||
|
|
@ -7521,6 +7537,22 @@ impl<'db> TypeMapping<'_, 'db> {
|
|||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new `TypeMapping` that should be applied in contravariant positions.
|
||||
pub(crate) fn flip(&self) -> Self {
|
||||
match self {
|
||||
TypeMapping::Materialize(materialization_kind) => {
|
||||
TypeMapping::Materialize(materialization_kind.flip())
|
||||
}
|
||||
TypeMapping::PromoteLiterals(mode) => TypeMapping::PromoteLiterals(mode.flip()),
|
||||
TypeMapping::Specialization(_)
|
||||
| TypeMapping::PartialSpecialization(_)
|
||||
| TypeMapping::BindLegacyTypevars(_)
|
||||
| TypeMapping::BindSelf(_)
|
||||
| TypeMapping::ReplaceSelf { .. }
|
||||
| TypeMapping::ReplaceParameterDefaults => self.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Salsa-tracked constraint set. This is only needed to have something appropriately small to
|
||||
|
|
|
|||
|
|
@ -509,20 +509,17 @@ impl<'db> Signature<'db> {
|
|||
tcx: TypeContext<'db>,
|
||||
visitor: &ApplyTypeMappingVisitor<'db>,
|
||||
) -> Self {
|
||||
let flipped_mapping = match type_mapping {
|
||||
TypeMapping::Materialize(materialization_kind) => {
|
||||
&TypeMapping::Materialize(materialization_kind.flip())
|
||||
}
|
||||
_ => type_mapping,
|
||||
};
|
||||
Self {
|
||||
generic_context: self
|
||||
.generic_context
|
||||
.map(|context| type_mapping.update_signature_generic_context(db, context)),
|
||||
definition: self.definition,
|
||||
parameters: self
|
||||
.parameters
|
||||
.apply_type_mapping_impl(db, flipped_mapping, tcx, visitor),
|
||||
parameters: self.parameters.apply_type_mapping_impl(
|
||||
db,
|
||||
&type_mapping.flip(),
|
||||
tcx,
|
||||
visitor,
|
||||
),
|
||||
return_ty: self
|
||||
.return_ty
|
||||
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, tcx, visitor)),
|
||||
|
|
|
|||
Loading…
Reference in New Issue