-
Notifications
You must be signed in to change notification settings - Fork 172
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
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
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
(Only applicable for extension issues) IDE Information
No response