@@ -1698,6 +1698,20 @@ impl<'db> Type<'db> {
16981698 /// Note: This function aims to have no false positives, but might return
16991699 /// wrong `false` answers in some cases.
17001700 pub ( crate ) fn is_disjoint_from ( self , db : & ' db dyn Db , other : Type < ' db > ) -> bool {
1701+ fn any_protocol_members_absent_or_disjoint < ' db > (
1702+ db : & ' db dyn Db ,
1703+ protocol : ProtocolInstanceType < ' db > ,
1704+ other : Type < ' db > ,
1705+ ) -> bool {
1706+ protocol. interface ( db) . members ( db) . any ( |member| {
1707+ other
1708+ . member ( db, member. name ( ) )
1709+ . place
1710+ . ignore_possibly_unbound ( )
1711+ . is_none_or ( |attribute_type| member. has_disjoint_type_from ( db, attribute_type) )
1712+ } )
1713+ }
1714+
17011715 match ( self , other) {
17021716 ( Type :: Never , _) | ( _, Type :: Never ) => true ,
17031717
@@ -1864,6 +1878,57 @@ impl<'db> Type<'db> {
18641878 Type :: SubclassOf ( _) ,
18651879 ) => true ,
18661880
1881+ ( Type :: AlwaysTruthy , ty) | ( ty, Type :: AlwaysTruthy ) => {
1882+ // `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint.
1883+ // Thus, they are only disjoint if `ty.bool() == AlwaysFalse`.
1884+ ty. bool ( db) . is_always_false ( )
1885+ }
1886+ ( Type :: AlwaysFalsy , ty) | ( ty, Type :: AlwaysFalsy ) => {
1887+ // Similarly, they are only disjoint if `ty.bool() == AlwaysTrue`.
1888+ ty. bool ( db) . is_always_true ( )
1889+ }
1890+
1891+ ( Type :: ProtocolInstance ( left) , Type :: ProtocolInstance ( right) ) => {
1892+ left. is_disjoint_from ( db, right)
1893+ }
1894+
1895+ ( Type :: ProtocolInstance ( protocol) , Type :: SpecialForm ( special_form) )
1896+ | ( Type :: SpecialForm ( special_form) , Type :: ProtocolInstance ( protocol) ) => {
1897+ any_protocol_members_absent_or_disjoint ( db, protocol, special_form. instance_fallback ( db) )
1898+ }
1899+
1900+ ( Type :: ProtocolInstance ( protocol) , Type :: KnownInstance ( known_instance) )
1901+ | ( Type :: KnownInstance ( known_instance) , Type :: ProtocolInstance ( protocol) ) => {
1902+ any_protocol_members_absent_or_disjoint ( db, protocol, known_instance. instance_fallback ( db) )
1903+ }
1904+
1905+ // The absence of a protocol member on one of these types guarantees
1906+ // that the type will be disjoint from the protocol,
1907+ // but the type will not be disjoint from the protocol if it has a member
1908+ // that is of the correct type but is possibly unbound.
1909+ // If accessing a member on this type returns a possibly unbound `Place`,
1910+ // the type will not be a subtype of the protocol but it will also not be
1911+ // disjoint from the protocol, since there are possible subtypes of the type
1912+ // that could satisfy the protocol.
1913+ //
1914+ // ```py
1915+ // class Foo:
1916+ // if coinflip():
1917+ // X = 42
1918+ //
1919+ // class HasX(Protocol):
1920+ // @property
1921+ // def x(self) -> int: ...
1922+ //
1923+ // # `TypeOf[Foo]` (a class-literal type) is not a subtype of `HasX`,
1924+ // # but `TypeOf[Foo]` & HasX` should not simplify to `Never`,
1925+ // # or this branch would be incorrectly understood to be unreachable,
1926+ // # since we would understand the type of `Foo` in this branch to be
1927+ // # `TypeOf[Foo] & HasX` due to `hasattr()` narrowing.
1928+ //
1929+ // if hasattr(Foo, "X"):
1930+ // print(Foo.X)
1931+ // ```
18671932 (
18681933 ty @ ( Type :: LiteralString
18691934 | Type :: StringLiteral ( ..)
@@ -1887,51 +1952,24 @@ impl<'db> Type<'db> {
18871952 | Type :: ModuleLiteral ( ..)
18881953 | Type :: GenericAlias ( ..)
18891954 | Type :: IntLiteral ( ..) ) ,
1890- ) => !ty. satisfies_protocol ( db, protocol, TypeRelation :: Assignability ) ,
1891-
1892- ( Type :: AlwaysTruthy , ty) | ( ty, Type :: AlwaysTruthy ) => {
1893- // `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint.
1894- // Thus, they are only disjoint if `ty.bool() == AlwaysFalse`.
1895- ty. bool ( db) . is_always_false ( )
1896- }
1897- ( Type :: AlwaysFalsy , ty) | ( ty, Type :: AlwaysFalsy ) => {
1898- // Similarly, they are only disjoint if `ty.bool() == AlwaysTrue`.
1899- ty. bool ( db) . is_always_true ( )
1900- }
1901-
1902- ( Type :: ProtocolInstance ( left) , Type :: ProtocolInstance ( right) ) => {
1903- left. is_disjoint_from ( db, right)
1904- }
1905-
1906- ( Type :: ProtocolInstance ( protocol) , Type :: SpecialForm ( special_form) )
1907- | ( Type :: SpecialForm ( special_form) , Type :: ProtocolInstance ( protocol) ) => !special_form
1908- . instance_fallback ( db)
1909- . satisfies_protocol ( db, protocol, TypeRelation :: Assignability ) ,
1910-
1911- ( Type :: ProtocolInstance ( protocol) , Type :: KnownInstance ( known_instance) )
1912- | ( Type :: KnownInstance ( known_instance) , Type :: ProtocolInstance ( protocol) ) => {
1913- !known_instance. instance_fallback ( db) . satisfies_protocol (
1914- db,
1915- protocol,
1916- TypeRelation :: Assignability ,
1917- )
1918- }
1955+ ) => any_protocol_members_absent_or_disjoint ( db, protocol, ty) ,
19191956
1957+ // This is the same as the branch above --
1958+ // once guard patterns are stabilised, it could be unified with that branch
1959+ // (<https://github.com/rust-lang/rust/issues/129967>)
19201960 ( Type :: ProtocolInstance ( protocol) , nominal @ Type :: NominalInstance ( n) )
19211961 | ( nominal @ Type :: NominalInstance ( n) , Type :: ProtocolInstance ( protocol) )
19221962 if n. class . is_final ( db) =>
19231963 {
1924- !nominal . satisfies_protocol ( db, protocol, TypeRelation :: Assignability )
1964+ any_protocol_members_absent_or_disjoint ( db, protocol, nominal )
19251965 }
19261966
19271967 ( Type :: ProtocolInstance ( protocol) , other)
19281968 | ( other, Type :: ProtocolInstance ( protocol) ) => {
19291969 protocol. interface ( db) . members ( db) . any ( |member| {
1930- // TODO: implement disjointness for property/method members as well as attribute members
1931- member. is_attribute_member ( )
1932- && matches ! (
1970+ matches ! (
19331971 other. member( db, member. name( ) ) . place,
1934- Place :: Type ( ty , Boundness :: Bound ) if ty . is_disjoint_from ( db, member . ty ( ) )
1972+ Place :: Type ( attribute_type , _ ) if member . has_disjoint_type_from ( db, attribute_type )
19351973 )
19361974 } )
19371975 }
0 commit comments