mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
[ty] Rework disjointness of protocol instances vs types with possibly unbound attributes (#19043)
This commit is contained in:
parent
c6fd11fe36
commit
ebf59e2bef
4 changed files with 207 additions and 52 deletions
|
@ -964,6 +964,121 @@ def _(arg1: Intersection[HasX, NotFinalNominal], arg2: Intersection[HasX, FinalN
|
|||
reveal_type(arg2) # revealed: Never
|
||||
```
|
||||
|
||||
The disjointness of a single protocol member with the type of an attribute on another type is enough
|
||||
to make the whole protocol disjoint from the other type, even if all other members on the protocol
|
||||
are satisfied by the other type. This applies to both `@final` types and non-final types:
|
||||
|
||||
```py
|
||||
class Proto(Protocol):
|
||||
x: int
|
||||
y: str
|
||||
z: bytes
|
||||
|
||||
class Foo:
|
||||
x: int
|
||||
y: str
|
||||
z: None
|
||||
|
||||
static_assert(is_disjoint_from(Proto, Foo))
|
||||
|
||||
@final
|
||||
class FinalFoo:
|
||||
x: int
|
||||
y: str
|
||||
z: None
|
||||
|
||||
static_assert(is_disjoint_from(Proto, FinalFoo))
|
||||
```
|
||||
|
||||
## Intersections of protocols with types that have possibly unbound attributes
|
||||
|
||||
Note that if a `@final` class has a possibly unbound attribute corresponding to the protocol member,
|
||||
instance types and class-literal types referring to that class cannot be a subtype of the protocol
|
||||
but will also not be disjoint from the protocol:
|
||||
|
||||
`a.py`:
|
||||
|
||||
```py
|
||||
from typing import final, ClassVar, Protocol
|
||||
from ty_extensions import TypeOf, static_assert, is_subtype_of, is_disjoint_from, is_assignable_to
|
||||
|
||||
def who_knows() -> bool:
|
||||
return False
|
||||
|
||||
@final
|
||||
class Foo:
|
||||
if who_knows():
|
||||
x: ClassVar[int] = 42
|
||||
|
||||
class HasReadOnlyX(Protocol):
|
||||
@property
|
||||
def x(self) -> int: ...
|
||||
|
||||
static_assert(not is_subtype_of(Foo, HasReadOnlyX))
|
||||
static_assert(not is_assignable_to(Foo, HasReadOnlyX))
|
||||
static_assert(not is_disjoint_from(Foo, HasReadOnlyX))
|
||||
|
||||
static_assert(not is_subtype_of(type[Foo], HasReadOnlyX))
|
||||
static_assert(not is_assignable_to(type[Foo], HasReadOnlyX))
|
||||
static_assert(not is_disjoint_from(type[Foo], HasReadOnlyX))
|
||||
|
||||
static_assert(not is_subtype_of(TypeOf[Foo], HasReadOnlyX))
|
||||
static_assert(not is_assignable_to(TypeOf[Foo], HasReadOnlyX))
|
||||
static_assert(not is_disjoint_from(TypeOf[Foo], HasReadOnlyX))
|
||||
```
|
||||
|
||||
A similar principle applies to module-literal types that have possibly unbound attributes:
|
||||
|
||||
`b.py`:
|
||||
|
||||
```py
|
||||
def who_knows() -> bool:
|
||||
return False
|
||||
|
||||
if who_knows():
|
||||
x: int = 42
|
||||
```
|
||||
|
||||
`c.py`:
|
||||
|
||||
```py
|
||||
import b
|
||||
from a import HasReadOnlyX
|
||||
from ty_extensions import TypeOf, static_assert, is_subtype_of, is_disjoint_from, is_assignable_to
|
||||
|
||||
static_assert(not is_subtype_of(TypeOf[b], HasReadOnlyX))
|
||||
static_assert(not is_assignable_to(TypeOf[b], HasReadOnlyX))
|
||||
static_assert(not is_disjoint_from(TypeOf[b], HasReadOnlyX))
|
||||
```
|
||||
|
||||
If the possibly unbound attribute's type is disjoint from the type of the protocol member, though,
|
||||
it is still disjoint from the protocol. This applies to both `@final` types and non-final types:
|
||||
|
||||
`d.py`:
|
||||
|
||||
```py
|
||||
from a import HasReadOnlyX, who_knows
|
||||
from typing import final, ClassVar, Protocol
|
||||
from ty_extensions import static_assert, is_disjoint_from, TypeOf
|
||||
|
||||
class Proto(Protocol):
|
||||
x: int
|
||||
|
||||
class Foo:
|
||||
def __init__(self):
|
||||
if who_knows():
|
||||
self.x: None = None
|
||||
|
||||
@final
|
||||
class FinalFoo:
|
||||
def __init__(self):
|
||||
if who_knows():
|
||||
self.x: None = None
|
||||
|
||||
static_assert(is_disjoint_from(Foo, Proto))
|
||||
static_assert(is_disjoint_from(FinalFoo, Proto))
|
||||
```
|
||||
|
||||
## Satisfying a protocol's interface
|
||||
|
||||
A type does not have to be an `Instance` type in order to be a subtype of a protocol. Other
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue