diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md index a5935725b6ae3..11fd001db830c 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md @@ -439,3 +439,29 @@ static_assert(is_disjoint_from(Callable[[], None], Literal[b""])) static_assert(is_disjoint_from(Callable[[], None], Literal[1])) static_assert(is_disjoint_from(Callable[[], None], Literal[True])) ``` + +## Bound Super + +If both `super` are fully static, they are disjoint when at least one of their parameters differs. +On the other hand, if either `super` is not fully static, they should never be considered disjoint. + +```py +from ty_extensions import TypeOf, is_disjoint_from, static_assert + +def f(x): + static_assert(is_disjoint_from(TypeOf[super(int, int())], TypeOf[super(str, str())])) + static_assert(is_disjoint_from(TypeOf[super(int, int())], TypeOf[super(int, bool())])) + static_assert(not is_disjoint_from(TypeOf[super(int, int)], TypeOf[super(int, int)])) + static_assert( + not is_disjoint_from( + TypeOf[super(int, int())], + TypeOf[super(int, int())], + ) + ) + + # includes gradual types + static_assert(not is_disjoint_from(TypeOf[super(x, x)], TypeOf[super(x, x)])) + static_assert(not is_disjoint_from(TypeOf[super(x, int())], TypeOf[super(x, str())])) + static_assert(not is_disjoint_from(TypeOf[super(int, x)], TypeOf[super(str, x)])) + static_assert(not is_disjoint_from(TypeOf[super(int, x)], TypeOf[super(x, int())])) +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 3eb740871c27e..9929cb641536e 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2087,10 +2087,23 @@ impl<'db> Type<'db> { .is_disjoint_from(db, other) } - (Type::BoundSuper(_), Type::BoundSuper(_)) => !self.is_equivalent_to(db, other), - (Type::BoundSuper(_), other) | (other, Type::BoundSuper(_)) => KnownClass::Super - .to_instance(db) - .is_disjoint_from(db, other), + (Type::BoundSuper(_), Type::BoundSuper(_)) => { + if self.is_fully_static(db) && other.is_fully_static(db) { + !self.is_equivalent_to(db, other) + } else { + false + } + } + (bound_super @ Type::BoundSuper(_), other) + | (other, bound_super @ Type::BoundSuper(_)) => { + if bound_super.is_fully_static(db) { + KnownClass::Super + .to_instance(db) + .is_disjoint_from(db, other) + } else { + false + } + } } } diff --git a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs index 362d603e6b85d..d52e28ae05153 100644 --- a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs +++ b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs @@ -1,8 +1,9 @@ use crate::db::tests::TestDb; use crate::symbol::{builtins_symbol, known_module_symbol}; use crate::types::{ - BoundMethodType, CallableType, IntersectionBuilder, KnownClass, KnownInstanceType, Parameter, - Parameters, Signature, SubclassOfType, TupleType, Type, UnionType, + BoundMethodType, BoundSuperType, CallableType, IntersectionBuilder, KnownClass, + KnownInstanceType, Parameter, Parameters, Signature, SubclassOfType, TupleType, Type, + UnionType, }; use crate::{Db, KnownModule}; use hashbrown::HashSet; @@ -53,6 +54,10 @@ pub(crate) enum Ty { params: CallableParams, returns: Option>, }, + BoundSuper { + pivot: SuperParamKind, + owner: SuperParamKind, + }, } #[derive(Debug, Clone, PartialEq)] @@ -117,6 +122,29 @@ fn create_bound_method<'db>( )) } +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum SuperParamKind { + BuiltinClass(&'static str), + BuiltinInstance(&'static str), + Unknown, +} + +impl SuperParamKind { + fn into_type(self, db: &TestDb) -> Type<'_> { + match self { + SuperParamKind::BuiltinClass(class_name) => { + builtins_symbol(db, class_name).symbol.expect_type() + } + SuperParamKind::BuiltinInstance(class_name) => builtins_symbol(db, class_name) + .symbol + .expect_type() + .to_instance(db) + .unwrap(), + SuperParamKind::Unknown => Type::unknown(), + } + } +} + impl Ty { pub(crate) fn into_type(self, db: &TestDb) -> Type<'_> { match self { @@ -195,6 +223,10 @@ impl Ty { returns.map(|ty| ty.into_type(db)), ), )), + Ty::BoundSuper { pivot, owner } => { + BoundSuperType::build(db, pivot.into_type(db), owner.into_type(db)) + .expect("Failed to build a BoundSuperType") + } } } } @@ -253,6 +285,18 @@ fn arbitrary_core_type(g: &mut Gen) -> Ty { class: "int", method: "bit_length", }, + Ty::BoundSuper { + pivot: SuperParamKind::BuiltinClass("int"), + owner: SuperParamKind::BuiltinInstance("bool"), + }, + Ty::BoundSuper { + pivot: SuperParamKind::BuiltinClass("int"), + owner: SuperParamKind::BuiltinClass("bool"), + }, + Ty::BoundSuper { + pivot: SuperParamKind::Unknown, + owner: SuperParamKind::Unknown, + }, ]) .unwrap() .clone()