ovld 0.4.4__py3-none-any.whl → 0.4.6__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 +0 -2
- ovld/core.py +186 -191
- ovld/dependent.py +59 -26
- ovld/mro.py +5 -0
- ovld/py.typed +0 -0
- ovld/recode.py +64 -75
- ovld/types.py +11 -7
- ovld/utils.py +47 -0
- ovld/version.py +1 -1
- {ovld-0.4.4.dist-info → ovld-0.4.6.dist-info}/METADATA +11 -10
- ovld-0.4.6.dist-info/RECORD +15 -0
- ovld-0.4.4.dist-info/RECORD +0 -14
- {ovld-0.4.4.dist-info → ovld-0.4.6.dist-info}/WHEEL +0 -0
- {ovld-0.4.4.dist-info → ovld-0.4.6.dist-info}/licenses/LICENSE +0 -0
ovld/__init__.py
CHANGED
@@ -4,7 +4,6 @@ from . import abc # noqa: F401
|
|
4
4
|
from .core import (
|
5
5
|
Ovld,
|
6
6
|
OvldBase,
|
7
|
-
OvldCall,
|
8
7
|
OvldMC,
|
9
8
|
extend_super,
|
10
9
|
is_ovld,
|
@@ -53,7 +52,6 @@ __all__ = [
|
|
53
52
|
"MultiTypeMap",
|
54
53
|
"Ovld",
|
55
54
|
"OvldBase",
|
56
|
-
"OvldCall",
|
57
55
|
"OvldMC",
|
58
56
|
"TypeMap",
|
59
57
|
"extend_super",
|
ovld/core.py
CHANGED
@@ -5,36 +5,24 @@ import itertools
|
|
5
5
|
import sys
|
6
6
|
import textwrap
|
7
7
|
import typing
|
8
|
-
from collections import defaultdict
|
8
|
+
from collections import OrderedDict, defaultdict
|
9
9
|
from dataclasses import dataclass, field, replace
|
10
10
|
from functools import cached_property, partial
|
11
|
-
from types import GenericAlias
|
11
|
+
from types import FunctionType, GenericAlias
|
12
12
|
|
13
13
|
from .recode import (
|
14
14
|
Conformer,
|
15
15
|
adapt_function,
|
16
16
|
generate_dispatch,
|
17
|
-
|
17
|
+
rename_code,
|
18
18
|
)
|
19
19
|
from .typemap import MultiTypeMap
|
20
20
|
from .types import clsstring, normalize_type
|
21
|
-
from .utils import UsageError, keyword_decorator, subtler_type
|
21
|
+
from .utils import MISSING, UsageError, keyword_decorator, subtler_type
|
22
22
|
|
23
23
|
_current_id = itertools.count()
|
24
24
|
|
25
25
|
|
26
|
-
def _fresh(t):
|
27
|
-
"""Returns a new subclass of type t.
|
28
|
-
|
29
|
-
Each Ovld corresponds to its own class, which allows for specialization of
|
30
|
-
methods.
|
31
|
-
"""
|
32
|
-
methods = {}
|
33
|
-
if not isinstance(getattr(t, "__doc__", None), str):
|
34
|
-
methods["__doc__"] = t.__doc__
|
35
|
-
return type(t.__name__, (t,), methods)
|
36
|
-
|
37
|
-
|
38
26
|
@keyword_decorator
|
39
27
|
def _setattrs(fn, **kwargs):
|
40
28
|
for k, v in kwargs.items():
|
@@ -42,23 +30,107 @@ def _setattrs(fn, **kwargs):
|
|
42
30
|
return fn
|
43
31
|
|
44
32
|
|
45
|
-
|
46
|
-
def
|
47
|
-
|
48
|
-
self.
|
49
|
-
method = getattr(self, fn.__name__)
|
50
|
-
assert method is not first_entry
|
51
|
-
return method(*args, **kwargs)
|
33
|
+
class LazySignature(inspect.Signature):
|
34
|
+
def __init__(self, ovld):
|
35
|
+
super().__init__([])
|
36
|
+
self.ovld = ovld
|
52
37
|
|
53
|
-
|
54
|
-
|
55
|
-
|
38
|
+
def replace(
|
39
|
+
self, *, parameters=inspect._void, return_annotation=inspect._void
|
40
|
+
): # pragma: no cover
|
41
|
+
if parameters is inspect._void:
|
42
|
+
parameters = self.parameters.values()
|
56
43
|
|
44
|
+
if return_annotation is inspect._void:
|
45
|
+
return_annotation = self._return_annotation
|
57
46
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
47
|
+
return inspect.Signature(
|
48
|
+
parameters, return_annotation=return_annotation
|
49
|
+
)
|
50
|
+
|
51
|
+
@property
|
52
|
+
def parameters(self):
|
53
|
+
anal = self.ovld.analyze_arguments()
|
54
|
+
parameters = []
|
55
|
+
if anal.is_method:
|
56
|
+
parameters.append(
|
57
|
+
inspect.Parameter(
|
58
|
+
name="self",
|
59
|
+
kind=inspect._POSITIONAL_ONLY,
|
60
|
+
)
|
61
|
+
)
|
62
|
+
parameters += [
|
63
|
+
inspect.Parameter(
|
64
|
+
name=p,
|
65
|
+
kind=inspect._POSITIONAL_ONLY,
|
66
|
+
)
|
67
|
+
for p in anal.strict_positional_required
|
68
|
+
]
|
69
|
+
parameters += [
|
70
|
+
inspect.Parameter(
|
71
|
+
name=p,
|
72
|
+
kind=inspect._POSITIONAL_ONLY,
|
73
|
+
default=MISSING,
|
74
|
+
)
|
75
|
+
for p in anal.strict_positional_optional
|
76
|
+
]
|
77
|
+
parameters += [
|
78
|
+
inspect.Parameter(
|
79
|
+
name=p,
|
80
|
+
kind=inspect._POSITIONAL_OR_KEYWORD,
|
81
|
+
)
|
82
|
+
for p in anal.positional_required
|
83
|
+
]
|
84
|
+
parameters += [
|
85
|
+
inspect.Parameter(
|
86
|
+
name=p,
|
87
|
+
kind=inspect._POSITIONAL_OR_KEYWORD,
|
88
|
+
default=MISSING,
|
89
|
+
)
|
90
|
+
for p in anal.positional_optional
|
91
|
+
]
|
92
|
+
parameters += [
|
93
|
+
inspect.Parameter(
|
94
|
+
name=p,
|
95
|
+
kind=inspect._KEYWORD_ONLY,
|
96
|
+
)
|
97
|
+
for p in anal.keyword_required
|
98
|
+
]
|
99
|
+
parameters += [
|
100
|
+
inspect.Parameter(
|
101
|
+
name=p,
|
102
|
+
kind=inspect._KEYWORD_ONLY,
|
103
|
+
default=MISSING,
|
104
|
+
)
|
105
|
+
for p in anal.keyword_optional
|
106
|
+
]
|
107
|
+
return OrderedDict({p.name: p for p in parameters})
|
108
|
+
|
109
|
+
|
110
|
+
def bootstrap_dispatch(ov, name):
|
111
|
+
def first_entry(*args, **kwargs):
|
112
|
+
ov.compile()
|
113
|
+
return ov.dispatch(*args, **kwargs)
|
114
|
+
|
115
|
+
dispatch = FunctionType(
|
116
|
+
rename_code(first_entry.__code__, name),
|
117
|
+
{},
|
118
|
+
name,
|
119
|
+
(),
|
120
|
+
first_entry.__closure__,
|
121
|
+
)
|
122
|
+
dispatch.__signature__ = LazySignature(ov)
|
123
|
+
dispatch.__ovld__ = ov
|
124
|
+
dispatch.register = ov.register
|
125
|
+
dispatch.resolve = ov.resolve
|
126
|
+
dispatch.copy = ov.copy
|
127
|
+
dispatch.variant = ov.variant
|
128
|
+
dispatch.display_methods = ov.display_methods
|
129
|
+
dispatch.display_resolution = ov.display_resolution
|
130
|
+
dispatch.add_mixins = ov.add_mixins
|
131
|
+
dispatch.unregister = ov.unregister
|
132
|
+
dispatch.next = ov.next
|
133
|
+
return dispatch
|
62
134
|
|
63
135
|
|
64
136
|
@dataclass(frozen=True)
|
@@ -171,8 +243,10 @@ class ArgumentAnalyzer:
|
|
171
243
|
self.complex_transforms = set()
|
172
244
|
self.total = 0
|
173
245
|
self.is_method = None
|
246
|
+
self.done = False
|
174
247
|
|
175
248
|
def add(self, fn):
|
249
|
+
self.done = False
|
176
250
|
sig = Signature.extract(fn)
|
177
251
|
self.complex_transforms.update(
|
178
252
|
arg.canonical for arg in sig.arginfo if arg.is_complex
|
@@ -197,6 +271,8 @@ class ArgumentAnalyzer:
|
|
197
271
|
)
|
198
272
|
|
199
273
|
def compile(self):
|
274
|
+
if self.done:
|
275
|
+
return
|
200
276
|
for name, pos in self.name_to_positions.items():
|
201
277
|
if len(pos) != 1:
|
202
278
|
if all(isinstance(p, int) for p in pos):
|
@@ -223,23 +299,23 @@ class ArgumentAnalyzer:
|
|
223
299
|
|
224
300
|
assert strict_positional + positional == p_to_n
|
225
301
|
|
226
|
-
strict_positional_required = [
|
302
|
+
self.strict_positional_required = [
|
227
303
|
f"ARG{pos + 1}"
|
228
304
|
for pos, _ in enumerate(strict_positional)
|
229
305
|
if self.counts[pos][0] == self.total
|
230
306
|
]
|
231
|
-
strict_positional_optional = [
|
307
|
+
self.strict_positional_optional = [
|
232
308
|
f"ARG{pos + 1}"
|
233
309
|
for pos, _ in enumerate(strict_positional)
|
234
310
|
if self.counts[pos][0] != self.total
|
235
311
|
]
|
236
312
|
|
237
|
-
positional_required = [
|
313
|
+
self.positional_required = [
|
238
314
|
names[0]
|
239
315
|
for pos, names in enumerate(positional)
|
240
316
|
if self.counts[pos + len(strict_positional)][0] == self.total
|
241
317
|
]
|
242
|
-
positional_optional = [
|
318
|
+
self.positional_optional = [
|
243
319
|
names[0]
|
244
320
|
for pos, names in enumerate(positional)
|
245
321
|
if self.counts[pos + len(strict_positional)][0] != self.total
|
@@ -250,27 +326,19 @@ class ArgumentAnalyzer:
|
|
250
326
|
for _, (name,) in self.name_to_positions.items()
|
251
327
|
if not isinstance(name, int)
|
252
328
|
]
|
253
|
-
keyword_required = [
|
329
|
+
self.keyword_required = [
|
254
330
|
name for name in keywords if self.counts[name][0] == self.total
|
255
331
|
]
|
256
|
-
keyword_optional = [
|
332
|
+
self.keyword_optional = [
|
257
333
|
name for name in keywords if self.counts[name][0] != self.total
|
258
334
|
]
|
259
|
-
|
260
|
-
return (
|
261
|
-
strict_positional_required,
|
262
|
-
strict_positional_optional,
|
263
|
-
positional_required,
|
264
|
-
positional_optional,
|
265
|
-
keyword_required,
|
266
|
-
keyword_optional,
|
267
|
-
)
|
335
|
+
self.done = True
|
268
336
|
|
269
337
|
def lookup_for(self, key):
|
270
338
|
return subtler_type if key in self.complex_transforms else type
|
271
339
|
|
272
340
|
|
273
|
-
class
|
341
|
+
class Ovld:
|
274
342
|
"""Overloaded function.
|
275
343
|
|
276
344
|
A function can be added with the `register` method. One of its parameters
|
@@ -293,7 +361,6 @@ class _Ovld:
|
|
293
361
|
self,
|
294
362
|
*,
|
295
363
|
mixins=[],
|
296
|
-
bootstrap=None,
|
297
364
|
name=None,
|
298
365
|
linkback=False,
|
299
366
|
allow_replacement=True,
|
@@ -304,15 +371,14 @@ class _Ovld:
|
|
304
371
|
self.linkback = linkback
|
305
372
|
self.children = []
|
306
373
|
self.allow_replacement = allow_replacement
|
307
|
-
self.bootstrap = bootstrap
|
308
374
|
self.name = name
|
309
375
|
self.shortname = name or f"__OVLD{self.id}"
|
310
376
|
self.__name__ = name
|
311
377
|
self._defns = {}
|
312
378
|
self._locked = False
|
313
379
|
self.mixins = []
|
380
|
+
self.argument_analysis = ArgumentAnalyzer()
|
314
381
|
self.add_mixins(*mixins)
|
315
|
-
self.ocls = _fresh(OvldCall)
|
316
382
|
|
317
383
|
@property
|
318
384
|
def defns(self):
|
@@ -322,11 +388,14 @@ class _Ovld:
|
|
322
388
|
defns.update(self._defns)
|
323
389
|
return defns
|
324
390
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
self.
|
391
|
+
def analyze_arguments(self):
|
392
|
+
self.argument_analysis = ArgumentAnalyzer()
|
393
|
+
for key, fn in list(self.defns.items()):
|
394
|
+
self.argument_analysis.add(fn)
|
395
|
+
self.argument_analysis.compile()
|
396
|
+
return self.argument_analysis
|
329
397
|
|
398
|
+
def mkdoc(self):
|
330
399
|
docs = [fn.__doc__ for fn in self.defns.values() if fn.__doc__]
|
331
400
|
if len(docs) == 1:
|
332
401
|
maindoc = docs[0]
|
@@ -347,16 +416,13 @@ class _Ovld:
|
|
347
416
|
return doc
|
348
417
|
|
349
418
|
@property
|
350
|
-
def
|
351
|
-
|
352
|
-
|
419
|
+
def __doc__(self):
|
420
|
+
self.ensure_compiled()
|
421
|
+
return self.mkdoc()
|
353
422
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
[v for k, v in sig.parameters.items() if k != "self"]
|
358
|
-
)
|
359
|
-
return sig
|
423
|
+
@property
|
424
|
+
def __signature__(self):
|
425
|
+
return self.dispatch.__signature__
|
360
426
|
|
361
427
|
def lock(self):
|
362
428
|
self._locked = True
|
@@ -367,11 +433,10 @@ class _Ovld:
|
|
367
433
|
|
368
434
|
def add_mixins(self, *mixins):
|
369
435
|
self._attempt_modify()
|
436
|
+
mixins = [o for m in mixins if (o := to_ovld(m)) is not self]
|
370
437
|
for mixin in mixins:
|
371
438
|
if self.linkback:
|
372
439
|
mixin.children.append(self)
|
373
|
-
if mixin._defns and self.bootstrap is None:
|
374
|
-
self.bootstrap = mixin.bootstrap
|
375
440
|
self.mixins += mixins
|
376
441
|
|
377
442
|
def _key_error(self, key, possibilities=None):
|
@@ -391,28 +456,23 @@ class _Ovld:
|
|
391
456
|
"Note: you can use @ovld(priority=X) to give higher priority to an overload."
|
392
457
|
)
|
393
458
|
|
394
|
-
def rename(self, name):
|
459
|
+
def rename(self, name, shortname=None):
|
395
460
|
"""Rename this Ovld."""
|
396
461
|
self.name = name
|
397
|
-
self.
|
462
|
+
self.shortname = shortname or name
|
463
|
+
self.__name__ = shortname
|
464
|
+
self.dispatch = bootstrap_dispatch(self, name=self.shortname)
|
398
465
|
|
399
466
|
def _set_attrs_from(self, fn):
|
400
467
|
"""Inherit relevant attributes from the function."""
|
401
|
-
if self.bootstrap is None:
|
402
|
-
self.bootstrap = arg0_is_self(fn)
|
403
|
-
|
404
468
|
if self.name is None:
|
405
|
-
self.name = f"{fn.__module__}.{fn.__qualname__}"
|
406
|
-
self.shortname = fn.__name__
|
407
|
-
self.__name__ = fn.__name__
|
408
469
|
self.__qualname__ = fn.__qualname__
|
409
470
|
self.__module__ = fn.__module__
|
471
|
+
self.rename(f"{fn.__module__}.{fn.__qualname__}", fn.__name__)
|
410
472
|
|
411
|
-
def
|
412
|
-
if
|
413
|
-
|
414
|
-
else:
|
415
|
-
return fn
|
473
|
+
def ensure_compiled(self):
|
474
|
+
if not self._compiled:
|
475
|
+
self.compile()
|
416
476
|
|
417
477
|
def compile(self):
|
418
478
|
"""Finalize this overload.
|
@@ -428,39 +488,32 @@ class _Ovld:
|
|
428
488
|
if self not in mixin.children:
|
429
489
|
mixin.lock()
|
430
490
|
|
431
|
-
cls = type(self)
|
432
491
|
if self.name is None:
|
433
492
|
self.name = self.__name__ = f"ovld{self.id}"
|
434
493
|
|
435
494
|
name = self.__name__
|
436
495
|
self.map = MultiTypeMap(name=name, key_error=self._key_error)
|
437
496
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
for key, fn in list(self.defns.items()):
|
450
|
-
anal.add(fn)
|
451
|
-
self.argument_analysis = anal
|
452
|
-
dispatch = generate_dispatch(anal)
|
453
|
-
self._dispatch = dispatch
|
454
|
-
target.__call__ = rename_function(dispatch, f"{name}.dispatch")
|
497
|
+
self.analyze_arguments()
|
498
|
+
dispatch = generate_dispatch(self, self.argument_analysis)
|
499
|
+
if not hasattr(self, "dispatch"):
|
500
|
+
self.dispatch = bootstrap_dispatch(self, name=self.shortname)
|
501
|
+
self.dispatch.__code__ = rename_code(dispatch.__code__, self.shortname)
|
502
|
+
self.dispatch.__kwdefaults__ = dispatch.__kwdefaults__
|
503
|
+
self.dispatch.__annotations__ = dispatch.__annotations__
|
504
|
+
self.dispatch.__defaults__ = dispatch.__defaults__
|
505
|
+
self.dispatch.__globals__.update(dispatch.__globals__)
|
506
|
+
self.dispatch.map = self.map
|
507
|
+
self.dispatch.__doc__ = self.mkdoc()
|
455
508
|
|
456
509
|
for key, fn in list(self.defns.items()):
|
457
510
|
self.register_signature(key, fn)
|
458
511
|
|
459
512
|
self._compiled = True
|
460
513
|
|
461
|
-
@_compile_first
|
462
514
|
def resolve(self, *args):
|
463
515
|
"""Find the correct method to call for the given arguments."""
|
516
|
+
self.ensure_compiled()
|
464
517
|
return self.map[tuple(map(subtler_type, args))]
|
465
518
|
|
466
519
|
def register_signature(self, sig, orig_fn):
|
@@ -516,6 +569,8 @@ class _Ovld:
|
|
516
569
|
self.compile()
|
517
570
|
for child in self.children:
|
518
571
|
child._update()
|
572
|
+
if hasattr(self, "dispatch"):
|
573
|
+
self.dispatch.__doc__ = self.mkdoc()
|
519
574
|
|
520
575
|
def copy(self, mixins=[], linkback=False):
|
521
576
|
"""Create a copy of this Ovld.
|
@@ -523,11 +578,7 @@ class _Ovld:
|
|
523
578
|
New functions can be registered to the copy without affecting the
|
524
579
|
original.
|
525
580
|
"""
|
526
|
-
return
|
527
|
-
bootstrap=self.bootstrap,
|
528
|
-
mixins=[self, *mixins],
|
529
|
-
linkback=linkback,
|
530
|
-
)
|
581
|
+
return Ovld(mixins=[self, *mixins], linkback=linkback)
|
531
582
|
|
532
583
|
def variant(self, fn=None, priority=0, **kwargs):
|
533
584
|
"""Decorator to create a variant of this Ovld.
|
@@ -542,34 +593,21 @@ class _Ovld:
|
|
542
593
|
ov.register(fn, priority=priority)
|
543
594
|
return ov
|
544
595
|
|
545
|
-
@_compile_first
|
546
596
|
def __get__(self, obj, cls):
|
547
|
-
if
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
if rval is None:
|
552
|
-
obj.__dict__[key] = rval = self.ocls(self, obj)
|
553
|
-
return rval
|
554
|
-
|
555
|
-
@_compile_first
|
556
|
-
def __getitem__(self, t):
|
557
|
-
if not isinstance(t, tuple):
|
558
|
-
t = (t,)
|
559
|
-
return self.map[t]
|
560
|
-
|
561
|
-
@_compile_first
|
597
|
+
if not self._compiled:
|
598
|
+
self.compile()
|
599
|
+
return self.dispatch.__get__(obj, cls)
|
600
|
+
|
562
601
|
@_setattrs(rename="dispatch")
|
563
|
-
def __call__(self, *args): # pragma: no cover
|
602
|
+
def __call__(self, *args, **kwargs): # pragma: no cover
|
564
603
|
"""Call the overloaded function.
|
565
604
|
|
566
605
|
This should be replaced by an auto-generated function.
|
567
606
|
"""
|
568
|
-
|
569
|
-
|
570
|
-
return
|
607
|
+
if not self._compiled:
|
608
|
+
self.compile()
|
609
|
+
return self.dispatch(*args, **kwargs)
|
571
610
|
|
572
|
-
@_compile_first
|
573
611
|
@_setattrs(rename="next")
|
574
612
|
def next(self, *args):
|
575
613
|
"""Call the next matching method after the caller, in terms of priority or specificity."""
|
@@ -581,65 +619,29 @@ class _Ovld:
|
|
581
619
|
def __repr__(self):
|
582
620
|
return f"<Ovld {self.name or hex(id(self))}>"
|
583
621
|
|
584
|
-
@_compile_first
|
585
622
|
def display_methods(self):
|
623
|
+
self.ensure_compiled()
|
586
624
|
self.map.display_methods()
|
587
625
|
|
588
|
-
@_compile_first
|
589
626
|
def display_resolution(self, *args, **kwargs):
|
627
|
+
self.ensure_compiled()
|
590
628
|
self.map.display_resolution(*args, **kwargs)
|
591
629
|
|
592
630
|
|
593
631
|
def is_ovld(x):
|
594
632
|
"""Return whether the argument is an ovld function/method."""
|
595
|
-
return isinstance(x,
|
596
|
-
|
597
|
-
|
598
|
-
class OvldCall:
|
599
|
-
"""Context for an Ovld call."""
|
600
|
-
|
601
|
-
def __init__(self, ovld, bind_to):
|
602
|
-
"""Initialize an OvldCall."""
|
603
|
-
self.ovld = ovld
|
604
|
-
self.map = ovld.map
|
605
|
-
self.obj = bind_to
|
606
|
-
|
607
|
-
@property
|
608
|
-
def __name__(self):
|
609
|
-
return self.ovld.__name__
|
610
|
-
|
611
|
-
@property
|
612
|
-
def __doc__(self):
|
613
|
-
return self.ovld.__doc__
|
614
|
-
|
615
|
-
@property
|
616
|
-
def __signature__(self):
|
617
|
-
return self.ovld.__signature__
|
618
|
-
|
619
|
-
def next(self, *args):
|
620
|
-
"""Call the next matching method after the caller, in terms of priority or specificity."""
|
621
|
-
fr = sys._getframe(1)
|
622
|
-
key = (fr.f_code, *map(subtler_type, args))
|
623
|
-
method = self.map[key]
|
624
|
-
return method(self.obj, *args)
|
625
|
-
|
626
|
-
def resolve(self, *args):
|
627
|
-
"""Find the right method to call for the given arguments."""
|
628
|
-
return self.map[tuple(map(subtler_type, args))].__get__(self.obj)
|
629
|
-
|
630
|
-
def __call__(self, *args): # pragma: no cover
|
631
|
-
"""Call this overloaded function.
|
632
|
-
|
633
|
-
This should be replaced by an auto-generated function.
|
634
|
-
"""
|
635
|
-
key = tuple(map(subtler_type, args))
|
636
|
-
method = self.map[key]
|
637
|
-
return method(self.obj, *args)
|
633
|
+
return isinstance(x, Ovld) or isinstance(
|
634
|
+
getattr(x, "__ovld__", False), Ovld
|
635
|
+
)
|
638
636
|
|
639
637
|
|
640
|
-
def
|
641
|
-
"""
|
642
|
-
|
638
|
+
def to_ovld(x):
|
639
|
+
"""Return whether the argument is an ovld function/method."""
|
640
|
+
x = getattr(x, "__ovld__", x)
|
641
|
+
if inspect.isfunction(x):
|
642
|
+
return ovld(x, fresh=True)
|
643
|
+
else:
|
644
|
+
return x if isinstance(x, Ovld) else None
|
643
645
|
|
644
646
|
|
645
647
|
def extend_super(fn):
|
@@ -666,33 +668,24 @@ class ovld_cls_dict(dict):
|
|
666
668
|
def __setitem__(self, attr, value):
|
667
669
|
prev = None
|
668
670
|
if attr in self:
|
669
|
-
prev = self[attr]
|
670
|
-
if inspect.isfunction(prev):
|
671
|
-
prev = ovld(prev, fresh=True)
|
672
|
-
elif not is_ovld(prev): # pragma: no cover
|
673
|
-
prev = None
|
671
|
+
prev = to_ovld(self[attr])
|
674
672
|
elif is_ovld(value) and getattr(value, "_extend_super", False):
|
675
673
|
mixins = []
|
676
674
|
for base in self._bases:
|
677
675
|
if (candidate := getattr(base, attr, None)) is not None:
|
678
|
-
if
|
679
|
-
mixins.append(
|
676
|
+
if mixin := to_ovld(candidate):
|
677
|
+
mixins.append(mixin)
|
680
678
|
if mixins:
|
681
679
|
prev, *others = mixins
|
682
|
-
|
683
|
-
prev = prev.copy()
|
684
|
-
else:
|
685
|
-
prev = ovld(prev, fresh=True)
|
680
|
+
prev = prev.copy()
|
686
681
|
for other in others:
|
687
|
-
|
688
|
-
prev.add_mixins(other)
|
689
|
-
else:
|
690
|
-
prev.register(other)
|
682
|
+
prev.add_mixins(other)
|
691
683
|
else:
|
692
684
|
prev = None
|
693
685
|
|
694
686
|
if prev is not None:
|
695
687
|
if is_ovld(value) and prev is not value:
|
688
|
+
value = getattr(value, "__ovld__", value)
|
696
689
|
if prev.name is None:
|
697
690
|
prev.rename(value.name)
|
698
691
|
prev.add_mixins(value)
|
@@ -701,7 +694,9 @@ class ovld_cls_dict(dict):
|
|
701
694
|
prev.register(value)
|
702
695
|
value = prev
|
703
696
|
|
704
|
-
super().__setitem__(
|
697
|
+
super().__setitem__(
|
698
|
+
attr, value.dispatch if isinstance(value, Ovld) else value
|
699
|
+
)
|
705
700
|
|
706
701
|
|
707
702
|
class OvldMC(type):
|
@@ -762,12 +757,12 @@ def _find_overload(fn, **kwargs):
|
|
762
757
|
dispatch = fr.f_locals.get(fn.__name__, None)
|
763
758
|
|
764
759
|
if dispatch is None:
|
765
|
-
dispatch =
|
760
|
+
dispatch = Ovld(**kwargs)
|
766
761
|
elif not is_ovld(dispatch): # pragma: no cover
|
767
762
|
raise TypeError("@ovld requires Ovld instance")
|
768
763
|
elif kwargs: # pragma: no cover
|
769
764
|
raise TypeError("Cannot configure an overload that already exists")
|
770
|
-
return dispatch
|
765
|
+
return getattr(dispatch, "__ovld__", dispatch)
|
771
766
|
|
772
767
|
|
773
768
|
@keyword_decorator
|
@@ -796,16 +791,16 @@ def ovld(fn, priority=0, fresh=False, **kwargs):
|
|
796
791
|
ovld so that updates can be propagated. (default: False)
|
797
792
|
"""
|
798
793
|
if fresh:
|
799
|
-
dispatch =
|
794
|
+
dispatch = Ovld(**kwargs)
|
800
795
|
else:
|
801
796
|
dispatch = _find_overload(fn, **kwargs)
|
802
|
-
|
797
|
+
dispatch.register(fn, priority=priority)
|
798
|
+
return dispatch.dispatch
|
803
799
|
|
804
800
|
|
805
801
|
__all__ = [
|
806
802
|
"Ovld",
|
807
803
|
"OvldBase",
|
808
|
-
"OvldCall",
|
809
804
|
"OvldMC",
|
810
805
|
"extend_super",
|
811
806
|
"is_ovld",
|
ovld/dependent.py
CHANGED
@@ -2,7 +2,6 @@ import inspect
|
|
2
2
|
import re
|
3
3
|
from collections.abc import Callable as _Callable
|
4
4
|
from collections.abc import Mapping, Sequence
|
5
|
-
from dataclasses import dataclass
|
6
5
|
from functools import partial
|
7
6
|
from itertools import count
|
8
7
|
from typing import (
|
@@ -16,6 +15,7 @@ from .types import (
|
|
16
15
|
Intersection,
|
17
16
|
Order,
|
18
17
|
clsstring,
|
18
|
+
get_args,
|
19
19
|
normalize_type,
|
20
20
|
subclasscheck,
|
21
21
|
typeorder,
|
@@ -28,13 +28,13 @@ def generate_checking_code(typ):
|
|
28
28
|
if hasattr(typ, "codegen"):
|
29
29
|
return typ.codegen()
|
30
30
|
else:
|
31
|
-
return CodeGen("isinstance({arg}, {this})",
|
31
|
+
return CodeGen("isinstance({arg}, {this})", this=typ)
|
32
32
|
|
33
33
|
|
34
|
-
@dataclass
|
35
34
|
class CodeGen:
|
36
|
-
template:
|
37
|
-
|
35
|
+
def __init__(self, template, substitutions={}, **substitutions_kw):
|
36
|
+
self.template = template
|
37
|
+
self.substitutions = {**substitutions, **substitutions_kw}
|
38
38
|
|
39
39
|
def mangle(self):
|
40
40
|
renamings = {
|
@@ -46,10 +46,7 @@ class CodeGen:
|
|
46
46
|
for k, newk in renamings.items()
|
47
47
|
if k in self.substitutions
|
48
48
|
}
|
49
|
-
return CodeGen(
|
50
|
-
template=self.template.format(**renamings),
|
51
|
-
substitutions=new_subs,
|
52
|
-
)
|
49
|
+
return CodeGen(self.template.format(**renamings), new_subs)
|
53
50
|
|
54
51
|
|
55
52
|
def combine(master_template, args):
|
@@ -65,7 +62,7 @@ def combine(master_template, args):
|
|
65
62
|
def is_dependent(t):
|
66
63
|
if isinstance(t, DependentType):
|
67
64
|
return True
|
68
|
-
elif any(is_dependent(subt) for subt in
|
65
|
+
elif any(is_dependent(subt) for subt in get_args(t)):
|
69
66
|
return True
|
70
67
|
return False
|
71
68
|
|
@@ -93,7 +90,7 @@ class DependentType(type):
|
|
93
90
|
raise NotImplementedError()
|
94
91
|
|
95
92
|
def codegen(self):
|
96
|
-
return CodeGen("{this}.check({arg})",
|
93
|
+
return CodeGen("{this}.check({arg})", this=self)
|
97
94
|
|
98
95
|
def __type_order__(self, other):
|
99
96
|
if isinstance(other, DependentType):
|
@@ -145,6 +142,11 @@ class ParametrizedDependentType(DependentType):
|
|
145
142
|
self.default_bound(*parameters) if bound is None else bound
|
146
143
|
)
|
147
144
|
self.__args__ = self.parameters = parameters
|
145
|
+
self.__origin__ = None
|
146
|
+
self.__post_init__()
|
147
|
+
|
148
|
+
def __post_init__(self):
|
149
|
+
pass
|
148
150
|
|
149
151
|
@property
|
150
152
|
def parameter(self):
|
@@ -184,10 +186,7 @@ class ParametrizedDependentType(DependentType):
|
|
184
186
|
|
185
187
|
class FuncDependentType(ParametrizedDependentType):
|
186
188
|
def default_bound(self, *_):
|
187
|
-
|
188
|
-
return normalize_type(
|
189
|
-
list(inspect.signature(fn).parameters.values())[0].annotation, fn
|
190
|
-
)
|
189
|
+
return self._default_bound
|
191
190
|
|
192
191
|
def __lt__(self, other):
|
193
192
|
if len(self.parameters) != len(other.parameters):
|
@@ -209,13 +208,40 @@ class FuncDependentType(ParametrizedDependentType):
|
|
209
208
|
def dependent_check(fn=None, bound_is_name=False):
|
210
209
|
if fn is None:
|
211
210
|
return partial(dependent_check, bound_is_name=bound_is_name)
|
212
|
-
|
213
|
-
|
214
|
-
(
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
211
|
+
|
212
|
+
if isinstance(fn, type):
|
213
|
+
params = inspect.signature(fn.check).parameters
|
214
|
+
bound = normalize_type(
|
215
|
+
list(inspect.signature(fn.check).parameters.values())[1].annotation,
|
216
|
+
fn.check,
|
217
|
+
)
|
218
|
+
t = type(
|
219
|
+
fn.__name__,
|
220
|
+
(FuncDependentType,),
|
221
|
+
{
|
222
|
+
"bound_is_name": bound_is_name,
|
223
|
+
"_default_bound": bound,
|
224
|
+
**vars(fn),
|
225
|
+
},
|
226
|
+
)
|
227
|
+
|
228
|
+
else:
|
229
|
+
params = inspect.signature(fn).parameters
|
230
|
+
bound = normalize_type(
|
231
|
+
list(inspect.signature(fn).parameters.values())[0].annotation, fn
|
232
|
+
)
|
233
|
+
t = type(
|
234
|
+
fn.__name__,
|
235
|
+
(FuncDependentType,),
|
236
|
+
{
|
237
|
+
"func": fn,
|
238
|
+
"bound_is_name": bound_is_name,
|
239
|
+
"_default_bound": bound,
|
240
|
+
},
|
241
|
+
)
|
242
|
+
if len(params) == 1:
|
243
|
+
t = t()
|
244
|
+
|
219
245
|
return t
|
220
246
|
|
221
247
|
|
@@ -237,9 +263,9 @@ class Equals(ParametrizedDependentType):
|
|
237
263
|
|
238
264
|
def codegen(self):
|
239
265
|
if len(self.parameters) == 1:
|
240
|
-
return CodeGen("({arg} == {p})",
|
266
|
+
return CodeGen("({arg} == {p})", p=self.parameter)
|
241
267
|
else:
|
242
|
-
return CodeGen("({arg} in {ps})",
|
268
|
+
return CodeGen("({arg} in {ps})", ps=self.parameters)
|
243
269
|
|
244
270
|
|
245
271
|
class ProductType(ParametrizedDependentType):
|
@@ -327,8 +353,15 @@ def EndsWith(value: str, suffix):
|
|
327
353
|
|
328
354
|
|
329
355
|
@dependent_check
|
330
|
-
|
331
|
-
|
356
|
+
class Regexp:
|
357
|
+
def __post_init__(self):
|
358
|
+
self.rx = re.compile(self.parameter)
|
359
|
+
|
360
|
+
def check(self, value: str):
|
361
|
+
return bool(self.rx.search(value))
|
362
|
+
|
363
|
+
def codegen(self):
|
364
|
+
return CodeGen("bool({rx}.search({arg}))", rx=self.rx)
|
332
365
|
|
333
366
|
|
334
367
|
class Dependent:
|
ovld/mro.py
CHANGED
@@ -3,6 +3,8 @@ from enum import Enum
|
|
3
3
|
from graphlib import TopologicalSorter
|
4
4
|
from typing import get_args, get_origin
|
5
5
|
|
6
|
+
from .utils import UnionTypes
|
7
|
+
|
6
8
|
|
7
9
|
class Order(Enum):
|
8
10
|
LESS = -1
|
@@ -121,6 +123,9 @@ def subclasscheck(t1, t2):
|
|
121
123
|
):
|
122
124
|
return result
|
123
125
|
|
126
|
+
if t2 in UnionTypes:
|
127
|
+
return isinstance(t1, t2)
|
128
|
+
|
124
129
|
o1 = get_origin(t1)
|
125
130
|
o2 = get_origin(t2)
|
126
131
|
|
ovld/py.typed
ADDED
File without changes
|
ovld/recode.py
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
import ast
|
2
2
|
import inspect
|
3
3
|
import linecache
|
4
|
-
import re
|
5
4
|
import textwrap
|
6
5
|
from ast import _splitlines_no_ff as splitlines
|
7
6
|
from functools import reduce
|
8
7
|
from itertools import count
|
9
8
|
from types import CodeType, FunctionType
|
10
9
|
|
11
|
-
from .utils import MISSING, Unusable, UsageError, subtler_type
|
10
|
+
from .utils import MISSING, NameDatabase, Unusable, UsageError, subtler_type
|
12
11
|
|
13
12
|
recurse = Unusable(
|
14
13
|
"recurse() can only be used from inside an @ovld-registered function."
|
@@ -18,20 +17,8 @@ call_next = Unusable(
|
|
18
17
|
)
|
19
18
|
|
20
19
|
|
21
|
-
dispatch_template = """
|
22
|
-
def __DISPATCH__(self, {args}):
|
23
|
-
{body}
|
24
|
-
"""
|
25
|
-
|
26
|
-
|
27
|
-
call_template = """
|
28
|
-
method = self.map[({lookup})]
|
29
|
-
return method({posargs})
|
30
|
-
"""
|
31
|
-
|
32
|
-
|
33
20
|
def instantiate_code(symbol, code, inject={}):
|
34
|
-
virtual_file = f"<ovld{hash(code)}>"
|
21
|
+
virtual_file = f"<ovld:{abs(hash(code)):x}>"
|
35
22
|
linecache.cache[virtual_file] = (None, None, splitlines(code), virtual_file)
|
36
23
|
code = compile(source=code, filename=virtual_file, mode="exec")
|
37
24
|
glb = {**inject}
|
@@ -51,36 +38,22 @@ def instantiate_code(symbol, code, inject={}):
|
|
51
38
|
# return rval
|
52
39
|
|
53
40
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
name = orig_name = getattr(value, "__name__", self.default_name)
|
71
|
-
if not re.match(string=name, pattern=r"[a-zA-Z_][a-zA-Z0-9_]+"):
|
72
|
-
name = self.default_name
|
73
|
-
i = 1
|
74
|
-
while name in self.registered:
|
75
|
-
name = f"{orig_name}{i}"
|
76
|
-
i += 1
|
77
|
-
self.variables[name] = value
|
78
|
-
self.names[id(value)] = name
|
79
|
-
self.registered.add(name)
|
80
|
-
return name
|
81
|
-
|
82
|
-
|
83
|
-
def generate_dispatch(arganal):
|
41
|
+
dispatch_template = """
|
42
|
+
def __WRAP_DISPATCH__(OVLD):
|
43
|
+
def __DISPATCH__({args}):
|
44
|
+
{body}
|
45
|
+
|
46
|
+
return __DISPATCH__
|
47
|
+
"""
|
48
|
+
|
49
|
+
|
50
|
+
call_template = """
|
51
|
+
{mvar} = OVLD.map[({lookup})]
|
52
|
+
return {mvar}({posargs})
|
53
|
+
"""
|
54
|
+
|
55
|
+
|
56
|
+
def generate_dispatch(ov, arganal):
|
84
57
|
def join(li, sep=", ", trail=False):
|
85
58
|
li = [x for x in li if x]
|
86
59
|
rval = sep.join(li)
|
@@ -88,16 +61,23 @@ def generate_dispatch(arganal):
|
|
88
61
|
rval += ","
|
89
62
|
return rval
|
90
63
|
|
91
|
-
|
64
|
+
arganal.compile()
|
65
|
+
|
66
|
+
spr = arganal.strict_positional_required
|
67
|
+
spo = arganal.strict_positional_optional
|
68
|
+
pr = arganal.positional_required
|
69
|
+
po = arganal.positional_optional
|
70
|
+
kr = arganal.keyword_required
|
71
|
+
ko = arganal.keyword_optional
|
92
72
|
|
93
73
|
inits = set()
|
94
74
|
|
95
75
|
kwargsstar = ""
|
96
76
|
targsstar = ""
|
97
77
|
|
98
|
-
args = []
|
78
|
+
args = ["self" if arganal.is_method else ""]
|
99
79
|
body = [""]
|
100
|
-
posargs = ["self
|
80
|
+
posargs = ["self" if arganal.is_method else ""]
|
101
81
|
lookup = []
|
102
82
|
|
103
83
|
i = 0
|
@@ -109,6 +89,8 @@ def generate_dispatch(arganal):
|
|
109
89
|
for name in spr + spo + pr + po + kr:
|
110
90
|
ndb.register(name)
|
111
91
|
|
92
|
+
mv = ndb.gensym(desired_name="method")
|
93
|
+
|
112
94
|
for name in spr + spo:
|
113
95
|
if name in spr:
|
114
96
|
args.append(name)
|
@@ -118,7 +100,7 @@ def generate_dispatch(arganal):
|
|
118
100
|
lookup.append(f"{lookup_for(i)}({name})")
|
119
101
|
i += 1
|
120
102
|
|
121
|
-
if len(po) <= 1:
|
103
|
+
if len(po) <= 1 and (spr or spo):
|
122
104
|
# If there are more than one non-strictly positional optional arguments,
|
123
105
|
# then all positional arguments are strictly positional, because if e.g.
|
124
106
|
# x and y are optional we want x==MISSING to imply that y==MISSING, but
|
@@ -161,6 +143,7 @@ def generate_dispatch(arganal):
|
|
161
143
|
fullcall = call_template.format(
|
162
144
|
lookup=join(lookup, trail=True),
|
163
145
|
posargs=join(posargs),
|
146
|
+
mvar=mv,
|
164
147
|
)
|
165
148
|
|
166
149
|
calls = []
|
@@ -170,19 +153,21 @@ def generate_dispatch(arganal):
|
|
170
153
|
call = call_template.format(
|
171
154
|
lookup=join(lookup[: req + i], trail=True),
|
172
155
|
posargs=join(posargs[: req + i + 1]),
|
156
|
+
mvar=mv,
|
173
157
|
)
|
174
|
-
call = textwrap.indent(call, "
|
158
|
+
call = textwrap.indent(call, " ")
|
175
159
|
calls.append(f"\nif {arg} is MISSING:{call}")
|
176
160
|
calls.append(fullcall)
|
177
161
|
|
178
|
-
lines = [*inits, *body, textwrap.indent("".join(calls), "
|
162
|
+
lines = [*inits, *body, textwrap.indent("".join(calls), " ")]
|
179
163
|
code = dispatch_template.format(
|
180
164
|
args=join(args),
|
181
|
-
body=join(lines, sep="\n
|
165
|
+
body=join(lines, sep="\n ").lstrip(),
|
182
166
|
)
|
183
|
-
|
184
|
-
"
|
167
|
+
wr = instantiate_code(
|
168
|
+
"__WRAP_DISPATCH__", code, inject={"MISSING": MISSING, **ndb.variables}
|
185
169
|
)
|
170
|
+
return wr(ov)
|
186
171
|
|
187
172
|
|
188
173
|
def generate_dependent_dispatch(tup, handlers, next_call, slf, name, err, nerr):
|
@@ -252,6 +237,9 @@ def generate_dependent_dispatch(tup, handlers, next_call, slf, name, err, nerr):
|
|
252
237
|
conj = "True"
|
253
238
|
conjs.append(conj)
|
254
239
|
|
240
|
+
if len(handlers) == 1:
|
241
|
+
exclusive = True
|
242
|
+
|
255
243
|
argspec = ", ".join(argname(x) for x in tup)
|
256
244
|
argcall = ", ".join(argprovide(x) for x in tup)
|
257
245
|
|
@@ -380,12 +368,19 @@ def rename_function(fn, newname):
|
|
380
368
|
|
381
369
|
class NameConverter(ast.NodeTransformer):
|
382
370
|
def __init__(
|
383
|
-
self,
|
371
|
+
self,
|
372
|
+
anal,
|
373
|
+
recurse_sym,
|
374
|
+
call_next_sym,
|
375
|
+
ovld_mangled,
|
376
|
+
map_mangled,
|
377
|
+
code_mangled,
|
384
378
|
):
|
385
379
|
self.analysis = anal
|
386
380
|
self.recurse_sym = recurse_sym
|
387
381
|
self.call_next_sym = call_next_sym
|
388
382
|
self.ovld_mangled = ovld_mangled
|
383
|
+
self.map_mangled = map_mangled
|
389
384
|
self.code_mangled = code_mangled
|
390
385
|
self.count = count()
|
391
386
|
|
@@ -450,14 +445,7 @@ class NameConverter(ast.NodeTransformer):
|
|
450
445
|
if cn:
|
451
446
|
type_parts.insert(0, ast.Name(id=self.code_mangled, ctx=ast.Load()))
|
452
447
|
method = ast.Subscript(
|
453
|
-
value=ast.
|
454
|
-
target=ast.Name(id=f"{tmp}M", ctx=ast.Store()),
|
455
|
-
value=ast.Attribute(
|
456
|
-
value=ast.Name(id=self.ovld_mangled, ctx=ast.Load()),
|
457
|
-
attr="map",
|
458
|
-
ctx=ast.Load(),
|
459
|
-
),
|
460
|
-
),
|
448
|
+
value=ast.Name(id=self.map_mangled, ctx=ast.Load()),
|
461
449
|
slice=ast.Tuple(
|
462
450
|
elts=type_parts,
|
463
451
|
ctx=ast.Load(),
|
@@ -465,19 +453,14 @@ class NameConverter(ast.NodeTransformer):
|
|
465
453
|
ctx=ast.Load(),
|
466
454
|
)
|
467
455
|
if self.analysis.is_method:
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
attr="__get__",
|
472
|
-
ctx=ast.Load(),
|
473
|
-
),
|
474
|
-
args=[ast.Name(id="self", ctx=ast.Load())],
|
475
|
-
keywords=[],
|
476
|
-
)
|
456
|
+
selfarg = [ast.Name(id="self", ctx=ast.Load())]
|
457
|
+
else:
|
458
|
+
selfarg = []
|
477
459
|
|
478
460
|
new_node = ast.Call(
|
479
461
|
func=method,
|
480
|
-
args=
|
462
|
+
args=selfarg
|
463
|
+
+ [
|
481
464
|
ast.Name(id=f"{tmp}{i}", ctx=ast.Load())
|
482
465
|
for i, arg in enumerate(node.args)
|
483
466
|
],
|
@@ -510,7 +493,10 @@ def adapt_function(fn, ovld, newname):
|
|
510
493
|
"""Create a copy of the function with a different name."""
|
511
494
|
rec_syms = list(
|
512
495
|
_search_names(
|
513
|
-
fn.__code__,
|
496
|
+
fn.__code__,
|
497
|
+
(recurse, ovld, ovld.dispatch),
|
498
|
+
fn.__globals__,
|
499
|
+
fn.__closure__,
|
514
500
|
)
|
515
501
|
)
|
516
502
|
cn_syms = list(
|
@@ -552,6 +538,7 @@ def closure_wrap(tree, fname, names):
|
|
552
538
|
|
553
539
|
def recode(fn, ovld, recurse_sym, call_next_sym, newname):
|
554
540
|
ovld_mangled = f"___OVLD{ovld.id}"
|
541
|
+
map_mangled = f"___MAP{ovld.id}"
|
555
542
|
code_mangled = f"___CODE{next(_current)}"
|
556
543
|
try:
|
557
544
|
src = inspect.getsource(fn)
|
@@ -568,6 +555,7 @@ def recode(fn, ovld, recurse_sym, call_next_sym, newname):
|
|
568
555
|
recurse_sym=recurse_sym,
|
569
556
|
call_next_sym=call_next_sym,
|
570
557
|
ovld_mangled=ovld_mangled,
|
558
|
+
map_mangled=map_mangled,
|
571
559
|
code_mangled=code_mangled,
|
572
560
|
).visit(tree)
|
573
561
|
new.body[0].decorator_list = []
|
@@ -592,6 +580,7 @@ def recode(fn, ovld, recurse_sym, call_next_sym, newname):
|
|
592
580
|
new_fn.__annotations__ = fn.__annotations__
|
593
581
|
new_fn = rename_function(new_fn, newname)
|
594
582
|
new_fn.__globals__["__SUBTLER_TYPE"] = subtler_type
|
595
|
-
new_fn.__globals__[ovld_mangled] = ovld
|
583
|
+
new_fn.__globals__[ovld_mangled] = ovld.dispatch
|
584
|
+
new_fn.__globals__[map_mangled] = ovld.map
|
596
585
|
new_fn.__globals__[code_mangled] = new_fn.__code__
|
597
586
|
return new_fn
|
ovld/types.py
CHANGED
@@ -3,16 +3,18 @@ import sys
|
|
3
3
|
import typing
|
4
4
|
from dataclasses import dataclass
|
5
5
|
from functools import partial
|
6
|
-
from typing import Protocol, runtime_checkable
|
6
|
+
from typing import Protocol, get_args, runtime_checkable
|
7
7
|
|
8
8
|
from .mro import Order, TypeRelationship, subclasscheck, typeorder
|
9
9
|
from .typemap import TypeMap
|
10
|
-
from .utils import UsageError, clsstring
|
10
|
+
from .utils import UnionType, UnionTypes, UsageError, clsstring
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
|
13
|
+
def get_args(tp):
|
14
|
+
args = getattr(tp, "__args__", None)
|
15
|
+
if not isinstance(args, tuple):
|
16
|
+
args = ()
|
17
|
+
return args
|
16
18
|
|
17
19
|
|
18
20
|
class TypeNormalizer:
|
@@ -37,6 +39,8 @@ class TypeNormalizer:
|
|
37
39
|
t = object
|
38
40
|
elif t is inspect._empty:
|
39
41
|
t = object
|
42
|
+
elif t in UnionTypes:
|
43
|
+
return type[t]
|
40
44
|
elif isinstance(t, typing._AnnotatedAlias):
|
41
45
|
t = t.__origin__
|
42
46
|
|
@@ -82,7 +86,7 @@ class MetaMC(type):
|
|
82
86
|
return super().__new__(T, name, (), {"_handler": handler})
|
83
87
|
|
84
88
|
def __init__(cls, name, handler):
|
85
|
-
cls.__args__ =
|
89
|
+
cls.__args__ = get_args(handler)
|
86
90
|
|
87
91
|
def codegen(cls):
|
88
92
|
return cls._handler.codegen()
|
ovld/utils.py
CHANGED
@@ -1,7 +1,18 @@
|
|
1
1
|
"""Miscellaneous utilities."""
|
2
2
|
|
3
3
|
import functools
|
4
|
+
import re
|
4
5
|
import typing
|
6
|
+
from itertools import count
|
7
|
+
|
8
|
+
try:
|
9
|
+
from types import UnionType
|
10
|
+
|
11
|
+
UnionTypes = (type(typing.Union[int, str]), UnionType)
|
12
|
+
|
13
|
+
except ImportError: # pragma: no cover
|
14
|
+
UnionType = None
|
15
|
+
UnionTypes = (type(typing.Union[int, str]),)
|
5
16
|
|
6
17
|
|
7
18
|
class Named:
|
@@ -88,6 +99,8 @@ def clsstring(cls):
|
|
88
99
|
def subtler_type(obj):
|
89
100
|
if isinstance(obj, GenericAlias):
|
90
101
|
return type[obj]
|
102
|
+
elif isinstance(obj, UnionTypes):
|
103
|
+
return type[obj]
|
91
104
|
elif obj is typing.Any:
|
92
105
|
return type[object]
|
93
106
|
elif isinstance(obj, type):
|
@@ -96,6 +109,40 @@ def subtler_type(obj):
|
|
96
109
|
return type(obj)
|
97
110
|
|
98
111
|
|
112
|
+
class NameDatabase:
|
113
|
+
def __init__(self, default_name):
|
114
|
+
self.default_name = default_name
|
115
|
+
self.count = count()
|
116
|
+
self.variables = {}
|
117
|
+
self.names = {}
|
118
|
+
self.registered = set()
|
119
|
+
|
120
|
+
def register(self, name):
|
121
|
+
self.registered.add(name)
|
122
|
+
|
123
|
+
def gensym(self, desired_name):
|
124
|
+
i = 1
|
125
|
+
name = desired_name
|
126
|
+
while name in self.registered:
|
127
|
+
name = f"{desired_name}{i}"
|
128
|
+
i += 1
|
129
|
+
self.registered.add(name)
|
130
|
+
return name
|
131
|
+
|
132
|
+
def __getitem__(self, value):
|
133
|
+
if isinstance(value, (int, float, str)):
|
134
|
+
return repr(value)
|
135
|
+
if id(value) in self.names:
|
136
|
+
return self.names[id(value)]
|
137
|
+
name = getattr(value, "__name__", self.default_name)
|
138
|
+
if not re.match(string=name, pattern=r"[a-zA-Z_][a-zA-Z0-9_]+"):
|
139
|
+
name = self.default_name
|
140
|
+
name = self.gensym(name)
|
141
|
+
self.variables[name] = value
|
142
|
+
self.names[id(value)] = name
|
143
|
+
return name
|
144
|
+
|
145
|
+
|
99
146
|
__all__ = [
|
100
147
|
"BOOTSTRAP",
|
101
148
|
"MISSING",
|
ovld/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
version = "0.4.
|
1
|
+
version = "0.4.6"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: ovld
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.6
|
4
4
|
Summary: Overloading Python functions
|
5
5
|
Project-URL: Homepage, https://ovld.readthedocs.io/en/latest/
|
6
6
|
Project-URL: Documentation, https://ovld.readthedocs.io/en/latest/
|
@@ -201,16 +201,17 @@ assert Two().f("s") == "a string"
|
|
201
201
|
|
202
202
|
# Benchmarks
|
203
203
|
|
204
|
-
`ovld` is pretty fast: the overhead is comparable to `isinstance` or `match`, and only 2-3x slower when dispatching on `Literal` types. Compared to other multiple dispatch libraries, it
|
204
|
+
`ovld` is pretty fast: the overhead is comparable to `isinstance` or `match`, and only 2-3x slower when dispatching on `Literal` types. Compared to other multiple dispatch libraries, it has 1.5x to 100x less overhead.
|
205
205
|
|
206
206
|
Time relative to the fastest implementation (1.00) (lower is better).
|
207
207
|
|
208
|
-
|
|
208
|
+
| Benchmark | custom | [ovld](https://github.com/breuleux/ovld) | [plum](https://github.com/beartype/plum) | [multim](https://github.com/coady/multimethod) | [multid](https://github.com/mrocklin/multipledispatch/) | [runtype](https://github.com/erezsh/runtype) | [fastcore](https://github.com/fastai/fastcore) | [sd](https://docs.python.org/3/library/functools.html#functools.singledispatch) |
|
209
209
|
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
210
|
-
|[trivial](https://github.com/breuleux/ovld/tree/master/benchmarks/test_trivial.py)|1.
|
211
|
-
|[
|
212
|
-
|[
|
213
|
-
|[ast](https://github.com/breuleux/ovld/tree/master/benchmarks/test_ast.py)|1.00|1.
|
214
|
-
|[calc](https://github.com/breuleux/ovld/tree/master/benchmarks/test_calc.py)|1.00|1.
|
215
|
-
|[
|
216
|
-
|[
|
210
|
+
|[trivial](https://github.com/breuleux/ovld/tree/master/benchmarks/test_trivial.py)|1.45|1.00|3.32|4.63|2.04|2.41|51.93|1.91|
|
211
|
+
|[multer](https://github.com/breuleux/ovld/tree/master/benchmarks/test_multer.py)|1.13|1.00|11.05|4.53|8.31|2.19|46.74|7.32|
|
212
|
+
|[add](https://github.com/breuleux/ovld/tree/master/benchmarks/test_add.py)|1.08|1.00|3.73|5.21|2.37|2.79|59.31|x|
|
213
|
+
|[ast](https://github.com/breuleux/ovld/tree/master/benchmarks/test_ast.py)|1.00|1.08|23.14|3.09|1.68|1.91|28.39|1.66|
|
214
|
+
|[calc](https://github.com/breuleux/ovld/tree/master/benchmarks/test_calc.py)|1.00|1.23|54.61|29.32|x|x|x|x|
|
215
|
+
|[regexp](https://github.com/breuleux/ovld/tree/master/benchmarks/test_regexp.py)|1.00|1.87|19.18|x|x|x|x|x|
|
216
|
+
|[fib](https://github.com/breuleux/ovld/tree/master/benchmarks/test_fib.py)|1.00|3.30|444.31|125.77|x|x|x|x|
|
217
|
+
|[tweaknum](https://github.com/breuleux/ovld/tree/master/benchmarks/test_tweaknum.py)|1.00|2.09|x|x|x|x|x|x|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
ovld/__init__.py,sha256=IVzs4_9skMa_UGZsGT7Ra_dIxj7KKNlV77XxlqcS6ow,1446
|
2
|
+
ovld/abc.py,sha256=4qpZyYwI8dWgY1Oiv5FhdKg2uzNcyWxIpGmGJVcjXrs,1177
|
3
|
+
ovld/core.py,sha256=MtRcJNhMwsix2Y7zP1fQS-taCy_t0bqkjHQdLF8WE8E,25719
|
4
|
+
ovld/dependent.py,sha256=M85EJPRGNnEpmsr3tEX1U-1di1cxQb5-0oQtLgvfRoA,10183
|
5
|
+
ovld/mro.py,sha256=D9KY3KEFnWL_SXZqHsVKNgRM1E0d8TPPILVZ3SxdUb4,4643
|
6
|
+
ovld/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
+
ovld/recode.py,sha256=bwsdM-TPpSQEmJhv8G30yDLrF8_I5wyTXpITMCC1W9k,17961
|
8
|
+
ovld/typemap.py,sha256=PH5dy8ddVCcqj2TkQbgsM7fmCdHsJT9WGXFPn4JZsCA,13131
|
9
|
+
ovld/types.py,sha256=lvOl1fldIqCJM-TvvdM2nMvAYDgcA45awfpkUB6kKpo,12984
|
10
|
+
ovld/utils.py,sha256=k-cVMbJigtdeuD5_JEYtq1a6fXhmUGqLfBu8YxYd5VY,3395
|
11
|
+
ovld/version.py,sha256=DWNNbWBv-hyqVy76bKbnHWaDUbqL0BOtdkPSV_88dx0,18
|
12
|
+
ovld-0.4.6.dist-info/METADATA,sha256=mXEwvqopBdpksDEKETVZkp-airCb48yt4AWZIQ2r2wU,7833
|
13
|
+
ovld-0.4.6.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
14
|
+
ovld-0.4.6.dist-info/licenses/LICENSE,sha256=cSwNTIzd1cbI89xt3PeZZYJP2y3j8Zus4bXgo4svpX8,1066
|
15
|
+
ovld-0.4.6.dist-info/RECORD,,
|
ovld-0.4.4.dist-info/RECORD
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
ovld/__init__.py,sha256=GEhOdG64GPUY5Y5BMnaik7UMgcq12mWVhxN9quIonYw,1476
|
2
|
-
ovld/abc.py,sha256=4qpZyYwI8dWgY1Oiv5FhdKg2uzNcyWxIpGmGJVcjXrs,1177
|
3
|
-
ovld/core.py,sha256=vzrYgUdagsQH6gbw32kzYzd-MvnCE3O9LjUyc-vVk1M,25366
|
4
|
-
ovld/dependent.py,sha256=vvWjjOHNtw8PegZVDj1fRKpyp6SCg1WdOWoXNxZSVrk,9288
|
5
|
-
ovld/mro.py,sha256=0cJK_v-POCiuwjluwf8fWeQ3G-2chEUv3KYe57GC61Q,4552
|
6
|
-
ovld/recode.py,sha256=NWAM0qyOQqXgVCZxtmhtnkygWe-NV5FRwdXGtqikGTg,18494
|
7
|
-
ovld/typemap.py,sha256=PH5dy8ddVCcqj2TkQbgsM7fmCdHsJT9WGXFPn4JZsCA,13131
|
8
|
-
ovld/types.py,sha256=EZNv8pThbo47KBULYM3R0R3sM2C5LC4DWraXZImkiNs,12877
|
9
|
-
ovld/utils.py,sha256=BsE7MmW5sfnO4Ym8hcEL8JNZZ1JIX_pgVRAAidh_8_w,2085
|
10
|
-
ovld/version.py,sha256=Qk6wsgzl7cSMWWZ902odoQ-wBV5Fda2ShX82NN7uHzM,18
|
11
|
-
ovld-0.4.4.dist-info/METADATA,sha256=wUsEDZyoQPooCN7P7q4MyObKAeSjg7DqpR-vRne5BW8,7713
|
12
|
-
ovld-0.4.4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
13
|
-
ovld-0.4.4.dist-info/licenses/LICENSE,sha256=cSwNTIzd1cbI89xt3PeZZYJP2y3j8Zus4bXgo4svpX8,1066
|
14
|
-
ovld-0.4.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|