mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-03 15:14:42 +00:00
[ty] Introduce TypeRelation::UnionSimplification
This commit is contained in:
parent
4e33501115
commit
463b5dc3ee
6 changed files with 78 additions and 55 deletions
|
@ -1050,13 +1050,6 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn into_intersection(self) -> Option<IntersectionType<'db>> {
|
|
||||||
match self {
|
|
||||||
Type::Intersection(intersection_type) => Some(intersection_type),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub(crate) fn expect_union(self) -> UnionType<'db> {
|
pub(crate) fn expect_union(self) -> UnionType<'db> {
|
||||||
|
@ -1471,6 +1464,11 @@ impl<'db> Type<'db> {
|
||||||
self.has_relation_to(db, target, TypeRelation::Assignability)
|
self.has_relation_to(db, target, TypeRelation::Assignability)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_redundant_in_union_with(self, db: &'db dyn Db, other: Type<'db>) -> bool {
|
||||||
|
self.has_relation_to(db, other, TypeRelation::UnionSimplification)
|
||||||
|
.is_always_satisfied()
|
||||||
|
}
|
||||||
|
|
||||||
fn has_relation_to(
|
fn has_relation_to(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
|
@ -1492,7 +1490,7 @@ impl<'db> Type<'db> {
|
||||||
//
|
//
|
||||||
// Note that we could do a full equivalence check here, but that would be both expensive
|
// Note that we could do a full equivalence check here, but that would be both expensive
|
||||||
// and unnecessary. This early return is only an optimisation.
|
// and unnecessary. This early return is only an optimisation.
|
||||||
if (relation.is_assignability() || self.subtyping_is_always_reflexive()) && self == target {
|
if (!relation.is_subtyping() || self.subtyping_is_always_reflexive()) && self == target {
|
||||||
return ConstraintSet::from(true);
|
return ConstraintSet::from(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1509,9 +1507,10 @@ impl<'db> Type<'db> {
|
||||||
// It is a subtype of all other types.
|
// It is a subtype of all other types.
|
||||||
(Type::Never, _) => ConstraintSet::from(true),
|
(Type::Never, _) => ConstraintSet::from(true),
|
||||||
|
|
||||||
// Dynamic is only a subtype of `object` and only a supertype of `Never`; both were
|
// In some specific situations, `Any` can be simplified out of unions and intersections,
|
||||||
// handled above. It's always assignable, though.
|
// but this is not true for divergent types.
|
||||||
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => {
|
(Type::Dynamic(DynamicType::Divergent(_)), _)
|
||||||
|
| (_, Type::Dynamic(DynamicType::Divergent(_))) => {
|
||||||
ConstraintSet::from(relation.is_assignability())
|
ConstraintSet::from(relation.is_assignability())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1536,10 +1535,27 @@ impl<'db> Type<'db> {
|
||||||
.has_relation_to_impl(db, right, relation, visitor)
|
.has_relation_to_impl(db, right, relation, visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => {
|
(Type::Dynamic(_), _) => ConstraintSet::from(match relation {
|
||||||
// TODO: Implement assignability and subtyping for TypedDict
|
TypeRelation::Subtyping => false,
|
||||||
ConstraintSet::from(relation.is_assignability())
|
TypeRelation::Assignability => true,
|
||||||
|
TypeRelation::UnionSimplification => match target {
|
||||||
|
Type::Dynamic(_) => true,
|
||||||
|
Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic),
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
(_, Type::Dynamic(_)) => ConstraintSet::from(match relation {
|
||||||
|
TypeRelation::Subtyping => false,
|
||||||
|
TypeRelation::Assignability => true,
|
||||||
|
TypeRelation::UnionSimplification => match self {
|
||||||
|
Type::Dynamic(_) => true,
|
||||||
|
Type::Intersection(intersection) => {
|
||||||
|
intersection.positive(db).iter().any(Type::is_dynamic)
|
||||||
}
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
// In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied:
|
// In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied:
|
||||||
// 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`.
|
// 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`.
|
||||||
|
@ -1687,6 +1703,11 @@ impl<'db> Type<'db> {
|
||||||
// TODO: Infer specializations here
|
// TODO: Infer specializations here
|
||||||
(Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false),
|
(Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false),
|
||||||
|
|
||||||
|
(Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => {
|
||||||
|
// TODO: Implement assignability and subtyping for TypedDict
|
||||||
|
ConstraintSet::from(relation.is_assignability())
|
||||||
|
}
|
||||||
|
|
||||||
// Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`.
|
// Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`.
|
||||||
// If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively.
|
// If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively.
|
||||||
(left, Type::AlwaysFalsy) => ConstraintSet::from(left.bool(db).is_always_false()),
|
(left, Type::AlwaysFalsy) => ConstraintSet::from(left.bool(db).is_always_false()),
|
||||||
|
@ -9098,6 +9119,7 @@ impl<'db> ConstructorCallError<'db> {
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||||
pub(crate) enum TypeRelation {
|
pub(crate) enum TypeRelation {
|
||||||
Subtyping,
|
Subtyping,
|
||||||
|
UnionSimplification,
|
||||||
Assignability,
|
Assignability,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9105,6 +9127,10 @@ impl TypeRelation {
|
||||||
pub(crate) const fn is_assignability(self) -> bool {
|
pub(crate) const fn is_assignability(self) -> bool {
|
||||||
matches!(self, TypeRelation::Assignability)
|
matches!(self, TypeRelation::Assignability)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn is_subtyping(self) -> bool {
|
||||||
|
matches!(self, TypeRelation::Subtyping)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, get_size2::GetSize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, get_size2::GetSize)]
|
||||||
|
|
|
@ -502,18 +502,9 @@ impl<'db> UnionBuilder<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_simplify_full && !matches!(element_type, Type::TypeAlias(_)) {
|
if should_simplify_full && !matches!(element_type, Type::TypeAlias(_)) {
|
||||||
if ty.is_equivalent_to(self.db, element_type)
|
if ty.is_redundant_in_union_with(self.db, element_type) {
|
||||||
|| ty.is_subtype_of(self.db, element_type)
|
|
||||||
|| ty.into_intersection().is_some_and(|intersection| {
|
|
||||||
intersection.positive(self.db).contains(&element_type)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
} else if element_type.is_subtype_of(self.db, ty)
|
} else if element_type.is_redundant_in_union_with(self.db, ty) {
|
||||||
|| element_type
|
|
||||||
.into_intersection()
|
|
||||||
.is_some_and(|intersection| intersection.positive(self.db).contains(&ty))
|
|
||||||
{
|
|
||||||
to_remove.push(index);
|
to_remove.push(index);
|
||||||
} else if ty_negated.is_subtype_of(self.db, element_type) {
|
} else if ty_negated.is_subtype_of(self.db, element_type) {
|
||||||
// We add `ty` to the union. We just checked that `~ty` is a subtype of an
|
// We add `ty` to the union. We just checked that `~ty` is a subtype of an
|
||||||
|
@ -930,13 +921,11 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||||
let mut to_remove = SmallVec::<[usize; 1]>::new();
|
let mut to_remove = SmallVec::<[usize; 1]>::new();
|
||||||
for (index, existing_positive) in self.positive.iter().enumerate() {
|
for (index, existing_positive) in self.positive.iter().enumerate() {
|
||||||
// S & T = S if S <: T
|
// S & T = S if S <: T
|
||||||
if existing_positive.is_subtype_of(db, new_positive)
|
if existing_positive.is_redundant_in_union_with(db, new_positive) {
|
||||||
|| existing_positive.is_equivalent_to(db, new_positive)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// same rule, reverse order
|
// same rule, reverse order
|
||||||
if new_positive.is_subtype_of(db, *existing_positive) {
|
if new_positive.is_redundant_in_union_with(db, *existing_positive) {
|
||||||
to_remove.push(index);
|
to_remove.push(index);
|
||||||
}
|
}
|
||||||
// A & B = Never if A and B are disjoint
|
// A & B = Never if A and B are disjoint
|
||||||
|
@ -953,7 +942,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||||
let mut to_remove = SmallVec::<[usize; 1]>::new();
|
let mut to_remove = SmallVec::<[usize; 1]>::new();
|
||||||
for (index, existing_negative) in self.negative.iter().enumerate() {
|
for (index, existing_negative) in self.negative.iter().enumerate() {
|
||||||
// S & ~T = Never if S <: T
|
// S & ~T = Never if S <: T
|
||||||
if new_positive.is_subtype_of(db, *existing_negative) {
|
if new_positive.is_redundant_in_union_with(db, *existing_negative) {
|
||||||
*self = Self::default();
|
*self = Self::default();
|
||||||
self.positive.insert(Type::Never);
|
self.positive.insert(Type::Never);
|
||||||
return;
|
return;
|
||||||
|
@ -1027,9 +1016,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||||
let mut to_remove = SmallVec::<[usize; 1]>::new();
|
let mut to_remove = SmallVec::<[usize; 1]>::new();
|
||||||
for (index, existing_negative) in self.negative.iter().enumerate() {
|
for (index, existing_negative) in self.negative.iter().enumerate() {
|
||||||
// ~S & ~T = ~T if S <: T
|
// ~S & ~T = ~T if S <: T
|
||||||
if existing_negative.is_subtype_of(db, new_negative)
|
if existing_negative.is_redundant_in_union_with(db, new_negative) {
|
||||||
|| existing_negative.is_equivalent_to(db, new_negative)
|
|
||||||
{
|
|
||||||
to_remove.push(index);
|
to_remove.push(index);
|
||||||
}
|
}
|
||||||
// same rule, reverse order
|
// same rule, reverse order
|
||||||
|
@ -1043,7 +1030,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||||
|
|
||||||
for existing_positive in &self.positive {
|
for existing_positive in &self.positive {
|
||||||
// S & ~T = Never if S <: T
|
// S & ~T = Never if S <: T
|
||||||
if existing_positive.is_subtype_of(db, new_negative) {
|
if existing_positive.is_redundant_in_union_with(db, new_negative) {
|
||||||
*self = Self::default();
|
*self = Self::default();
|
||||||
self.positive.insert(Type::Never);
|
self.positive.insert(Type::Never);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -551,7 +551,9 @@ impl<'db> ClassType<'db> {
|
||||||
self.iter_mro(db).when_any(db, |base| {
|
self.iter_mro(db).when_any(db, |base| {
|
||||||
match base {
|
match base {
|
||||||
ClassBase::Dynamic(_) => match relation {
|
ClassBase::Dynamic(_) => match relation {
|
||||||
TypeRelation::Subtyping => ConstraintSet::from(other.is_object(db)),
|
TypeRelation::Subtyping | TypeRelation::UnionSimplification => {
|
||||||
|
ConstraintSet::from(other.is_object(db))
|
||||||
|
}
|
||||||
TypeRelation::Assignability => ConstraintSet::from(!other.is_final(db)),
|
TypeRelation::Assignability => ConstraintSet::from(!other.is_final(db)),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -952,7 +952,9 @@ impl<'db> FunctionType<'db> {
|
||||||
_visitor: &HasRelationToVisitor<'db>,
|
_visitor: &HasRelationToVisitor<'db>,
|
||||||
) -> ConstraintSet<'db> {
|
) -> ConstraintSet<'db> {
|
||||||
match relation {
|
match relation {
|
||||||
TypeRelation::Subtyping => ConstraintSet::from(self.is_subtype_of(db, other)),
|
TypeRelation::Subtyping | TypeRelation::UnionSimplification => {
|
||||||
|
ConstraintSet::from(self.is_subtype_of(db, other))
|
||||||
|
}
|
||||||
TypeRelation::Assignability => ConstraintSet::from(self.is_assignable_to(db, other)),
|
TypeRelation::Assignability => ConstraintSet::from(self.is_assignable_to(db, other)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -567,7 +567,9 @@ fn has_relation_in_invariant_position<'db>(
|
||||||
),
|
),
|
||||||
// Subtyping between invariant type parameters without a top/bottom materialization involved
|
// Subtyping between invariant type parameters without a top/bottom materialization involved
|
||||||
// is equivalence
|
// is equivalence
|
||||||
(None, None, TypeRelation::Subtyping) => derived_type.when_equivalent_to(db, *base_type),
|
(None, None, TypeRelation::Subtyping | TypeRelation::UnionSimplification) => {
|
||||||
|
derived_type.when_equivalent_to(db, *base_type)
|
||||||
|
}
|
||||||
(None, None, TypeRelation::Assignability) => derived_type
|
(None, None, TypeRelation::Assignability) => derived_type
|
||||||
.has_relation_to_impl(db, *base_type, TypeRelation::Assignability, visitor)
|
.has_relation_to_impl(db, *base_type, TypeRelation::Assignability, visitor)
|
||||||
.and(db, || {
|
.and(db, || {
|
||||||
|
@ -579,22 +581,26 @@ fn has_relation_in_invariant_position<'db>(
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
// For gradual types, A <: B (subtyping) is defined as Top[A] <: Bottom[B]
|
// For gradual types, A <: B (subtyping) is defined as Top[A] <: Bottom[B]
|
||||||
(None, Some(base_mat), TypeRelation::Subtyping) => is_subtype_in_invariant_position(
|
(None, Some(base_mat), TypeRelation::Subtyping | TypeRelation::UnionSimplification) => {
|
||||||
|
is_subtype_in_invariant_position(
|
||||||
db,
|
db,
|
||||||
derived_type,
|
derived_type,
|
||||||
MaterializationKind::Top,
|
MaterializationKind::Top,
|
||||||
base_type,
|
base_type,
|
||||||
base_mat,
|
base_mat,
|
||||||
visitor,
|
visitor,
|
||||||
),
|
)
|
||||||
(Some(derived_mat), None, TypeRelation::Subtyping) => is_subtype_in_invariant_position(
|
}
|
||||||
|
(Some(derived_mat), None, TypeRelation::Subtyping | TypeRelation::UnionSimplification) => {
|
||||||
|
is_subtype_in_invariant_position(
|
||||||
db,
|
db,
|
||||||
derived_type,
|
derived_type,
|
||||||
derived_mat,
|
derived_mat,
|
||||||
base_type,
|
base_type,
|
||||||
MaterializationKind::Bottom,
|
MaterializationKind::Bottom,
|
||||||
visitor,
|
visitor,
|
||||||
),
|
)
|
||||||
|
}
|
||||||
// And A <~ B (assignability) is Bottom[A] <: Top[B]
|
// And A <~ B (assignability) is Bottom[A] <: Top[B]
|
||||||
(None, Some(base_mat), TypeRelation::Assignability) => is_subtype_in_invariant_position(
|
(None, Some(base_mat), TypeRelation::Assignability) => is_subtype_in_invariant_position(
|
||||||
db,
|
db,
|
||||||
|
|
|
@ -138,7 +138,7 @@ impl<'db> SubclassOfType<'db> {
|
||||||
) -> ConstraintSet<'db> {
|
) -> ConstraintSet<'db> {
|
||||||
match (self.subclass_of, other.subclass_of) {
|
match (self.subclass_of, other.subclass_of) {
|
||||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
|
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
|
||||||
ConstraintSet::from(relation.is_assignability())
|
ConstraintSet::from(!relation.is_subtyping())
|
||||||
}
|
}
|
||||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => {
|
(SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => {
|
||||||
ConstraintSet::from(other_class.is_object(db) || relation.is_assignability())
|
ConstraintSet::from(other_class.is_object(db) || relation.is_assignability())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue