Skip to content

Commit 6f91ef0

Browse files
committed
Enforce subtype origin is an acceptable base type.
1 parent 8196b76 commit 6f91ef0

File tree

2 files changed

+16
-19
lines changed

2 files changed

+16
-19
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ method[type, ...] # get registered function
6161
method[type, ...] = func # register function by explicit types
6262
```
6363

64-
Multimethods support any types that satisfy the `issubclass` relation, including abstract base classes in `collections.abc`. Note `typing` aliases do not support `issubclass` consistently, and are no longer needed for subscripts. Using ABCs instead is recommended. Subscripted generics are supported:
64+
Multimethods support any types that satisfy the `issubclass` relation, including abstract base classes in `collections.abc`. Note `typing` aliases do not support `issubclass` consistently, and are no longer needed for subscripts. Using ABCs instead is recommended. Subscripted generics are supported by custom `isinstance` checks:
6565
* `Mapping[...]` - the first key-value pair is checked
6666
* `tuple[...]` - all args are checked
6767
* `Iterable[...]` - the first arg is checked
68-
* `type[...]`
69-
* `Literal[...]`
68+
* `type[...]` - `issubclass` of type
69+
* `Literal[...]` - equality and type match
7070
* `Callable[[...], ...]` - parameter types are contravariant, return type is covariant
7171

7272
Naturally checking subscripts is slower, but the implementation is optimized, cached, and bypassed if no subscripts are in use in the parameter. Empty iterables match any subscript, but don't special-case how the types are normally resolved.

multimethod/__init__.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from collections.abc import Callable, Iterable, Iterator, Mapping
1111
from typing import Any, TypeVar, Union, get_type_hints
1212

13+
TypeAliasType = getattr(typing, 'TypeAliasType', types.new_class('')) # python <3.12
14+
1315

1416
class DispatchError(TypeError): ... # pragma: no branch
1517

@@ -24,14 +26,8 @@ def get_args(tp) -> tuple:
2426
return typing.get_args(tp)
2527

2628

27-
def get_mro(cls) -> tuple: # `inspect.getmro` doesn't handle all cases
28-
return tuple(type.mro(cls)) if isinstance(cls, type) else cls.mro()
29-
30-
3129
def common_bases(*bases):
32-
counts = collections.Counter()
33-
for base in bases:
34-
counts.update(cls for cls in get_mro(base) if issubclass(abc.ABCMeta, type(cls)))
30+
counts = collections.Counter(cls for base in bases for cls in base.mro())
3531
return tuple(cls for cls in counts if counts[cls] == len(bases))
3632

3733

@@ -49,23 +45,26 @@ def __new__(cls, tp):
4945
match tp:
5046
case typing.Any:
5147
return object
52-
case subtype(): # If already a subtype, return it directly
53-
return tp
5448
case typing.NewType():
5549
return cls(tp.__supertype__)
5650
case TypeVar():
5751
return cls(Union[tp.__constraints__]) if tp.__constraints__ else object
5852
case typing._AnnotatedAlias():
5953
return cls(tp.__origin__)
60-
if hasattr(typing, 'TypeAliasType') and isinstance(tp, typing.TypeAliasType):
61-
return cls(tp.__value__)
54+
case TypeAliasType():
55+
return cls(tp.__value__)
56+
case typing.GenericAlias(): # python <3.11
57+
...
58+
case type():
59+
return tp
6260
origin = get_origin(tp) or tp
6361
args = tuple(map(cls, get_args(tp)))
64-
if set(args) <= {object} and (origin is not tuple or tp is tuple):
62+
if not args and origin is not tuple:
6563
return origin
66-
bases = (origin,) if type(origin) in (type, abc.ABCMeta) else ()
64+
bases = (origin,)
6765
match origin:
6866
case typing.Literal:
67+
args = get_args(tp)
6968
bases = (cls(Union[tuple(map(type, args))]),)
7069
case typing.Union | types.UnionType:
7170
origin = types.UnionType
@@ -75,15 +74,13 @@ def __new__(cls, tp):
7574
namespace = {'__origin__': origin, '__args__': args}
7675
return type.__new__(cls, str(tp), bases, namespace)
7776

78-
def __init__(self, tp, *args): ...
79-
8077
def key(self) -> tuple:
8178
return self.__origin__, *self.__args__
8279

8380
def __eq__(self, other) -> bool:
8481
return isinstance(other, subtype) and self.key() == other.key()
8582

86-
def __hash__(self) -> int:
83+
def __hash__(self):
8784
return hash(self.key())
8885

8986
def __subclasscheck__(self, subclass):

0 commit comments

Comments
 (0)