mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:10 +00:00
[red-knot] Add more tests for protocol members (#17550)
This commit is contained in:
parent
99fa850e53
commit
f9c7908bb7
1 changed files with 73 additions and 0 deletions
|
@ -403,12 +403,37 @@ class Lumberjack(Protocol):
|
|||
reveal_type(get_protocol_members(Lumberjack)) # revealed: @Todo(specialized non-generic class)
|
||||
```
|
||||
|
||||
A sub-protocol inherits and extends the members of its superclass protocol(s):
|
||||
|
||||
```py
|
||||
class Bar(Protocol):
|
||||
spam: str
|
||||
|
||||
class Baz(Bar, Protocol):
|
||||
ham: memoryview
|
||||
|
||||
# TODO: `tuple[Literal["spam", "ham"]]` or `frozenset[Literal["spam", "ham"]]`
|
||||
reveal_type(get_protocol_members(Baz)) # revealed: @Todo(specialized non-generic class)
|
||||
|
||||
class Baz2(Bar, Foo, Protocol): ...
|
||||
|
||||
# TODO: either
|
||||
# `tuple[Literal["spam"], Literal["x"], Literal["y"], Literal["z"], Literal["method_member"]]`
|
||||
# or `frozenset[Literal["spam", "x", "y", "z", "method_member"]]`
|
||||
reveal_type(get_protocol_members(Baz2)) # revealed: @Todo(specialized non-generic class)
|
||||
```
|
||||
|
||||
## Subtyping of protocols with attribute members
|
||||
|
||||
In the following example, the protocol class `HasX` defines an interface such that any other fully
|
||||
static type can be said to be a subtype of `HasX` if all inhabitants of that other type have a
|
||||
mutable `x` attribute of type `int`:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
from knot_extensions import static_assert, is_assignable_to, is_subtype_of
|
||||
|
@ -548,6 +573,54 @@ def f(arg: HasXWithDefault):
|
|||
reveal_type(type(arg).x) # revealed: int
|
||||
```
|
||||
|
||||
Assignments in a class body of a protocol -- of any kind -- are not permitted by red-knot unless the
|
||||
symbol being assigned to is also explicitly declared in the protocol's class body. Note that this is
|
||||
stricter validation of protocol members than many other type checkers currently apply (as of
|
||||
2025/04/21).
|
||||
|
||||
The reason for this strict validation is that undeclared variables in the class body would lead to
|
||||
an ambiguous interface being declared by the protocol.
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAlias, get_protocol_members
|
||||
|
||||
class MyContext:
|
||||
def __enter__(self) -> int:
|
||||
return 42
|
||||
|
||||
def __exit__(self, *args) -> None: ...
|
||||
|
||||
class LotsOfBindings(Protocol):
|
||||
a: int
|
||||
a = 42 # this is fine, since `a` is declared in the class body
|
||||
b: int = 56 # this is also fine, by the same principle
|
||||
|
||||
type c = str # this is very strange but I can't see a good reason to disallow it
|
||||
d: TypeAlias = bytes # same here
|
||||
|
||||
class Nested: ... # also weird, but we should also probably allow it
|
||||
class NestedProtocol(Protocol): ... # same here...
|
||||
e = 72 # TODO: this should error with `[invalid-protocol]` (`e` is not declared)
|
||||
|
||||
f, g = (1, 2) # TODO: this should error with `[invalid-protocol]` (`f` and `g` are not declared)
|
||||
|
||||
h: int = (i := 3) # TODO: this should error with `[invalid-protocol]` (`i` is not declared)
|
||||
|
||||
for j in range(42): # TODO: this should error with `[invalid-protocol]` (`j` is not declared)
|
||||
pass
|
||||
|
||||
with MyContext() as k: # TODO: this should error with `[invalid-protocol]` (`k` is not declared)
|
||||
pass
|
||||
|
||||
match object():
|
||||
case l: # TODO: this should error with `[invalid-protocol]` (`l` is not declared)
|
||||
...
|
||||
|
||||
# TODO: all bindings in the above class should be understood as protocol members,
|
||||
# even those that we complained about with a diagnostic
|
||||
reveal_type(get_protocol_members(LotsOfBindings)) # revealed: @Todo(specialized non-generic class)
|
||||
```
|
||||
|
||||
Attribute members are allowed to have assignments in methods on the protocol class, just like
|
||||
non-protocol classes. Unlike other classes, however, *implicit* instance attributes -- those that
|
||||
are not declared in the class body -- are not allowed:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue