mirror of
https://github.com/astral-sh/ruff.git
synced 2025-12-23 09:19:39 +00:00
Divergent is not equivalent to other dynamic types
This commit is contained in:
parent
7e418a75ee
commit
aaa66f3e4d
3 changed files with 35 additions and 93 deletions
|
|
@ -352,6 +352,15 @@ def call_divergent(x: int):
|
|||
# TODO: it would be better to reveal `tuple[Divergent | None, int]`
|
||||
reveal_type(call_divergent(1)) # revealed: Divergent
|
||||
|
||||
def tuple_obj(cond: bool):
|
||||
if cond:
|
||||
x = object()
|
||||
else:
|
||||
x = tuple_obj(cond)
|
||||
return (x,)
|
||||
|
||||
reveal_type(tuple_obj(True)) # revealed: tuple[object]
|
||||
|
||||
def get_non_empty(node):
|
||||
for child in node.children:
|
||||
node = get_non_empty(child)
|
||||
|
|
|
|||
|
|
@ -1905,6 +1905,10 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
match (self, other) {
|
||||
// The `Divergent` type is a special type that is not equivalent to other kinds of dynamic types,
|
||||
// which prevents `Divergent` from being eliminated during union reduction.
|
||||
(Type::Dynamic(_), Type::Dynamic(DynamicType::Divergent))
|
||||
| (Type::Dynamic(DynamicType::Divergent), Type::Dynamic(_)) => C::unsatisfiable(db),
|
||||
(Type::Dynamic(_), Type::Dynamic(_)) => C::always_satisfiable(db),
|
||||
|
||||
(Type::SubclassOf(first), Type::SubclassOf(second)) => {
|
||||
|
|
@ -10797,65 +10801,41 @@ pub(crate) mod tests {
|
|||
|
||||
let div = Type::Dynamic(DynamicType::Divergent);
|
||||
|
||||
// The `Divergent` type must not be eliminated in union with other dynamic types,
|
||||
// as this would prevent detection of divergent type inference using `Divergent`.
|
||||
let union = UnionType::from_elements(&db, [Type::unknown(), div]);
|
||||
assert_eq!(union.display(&db).to_string(), "Unknown | Divergent");
|
||||
|
||||
let union = UnionType::from_elements(&db, [div, Type::unknown()]);
|
||||
assert_eq!(union.display(&db).to_string(), "Divergent");
|
||||
assert_eq!(union.display(&db).to_string(), "Divergent | Unknown");
|
||||
|
||||
let union = UnionType::from_elements(&db, [div, Type::unknown(), todo_type!("1")]);
|
||||
assert_eq!(union.display(&db).to_string(), "Divergent | Unknown");
|
||||
|
||||
assert!(div.is_equivalent_to(&db, div));
|
||||
assert!(!div.is_equivalent_to(&db, Type::unknown()));
|
||||
assert!(!Type::unknown().is_equivalent_to(&db, div));
|
||||
|
||||
// The `object` type has a good convergence property, that is, its union with all other types is `object`.
|
||||
// (e.g. `object | tuple[Divergent] == object`, `object | tuple[object] == object`)
|
||||
// So we can safely eliminate `Divergent`.
|
||||
let union = UnionType::from_elements(&db, [div, KnownClass::Object.to_instance(&db)]);
|
||||
assert_eq!(union.display(&db).to_string(), "object | Divergent");
|
||||
assert_eq!(union.display(&db).to_string(), "object");
|
||||
|
||||
let union = UnionType::from_elements(&db, [KnownClass::Object.to_instance(&db), div]);
|
||||
assert_eq!(union.display(&db).to_string(), "object | Divergent");
|
||||
|
||||
let union = UnionType::from_elements(
|
||||
&db,
|
||||
[
|
||||
KnownClass::Object.to_instance(&db),
|
||||
KnownClass::List.to_specialized_instance(&db, [div]),
|
||||
],
|
||||
);
|
||||
assert_eq!(union.display(&db).to_string(), "object | list[Divergent]");
|
||||
|
||||
let union = UnionType::from_elements(
|
||||
&db,
|
||||
[
|
||||
KnownClass::Object.to_instance(&db),
|
||||
KnownClass::List.to_specialized_instance(&db, [div]),
|
||||
KnownClass::Int.to_instance(&db),
|
||||
],
|
||||
);
|
||||
assert_eq!(union.display(&db).to_string(), "object | list[Divergent]");
|
||||
assert_eq!(union.display(&db).to_string(), "object");
|
||||
|
||||
// The same can be said about intersections for the `Never` type.
|
||||
let intersection = IntersectionBuilder::new(&db)
|
||||
.add_positive(Type::Never)
|
||||
.add_positive(div)
|
||||
.build();
|
||||
assert_eq!(intersection.display(&db).to_string(), "Never & Divergent");
|
||||
assert_eq!(intersection.display(&db).to_string(), "Never");
|
||||
|
||||
let intersection = IntersectionBuilder::new(&db)
|
||||
.add_positive(div)
|
||||
.add_positive(Type::Never)
|
||||
.build();
|
||||
assert_eq!(intersection.display(&db).to_string(), "Divergent & Never");
|
||||
|
||||
let intersection = IntersectionBuilder::new(&db)
|
||||
.add_positive(KnownClass::List.to_specialized_instance(&db, [div]))
|
||||
.add_positive(Type::Never)
|
||||
.build();
|
||||
assert_eq!(
|
||||
intersection.display(&db).to_string(),
|
||||
"list[Divergent] & Never"
|
||||
);
|
||||
|
||||
let intersection = IntersectionBuilder::new(&db)
|
||||
.add_positive(KnownClass::Int.to_instance(&db))
|
||||
.add_positive(KnownClass::List.to_specialized_instance(&db, [div]))
|
||||
.build();
|
||||
assert_eq!(
|
||||
intersection.display(&db).to_string(),
|
||||
"int & list[Divergent]"
|
||||
);
|
||||
assert_eq!(intersection.display(&db).to_string(), "Never");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
use crate::types::enums::{enum_member_literals, enum_metadata};
|
||||
use crate::types::type_ordering::union_or_intersection_elements_ordering;
|
||||
use crate::types::{
|
||||
BytesLiteralType, DynamicType, IntersectionType, KnownClass, StringLiteralType, Type,
|
||||
BytesLiteralType, IntersectionType, KnownClass, StringLiteralType, Type,
|
||||
TypeVarBoundOrConstraints, UnionType,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
|
@ -241,22 +241,9 @@ impl<'db> UnionBuilder<'db> {
|
|||
|
||||
/// Collapse the union to a single type: `object`.
|
||||
fn collapse_to_object(&mut self) {
|
||||
let divergent = self.elements.iter().find_map(|elem| {
|
||||
let UnionElement::Type(ty) = elem else {
|
||||
return None;
|
||||
};
|
||||
if ty.has_divergent_type(self.db) {
|
||||
Some(*ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
self.elements.clear();
|
||||
self.elements
|
||||
.push(UnionElement::Type(Type::object(self.db)));
|
||||
if let Some(divergent) = divergent {
|
||||
self.elements.push(UnionElement::Type(divergent));
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a type to this union.
|
||||
|
|
@ -465,15 +452,6 @@ impl<'db> UnionBuilder<'db> {
|
|||
ty if ty.is_object(self.db) => {
|
||||
self.collapse_to_object();
|
||||
}
|
||||
Type::Dynamic(DynamicType::Divergent) => {
|
||||
if !self
|
||||
.elements
|
||||
.iter()
|
||||
.any(|elem| matches!(elem, UnionElement::Type(elem) if ty == *elem))
|
||||
{
|
||||
self.elements.push(UnionElement::Type(ty));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let bool_pair = if let Type::BooleanLiteral(b) = ty {
|
||||
Some(Type::BooleanLiteral(!b))
|
||||
|
|
@ -493,16 +471,6 @@ impl<'db> UnionBuilder<'db> {
|
|||
Type::Never // won't be used
|
||||
};
|
||||
|
||||
if ty.has_divergent_type(self.db)
|
||||
&& !self
|
||||
.elements
|
||||
.iter()
|
||||
.any(|elem| matches!(elem, UnionElement::Type(elem) if ty == *elem))
|
||||
{
|
||||
self.elements.push(UnionElement::Type(ty));
|
||||
return;
|
||||
}
|
||||
|
||||
for (index, element) in self.elements.iter_mut().enumerate() {
|
||||
let element_type = match element.try_reduce(self.db, ty) {
|
||||
ReduceResult::KeepIf(keep) => {
|
||||
|
|
@ -896,9 +864,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
self.add_positive(db, Type::LiteralString);
|
||||
self.add_negative(db, Type::string_literal(db, ""));
|
||||
}
|
||||
Type::Dynamic(DynamicType::Divergent) => {
|
||||
self.positive.insert(new_positive);
|
||||
}
|
||||
|
||||
_ => {
|
||||
let known_instance = new_positive
|
||||
.into_nominal_instance()
|
||||
|
|
@ -967,9 +933,6 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
|
||||
let mut to_remove = SmallVec::<[usize; 1]>::new();
|
||||
for (index, existing_positive) in self.positive.iter().enumerate() {
|
||||
if new_positive.has_divergent_type(db) {
|
||||
break;
|
||||
}
|
||||
// S & T = S if S <: T
|
||||
if existing_positive.is_subtype_of(db, new_positive)
|
||||
|| existing_positive.is_equivalent_to(db, new_positive)
|
||||
|
|
@ -977,18 +940,11 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
return;
|
||||
}
|
||||
// same rule, reverse order
|
||||
if new_positive.is_subtype_of(db, *existing_positive)
|
||||
&& !existing_positive.has_divergent_type(db)
|
||||
{
|
||||
if new_positive.is_subtype_of(db, *existing_positive) {
|
||||
to_remove.push(index);
|
||||
}
|
||||
// A & B = Never if A and B are disjoint
|
||||
if new_positive.is_disjoint_from(db, *existing_positive)
|
||||
&& self
|
||||
.positive
|
||||
.iter()
|
||||
.all(|existing_positive| !existing_positive.has_divergent_type(db))
|
||||
{
|
||||
if new_positive.is_disjoint_from(db, *existing_positive) {
|
||||
*self = Self::default();
|
||||
self.positive.insert(Type::Never);
|
||||
return;
|
||||
|
|
@ -1000,9 +956,6 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
|
||||
let mut to_remove = SmallVec::<[usize; 1]>::new();
|
||||
for (index, existing_negative) in self.negative.iter().enumerate() {
|
||||
if new_positive.has_divergent_type(db) {
|
||||
break;
|
||||
}
|
||||
// S & ~T = Never if S <: T
|
||||
if new_positive.is_subtype_of(db, *existing_negative) {
|
||||
*self = Self::default();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue