@@ -1801,6 +1801,136 @@ static_assert(is_assignable_to(str, SupportsLessThan))
18011801static_assert(is_assignable_to(int , Invertable))
18021802```
18031803
1804+ ## Subtyping of protocols with generic method members
1805+
1806+ Protocol method members can be generic. They can have generic contexts scoped to the class:
1807+
1808+ ``` toml
1809+ [environment ]
1810+ python-version = " 3.12"
1811+ ```
1812+
1813+ ``` py
1814+ from typing_extensions import TypeVar, Self, Protocol
1815+ from ty_extensions import is_equivalent_to, static_assert, is_assignable_to, is_subtype_of
1816+
1817+ class NewStyleClassScoped[T](Protocol):
1818+ def method (self , input : T) -> None : ...
1819+
1820+ S = TypeVar(" S" )
1821+
1822+ class LegacyClassScoped (Protocol[S]):
1823+ def method (self , input : S) -> None : ...
1824+
1825+ static_assert(is_equivalent_to(NewStyleClassScoped, LegacyClassScoped))
1826+ static_assert(is_equivalent_to(NewStyleClassScoped[int ], LegacyClassScoped[int ]))
1827+
1828+ class NominalGeneric[T]:
1829+ def method (self , input : T) -> None : ...
1830+
1831+ def _[T](x: T) -> T:
1832+ static_assert(is_equivalent_to(NewStyleClassScoped[T], LegacyClassScoped[T]))
1833+ static_assert(is_subtype_of(NominalGeneric[T], NewStyleClassScoped[T]))
1834+ static_assert(is_subtype_of(NominalGeneric[T], LegacyClassScoped[T]))
1835+ return x
1836+
1837+ class NominalConcrete :
1838+ def method (self , input : int ) -> None : ...
1839+
1840+ static_assert(is_assignable_to(NominalConcrete, NewStyleClassScoped))
1841+ static_assert(is_assignable_to(NominalConcrete, LegacyClassScoped))
1842+ static_assert(is_assignable_to(NominalGeneric[int ], NewStyleClassScoped))
1843+ static_assert(is_assignable_to(NominalGeneric[int ], LegacyClassScoped))
1844+ static_assert(is_assignable_to(NominalGeneric, NewStyleClassScoped[int ]))
1845+ static_assert(is_assignable_to(NominalGeneric, LegacyClassScoped[int ]))
1846+
1847+ # `NewStyleClassScoped` is implicitly `NewStyleClassScoped[Unknown]`,
1848+ # and there exist fully static materializations of `NewStyleClassScoped[Unknown]`
1849+ # where `Nominal` would not be a subtype of the given materialization,
1850+ # hence there is no subtyping relation:
1851+ #
1852+ # TODO : these should pass
1853+ static_assert(not is_subtype_of(NominalConcrete, NewStyleClassScoped)) # error: [static-assert-error]
1854+ static_assert(not is_subtype_of(NominalConcrete, LegacyClassScoped)) # error: [static-assert-error]
1855+
1856+ # Similarly, `NominalGeneric` is implicitly `NominalGeneric[Unknown`]
1857+ #
1858+ # TODO : these should pass
1859+ static_assert(not is_subtype_of(NominalGeneric, NewStyleClassScoped[int ])) # error: [static-assert-error]
1860+ static_assert(not is_subtype_of(NominalGeneric, LegacyClassScoped[int ])) # error: [static-assert-error]
1861+
1862+ static_assert(is_subtype_of(NominalConcrete, NewStyleClassScoped[int ]))
1863+ static_assert(is_subtype_of(NominalConcrete, LegacyClassScoped[int ]))
1864+ static_assert(is_subtype_of(NominalGeneric[int ], NewStyleClassScoped[int ]))
1865+ static_assert(is_subtype_of(NominalGeneric[int ], LegacyClassScoped[int ]))
1866+
1867+ # TODO : these should pass
1868+ static_assert(not is_assignable_to(NominalConcrete, NewStyleClassScoped[str ])) # error: [static-assert-error]
1869+ static_assert(not is_assignable_to(NominalConcrete, LegacyClassScoped[str ])) # error: [static-assert-error]
1870+ static_assert(not is_subtype_of(NominalGeneric[int ], NewStyleClassScoped[str ])) # error: [static-assert-error]
1871+ static_assert(not is_subtype_of(NominalGeneric[int ], LegacyClassScoped[str ])) # error: [static-assert-error]
1872+ ```
1873+
1874+ And they can also have generic contexts scoped to the method:
1875+
1876+ ``` py
1877+ class NewStyleFunctionScoped (Protocol ):
1878+ def f[T](self , input : T) -> T: ...
1879+
1880+ S = TypeVar(" S" )
1881+
1882+ class LegacyFunctionScoped (Protocol ):
1883+ def f (self , input : S) -> S: ...
1884+
1885+ class UsesSelf (Protocol ):
1886+ def g (self : Self) -> Self: ...
1887+
1888+ class NominalNewStyle :
1889+ def f[T](self , input : T) -> T:
1890+ return input
1891+
1892+ class NominalLegacy :
1893+ def f (self , input : S) -> S:
1894+ return input
1895+
1896+ class NominalWithSelf :
1897+ def g (self : Self) -> Self:
1898+ return self
1899+
1900+ class NominalNotGeneric :
1901+ def f (self , input : int ) -> int :
1902+ return input
1903+
1904+ class NominalReturningSelfNotGeneric :
1905+ def g (self ) -> " NominalReturningSelfNotGeneric" :
1906+ return self
1907+
1908+ # TODO : should pass
1909+ static_assert(is_equivalent_to(LegacyFunctionScoped, NewStyleFunctionScoped)) # error: [static-assert-error]
1910+
1911+ static_assert(is_subtype_of(NominalNewStyle, NewStyleFunctionScoped))
1912+ static_assert(is_subtype_of(NominalNewStyle, LegacyFunctionScoped))
1913+ static_assert(not is_assignable_to(NominalNewStyle, UsesSelf))
1914+
1915+ static_assert(is_subtype_of(NominalLegacy, NewStyleFunctionScoped))
1916+ static_assert(is_subtype_of(NominalLegacy, LegacyFunctionScoped))
1917+ static_assert(not is_assignable_to(NominalLegacy, UsesSelf))
1918+
1919+ static_assert(not is_assignable_to(NominalWithSelf, NewStyleFunctionScoped))
1920+ static_assert(not is_assignable_to(NominalWithSelf, LegacyFunctionScoped))
1921+ static_assert(is_subtype_of(NominalWithSelf, UsesSelf))
1922+
1923+ # TODO : these should pass
1924+ static_assert(not is_assignable_to(NominalNotGeneric, NewStyleFunctionScoped)) # error: [static-assert-error]
1925+ static_assert(not is_assignable_to(NominalNotGeneric, LegacyFunctionScoped)) # error: [static-assert-error]
1926+ static_assert(not is_assignable_to(NominalNotGeneric, UsesSelf))
1927+
1928+ static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, NewStyleFunctionScoped))
1929+ static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, LegacyFunctionScoped))
1930+ # TODO : should pass
1931+ static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, UsesSelf)) # error: [static-assert-error]
1932+ ```
1933+
18041934## Equivalence of protocols with method or property members
18051935
18061936Two protocols ` P1 ` and ` P2 ` , both with a method member ` x ` , are considered equivalent if the
@@ -1919,7 +2049,7 @@ def f(arg1: type, arg2: type):
19192049 reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
19202050```
19212051
1922- ## Truthiness of protocol instance
2052+ ## Truthiness of protocol instances
19232053
19242054An instance of a protocol type generally has ambiguous truthiness:
19252055
@@ -2520,7 +2650,6 @@ Add tests for:
25202650- ` super() ` on nominal subtypes (explicit and implicit) of protocol classes
25212651- [ Recursive protocols] [ recursive_protocols_spec ]
25222652- Generic protocols
2523- - Non-generic protocols with function-scoped generic methods
25242653- Protocols with instance attributes annotated with ` Callable ` (can a nominal type with a method
25252654 satisfy that protocol, and if so in what cases?)
25262655- Protocols decorated with ` @final `
0 commit comments