ovld 0.4.3__py3-none-any.whl → 0.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ovld/__init__.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from typing import TYPE_CHECKING
2
2
 
3
+ from . import abc # noqa: F401
3
4
  from .core import (
4
5
  Ovld,
5
6
  OvldBase,
ovld/abc.py ADDED
@@ -0,0 +1,48 @@
1
+ import typing
2
+ from collections.abc import Callable, Collection, Mapping, Sequence
3
+
4
+ from .dependent import Callable as OvldCallable
5
+ from .dependent import (
6
+ CollectionFastCheck,
7
+ Equals,
8
+ MappingFastCheck,
9
+ ProductType,
10
+ SequenceFastCheck,
11
+ )
12
+ from .types import normalize_type
13
+
14
+
15
+ @normalize_type.register_generic(typing.Literal)
16
+ def _(self, t, fn):
17
+ return Equals[t.__args__]
18
+
19
+
20
+ @normalize_type.register_generic(tuple)
21
+ def _(self, t, fn):
22
+ args = tuple(self(arg, fn) for arg in t.__args__)
23
+ return ProductType[args]
24
+
25
+
26
+ @normalize_type.register_generic(Sequence)
27
+ def _(self, t, fn):
28
+ args = tuple(self(arg, fn) for arg in t.__args__)
29
+ return SequenceFastCheck[args]
30
+
31
+
32
+ @normalize_type.register_generic(Collection)
33
+ def _(self, t, fn):
34
+ args = tuple(self(arg, fn) for arg in t.__args__)
35
+ return CollectionFastCheck[args]
36
+
37
+
38
+ @normalize_type.register_generic(Mapping)
39
+ def _(self, t, fn):
40
+ args = tuple(self(arg, fn) for arg in t.__args__)
41
+ return MappingFastCheck[args]
42
+
43
+
44
+ @normalize_type.register_generic(Callable)
45
+ def _(self, t, fn):
46
+ *at, rt = t.__args__
47
+ at = tuple(self(arg, fn) for arg in at)
48
+ return OvldCallable[at, self(rt, fn)]
ovld/core.py CHANGED
@@ -16,9 +16,9 @@ from .recode import (
16
16
  generate_dispatch,
17
17
  rename_function,
18
18
  )
19
- from .typemap import MultiTypeMap, is_type_of_type
20
- from .types import normalize_type
21
- from .utils import UsageError, keyword_decorator
19
+ from .typemap import MultiTypeMap
20
+ from .types import clsstring, normalize_type
21
+ from .utils import UsageError, keyword_decorator, subtler_type
22
22
 
23
23
  _current_id = itertools.count()
24
24
 
@@ -80,6 +80,7 @@ class Arginfo:
80
80
  @dataclass(frozen=True)
81
81
  class Signature:
82
82
  types: tuple
83
+ return_type: type
83
84
  req_pos: int
84
85
  max_pos: int
85
86
  req_names: frozenset
@@ -139,6 +140,7 @@ class Signature:
139
140
 
140
141
  return cls(
141
142
  types=tuple(typelist),
143
+ return_type=normalize_type(sig.return_annotation, fn),
142
144
  req_pos=req_pos,
143
145
  max_pos=max_pos,
144
146
  req_names=frozenset(req_names),
@@ -149,28 +151,16 @@ class Signature:
149
151
  )
150
152
 
151
153
 
152
- def clsstring(cls):
153
- if cls is object:
154
- return "*"
155
- elif isinstance(cls, tuple):
154
+ def typemap_entry_string(cls):
155
+ if isinstance(cls, tuple):
156
156
  key, typ = cls
157
157
  return f"{key}: {clsstring(typ)}"
158
- elif is_type_of_type(cls):
159
- arg = clsstring(cls.__args__[0])
160
- return f"type[{arg}]"
161
- elif hasattr(cls, "__origin__"):
162
- if cls.__origin__ is typing.Union:
163
- return "|".join(map(clsstring, cls.__args__))
164
- else:
165
- return repr(cls)
166
- elif hasattr(cls, "__name__"):
167
- return cls.__name__
168
158
  else:
169
- return repr(cls)
159
+ return clsstring(cls)
170
160
 
171
161
 
172
162
  def sigstring(types):
173
- return ", ".join(map(clsstring, types))
163
+ return ", ".join(map(typemap_entry_string, types))
174
164
 
175
165
 
176
166
  class ArgumentAnalyzer:
@@ -277,9 +267,7 @@ class ArgumentAnalyzer:
277
267
  )
278
268
 
279
269
  def lookup_for(self, key):
280
- return (
281
- "self.map.transform" if key in self.complex_transforms else "type"
282
- )
270
+ return subtler_type if key in self.complex_transforms else type
283
271
 
284
272
 
285
273
  class _Ovld:
@@ -473,7 +461,7 @@ class _Ovld:
473
461
  @_compile_first
474
462
  def resolve(self, *args):
475
463
  """Find the correct method to call for the given arguments."""
476
- return self.map[tuple(map(self.map.transform, args))]
464
+ return self.map[tuple(map(subtler_type, args))]
477
465
 
478
466
  def register_signature(self, sig, orig_fn):
479
467
  """Register a function for the given signature."""
@@ -577,7 +565,7 @@ class _Ovld:
577
565
 
578
566
  This should be replaced by an auto-generated function.
579
567
  """
580
- key = tuple(map(self.map.transform, args))
568
+ key = tuple(map(subtler_type, args))
581
569
  method = self.map[key]
582
570
  return method(*args)
583
571
 
@@ -586,7 +574,7 @@ class _Ovld:
586
574
  def next(self, *args):
587
575
  """Call the next matching method after the caller, in terms of priority or specificity."""
588
576
  fr = sys._getframe(1)
589
- key = (fr.f_code, *map(self.map.transform, args))
577
+ key = (fr.f_code, *map(subtler_type, args))
590
578
  method = self.map[key]
591
579
  return method(*args)
592
580
 
@@ -631,20 +619,20 @@ class OvldCall:
631
619
  def next(self, *args):
632
620
  """Call the next matching method after the caller, in terms of priority or specificity."""
633
621
  fr = sys._getframe(1)
634
- key = (fr.f_code, *map(self.map.transform, args))
622
+ key = (fr.f_code, *map(subtler_type, args))
635
623
  method = self.map[key]
636
624
  return method(self.obj, *args)
637
625
 
638
626
  def resolve(self, *args):
639
627
  """Find the right method to call for the given arguments."""
640
- return self.map[tuple(map(self.map.transform, args))].__get__(self.obj)
628
+ return self.map[tuple(map(subtler_type, args))].__get__(self.obj)
641
629
 
642
630
  def __call__(self, *args): # pragma: no cover
643
631
  """Call this overloaded function.
644
632
 
645
633
  This should be replaced by an auto-generated function.
646
634
  """
647
- key = tuple(map(self.map.transform, args))
635
+ key = tuple(map(subtler_type, args))
648
636
  method = self.map[key]
649
637
  return method(self.obj, *args)
650
638
 
ovld/dependent.py CHANGED
@@ -1,12 +1,21 @@
1
1
  import inspect
2
2
  import re
3
+ from collections.abc import Callable as _Callable
4
+ from collections.abc import Mapping, Sequence
3
5
  from dataclasses import dataclass
6
+ from functools import partial
4
7
  from itertools import count
5
- from typing import TYPE_CHECKING, Any, Mapping, TypeVar, Union
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Any,
11
+ Collection,
12
+ TypeVar,
13
+ )
6
14
 
7
15
  from .types import (
8
16
  Intersection,
9
17
  Order,
18
+ clsstring,
10
19
  normalize_type,
11
20
  subclasscheck,
12
21
  typeorder,
@@ -15,6 +24,13 @@ from .types import (
15
24
  _current = count()
16
25
 
17
26
 
27
+ def generate_checking_code(typ):
28
+ if hasattr(typ, "codegen"):
29
+ return typ.codegen()
30
+ else:
31
+ return CodeGen("isinstance({arg}, {this})", {"this": typ})
32
+
33
+
18
34
  @dataclass
19
35
  class CodeGen:
20
36
  template: str
@@ -46,9 +62,22 @@ def combine(master_template, args):
46
62
  return CodeGen(master_template.format(*fmts), subs)
47
63
 
48
64
 
49
- class DependentType:
65
+ def is_dependent(t):
66
+ if isinstance(t, DependentType):
67
+ return True
68
+ elif any(is_dependent(subt) for subt in getattr(t, "__args__", ())):
69
+ return True
70
+ return False
71
+
72
+
73
+ class DependentType(type):
50
74
  exclusive_type = False
51
75
  keyable_type = False
76
+ bound_is_name = False
77
+
78
+ def __new__(cls, *args, **kwargs):
79
+ value = super().__new__(cls, cls.__name__, (), {})
80
+ return value
52
81
 
53
82
  def __init__(self, bound):
54
83
  self.bound = bound
@@ -95,18 +124,19 @@ class DependentType:
95
124
  else:
96
125
  return False
97
126
 
127
+ def __instancecheck__(self, other):
128
+ return isinstance(other, self.bound) and self.check(other)
129
+
98
130
  def __lt__(self, other):
99
131
  return False
100
132
 
101
- def __or__(self, other):
102
- if not isinstance(other, DependentType):
103
- return NotImplemented
104
- return Or(self, other)
105
-
106
133
  def __and__(self, other):
107
- if not isinstance(other, DependentType):
108
- return NotImplemented
109
- return And(self, other)
134
+ return Intersection[self, other]
135
+
136
+ def __rand__(self, other):
137
+ return Intersection[other, self]
138
+
139
+ __repr__ = __str__ = clsstring
110
140
 
111
141
 
112
142
  class ParametrizedDependentType(DependentType):
@@ -114,7 +144,7 @@ class ParametrizedDependentType(DependentType):
114
144
  super().__init__(
115
145
  self.default_bound(*parameters) if bound is None else bound
116
146
  )
117
- self.parameters = parameters
147
+ self.__args__ = self.parameters = parameters
118
148
 
119
149
  @property
120
150
  def parameter(self):
@@ -137,8 +167,17 @@ class ParametrizedDependentType(DependentType):
137
167
  return hash(self.parameters) ^ hash(self.bound)
138
168
 
139
169
  def __str__(self):
140
- params = ", ".join(map(repr, self.parameters))
141
- return f"{type(self).__name__}({params})"
170
+ if self.bound_is_name:
171
+ origin = self.bound
172
+ bound = ""
173
+ else:
174
+ origin = self
175
+ if self.bound != self.default_bound(*self.parameters):
176
+ bound = f" < {clsstring(self.bound)}"
177
+ else:
178
+ bound = ""
179
+ args = ", ".join(map(clsstring, self.__args__))
180
+ return f"{origin.__name__}[{args}]{bound}"
142
181
 
143
182
  __repr__ = __str__
144
183
 
@@ -167,8 +206,14 @@ class FuncDependentType(ParametrizedDependentType):
167
206
  return type(self).func(value, *self.parameters)
168
207
 
169
208
 
170
- def dependent_check(fn):
171
- t = type(fn.__name__, (FuncDependentType,), {"func": fn})
209
+ def dependent_check(fn=None, bound_is_name=False):
210
+ if fn is None:
211
+ return partial(dependent_check, bound_is_name=bound_is_name)
212
+ t = type(
213
+ fn.__name__,
214
+ (FuncDependentType,),
215
+ {"func": fn, "bound_is_name": bound_is_name},
216
+ )
172
217
  if len(inspect.signature(fn).parameters) == 1:
173
218
  t = t()
174
219
  return t
@@ -197,8 +242,77 @@ class Equals(ParametrizedDependentType):
197
242
  return CodeGen("({arg} in {ps})", {"ps": self.parameters})
198
243
 
199
244
 
245
+ class ProductType(ParametrizedDependentType):
246
+ bound_is_name = True
247
+
248
+ def default_bound(self, *subtypes):
249
+ return tuple
250
+
251
+ def check(self, value):
252
+ return (
253
+ isinstance(value, tuple)
254
+ and len(value) == len(self.parameters)
255
+ and all(isinstance(x, t) for x, t in zip(value, self.parameters))
256
+ )
257
+
258
+ def codegen(self):
259
+ checks = ["len({arg}) == {n}"]
260
+ params = {"n": len(self.parameters)}
261
+ for i, p in enumerate(self.parameters):
262
+ checks.append(f"isinstance({{arg}}[{i}], {{p{i}}})")
263
+ params[f"p{i}"] = p
264
+ return CodeGen(" and ".join(checks), params)
265
+
266
+ def __type_order__(self, other):
267
+ if isinstance(other, ProductType):
268
+ if len(other.parameters) == len(self.parameters):
269
+ return Order.merge(
270
+ typeorder(a, b)
271
+ for a, b in zip(self.parameters, other.parameters)
272
+ )
273
+ else:
274
+ return Order.NONE
275
+ else:
276
+ return NotImplemented
277
+
278
+
279
+ @dependent_check(bound_is_name=True)
280
+ def SequenceFastCheck(value: Sequence, typ):
281
+ return not value or isinstance(value[0], typ)
282
+
283
+
284
+ @dependent_check(bound_is_name=True)
285
+ def CollectionFastCheck(value: Collection, typ):
286
+ for x in value:
287
+ return isinstance(x, typ)
288
+ else:
289
+ return True
290
+
291
+
292
+ @dependent_check(bound_is_name=True)
293
+ def MappingFastCheck(value: Mapping, kt, vt):
294
+ if not value:
295
+ return True
296
+ for k in value:
297
+ break
298
+ return isinstance(k, kt) and isinstance(value[k], vt)
299
+
300
+
301
+ @dependent_check
302
+ def Callable(fn: _Callable, argt, rett):
303
+ from .core import Signature
304
+
305
+ sig = Signature.extract(fn)
306
+ return (
307
+ sig.max_pos >= len(argt) >= sig.req_pos
308
+ and not sig.req_names
309
+ and all(subclasscheck(t1, t2) for t1, t2 in zip(argt, sig.types))
310
+ and subclasscheck(sig.return_type, rett)
311
+ )
312
+
313
+
200
314
  @dependent_check
201
- def HasKeys(value: Mapping, *keys):
315
+ def HasKey(value: Mapping, *keys):
202
316
  return all(k in value for k in keys)
203
317
 
204
318
 
@@ -225,44 +339,6 @@ class Dependent:
225
339
  return dt.with_bound(bound)
226
340
 
227
341
 
228
- class Or(DependentType):
229
- def __init__(self, *types, bound=None):
230
- self.types = types
231
- super().__init__(bound or self.default_bound())
232
-
233
- def default_bound(self):
234
- return Union[tuple([t.bound for t in self.types])]
235
-
236
- def codegen(self):
237
- template = " or ".join("{}" for t in self.types)
238
- return combine(template, [t.codegen() for t in self.types])
239
-
240
- def check(self, value):
241
- return any(t.check(value) for t in self.types)
242
-
243
-
244
- class And(DependentType):
245
- def __init__(self, *types, bound=None):
246
- self.types = types
247
- super().__init__(bound or self.default_bound())
248
-
249
- def default_bound(self):
250
- bounds = frozenset(t.bound for t in self.types)
251
- return Intersection[tuple(bounds)]
252
-
253
- def codegen(self):
254
- template = " and ".join("{}" for t in self.types)
255
- return combine(template, [t.codegen() for t in self.types])
256
-
257
- def check(self, value):
258
- return all(t.check(value) for t in self.types)
259
-
260
- def __str__(self):
261
- return " & ".join(map(repr, self.types))
262
-
263
- __repr__ = __str__
264
-
265
-
266
342
  if TYPE_CHECKING: # pragma: no cover
267
343
  from typing import Annotated, TypeAlias
268
344
 
@@ -275,7 +351,7 @@ __all__ = [
275
351
  "Dependent",
276
352
  "DependentType",
277
353
  "Equals",
278
- "HasKeys",
354
+ "HasKey",
279
355
  "StartsWith",
280
356
  "EndsWith",
281
357
  "dependent_check",
ovld/mro.py CHANGED
@@ -1,7 +1,7 @@
1
- import typing
2
1
  from dataclasses import dataclass
3
2
  from enum import Enum
4
3
  from graphlib import TopologicalSorter
4
+ from typing import get_args, get_origin
5
5
 
6
6
 
7
7
  class Order(Enum):
@@ -18,6 +18,18 @@ class Order(Enum):
18
18
  else:
19
19
  return self
20
20
 
21
+ @staticmethod
22
+ def merge(orders):
23
+ orders = set(orders)
24
+ if orders == {Order.SAME}:
25
+ return Order.SAME
26
+ elif not (orders - {Order.LESS, Order.SAME}):
27
+ return Order.LESS
28
+ elif not (orders - {Order.MORE, Order.SAME}):
29
+ return Order.MORE
30
+ else:
31
+ return Order.NONE
32
+
21
33
 
22
34
  @dataclass
23
35
  class TypeRelationship:
@@ -26,16 +38,6 @@ class TypeRelationship:
26
38
  subtype: bool = NotImplemented
27
39
 
28
40
 
29
- def _issubclass(t1, t2):
30
- try:
31
- return issubclass(t1, t2)
32
- except TypeError:
33
- try:
34
- return isinstance(t1, t2)
35
- except TypeError: # pragma: no cover
36
- return False
37
-
38
-
39
41
  def typeorder(t1, t2):
40
42
  """Order relation between two types.
41
43
 
@@ -60,30 +62,14 @@ def typeorder(t1, t2):
60
62
  ):
61
63
  return result.opposite()
62
64
 
63
- o1 = getattr(t1, "__origin__", None)
64
- o2 = getattr(t2, "__origin__", None)
65
-
66
- if o2 is typing.Union:
67
- if t1 is typing.Union:
68
- return Order.MORE
69
- compare = [
70
- x for t in t2.__args__ if (x := typeorder(t1, t)) is not Order.NONE
71
- ]
72
- if not compare:
73
- return Order.NONE
74
- elif any(x is Order.LESS or x is Order.SAME for x in compare):
75
- return Order.LESS
76
- else:
77
- return Order.MORE
78
-
79
- if o1 is typing.Union:
80
- return typeorder(t2, t1).opposite()
65
+ o1 = get_origin(t1)
66
+ o2 = get_origin(t2)
81
67
 
82
68
  if o2 and not o1:
83
69
  return typeorder(t2, t1).opposite()
84
70
 
85
71
  if o1:
86
- if not o2: # or getattr(t2, "__args__", None) is None:
72
+ if not o2:
87
73
  order = typeorder(o1, t2)
88
74
  if order is order.SAME:
89
75
  order = order.LESS
@@ -92,8 +78,8 @@ def typeorder(t1, t2):
92
78
  if (order := typeorder(o1, o2)) is not Order.SAME:
93
79
  return order
94
80
 
95
- args1 = getattr(t1, "__args__", ())
96
- args2 = getattr(t2, "__args__", ())
81
+ args1 = get_args(t1)
82
+ args2 = get_args(t2)
97
83
 
98
84
  if args1 and not args2:
99
85
  return Order.LESS
@@ -103,20 +89,10 @@ def typeorder(t1, t2):
103
89
  return Order.NONE
104
90
 
105
91
  ords = [typeorder(a1, a2) for a1, a2 in zip(args1, args2)]
106
- if Order.MORE in ords and Order.LESS in ords:
107
- return Order.NONE
108
- elif Order.NONE in ords:
109
- return Order.NONE
110
- elif Order.MORE in ords:
111
- return Order.MORE
112
- elif Order.LESS in ords:
113
- return Order.LESS
114
- else: # pragma: no cover
115
- # Not sure when t1 != t2 and that happens
116
- return Order.SAME
92
+ return Order.merge(ords)
117
93
 
118
- sx = _issubclass(t1, t2)
119
- sy = _issubclass(t2, t1)
94
+ sx = issubclass(t1, t2)
95
+ sy = issubclass(t2, t1)
120
96
  if sx and sy: # pragma: no cover
121
97
  # Not sure when t1 != t2 and that happens
122
98
  return Order.SAME
@@ -145,17 +121,8 @@ def subclasscheck(t1, t2):
145
121
  ):
146
122
  return result
147
123
 
148
- o1 = getattr(t1, "__origin__", None)
149
- o2 = getattr(t2, "__origin__", None)
150
-
151
- if o2 is typing.Union:
152
- return t1 is typing.Union or any(
153
- subclasscheck(t1, t) for t in t2.__args__
154
- )
155
- elif o1 is typing.Union:
156
- return t2 is typing.Union or all(
157
- subclasscheck(t, t2) for t in t1.__args__
158
- )
124
+ o1 = get_origin(t1)
125
+ o2 = get_origin(t2)
159
126
 
160
127
  if not isinstance(o1, type):
161
128
  o1 = None
@@ -165,12 +132,12 @@ def subclasscheck(t1, t2):
165
132
  if o1 or o2:
166
133
  o1 = o1 or t1
167
134
  o2 = o2 or t2
168
- if _issubclass(o1, o2):
135
+ if issubclass(o1, o2):
169
136
  if o2 is t2: # pragma: no cover
170
137
  return True
171
138
  else:
172
- args1 = getattr(t1, "__args__", ())
173
- args2 = getattr(t2, "__args__", ())
139
+ args1 = get_args(t1)
140
+ args2 = get_args(t2)
174
141
  if len(args1) != len(args2):
175
142
  return False
176
143
  return all(
@@ -179,7 +146,10 @@ def subclasscheck(t1, t2):
179
146
  else:
180
147
  return False
181
148
  else:
182
- return _issubclass(t1, t2)
149
+ try:
150
+ return issubclass(t1, t2)
151
+ except TypeError:
152
+ return False
183
153
 
184
154
 
185
155
  def sort_types(cls, avail):