diff --git a/crates/ty_python_semantic/resources/mdtest/function/return_type.md b/crates/ty_python_semantic/resources/mdtest/function/return_type.md index e22e9f407b..50c15f1ed0 100644 --- a/crates/ty_python_semantic/resources/mdtest/function/return_type.md +++ b/crates/ty_python_semantic/resources/mdtest/function/return_type.md @@ -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) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9a1db3bf06..460fb65f6e 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -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"); } } diff --git a/crates/ty_python_semantic/src/types/builder.rs b/crates/ty_python_semantic/src/types/builder.rs index 8d2ddd60ad..1bdb85fa50 100644 --- a/crates/ty_python_semantic/src/types/builder.rs +++ b/crates/ty_python_semantic/src/types/builder.rs @@ -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();