114114
115115See our `official docs <https://classes.readthedocs.io>`_ to learn more!
116116"""
117-
118- from abc import get_cache_token
119117from functools import _find_impl # type: ignore # noqa: WPS450
120- from types import MethodType
121118from typing import ( # noqa: WPS235
122119 TYPE_CHECKING ,
123120 Callable ,
124121 Dict ,
125122 Generic ,
126- NoReturn ,
127123 Optional ,
128124 Type ,
129125 TypeVar ,
134130
135131from typing_extensions import TypeGuard , final
136132
133+ from classes ._registry import (
134+ TypeRegistry ,
135+ choose_registry ,
136+ default_implementation ,
137+ )
138+
137139_InstanceType = TypeVar ('_InstanceType' )
138140_SignatureType = TypeVar ('_SignatureType' , bound = Callable )
139141_AssociatedType = TypeVar ('_AssociatedType' )
@@ -305,12 +307,17 @@ class _TypeClass( # noqa: WPS214
305307 """
306308
307309 __slots__ = (
310+ # Str:
308311 '_signature' ,
309312 '_associated_type' ,
313+
314+ # Registry:
315+ '_concretes' ,
310316 '_instances' ,
311317 '_protocols' ,
318+
319+ # Cache:
312320 '_dispatch_cache' ,
313- '_cache_token' ,
314321 )
315322
316323 _dispatch_cache : Dict [type , Callable ]
@@ -349,16 +356,17 @@ def __init__(
349356 The only exception is the first argument: it is polymorfic.
350357
351358 """
352- self ._instances : Dict [type , Callable ] = {}
353- self ._protocols : Dict [type , Callable ] = {}
354-
355359 # We need this for `repr`:
356360 self ._signature = signature
357361 self ._associated_type = associated_type
358362
363+ # Registries:
364+ self ._concretes : TypeRegistry = {}
365+ self ._instances : TypeRegistry = {}
366+ self ._protocols : TypeRegistry = {}
367+
359368 # Cache parts:
360369 self ._dispatch_cache = WeakKeyDictionary () # type: ignore
361- self ._cache_token = None
362370
363371 def __call__ (
364372 self ,
@@ -410,7 +418,16 @@ def __call__(
410418 And all typeclasses that match ``Callable[[int, int], int]`` signature
411419 will typecheck.
412420 """
413- self ._control_abc_cache ()
421+ # At first, we try all our concrete types,
422+ # we don't cache it, because we cannot.
423+ # We only have runtime type info: `type([1]) == type(['a'])`.
424+ # It might be slow!
425+ # Don't add concrete types unless
426+ # you are absolutely know what you are doing.
427+ impl = self ._dispatch_concrete (instance )
428+ if impl is not None :
429+ return impl (instance , * args , ** kwargs )
430+
414431 instance_type = type (instance )
415432
416433 try :
@@ -419,7 +436,7 @@ def __call__(
419436 impl = self ._dispatch (
420437 instance ,
421438 instance_type ,
422- ) or self . _default_implementation
439+ ) or default_implementation
423440 self ._dispatch_cache [instance_type ] = impl
424441 return impl (instance , * args , ** kwargs )
425442
@@ -481,16 +498,24 @@ def supports(
481498
482499 See also: https://www.python.org/dev/peps/pep-0647
483500 """
484- self ._control_abc_cache ()
485-
501+ # Here we first check that instance is already in the cache
502+ # and only then we check concrete types.
503+ # Why?
504+ # Because if some type is already in the cache,
505+ # it means that it is not concrete.
506+ # So, this is simply faster.
486507 instance_type = type (instance )
487508 if instance_type in self ._dispatch_cache :
488509 return True
489510
490- # This only happens when we don't have a cache in place:
511+ # We never cache concrete types.
512+ if self ._dispatch_concrete (instance ) is not None :
513+ return True
514+
515+ # This only happens when we don't have a cache in place
516+ # and this is not a concrete generic:
491517 impl = self ._dispatch (instance , instance_type )
492518 if impl is None :
493- self ._dispatch_cache [instance_type ] = self ._default_implementation
494519 return False
495520
496521 self ._dispatch_cache [instance_type ] = impl
@@ -503,14 +528,36 @@ def instance(
503528 # TODO: at one point I would like to remove `is_protocol`
504529 # and make this function decide whether this type is protocol or not.
505530 is_protocol : bool = False ,
531+ delegate : Optional [type ] = None ,
506532 ) -> '_TypeClassInstanceDef[_NewInstanceType, _TypeClassType]' :
507533 """
508534 We use this method to store implementation for each specific type.
509535
510- The only setting we provide is ``is_protocol`` which is required
511- when passing protocols. See our ``mypy`` plugin for that.
536+ Args:
537+ is_protocol - required when passing protocols.
538+ delegate - required when using concrete generics like ``List[str]``.
539+
540+ Returns:
541+ Decorator for instance handler.
542+
543+ .. note::
544+
545+ ``is_protocol`` and ``delegate`` are mutually exclusive.
546+
547+ We don't use ``@overload`` decorator here
548+ (which makes our ``mypy`` plugin even more complex)
549+ because ``@overload`` functions do not
550+ work well with ``ctx.api.fail`` inside the plugin.
551+ They start to try other overloads, which produces wrong results.
512552 """
513- typ = type_argument or type (None ) # `None` is a special case
553+ # This might seem like a strange line at first, let's dig into it:
554+ #
555+ # First, if `delegate` is passed, then we use delegate, not a real type.
556+ # We use delegates for concrete generics.
557+ # Then, we have a regular `type_argument`. It is used for most types.
558+ # Lastly, we have `type(None)` to handle cases
559+ # when we want to register `None` as a type / singleton value.
560+ typ = delegate or type_argument or type (None )
514561
515562 # That's how we check for generics,
516563 # generics that look like `List[int]` or `set[T]` will fail this check,
@@ -519,34 +566,20 @@ def instance(
519566 isinstance (object (), typ )
520567
521568 def decorator (implementation ):
522- container = self ._protocols if is_protocol else self ._instances
569+ container = choose_registry (
570+ typ = typ ,
571+ is_protocol = is_protocol ,
572+ delegate = delegate ,
573+ concretes = self ._concretes ,
574+ instances = self ._instances ,
575+ protocols = self ._protocols ,
576+ )
523577 container [typ ] = implementation
524578
525- if isinstance (getattr (typ , '__instancecheck__' , None ), MethodType ):
526- # This means that this type has `__instancecheck__` defined,
527- # which allows dynamic checks of what `isinstance` of this type.
528- # That's why we also treat this type as a protocol.
529- self ._protocols [typ ] = implementation
530-
531- if self ._cache_token is None : # pragma: no cover
532- if getattr (typ , '__abstractmethods__' , None ):
533- self ._cache_token = get_cache_token ()
534-
535579 self ._dispatch_cache .clear ()
536580 return implementation
537- return decorator
538581
539- def _control_abc_cache (self ) -> None :
540- """
541- Required to drop cache if ``abc`` type got new subtypes in runtime.
542-
543- Copied from ``cpython``.
544- """
545- if self ._cache_token is not None :
546- current_token = get_cache_token ()
547- if self ._cache_token != current_token :
548- self ._dispatch_cache .clear ()
549- self ._cache_token = current_token
582+ return decorator
550583
551584 def _dispatch (self , instance , instance_type : type ) -> Optional [Callable ]:
552585 """
@@ -567,13 +600,11 @@ def _dispatch(self, instance, instance_type: type) -> Optional[Callable]:
567600
568601 return _find_impl (instance_type , self ._instances )
569602
570- def _default_implementation (self , instance , * args , ** kwargs ) -> NoReturn :
571- """By default raises an exception."""
572- raise NotImplementedError (
573- 'Missing matched typeclass instance for type: {0}' .format (
574- type (instance ).__qualname__ ,
575- ),
576- )
603+ def _dispatch_concrete (self , instance ) -> Optional [Callable ]:
604+ for concrete , callback in self ._concretes .items ():
605+ if isinstance (instance , concrete ):
606+ return callback
607+ return None
577608
578609
579610if TYPE_CHECKING :
0 commit comments