Clarification - protocol properties vs attributes
- Dominant language
- Rust
- Stars
- 7k
- Forks
- 516
- PR merge metrics
- No merged PRs in 30d
Description
### Describe the Bug
When implementing a `typing.Protocol` it's not entirely clear to me when an attribute conforms to a property protocol method and vice versa. In particular, I've noticed that Pyrefly will allow `typing.Protocol` properties to be implemented with attributes, but not vice versa; Pyright somewhat contradictorily, permits properties to be implemented with attributes at a call site, but not if the protocol is inherited.
The Pyrefly behavior is more self-consistent, but I would like to confirm why this is the case. Is there official Python documentation to substantiate this behavior? If not, then why does Pyrefly accept attributes as protocol property implementations but not vice versa?
> Note, the PEP 544, which introduced Protocols, explicitly ruled _in_ protocol variable members (what I've referred to as attributes above): https://peps.python.org/pep-0544/#allow-only-protocol-methods-and-force-use-of-getters-and-setters
### Examples
#### Protocol inheritance
```python
from typing import List, Protocol
from dataclasses import dataclass
class Example1(Protocol):
@property
def samples(self) -> List[int]: ...
@dataclass(kw_only=True)
class Example1Impl(Example1):
samples: List[int]
def _example1(result: Example1) -> List[int]:
return result.samples
class Example2(Protocol):
samples: List[int]
@dataclass(kw_only=True)
class Example2Impl(Example2):
@property
def samples(self) -> List[int]:
return [1, 2, 3]
def _example2(result: Example2) -> List[int]:
return result.samples
def main():
r1 = Example1Impl(samples=[4, 5, 6])
_example1(r1)
r2 = Example2Impl()
_example2(r2)
```
_Pyrefly_:
```
--> src/my_project/example.py:26:9
|
26 | def samples(self) -> List[int]:
| ^^^^^^^
|
`Example2Impl.samples` is a property, but `Example2.samples` is not
```
_Pyright_:
```
src/my_project/example.py
src/my_project/example.py:12:5 - error: "samples" incorrectly overrides property of same name in class "Example1" (reportIncompatibleMethodOverride)
src/my_project/example.py:26:9 - error: "samples" overrides symbol of same name in class "Example2"
"property" is not assignable to "List[int]" (reportIncompatibleVariableOverride)
2 errors, 0 warnings, 0 informations
```
#### Protocol at call site
```python
from typing import List, Protocol
from dataclasses import dataclass
class Example1(Protocol):
@property
def samples(self) -> List[int]: ...
@dataclass(kw_only=True)
class Example1Impl:
samples: List[int]
def _example1(result: Example1) -> List[int]:
return result.samples
class Example2(Protocol):
samples: List[int]
@dataclass(kw_only=True)
class Example2Impl:
@property
def samples(self) -> List[int]:
return [1, 2, 3]
def _example2(result: Example2) -> List[int]:
return result.samples
def main():
r1 = Example1Impl(samples=[4, 5, 6])
_example1(r1)
r2 = Example2Impl()
_example2(r2)
```
_Pyrefly_:
```
ERROR Argument `Example2Impl` is not assignable to parameter `result` with type `Example2` in function `_example2` [bad-argument-type]
--> src/my_project/example.py:38:15
|
38 | _example2(r2)
```
_Pyright_:
```
src/my_project/example.py:38:15 - error: Argument of type "Example2Impl" cannot be assigned to parameter "result" of type "Example2" in function "_example2"
"Example2Impl" is incompatible with protocol "Example2"
"samples" is invariant because it is mutable
"samples" is an incompatible type
"property" is not assignable to "List[int]" (reportArgumentType)
1 error, 0 warnings, 0 informations
```
### Sandbox Link
https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeS4ATrgLYAEALqcROgOZ0Q3G6UN0AZCHAYAaOgAVqDXAGNcUADrow1eplQNUsqKjhx4nbr34atOvXGXX0F-XQCi%2BVN1gBGABRTcM%2BVACUiMp0IXQAAsTUxDB8pMGhmDBgdGiu8B4GUGD%2BdAC0AHyCwgwA2qwMALqIdIS1NsphZtq6%2Bh4A1gDuAPq46FCkALwAKpQArjD%2BynZwjs5pbgCSrh5OLsTugfEhqevw1UIiZeiV9VhJdF0wc7uelPCjUAzVq-M5BUWH5VVbdHcMo5R0L97o9CDtYFZ0DZprM1rAAExeaRyBSbIGhcF7D6lL6nRqaZqWdrdXr9YZjCZTFozF67eFLdYra4ItGhcKRXDRWI-RLJTFwDIwLJvQoHHHHb7otkhP4AoElNzieHiADMFRsvIuVzhMERdzgDyesLS8JF2KOlSCUuB-0BwINoP5p01NFQrA8rNClDcdAGxpuDKgGR1cAGJQALOIAKziABsFUm1suzJgtzciZ%2BlHhvv9CMDHp%2ByZ1etNNhAohAowY0DgJHIiBAAGI6ABVatQCBMOhgUa2au9SHKTVgXiuhhddCjGjYGIefDVcpmkSUK1s2V2sCKEAAOSnM5XdGA%2BAAvlvlOWQGQ7mB%2BoQZDQoBRmxJSNf%2BikMDgCHR5CoIGwAU0CBekIZRmwAZRgGA6AACwYBhiDgRAAHpkKvJJb14NhkJgdBkMwOQ4GQ39IAAyggN6ZDu14OhUAANzdXRsFgH9elIwD%2ByBTlONrZQyAYGDelyOiYjgYCgT9LcVUINx4TPdAQGPCttGrESADFoBgCg0CwPAiDIRSgA
### (Only applicable for extension issues) IDE Information
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.