Skip to content

Commit

Permalink
gh-104555: Sidestep the ABCMeta.__instancecheck__ cache in typing._Pr…
Browse files Browse the repository at this point in the history
…otocolMeta.__instancecheck__
  • Loading branch information
AlexWaygood committed May 16, 2023
1 parent 1163782 commit 2601138
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 2 deletions.
36 changes: 36 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2654,6 +2654,42 @@ class D(PNonCall): ...
with self.assertRaises(TypeError):
issubclass(D, PNonCall)

def test_no_weird_caching_with_issubclass_after_isinstance(self):
@runtime_checkable
class Spam(Protocol[T]):
x: T

class Eggs(Generic[T]):
def __init__(self, x: T) -> None:
self.x = x

# gh-104555: ABCMeta might cache the result of this isinstance check
# if we called super().__instancecheck__ in the wrong place
# in _ProtocolMeta.__instancheck__...
self.assertIsInstance(Eggs(42), Spam)

# ...and if it did, then TypeError wouldn't be raised here!
with self.assertRaises(TypeError):
issubclass(Eggs, Spam)

def test_no_weird_caching_with_issubclass_after_isinstance_pep695(self):
@runtime_checkable
class Spam[T](Protocol):
x: T

class Eggs[T]:
def __init__(self, x: T) -> None:
self.x = x

# gh-104555: ABCMeta might cache the result of this isinstance check
# if we called super().__instancecheck__ in the wrong place
# in _ProtocolMeta.__instancheck__...
self.assertIsInstance(Eggs(42), Spam)

# ...and if it did, then TypeError wouldn't be raised here!
with self.assertRaises(TypeError):
issubclass(Eggs, Spam)

def test_protocols_isinstance(self):
T = TypeVar('T')

Expand Down
7 changes: 5 additions & 2 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1798,7 +1798,10 @@ def __instancecheck__(cls, instance):
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")

if super().__instancecheck__(instance):
# gh-104555: Don't call super().__instancecheck__ here,
# ABCMeta.__instancecheck__ would erroneously use it to populate the cache,
# which would cause incorrect results for *issubclass()* calls
if type.__instancecheck__(cls, instance):
return True

if is_protocol_cls:
Expand All @@ -1813,7 +1816,7 @@ def __instancecheck__(cls, instance):
else:
return True

return False
return super().__instancecheck__(instance)


class Protocol(Generic, metaclass=_ProtocolMeta):
Expand Down

0 comments on commit 2601138

Please sign in to comment.