Skip to content

Commit 801147a

Browse files
committed
[red-knot] Add more tests for protocol members
1 parent 83d5ad8 commit 801147a

File tree

1 file changed

+73
-0
lines changed
  • crates/red_knot_python_semantic/resources/mdtest

1 file changed

+73
-0
lines changed

crates/red_knot_python_semantic/resources/mdtest/protocols.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,12 +403,37 @@ class Lumberjack(Protocol):
403403
reveal_type(get_protocol_members(Lumberjack)) # revealed: @Todo(specialized non-generic class)
404404
```
405405

406+
A sub-protocol inherits and extends the members of its superclass protocol(s):
407+
408+
```py
409+
class Bar(Protocol):
410+
spam: str
411+
412+
class Baz(Bar, Protocol):
413+
ham: memoryview
414+
415+
# TODO: `tuple[Literal["spam", "ham"]]` or `frozenset[Literal["spam", "ham"]]`
416+
reveal_type(get_protocol_members(Baz)) # revealed: @Todo(specialized non-generic class)
417+
418+
class Baz2(Bar, Foo, Protocol): ...
419+
420+
# TODO: either
421+
# `tuple[Literal["spam"], Literal["x"], Literal["y"], Literal["z"], Literal["method_member"]]`
422+
# or `frozenset[Literal["spam", "x", "y", "z", "method_member"]]`
423+
reveal_type(get_protocol_members(Baz2)) # revealed: @Todo(specialized non-generic class)
424+
```
425+
406426
## Subtyping of protocols with attribute members
407427

408428
In the following example, the protocol class `HasX` defines an interface such that any other fully
409429
static type can be said to be a subtype of `HasX` if all inhabitants of that other type have a
410430
mutable `x` attribute of type `int`:
411431

432+
```toml
433+
[environment]
434+
python-version = "3.12"
435+
```
436+
412437
```py
413438
from typing import Protocol
414439
from knot_extensions import static_assert, is_assignable_to, is_subtype_of
@@ -548,6 +573,54 @@ def f(arg: HasXWithDefault):
548573
reveal_type(type(arg).x) # revealed: int
549574
```
550575

576+
Assignments in a class body of a protocol -- of any kind -- are not permitted by red-knot unless the
577+
symbol being assigned to is also explicitly declared in the protocol's class body. Note that this is
578+
stricter validation of protocol members than many other type checkers currently apply (as of
579+
2025/04/21).
580+
581+
The reason for this strict validation is that undeclared variables in the class body would lead to
582+
an ambiguous interface being declared by the protocol.
583+
584+
```py
585+
from typing_extensions import TypeAlias, get_protocol_members
586+
587+
class MyContext:
588+
def __enter__(self) -> int:
589+
return 42
590+
591+
def __exit__(self, *args) -> None: ...
592+
593+
class LotsOfBindings(Protocol):
594+
a: int
595+
a = 42 # this is fine, since `a` is declared in the class body
596+
b: int = 56 # this is also fine, by the same principle
597+
598+
type c = str # this is very strange but I can't see a good reason to disallow it
599+
d: TypeAlias = bytes # same here
600+
601+
class Nested: ... # also weird, but we should also probably allow it
602+
class NestedProtocol(Protocol): ... # same here...
603+
e = 72 # TODO: this should error with `[invalid-protocol]` (`e` is not declared)
604+
605+
f, g = (1, 2) # TODO: this should error with `[invalid-protocol]` (`f` and `g` are not declared)
606+
607+
h: int = (i := 3) # TODO: this should error with `[invalid-protocol]` (`i` is not declared)
608+
609+
for j in range(42): # TODO: this should error with `[invalid-protocol]` (`j` is not declared)
610+
pass
611+
612+
with MyContext() as k: # TODO: this should error with `[invalid-protocol]` (`k` is not declared)
613+
pass
614+
615+
match object():
616+
case l: # TODO: this should error with `[invalid-protocol]` (`l` is not declared)
617+
...
618+
619+
# TODO: all bindings in the above class should be understood as protocol members,
620+
# even those that we complained about with a diagnostic
621+
reveal_type(get_protocol_members(LotsOfBindings)) # revealed: @Todo(specialized non-generic class)
622+
```
623+
551624
Attribute members are allowed to have assignments in methods on the protocol class, just like
552625
non-protocol classes. Unlike other classes, however, *implicit* instance attributes -- those that
553626
are not declared in the class body -- are not allowed:

0 commit comments

Comments
 (0)