-
Notifications
You must be signed in to change notification settings - Fork 219
Description
Summary
Other than object, all nominal instance types are disjoint from None. But... this is not true for instances of structural (Protocol) types -- and it turns out that this causes issues for our current implementation of the descriptor protocol.
For example, compare and contrast the different behaviour of these two protocols. SupportsFoo is disjoint from None; SupportsStr is not:
from typing import Protocol
class SupportsFoo(Protocol):
def foo(self) -> str: ...
class SupportsStr(Protocol):
def __str__(self) -> str: ...
def f(f: SupportsFoo, s: SupportsStr):
reveal_type(f.foo) # revealed: `bound method SupportsFoo.foo() -> str`
f.foo() # no diagnostic
reveal_type(s.__str__) # revealed: `def __str__(self) -> str`
s.__str__() # error: No argument provided for required parameter `self` of function `__str__` (missing-argument)Whether or not a type is disjoint from None impacts our understanding of the way the descriptor protocol is invoked when a method is accessed on that type. The type that a method is accessed on is passed to FunctionType.__get__ when we invoke the descriptor protocol; if that type is not disjoint from None, we end up having to pick the first overload here, which means that the attribute access is (incorrectly!) evaluated as resolving to the original function object rather than a bound method:
Many thanks to @sharkdp who helped me track this bug down over the last couple of days!