mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] ensure union normalization really normalizes (#20147)
## Summary Now that we have `Type::TypeAlias`, which can wrap a union, and the possibility of unions including non-unpacked type aliases (which is necessary to support recursive type aliases), we can no longer assume in `UnionType::normalized_impl` that normalizing each element of an existing union will result in a set of elements that we can order and then place raw into `UnionType` to create a normalized union. It's now possible for those elements to themselves include union types (unpacked from an alias). So instead, we need to feed those elements into the full `UnionBuilder` (with alias-unpacking turned on) to flatten/normalize them, and then order them. ## Test Plan Added mdtest. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
5a608f7366
commit
8223fea062
3 changed files with 58 additions and 15 deletions
|
@ -1118,9 +1118,7 @@ impl<'db> Type<'db> {
|
|||
#[must_use]
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
match self {
|
||||
Type::Union(union) => {
|
||||
visitor.visit(self, || Type::Union(union.normalized_impl(db, visitor)))
|
||||
}
|
||||
Type::Union(union) => visitor.visit(self, || union.normalized_impl(db, visitor)),
|
||||
Type::Intersection(intersection) => visitor.visit(self, || {
|
||||
Type::Intersection(intersection.normalized_impl(db, visitor))
|
||||
}),
|
||||
|
@ -1887,14 +1885,14 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
(Type::TypeAlias(self_alias), _) => {
|
||||
let self_alias_ty = self_alias.value_type(db);
|
||||
let self_alias_ty = self_alias.value_type(db).normalized(db);
|
||||
visitor.visit((self_alias_ty, other), || {
|
||||
self_alias_ty.is_equivalent_to_impl(db, other, visitor)
|
||||
})
|
||||
}
|
||||
|
||||
(_, Type::TypeAlias(other_alias)) => {
|
||||
let other_alias_ty = other_alias.value_type(db);
|
||||
let other_alias_ty = other_alias.value_type(db).normalized(db);
|
||||
visitor.visit((self, other_alias_ty), || {
|
||||
self.is_equivalent_to_impl(db, other_alias_ty, visitor)
|
||||
})
|
||||
|
@ -7697,7 +7695,17 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
|||
TypeVarBoundOrConstraints::UpperBound(bound.normalized_impl(db, visitor))
|
||||
}
|
||||
TypeVarBoundOrConstraints::Constraints(constraints) => {
|
||||
TypeVarBoundOrConstraints::Constraints(constraints.normalized_impl(db, visitor))
|
||||
// Constraints are a non-normalized union by design (it's not really a union at
|
||||
// all, we are just using a union to store the types). Normalize the types but not
|
||||
// the containing union.
|
||||
TypeVarBoundOrConstraints::Constraints(UnionType::new(
|
||||
db,
|
||||
constraints
|
||||
.elements(db)
|
||||
.iter()
|
||||
.map(|ty| ty.normalized_impl(db, visitor))
|
||||
.collect::<Box<_>>(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9654,18 +9662,25 @@ impl<'db> UnionType<'db> {
|
|||
///
|
||||
/// See [`Type::normalized`] for more details.
|
||||
#[must_use]
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.normalized_impl(db, &NormalizedVisitor::default())
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
let mut new_elements: Vec<Type<'db>> = self
|
||||
.elements(db)
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &NormalizedVisitor<'db>,
|
||||
) -> Type<'db> {
|
||||
self.elements(db)
|
||||
.iter()
|
||||
.map(|element| element.normalized_impl(db, visitor))
|
||||
.collect();
|
||||
new_elements.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r));
|
||||
UnionType::new(db, new_elements.into_boxed_slice())
|
||||
.map(|ty| ty.normalized_impl(db, visitor))
|
||||
.fold(
|
||||
UnionBuilder::new(db)
|
||||
.order_elements(true)
|
||||
.unpack_aliases(true),
|
||||
UnionBuilder::add,
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||
|
@ -9687,7 +9702,7 @@ impl<'db> UnionType<'db> {
|
|||
|
||||
let sorted_self = self.normalized(db);
|
||||
|
||||
if sorted_self == other {
|
||||
if sorted_self == Type::Union(other) {
|
||||
return C::always_satisfiable(db);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
//! unnecessary `is_subtype_of` checks.
|
||||
|
||||
use crate::types::enums::{enum_member_literals, enum_metadata};
|
||||
use crate::types::type_ordering::union_or_intersection_elements_ordering;
|
||||
use crate::types::{
|
||||
BytesLiteralType, IntersectionType, KnownClass, StringLiteralType, Type,
|
||||
TypeVarBoundOrConstraints, UnionType,
|
||||
|
@ -211,6 +212,7 @@ pub(crate) struct UnionBuilder<'db> {
|
|||
elements: Vec<UnionElement<'db>>,
|
||||
db: &'db dyn Db,
|
||||
unpack_aliases: bool,
|
||||
order_elements: bool,
|
||||
}
|
||||
|
||||
impl<'db> UnionBuilder<'db> {
|
||||
|
@ -219,6 +221,7 @@ impl<'db> UnionBuilder<'db> {
|
|||
db,
|
||||
elements: vec![],
|
||||
unpack_aliases: true,
|
||||
order_elements: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,6 +230,11 @@ impl<'db> UnionBuilder<'db> {
|
|||
self
|
||||
}
|
||||
|
||||
pub(crate) fn order_elements(mut self, val: bool) -> Self {
|
||||
self.order_elements = val;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.elements.is_empty()
|
||||
}
|
||||
|
@ -545,6 +553,9 @@ impl<'db> UnionBuilder<'db> {
|
|||
UnionElement::Type(ty) => types.push(ty),
|
||||
}
|
||||
}
|
||||
if self.order_elements {
|
||||
types.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(self.db, l, r));
|
||||
}
|
||||
match types.len() {
|
||||
0 => None,
|
||||
1 => Some(types[0]),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue