mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] Add tests for protocols with generic method members (#20316)
This commit is contained in:
parent
9cdac2d6fb
commit
bf66178959
1 changed files with 131 additions and 2 deletions
|
@ -1801,6 +1801,136 @@ static_assert(is_assignable_to(str, SupportsLessThan))
|
||||||
static_assert(is_assignable_to(int, Invertable))
|
static_assert(is_assignable_to(int, Invertable))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Subtyping of protocols with generic method members
|
||||||
|
|
||||||
|
Protocol method members can be generic. They can have generic contexts scoped to the class:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing_extensions import TypeVar, Self, Protocol
|
||||||
|
from ty_extensions import is_equivalent_to, static_assert, is_assignable_to, is_subtype_of
|
||||||
|
|
||||||
|
class NewStyleClassScoped[T](Protocol):
|
||||||
|
def method(self, input: T) -> None: ...
|
||||||
|
|
||||||
|
S = TypeVar("S")
|
||||||
|
|
||||||
|
class LegacyClassScoped(Protocol[S]):
|
||||||
|
def method(self, input: S) -> None: ...
|
||||||
|
|
||||||
|
static_assert(is_equivalent_to(NewStyleClassScoped, LegacyClassScoped))
|
||||||
|
static_assert(is_equivalent_to(NewStyleClassScoped[int], LegacyClassScoped[int]))
|
||||||
|
|
||||||
|
class NominalGeneric[T]:
|
||||||
|
def method(self, input: T) -> None: ...
|
||||||
|
|
||||||
|
def _[T](x: T) -> T:
|
||||||
|
static_assert(is_equivalent_to(NewStyleClassScoped[T], LegacyClassScoped[T]))
|
||||||
|
static_assert(is_subtype_of(NominalGeneric[T], NewStyleClassScoped[T]))
|
||||||
|
static_assert(is_subtype_of(NominalGeneric[T], LegacyClassScoped[T]))
|
||||||
|
return x
|
||||||
|
|
||||||
|
class NominalConcrete:
|
||||||
|
def method(self, input: int) -> None: ...
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(NominalConcrete, NewStyleClassScoped))
|
||||||
|
static_assert(is_assignable_to(NominalConcrete, LegacyClassScoped))
|
||||||
|
static_assert(is_assignable_to(NominalGeneric[int], NewStyleClassScoped))
|
||||||
|
static_assert(is_assignable_to(NominalGeneric[int], LegacyClassScoped))
|
||||||
|
static_assert(is_assignable_to(NominalGeneric, NewStyleClassScoped[int]))
|
||||||
|
static_assert(is_assignable_to(NominalGeneric, LegacyClassScoped[int]))
|
||||||
|
|
||||||
|
# `NewStyleClassScoped` is implicitly `NewStyleClassScoped[Unknown]`,
|
||||||
|
# and there exist fully static materializations of `NewStyleClassScoped[Unknown]`
|
||||||
|
# where `Nominal` would not be a subtype of the given materialization,
|
||||||
|
# hence there is no subtyping relation:
|
||||||
|
#
|
||||||
|
# TODO: these should pass
|
||||||
|
static_assert(not is_subtype_of(NominalConcrete, NewStyleClassScoped)) # error: [static-assert-error]
|
||||||
|
static_assert(not is_subtype_of(NominalConcrete, LegacyClassScoped)) # error: [static-assert-error]
|
||||||
|
|
||||||
|
# Similarly, `NominalGeneric` is implicitly `NominalGeneric[Unknown`]
|
||||||
|
#
|
||||||
|
# TODO: these should pass
|
||||||
|
static_assert(not is_subtype_of(NominalGeneric, NewStyleClassScoped[int])) # error: [static-assert-error]
|
||||||
|
static_assert(not is_subtype_of(NominalGeneric, LegacyClassScoped[int])) # error: [static-assert-error]
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(NominalConcrete, NewStyleClassScoped[int]))
|
||||||
|
static_assert(is_subtype_of(NominalConcrete, LegacyClassScoped[int]))
|
||||||
|
static_assert(is_subtype_of(NominalGeneric[int], NewStyleClassScoped[int]))
|
||||||
|
static_assert(is_subtype_of(NominalGeneric[int], LegacyClassScoped[int]))
|
||||||
|
|
||||||
|
# TODO: these should pass
|
||||||
|
static_assert(not is_assignable_to(NominalConcrete, NewStyleClassScoped[str])) # error: [static-assert-error]
|
||||||
|
static_assert(not is_assignable_to(NominalConcrete, LegacyClassScoped[str])) # error: [static-assert-error]
|
||||||
|
static_assert(not is_subtype_of(NominalGeneric[int], NewStyleClassScoped[str])) # error: [static-assert-error]
|
||||||
|
static_assert(not is_subtype_of(NominalGeneric[int], LegacyClassScoped[str])) # error: [static-assert-error]
|
||||||
|
```
|
||||||
|
|
||||||
|
And they can also have generic contexts scoped to the method:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class NewStyleFunctionScoped(Protocol):
|
||||||
|
def f[T](self, input: T) -> T: ...
|
||||||
|
|
||||||
|
S = TypeVar("S")
|
||||||
|
|
||||||
|
class LegacyFunctionScoped(Protocol):
|
||||||
|
def f(self, input: S) -> S: ...
|
||||||
|
|
||||||
|
class UsesSelf(Protocol):
|
||||||
|
def g(self: Self) -> Self: ...
|
||||||
|
|
||||||
|
class NominalNewStyle:
|
||||||
|
def f[T](self, input: T) -> T:
|
||||||
|
return input
|
||||||
|
|
||||||
|
class NominalLegacy:
|
||||||
|
def f(self, input: S) -> S:
|
||||||
|
return input
|
||||||
|
|
||||||
|
class NominalWithSelf:
|
||||||
|
def g(self: Self) -> Self:
|
||||||
|
return self
|
||||||
|
|
||||||
|
class NominalNotGeneric:
|
||||||
|
def f(self, input: int) -> int:
|
||||||
|
return input
|
||||||
|
|
||||||
|
class NominalReturningSelfNotGeneric:
|
||||||
|
def g(self) -> "NominalReturningSelfNotGeneric":
|
||||||
|
return self
|
||||||
|
|
||||||
|
# TODO: should pass
|
||||||
|
static_assert(is_equivalent_to(LegacyFunctionScoped, NewStyleFunctionScoped)) # error: [static-assert-error]
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(NominalNewStyle, NewStyleFunctionScoped))
|
||||||
|
static_assert(is_subtype_of(NominalNewStyle, LegacyFunctionScoped))
|
||||||
|
static_assert(not is_assignable_to(NominalNewStyle, UsesSelf))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(NominalLegacy, NewStyleFunctionScoped))
|
||||||
|
static_assert(is_subtype_of(NominalLegacy, LegacyFunctionScoped))
|
||||||
|
static_assert(not is_assignable_to(NominalLegacy, UsesSelf))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(NominalWithSelf, NewStyleFunctionScoped))
|
||||||
|
static_assert(not is_assignable_to(NominalWithSelf, LegacyFunctionScoped))
|
||||||
|
static_assert(is_subtype_of(NominalWithSelf, UsesSelf))
|
||||||
|
|
||||||
|
# TODO: these should pass
|
||||||
|
static_assert(not is_assignable_to(NominalNotGeneric, NewStyleFunctionScoped)) # error: [static-assert-error]
|
||||||
|
static_assert(not is_assignable_to(NominalNotGeneric, LegacyFunctionScoped)) # error: [static-assert-error]
|
||||||
|
static_assert(not is_assignable_to(NominalNotGeneric, UsesSelf))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, NewStyleFunctionScoped))
|
||||||
|
static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, LegacyFunctionScoped))
|
||||||
|
# TODO: should pass
|
||||||
|
static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, UsesSelf)) # error: [static-assert-error]
|
||||||
|
```
|
||||||
|
|
||||||
## Equivalence of protocols with method or property members
|
## Equivalence of protocols with method or property members
|
||||||
|
|
||||||
Two protocols `P1` and `P2`, both with a method member `x`, are considered equivalent if the
|
Two 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):
|
||||||
reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Truthiness of protocol instance
|
## Truthiness of protocol instances
|
||||||
|
|
||||||
An instance of a protocol type generally has ambiguous truthiness:
|
An instance of a protocol type generally has ambiguous truthiness:
|
||||||
|
|
||||||
|
@ -2520,7 +2650,6 @@ Add tests for:
|
||||||
- `super()` on nominal subtypes (explicit and implicit) of protocol classes
|
- `super()` on nominal subtypes (explicit and implicit) of protocol classes
|
||||||
- [Recursive protocols][recursive_protocols_spec]
|
- [Recursive protocols][recursive_protocols_spec]
|
||||||
- Generic protocols
|
- Generic protocols
|
||||||
- Non-generic protocols with function-scoped generic methods
|
|
||||||
- Protocols with instance attributes annotated with `Callable` (can a nominal type with a method
|
- Protocols with instance attributes annotated with `Callable` (can a nominal type with a method
|
||||||
satisfy that protocol, and if so in what cases?)
|
satisfy that protocol, and if so in what cases?)
|
||||||
- Protocols decorated with `@final`
|
- Protocols decorated with `@final`
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue