@@ -607,11 +607,22 @@ class HasXY(Protocol):
607607class Foo :
608608 x: int
609609
610+ class IntSub (int ): ...
611+
612+ class HasXIntSub (Protocol ):
613+ x: IntSub
614+
610615static_assert(is_subtype_of(Foo, HasX))
611616static_assert(is_assignable_to(Foo, HasX))
612617static_assert(not is_subtype_of(Foo, HasXY))
613618static_assert(not is_assignable_to(Foo, HasXY))
614619
620+ # TODO : these should pass
621+ static_assert(not is_subtype_of(HasXIntSub, HasX)) # error: [static-assert-error]
622+ static_assert(not is_assignable_to(HasXIntSub, HasX)) # error: [static-assert-error]
623+ static_assert(not is_subtype_of(HasX, HasXIntSub)) # error: [static-assert-error]
624+ static_assert(not is_assignable_to(HasX, HasXIntSub)) # error: [static-assert-error]
625+
615626class FooSub (Foo ): ...
616627
617628static_assert(is_subtype_of(FooSub, HasX))
@@ -1546,6 +1557,22 @@ static_assert(is_subtype_of(XImplicitFinal, HasXProperty))
15461557static_assert(is_assignable_to(XImplicitFinal, HasXProperty))
15471558```
15481559
1560+ But only if it has the correct type:
1561+
1562+ ``` py
1563+ class XAttrBad :
1564+ x: str
1565+
1566+ class HasStrXProperty (Protocol ):
1567+ @ property
1568+ def x (self ) -> str : ...
1569+
1570+ # TODO : these should pass
1571+ static_assert(not is_assignable_to(XAttrBad, HasXProperty)) # error: [static-assert-error]
1572+ static_assert(not is_assignable_to(HasStrXProperty, HasXProperty)) # error: [static-assert-error]
1573+ static_assert(not is_assignable_to(HasXProperty, HasStrXProperty)) # error: [static-assert-error]
1574+ ```
1575+
15491576A read-only property on a protocol, unlike a mutable attribute, is covariant: ` XSub ` in the below
15501577example satisfies the ` HasXProperty ` interface even though the type of the ` x ` attribute on ` XSub `
15511578is a subtype of ` int ` rather than being exactly ` int ` .
@@ -1558,6 +1585,13 @@ class XSub:
15581585
15591586static_assert(is_subtype_of(XSub, HasXProperty))
15601587static_assert(is_assignable_to(XSub, HasXProperty))
1588+
1589+ class XSubProto (Protocol ):
1590+ @ property
1591+ def x (self ) -> XSub: ...
1592+
1593+ static_assert(is_subtype_of(XSubProto, HasXProperty))
1594+ static_assert(is_assignable_to(XSubProto, HasXProperty))
15611595```
15621596
15631597A read/write property on a protocol, where the getter returns the same type that the setter takes,
@@ -1582,8 +1616,8 @@ class XReadProperty:
15821616 return 42
15831617
15841618# TODO : these should pass
1585- static_assert(not is_subtype_of(XReadProperty, HasXProperty )) # error: [static-assert-error]
1586- static_assert(not is_assignable_to(XReadProperty, HasXProperty )) # error: [static-assert-error]
1619+ static_assert(not is_subtype_of(XReadProperty, HasMutableXProperty )) # error: [static-assert-error]
1620+ static_assert(not is_assignable_to(XReadProperty, HasMutableXProperty )) # error: [static-assert-error]
15871621
15881622class XReadWriteProperty :
15891623 @ property
@@ -1593,18 +1627,19 @@ class XReadWriteProperty:
15931627 @x.setter
15941628 def x (self , val : int ) -> None : ...
15951629
1596- static_assert(is_subtype_of(XReadWriteProperty, HasXProperty ))
1597- static_assert(is_assignable_to(XReadWriteProperty, HasXProperty ))
1630+ static_assert(is_subtype_of(XReadWriteProperty, HasMutableXProperty ))
1631+ static_assert(is_assignable_to(XReadWriteProperty, HasMutableXProperty ))
15981632
15991633class XSub :
16001634 x: MyInt
16011635
1602- static_assert(not is_subtype_of(XSub, XReadWriteProperty))
1603- static_assert(not is_assignable_to(XSub, XReadWriteProperty))
1636+ # TODO : these should pass
1637+ static_assert(not is_subtype_of(XSub, HasMutableXProperty)) # error: [static-assert-error]
1638+ static_assert(not is_assignable_to(XSub, HasMutableXProperty)) # error: [static-assert-error]
16041639```
16051640
16061641A protocol with a read/write property ` x ` is exactly equivalent to a protocol with a mutable
1607- attribute ` x ` . Both are subtypes of a protocol with a read-only prooperty ` x ` :
1642+ attribute ` x ` . Both are subtypes of a protocol with a read-only property ` x ` :
16081643
16091644``` py
16101645from ty_extensions import is_equivalent_to
@@ -1618,8 +1653,22 @@ static_assert(is_equivalent_to(HasMutableXAttr, HasMutableXProperty)) # error:
16181653static_assert(is_subtype_of(HasMutableXAttr, HasXProperty))
16191654static_assert(is_assignable_to(HasMutableXAttr, HasXProperty))
16201655
1656+ static_assert(is_subtype_of(HasMutableXAttr, HasMutableXProperty))
1657+ static_assert(is_assignable_to(HasMutableXAttr, HasMutableXProperty))
1658+
16211659static_assert(is_subtype_of(HasMutableXProperty, HasXProperty))
16221660static_assert(is_assignable_to(HasMutableXProperty, HasXProperty))
1661+
1662+ static_assert(is_subtype_of(HasMutableXProperty, HasMutableXAttr))
1663+ static_assert(is_assignable_to(HasMutableXProperty, HasMutableXAttr))
1664+
1665+ class HasMutableXAttrWrongType (Protocol ):
1666+ x: str
1667+
1668+ # TODO : these should pass
1669+ static_assert(not is_assignable_to(HasMutableXAttrWrongType, HasXProperty)) # error: [static-assert-error]
1670+ static_assert(not is_assignable_to(HasMutableXAttrWrongType, HasMutableXProperty)) # error: [static-assert-error]
1671+ static_assert(not is_assignable_to(HasMutableXProperty, HasMutableXAttrWrongType)) # error: [static-assert-error]
16231672```
16241673
16251674A read/write property on a protocol, where the setter accepts a subtype of the type returned by the
@@ -2212,6 +2261,122 @@ static_assert(is_equivalent_to(A | B | P1, P2 | B | A))
22122261static_assert(is_equivalent_to(A | B | P3, P4 | B | A)) # error: [static-assert-error]
22132262```
22142263
2264+ ## Subtyping between two protocol types with method members
2265+
2266+ A protocol ` PSub ` with a method member can be considered a subtype of a protocol ` PSuper ` with a
2267+ method member if the signature of the member on ` PSub ` is a subtype of the signature of the member
2268+ on ` PSuper ` :
2269+
2270+ ``` py
2271+ from typing import Protocol
2272+ from ty_extensions import static_assert, is_subtype_of, is_assignable_to
2273+
2274+ class Super : ...
2275+ class Sub (Super ): ...
2276+ class Unrelated : ...
2277+
2278+ class MethodPSuper (Protocol ):
2279+ def f (self ) -> Super: ...
2280+
2281+ class MethodPSub (Protocol ):
2282+ def f (self ) -> Sub: ...
2283+
2284+ class MethodPUnrelated (Protocol ):
2285+ def f (self ) -> Unrelated: ...
2286+
2287+ static_assert(is_subtype_of(MethodPSub, MethodPSuper))
2288+
2289+ # TODO : these should pass
2290+ static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper)) # error: [static-assert-error]
2291+ static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated)) # error: [static-assert-error]
2292+ static_assert(not is_assignable_to(MethodPSuper, MethodPSub)) # error: [static-assert-error]
2293+ ```
2294+
2295+ ## Subtyping between protocols with method members and protocols with non-method members
2296+
2297+ A protocol with a method member can be considered a subtype of a protocol with a read-only
2298+ ` @property ` member that returns a ` Callable ` type:
2299+
2300+ ``` py
2301+ from typing import Protocol, Callable
2302+ from ty_extensions import static_assert, is_subtype_of, is_assignable_to
2303+
2304+ class PropertyInt (Protocol ):
2305+ @ property
2306+ def f (self ) -> Callable[[], int ]: ...
2307+
2308+ class PropertyBool (Protocol ):
2309+ @ property
2310+ def f (self ) -> Callable[[], bool ]: ...
2311+
2312+ class PropertyNotReturningCallable (Protocol ):
2313+ @ property
2314+ def f (self ) -> int : ...
2315+
2316+ class PropertyWithIncorrectSignature (Protocol ):
2317+ @ property
2318+ def f (self ) -> Callable[[object ], int ]: ...
2319+
2320+ class Method (Protocol ):
2321+ def f (self ) -> bool : ...
2322+
2323+ static_assert(is_subtype_of(Method, PropertyInt))
2324+ static_assert(is_subtype_of(Method, PropertyBool))
2325+
2326+ # TODO : these should pass
2327+ static_assert(not is_assignable_to(Method, PropertyNotReturningCallable)) # error: [static-assert-error]
2328+ static_assert(not is_assignable_to(Method, PropertyWithIncorrectSignature)) # error: [static-assert-error]
2329+ ```
2330+
2331+ However, a protocol with a method member can never be considered a subtype of a protocol with a
2332+ writable property member of the same name, as method members are covariant and immutable:
2333+
2334+ ``` py
2335+ class ReadWriteProperty (Protocol ):
2336+ @ property
2337+ def f (self ) -> Callable[[], bool ]: ...
2338+ @f.setter
2339+ def f (self , val : Callable[[], bool ]): ...
2340+
2341+ # TODO : should pass
2342+ static_assert(not is_assignable_to(Method, ReadWriteProperty)) # error: [static-assert-error]
2343+ ```
2344+
2345+ And for the same reason, they are never assignable to attribute members (which are also mutable):
2346+
2347+ ``` py
2348+ class Attribute (Protocol ):
2349+ f: Callable[[], bool ]
2350+
2351+ # TODO : should pass
2352+ static_assert(not is_assignable_to(Method, Attribute)) # error: [static-assert-error]
2353+ ```
2354+
2355+ Protocols with attribute members, meanwhile, cannot be assigned to protocols with method members,
2356+ since a method member is guaranteed to exist on the meta-type as well as the instance type, whereas
2357+ this is not true for attribute members. The same principle also applies for protocols with property
2358+ members
2359+
2360+ ``` py
2361+ # TODO : this should pass
2362+ static_assert(not is_assignable_to(PropertyBool, Method)) # error: [static-assert-error]
2363+ static_assert(not is_assignable_to(Attribute, Method)) # error: [static-assert-error]
2364+ ```
2365+
2366+ But an exception to this rule is if an attribute member is marked as ` ClassVar ` , as this guarantees
2367+ that the member will be available on the meta-type as well as the instance type for inhabitants of
2368+ the protocol:
2369+
2370+ ``` py
2371+ from typing import ClassVar
2372+
2373+ class ClassVarAttribute (Protocol ):
2374+ f: ClassVar[Callable[[], bool ]]
2375+
2376+ static_assert(is_subtype_of(ClassVarAttribute, Method))
2377+ static_assert(is_assignable_to(ClassVarAttribute, Method))
2378+ ```
2379+
22152380## Narrowing of protocols
22162381
22172382<!-- snapshot-diagnostics -->
@@ -2549,7 +2714,10 @@ class RecursiveOptionalParent(Protocol):
25492714
25502715static_assert(is_assignable_to(RecursiveOptionalParent, RecursiveOptionalParent))
25512716
2552- static_assert(is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent))
2717+ # Due to invariance of mutable attribute members, neither is assignable to the other
2718+ #
2719+ # TODO : should pass
2720+ static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent)) # error: [static-assert-error]
25532721static_assert(not is_assignable_to(RecursiveOptionalParent, RecursiveNonFullyStatic))
25542722
25552723class Other (Protocol ):
0 commit comments