mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[ty] Add more tests for protocols (#20095)
Co-authored-by: Shunsuke Shibayama <sbym1346@gmail.com>
This commit is contained in:
parent
9ab276b345
commit
d71518b369
1 changed files with 108 additions and 7 deletions
|
@ -1392,10 +1392,16 @@ static_assert(is_subtype_of(XClassVar, HasXProperty))
|
||||||
static_assert(is_assignable_to(XClassVar, HasXProperty))
|
static_assert(is_assignable_to(XClassVar, HasXProperty))
|
||||||
|
|
||||||
class XFinal:
|
class XFinal:
|
||||||
x: Final = 42
|
x: Final[int] = 42
|
||||||
|
|
||||||
static_assert(is_subtype_of(XFinal, HasXProperty))
|
static_assert(is_subtype_of(XFinal, HasXProperty))
|
||||||
static_assert(is_assignable_to(XFinal, HasXProperty))
|
static_assert(is_assignable_to(XFinal, HasXProperty))
|
||||||
|
|
||||||
|
class XImplicitFinal:
|
||||||
|
x: Final = 42
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(XImplicitFinal, HasXProperty))
|
||||||
|
static_assert(is_assignable_to(XImplicitFinal, HasXProperty))
|
||||||
```
|
```
|
||||||
|
|
||||||
A read-only property on a protocol, unlike a mutable attribute, is covariant: `XSub` in the below
|
A read-only property on a protocol, unlike a mutable attribute, is covariant: `XSub` in the below
|
||||||
|
@ -1451,9 +1457,8 @@ static_assert(is_assignable_to(XReadWriteProperty, HasXProperty))
|
||||||
class XSub:
|
class XSub:
|
||||||
x: MyInt
|
x: MyInt
|
||||||
|
|
||||||
# TODO: should pass
|
static_assert(not is_subtype_of(XSub, XReadWriteProperty))
|
||||||
static_assert(not is_subtype_of(XSub, HasXProperty)) # error: [static-assert-error]
|
static_assert(not is_assignable_to(XSub, XReadWriteProperty))
|
||||||
static_assert(not is_assignable_to(XSub, HasXProperty)) # error: [static-assert-error]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
A protocol with a read/write property `x` is exactly equivalent to a protocol with a mutable
|
A protocol with a read/write property `x` is exactly equivalent to a protocol with a mutable
|
||||||
|
@ -1549,7 +1554,7 @@ class Descriptor:
|
||||||
def __get__(self, instance, owner) -> MyInt:
|
def __get__(self, instance, owner) -> MyInt:
|
||||||
return MyInt(0)
|
return MyInt(0)
|
||||||
|
|
||||||
def __set__(self, value: int) -> None: ...
|
def __set__(self, instance, value: int) -> None: ...
|
||||||
|
|
||||||
class XCustomDescriptor:
|
class XCustomDescriptor:
|
||||||
x: Descriptor = Descriptor()
|
x: Descriptor = Descriptor()
|
||||||
|
@ -1595,6 +1600,16 @@ static_assert(is_assignable_to(HasGetAttrAndSetAttr, HasXProperty))
|
||||||
# TODO: these should pass
|
# TODO: these should pass
|
||||||
static_assert(is_subtype_of(HasGetAttrAndSetAttr, XAsymmetricProperty)) # error: [static-assert-error]
|
static_assert(is_subtype_of(HasGetAttrAndSetAttr, XAsymmetricProperty)) # error: [static-assert-error]
|
||||||
static_assert(is_assignable_to(HasGetAttrAndSetAttr, XAsymmetricProperty)) # error: [static-assert-error]
|
static_assert(is_assignable_to(HasGetAttrAndSetAttr, XAsymmetricProperty)) # error: [static-assert-error]
|
||||||
|
|
||||||
|
class HasSetAttrWithUnsuitableInput:
|
||||||
|
def __getattr__(self, attr: str) -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def __setattr__(self, attr: str, value: str) -> None: ...
|
||||||
|
|
||||||
|
# TODO: these should pass
|
||||||
|
static_assert(not is_subtype_of(HasSetAttrWithUnsuitableInput, HasMutableXProperty)) # error: [static-assert-error]
|
||||||
|
static_assert(not is_assignable_to(HasSetAttrWithUnsuitableInput, HasMutableXProperty)) # error: [static-assert-error]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Subtyping of protocols with method members
|
## Subtyping of protocols with method members
|
||||||
|
@ -1684,11 +1699,12 @@ class Bar:
|
||||||
f(Bar()) # error: [invalid-argument-type]
|
f(Bar()) # error: [invalid-argument-type]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Equivalence of protocols with method 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
|
||||||
signature of `P1.x` is equivalent to the signature of `P2.x`, even though ty would normally model
|
signature of `P1.x` is equivalent to the signature of `P2.x`, even though ty would normally model
|
||||||
any two function definitions as inhabiting distinct function-literal types.
|
any two function definitions as inhabiting distinct function-literal types. The same is also true
|
||||||
|
for property members.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
|
@ -1700,7 +1716,26 @@ class P1(Protocol):
|
||||||
class P2(Protocol):
|
class P2(Protocol):
|
||||||
def x(self, y: int) -> None: ...
|
def x(self, y: int) -> None: ...
|
||||||
|
|
||||||
|
class P3(Protocol):
|
||||||
|
@property
|
||||||
|
def y(self) -> str: ...
|
||||||
|
@property
|
||||||
|
def z(self) -> bytes: ...
|
||||||
|
@z.setter
|
||||||
|
def z(self, value: int) -> None: ...
|
||||||
|
|
||||||
|
class P4(Protocol):
|
||||||
|
@property
|
||||||
|
def y(self) -> str: ...
|
||||||
|
@property
|
||||||
|
def z(self) -> bytes: ...
|
||||||
|
@z.setter
|
||||||
|
def z(self, value: int) -> None: ...
|
||||||
|
|
||||||
static_assert(is_equivalent_to(P1, P2))
|
static_assert(is_equivalent_to(P1, P2))
|
||||||
|
|
||||||
|
# TODO: should pass
|
||||||
|
static_assert(is_equivalent_to(P3, P4)) # error: [static-assert-error]
|
||||||
```
|
```
|
||||||
|
|
||||||
As with protocols that only have non-method members, this also holds true when they appear in
|
As with protocols that only have non-method members, this also holds true when they appear in
|
||||||
|
@ -1711,6 +1746,9 @@ class A: ...
|
||||||
class B: ...
|
class B: ...
|
||||||
|
|
||||||
static_assert(is_equivalent_to(A | B | P1, P2 | B | A))
|
static_assert(is_equivalent_to(A | B | P1, P2 | B | A))
|
||||||
|
|
||||||
|
# TODO: should pass
|
||||||
|
static_assert(is_equivalent_to(A | B | P3, P4 | B | A)) # error: [static-assert-error]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Narrowing of protocols
|
## Narrowing of protocols
|
||||||
|
@ -2198,6 +2236,69 @@ def f(value: Iterator):
|
||||||
cast(Iterator, value) # error: [redundant-cast]
|
cast(Iterator, value) # error: [redundant-cast]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Recursive generic protocols
|
||||||
|
|
||||||
|
This snippet caused us to stack overflow on an early version of
|
||||||
|
<https://github.com/astral-sh/ruff/pull/19866>:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Protocol, TypeVar
|
||||||
|
|
||||||
|
class A: ...
|
||||||
|
|
||||||
|
class Foo[T](Protocol):
|
||||||
|
def x(self) -> "T | Foo[T]": ...
|
||||||
|
|
||||||
|
y: A | Foo[A]
|
||||||
|
|
||||||
|
# The same thing, but using the legacy syntax:
|
||||||
|
|
||||||
|
S = TypeVar("S")
|
||||||
|
|
||||||
|
class Bar(Protocol[S]):
|
||||||
|
def x(self) -> "S | Bar[S]": ...
|
||||||
|
|
||||||
|
z: S | Bar[S]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recursive generic protocols with property members
|
||||||
|
|
||||||
|
An early version of <https://github.com/astral-sh/ruff/pull/19936> caused stack overflows on this
|
||||||
|
snippet:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
class Foo[T]: ...
|
||||||
|
|
||||||
|
class A(Protocol):
|
||||||
|
@property
|
||||||
|
def _(self: "A") -> Foo: ...
|
||||||
|
|
||||||
|
class B(Protocol):
|
||||||
|
@property
|
||||||
|
def b(self) -> Foo[A]: ...
|
||||||
|
|
||||||
|
class C(Undefined): ... # error: "Name `Undefined` used when not defined"
|
||||||
|
|
||||||
|
class D:
|
||||||
|
b: Foo[C]
|
||||||
|
|
||||||
|
class E[T: B](Protocol): ...
|
||||||
|
|
||||||
|
x: E[D]
|
||||||
|
```
|
||||||
|
|
||||||
## Meta-protocols
|
## Meta-protocols
|
||||||
|
|
||||||
Where `P` is a protocol type, a class object `N` can be said to inhabit the type `type[P]` if:
|
Where `P` is a protocol type, a class object `N` can be said to inhabit the type `type[P]` if:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue