ovld 0.3.9__py3-none-any.whl → 0.4.1__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 +41 -15
- ovld/core.py +404 -589
- ovld/dependent.py +275 -0
- ovld/mro.py +193 -161
- ovld/recode.py +518 -16
- ovld/typemap.py +383 -0
- ovld/types.py +219 -0
- ovld/utils.py +9 -110
- ovld/version.py +1 -1
- ovld-0.4.1.dist-info/METADATA +216 -0
- ovld-0.4.1.dist-info/RECORD +13 -0
- ovld-0.3.9.dist-info/METADATA +0 -305
- ovld-0.3.9.dist-info/RECORD +0 -10
- {ovld-0.3.9.dist-info → ovld-0.4.1.dist-info}/WHEEL +0 -0
- {ovld-0.3.9.dist-info → ovld-0.4.1.dist-info}/licenses/LICENSE +0 -0
ovld/core.py
CHANGED
@@ -2,328 +2,283 @@
|
|
2
2
|
|
3
3
|
import inspect
|
4
4
|
import itertools
|
5
|
-
import math
|
6
5
|
import sys
|
7
6
|
import textwrap
|
8
7
|
import typing
|
9
|
-
from
|
10
|
-
from
|
8
|
+
from collections import defaultdict
|
9
|
+
from dataclasses import dataclass, field, replace
|
10
|
+
from functools import cached_property, partial
|
11
|
+
from types import GenericAlias
|
11
12
|
|
12
|
-
from .
|
13
|
-
|
14
|
-
|
13
|
+
from .recode import (
|
14
|
+
Conformer,
|
15
|
+
adapt_function,
|
16
|
+
generate_dispatch,
|
17
|
+
rename_function,
|
18
|
+
)
|
19
|
+
from .typemap import MultiTypeMap, is_type_of_type
|
20
|
+
from .types import normalize_type
|
21
|
+
from .utils import UsageError, keyword_decorator
|
15
22
|
|
16
|
-
|
17
|
-
from types import UnionType
|
18
|
-
except ImportError: # pragma: no cover
|
19
|
-
UnionType = None
|
23
|
+
_current_id = itertools.count()
|
20
24
|
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
return hasattr(obj, "__origin__")
|
25
|
-
|
26
|
+
def _fresh(t):
|
27
|
+
"""Returns a new subclass of type t.
|
26
28
|
|
27
|
-
class
|
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)
|
29
36
|
|
30
37
|
|
31
|
-
|
32
|
-
|
38
|
+
@keyword_decorator
|
39
|
+
def _setattrs(fn, **kwargs):
|
40
|
+
for k, v in kwargs.items():
|
41
|
+
setattr(fn, k, v)
|
42
|
+
return fn
|
33
43
|
|
34
44
|
|
35
|
-
|
36
|
-
|
45
|
+
@keyword_decorator
|
46
|
+
def _compile_first(fn, rename=None):
|
47
|
+
def first_entry(self, *args, **kwargs):
|
48
|
+
self.compile()
|
49
|
+
method = getattr(self, fn.__name__)
|
50
|
+
assert method is not first_entry
|
51
|
+
return method(*args, **kwargs)
|
37
52
|
|
38
|
-
|
39
|
-
|
53
|
+
first_entry._replace_by = fn
|
54
|
+
first_entry._rename = rename
|
55
|
+
return first_entry
|
40
56
|
|
41
|
-
typemap[some_type] returns a tuple of a handler and a "level" that
|
42
|
-
represents the distance from the handler to the type `object`. Essentially,
|
43
|
-
the level is the index of the type for which the handler was registered
|
44
|
-
in the mro of `some_type`. So for example, `object` has level 0, a class
|
45
|
-
that inherits directly from `object` has level 1, and so on.
|
46
|
-
"""
|
47
57
|
|
48
|
-
|
49
|
-
|
50
|
-
|
58
|
+
def arg0_is_self(fn):
|
59
|
+
sgn = inspect.signature(fn)
|
60
|
+
params = list(sgn.parameters.values())
|
61
|
+
return params and params[0].name == "self"
|
51
62
|
|
52
|
-
def register(self, obj_t, handler):
|
53
|
-
"""Register a handler for the given object type."""
|
54
|
-
if isinstance(obj_t, str):
|
55
|
-
obj_t = eval(obj_t, getattr(handler[0], "__globals__", {}))
|
56
63
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
s.add(handler)
|
64
|
+
@dataclass(frozen=True)
|
65
|
+
class Arginfo:
|
66
|
+
position: typing.Optional[int]
|
67
|
+
name: typing.Optional[str]
|
68
|
+
required: bool
|
69
|
+
ann: type
|
64
70
|
|
65
|
-
|
66
|
-
|
71
|
+
@cached_property
|
72
|
+
def is_complex(self):
|
73
|
+
return isinstance(self.ann, GenericAlias)
|
67
74
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
results = {}
|
72
|
-
abscollect = set()
|
73
|
-
if is_type_of_type(obj_t):
|
74
|
-
mro = [
|
75
|
-
type[t]
|
76
|
-
for t in compose_mro(obj_t.__args__[0], self.types, abscollect)
|
77
|
-
]
|
78
|
-
mro.append(type)
|
79
|
-
mro.append(object)
|
80
|
-
else:
|
81
|
-
mro = compose_mro(obj_t, self.types, abscollect)
|
82
|
-
|
83
|
-
lvl = -1
|
84
|
-
prev_is_abstract = False
|
85
|
-
for cls in reversed(mro):
|
86
|
-
if cls not in abscollect or not prev_is_abstract:
|
87
|
-
lvl += 1
|
88
|
-
prev_is_abstract = cls in abscollect
|
89
|
-
handlers = self.entries.get(cls, None)
|
90
|
-
if handlers:
|
91
|
-
results.update({h: lvl for h in handlers})
|
92
|
-
|
93
|
-
if results:
|
94
|
-
self[obj_t] = results
|
95
|
-
return results
|
96
|
-
else:
|
97
|
-
raise KeyError(obj_t)
|
75
|
+
@cached_property
|
76
|
+
def canonical(self):
|
77
|
+
return self.name if self.position is None else self.position
|
98
78
|
|
99
79
|
|
100
|
-
|
101
|
-
|
80
|
+
@dataclass(frozen=True)
|
81
|
+
class Signature:
|
82
|
+
types: tuple
|
83
|
+
req_pos: int
|
84
|
+
max_pos: int
|
85
|
+
req_names: frozenset
|
86
|
+
vararg: bool
|
87
|
+
priority: float
|
88
|
+
is_method: bool = False
|
89
|
+
arginfo: list[Arginfo] = field(
|
90
|
+
default_factory=list, hash=False, compare=False
|
91
|
+
)
|
102
92
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
93
|
+
@classmethod
|
94
|
+
def extract(cls, fn):
|
95
|
+
typelist = []
|
96
|
+
sig = inspect.signature(fn)
|
97
|
+
max_pos = 0
|
98
|
+
req_pos = 0
|
99
|
+
req_names = set()
|
100
|
+
is_method = False
|
101
|
+
|
102
|
+
arginfo = []
|
103
|
+
for i, (name, param) in enumerate(sig.parameters.items()):
|
104
|
+
if name == "self":
|
105
|
+
assert i == 0
|
106
|
+
is_method = True
|
107
|
+
continue
|
108
|
+
pos = nm = None
|
109
|
+
ann = normalize_type(param.annotation, fn)
|
110
|
+
if param.kind is inspect._POSITIONAL_ONLY:
|
111
|
+
pos = i - is_method
|
112
|
+
typelist.append(ann)
|
113
|
+
req_pos += param.default is inspect._empty
|
114
|
+
max_pos += 1
|
115
|
+
elif param.kind is inspect._POSITIONAL_OR_KEYWORD:
|
116
|
+
pos = i - is_method
|
117
|
+
nm = param.name
|
118
|
+
typelist.append(ann)
|
119
|
+
req_pos += param.default is inspect._empty
|
120
|
+
max_pos += 1
|
121
|
+
elif param.kind is inspect._KEYWORD_ONLY:
|
122
|
+
nm = param.name
|
123
|
+
typelist.append((param.name, ann))
|
124
|
+
if param.default is inspect._empty:
|
125
|
+
req_names.add(param.name)
|
126
|
+
elif param.kind is inspect._VAR_POSITIONAL:
|
127
|
+
raise TypeError("ovld does not support *args")
|
128
|
+
elif param.kind is inspect._VAR_KEYWORD:
|
129
|
+
raise TypeError("ovld does not support **kwargs")
|
130
|
+
arginfo.append(
|
131
|
+
Arginfo(
|
132
|
+
position=pos,
|
133
|
+
name=nm,
|
134
|
+
required=param.default is inspect._empty,
|
135
|
+
ann=normalize_type(param.annotation, fn),
|
136
|
+
)
|
137
|
+
)
|
107
138
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
139
|
+
return cls(
|
140
|
+
types=tuple(typelist),
|
141
|
+
req_pos=req_pos,
|
142
|
+
max_pos=max_pos,
|
143
|
+
req_names=frozenset(req_names),
|
144
|
+
vararg=False,
|
145
|
+
is_method=is_method,
|
146
|
+
priority=None,
|
147
|
+
arginfo=arginfo,
|
148
|
+
)
|
113
149
|
|
114
|
-
In other words, [int, object] is more specific than [object, object] and
|
115
|
-
less specific than [int, int], but it is neither less specific nor more
|
116
|
-
specific than [object, int] (which means there is an ambiguity).
|
117
|
-
"""
|
118
150
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
if
|
130
|
-
return
|
131
|
-
elif obj is typing.Any:
|
132
|
-
return type[object]
|
133
|
-
elif isinstance(obj, type):
|
134
|
-
return type[obj]
|
151
|
+
def clsstring(cls):
|
152
|
+
if cls is object:
|
153
|
+
return "*"
|
154
|
+
elif isinstance(cls, tuple):
|
155
|
+
key, typ = cls
|
156
|
+
return f"{key}: {clsstring(typ)}"
|
157
|
+
elif is_type_of_type(cls):
|
158
|
+
arg = clsstring(cls.__args__[0])
|
159
|
+
return f"type[{arg}]"
|
160
|
+
elif hasattr(cls, "__origin__"):
|
161
|
+
if cls.__origin__ is typing.Union:
|
162
|
+
return "|".join(map(clsstring, cls.__args__))
|
135
163
|
else:
|
136
|
-
return
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
Arguments:
|
142
|
-
obj_t_tup: A tuple of argument types.
|
143
|
-
nargs: A (amin, amax, varargs) tuple where amin is the minimum
|
144
|
-
number of arguments needed to match this tuple (if there are
|
145
|
-
default arguments, it is possible that amin < len(obj_t_tup)),
|
146
|
-
amax is the maximum number of arguments, and varargs is a
|
147
|
-
boolean indicating whether there can be an arbitrary number
|
148
|
-
of arguments.
|
149
|
-
handler: A function to handle the tuple.
|
150
|
-
"""
|
151
|
-
self.clear()
|
152
|
-
|
153
|
-
if any(isinstance(x, GenericAlias) for x in obj_t_tup):
|
154
|
-
self.transform = self._transform
|
155
|
-
|
156
|
-
amin, amax, vararg, priority = nargs
|
157
|
-
|
158
|
-
entry = (handler, amin, amax, vararg)
|
159
|
-
if not obj_t_tup:
|
160
|
-
self.empty = entry
|
161
|
-
|
162
|
-
self.priorities[handler] = priority
|
163
|
-
|
164
|
-
for i, cls in enumerate(obj_t_tup):
|
165
|
-
if i not in self.maps:
|
166
|
-
self.maps[i] = TypeMap()
|
167
|
-
self.maps[i].register(cls, entry)
|
168
|
-
if vararg:
|
169
|
-
if -1 not in self.maps:
|
170
|
-
self.maps[-1] = TypeMap()
|
171
|
-
self.maps[-1].register(object, entry)
|
172
|
-
|
173
|
-
def resolve(self, obj_t_tup):
|
174
|
-
specificities = {}
|
175
|
-
candidates = None
|
176
|
-
nargs = len(obj_t_tup)
|
177
|
-
|
178
|
-
for i, cls in enumerate(obj_t_tup):
|
179
|
-
try:
|
180
|
-
results = self.maps[i][cls]
|
181
|
-
except KeyError:
|
182
|
-
results = {}
|
183
|
-
|
184
|
-
results = {
|
185
|
-
handler: spc
|
186
|
-
for (handler, min, max, va), spc in results.items()
|
187
|
-
if min <= nargs <= (math.inf if va else max)
|
188
|
-
}
|
189
|
-
|
190
|
-
try:
|
191
|
-
vararg_results = self.maps[-1][cls]
|
192
|
-
except KeyError:
|
193
|
-
vararg_results = {}
|
194
|
-
|
195
|
-
vararg_results = {
|
196
|
-
handler: spc
|
197
|
-
for (handler, min, max, va), spc in vararg_results.items()
|
198
|
-
if min <= nargs and i >= max
|
199
|
-
}
|
200
|
-
|
201
|
-
results.update(vararg_results)
|
202
|
-
|
203
|
-
if candidates is None:
|
204
|
-
candidates = set(results.keys())
|
205
|
-
else:
|
206
|
-
candidates &= results.keys()
|
207
|
-
for c in candidates:
|
208
|
-
specificities.setdefault(c, []).append(results[c])
|
164
|
+
return repr(cls)
|
165
|
+
elif hasattr(cls, "__name__"):
|
166
|
+
return cls.__name__
|
167
|
+
else:
|
168
|
+
return repr(cls)
|
209
169
|
|
210
|
-
if not candidates:
|
211
|
-
raise self.key_error(obj_t_tup, ())
|
212
170
|
|
213
|
-
|
214
|
-
|
215
|
-
for c in candidates
|
216
|
-
]
|
171
|
+
def sigstring(types):
|
172
|
+
return ", ".join(map(clsstring, types))
|
217
173
|
|
218
|
-
# The sort ensures that if candidate A dominates candidate B, A will
|
219
|
-
# appear before B in the list. That's because it must dominate all
|
220
|
-
# other possibilities on all arguments, so the sum of all specificities
|
221
|
-
# has to be greater.
|
222
|
-
# Note: priority is always more important than specificity
|
223
|
-
candidates.sort(key=lambda cspc: (cspc[1], sum(cspc[2])), reverse=True)
|
224
|
-
|
225
|
-
self.all[obj_t_tup] = {
|
226
|
-
getattr(c[0], "__code__", None) for c in candidates
|
227
|
-
}
|
228
|
-
|
229
|
-
def _pull(candidates):
|
230
|
-
if not candidates:
|
231
|
-
return
|
232
|
-
rval = [candidates[0]]
|
233
|
-
c1, p1, spc1 = candidates[0]
|
234
|
-
for c2, p2, spc2 in candidates[1:]:
|
235
|
-
if p1 > p2 or (
|
236
|
-
spc1 != spc2 and all(s1 >= s2 for s1, s2 in zip(spc1, spc2))
|
237
|
-
):
|
238
|
-
# Candidate 1 dominates candidate 2
|
239
|
-
continue
|
240
|
-
else:
|
241
|
-
# Candidate 1 does not dominate candidate 2, so we add it
|
242
|
-
# to the list.
|
243
|
-
rval.append((c2, p2, spc2))
|
244
|
-
yield rval
|
245
|
-
if len(rval) == 1:
|
246
|
-
# Only groups of length 1 are correct and reachable, so we don't
|
247
|
-
# care about the rest.
|
248
|
-
yield from _pull(candidates[1:])
|
249
|
-
|
250
|
-
results = list(_pull(candidates))
|
251
|
-
parent = None
|
252
|
-
for group in results:
|
253
|
-
tup = obj_t_tup if parent is None else (parent, *obj_t_tup)
|
254
|
-
if len(group) != 1:
|
255
|
-
self.errors[tup] = self.key_error(obj_t_tup, group)
|
256
|
-
break
|
257
|
-
else:
|
258
|
-
((fn, _, _),) = group
|
259
|
-
self[tup] = fn
|
260
|
-
if hasattr(fn, "__code__"):
|
261
|
-
parent = fn.__code__
|
262
|
-
else:
|
263
|
-
break
|
264
|
-
|
265
|
-
return True
|
266
|
-
|
267
|
-
def __missing__(self, obj_t_tup):
|
268
|
-
if obj_t_tup and isinstance(obj_t_tup[0], CodeType):
|
269
|
-
real_tup = obj_t_tup[1:]
|
270
|
-
self[real_tup]
|
271
|
-
if obj_t_tup[0] not in self.all[real_tup]:
|
272
|
-
return self[real_tup]
|
273
|
-
elif obj_t_tup in self.errors:
|
274
|
-
raise self.errors[obj_t_tup]
|
275
|
-
elif obj_t_tup in self: # pragma: no cover
|
276
|
-
# PROBABLY not reachable
|
277
|
-
return self[obj_t_tup]
|
278
|
-
else:
|
279
|
-
raise self.key_error(real_tup, ())
|
280
|
-
|
281
|
-
if not obj_t_tup:
|
282
|
-
if self.empty is MISSING:
|
283
|
-
raise self.key_error(obj_t_tup, ())
|
284
|
-
else:
|
285
|
-
return self.empty[0]
|
286
174
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
175
|
+
class ArgumentAnalyzer:
|
176
|
+
def __init__(self):
|
177
|
+
self.name_to_positions = defaultdict(set)
|
178
|
+
self.position_to_names = defaultdict(set)
|
179
|
+
self.counts = defaultdict(lambda: [0, 0])
|
180
|
+
self.complex_transforms = set()
|
181
|
+
self.total = 0
|
182
|
+
self.is_method = None
|
183
|
+
|
184
|
+
def add(self, fn):
|
185
|
+
sig = Signature.extract(fn)
|
186
|
+
self.complex_transforms.update(
|
187
|
+
arg.canonical for arg in sig.arginfo if arg.is_complex
|
188
|
+
)
|
189
|
+
for arg in sig.arginfo:
|
190
|
+
if arg.position is not None:
|
191
|
+
self.position_to_names[arg.position].add(arg.name)
|
192
|
+
if arg.name is not None:
|
193
|
+
self.name_to_positions[arg.name].add(arg.canonical)
|
194
|
+
|
195
|
+
cnt = self.counts[arg.canonical]
|
196
|
+
cnt[0] += arg.required
|
197
|
+
cnt[1] += 1
|
198
|
+
|
199
|
+
self.total += 1
|
200
|
+
|
201
|
+
if self.is_method is None:
|
202
|
+
self.is_method = sig.is_method
|
203
|
+
elif self.is_method != sig.is_method: # pragma: no cover
|
204
|
+
raise TypeError(
|
205
|
+
"Some, but not all registered methods define `self`. It should be all or none."
|
206
|
+
)
|
292
207
|
|
208
|
+
def compile(self):
|
209
|
+
for name, pos in self.name_to_positions.items():
|
210
|
+
if len(pos) != 1:
|
211
|
+
if all(isinstance(p, int) for p in pos):
|
212
|
+
raise TypeError(
|
213
|
+
f"Argument '{name}' is declared in different positions by different methods. The same argument name should always be in the same position unless it is strictly positional."
|
214
|
+
)
|
215
|
+
else:
|
216
|
+
raise TypeError(
|
217
|
+
f"Argument '{name}' is declared in a positional and keyword setting by different methods. It should be either."
|
218
|
+
)
|
293
219
|
|
294
|
-
|
295
|
-
|
220
|
+
p_to_n = [
|
221
|
+
list(names) for _, names in sorted(self.position_to_names.items())
|
222
|
+
]
|
296
223
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
224
|
+
positional = list(
|
225
|
+
itertools.takewhile(
|
226
|
+
lambda names: len(names) == 1 and isinstance(names[0], str),
|
227
|
+
reversed(p_to_n),
|
228
|
+
)
|
229
|
+
)
|
230
|
+
positional.reverse()
|
231
|
+
strict_positional = p_to_n[: len(p_to_n) - len(positional)]
|
301
232
|
|
233
|
+
assert strict_positional + positional == p_to_n
|
302
234
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
235
|
+
strict_positional_required = [
|
236
|
+
f"ARG{pos + 1}"
|
237
|
+
for pos, _ in enumerate(strict_positional)
|
238
|
+
if self.counts[pos][0] == self.total
|
239
|
+
]
|
240
|
+
strict_positional_optional = [
|
241
|
+
f"ARG{pos + 1}"
|
242
|
+
for pos, _ in enumerate(strict_positional)
|
243
|
+
if self.counts[pos][0] != self.total
|
244
|
+
]
|
308
245
|
|
246
|
+
positional_required = [
|
247
|
+
names[0]
|
248
|
+
for pos, names in enumerate(positional)
|
249
|
+
if self.counts[pos + len(strict_positional)][0] == self.total
|
250
|
+
]
|
251
|
+
positional_optional = [
|
252
|
+
names[0]
|
253
|
+
for pos, names in enumerate(positional)
|
254
|
+
if self.counts[pos + len(strict_positional)][0] != self.total
|
255
|
+
]
|
309
256
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
257
|
+
keywords = [
|
258
|
+
name
|
259
|
+
for _, (name,) in self.name_to_positions.items()
|
260
|
+
if not isinstance(name, int)
|
261
|
+
]
|
262
|
+
keyword_required = [
|
263
|
+
name for name in keywords if self.counts[name][0] == self.total
|
264
|
+
]
|
265
|
+
keyword_optional = [
|
266
|
+
name for name in keywords if self.counts[name][0] != self.total
|
267
|
+
]
|
317
268
|
|
318
|
-
|
319
|
-
|
320
|
-
|
269
|
+
return (
|
270
|
+
strict_positional_required,
|
271
|
+
strict_positional_optional,
|
272
|
+
positional_required,
|
273
|
+
positional_optional,
|
274
|
+
keyword_required,
|
275
|
+
keyword_optional,
|
276
|
+
)
|
321
277
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
return deco
|
278
|
+
def lookup_for(self, key):
|
279
|
+
return (
|
280
|
+
"self.map.transform" if key in self.complex_transforms else "type"
|
281
|
+
)
|
327
282
|
|
328
283
|
|
329
284
|
class _Ovld:
|
@@ -334,19 +289,11 @@ class _Ovld:
|
|
334
289
|
function should annotate the same parameter.
|
335
290
|
|
336
291
|
Arguments:
|
337
|
-
dispatch: A function to use as the entry point. It must find the
|
338
|
-
function to dispatch to and call it.
|
339
|
-
postprocess: A function to call on the return value. It is not called
|
340
|
-
after recursive calls.
|
341
292
|
mixins: A list of Ovld instances that contribute functions to this
|
342
293
|
Ovld.
|
343
|
-
type_error: The error type to raise when no function can be found to
|
344
|
-
dispatch to (default: TypeError).
|
345
294
|
name: Optional name for the Ovld. If not provided, it will be
|
346
295
|
gotten automatically from the first registered function or
|
347
296
|
dispatch.
|
348
|
-
mapper: Class implementing a mapping interface from a tuple of
|
349
|
-
types to a handler (default: MultiTypeMap).
|
350
297
|
linkback: Whether to keep a pointer in the parent mixins to this
|
351
298
|
ovld so that updates can be propagated. (default: False)
|
352
299
|
allow_replacement: Allow replacing a method by another with the
|
@@ -356,43 +303,27 @@ class _Ovld:
|
|
356
303
|
def __init__(
|
357
304
|
self,
|
358
305
|
*,
|
359
|
-
dispatch=None,
|
360
|
-
postprocess=None,
|
361
|
-
type_error=TypeError,
|
362
306
|
mixins=[],
|
363
307
|
bootstrap=None,
|
364
308
|
name=None,
|
365
|
-
mapper=MultiTypeMap,
|
366
309
|
linkback=False,
|
367
310
|
allow_replacement=True,
|
368
311
|
):
|
369
312
|
"""Initialize an Ovld."""
|
313
|
+
self.id = next(_current_id)
|
370
314
|
self._compiled = False
|
371
|
-
self._dispatch = dispatch
|
372
|
-
self.maindoc = None
|
373
|
-
self.mapper = mapper
|
374
315
|
self.linkback = linkback
|
375
316
|
self.children = []
|
376
|
-
self.type_error = type_error
|
377
|
-
self.postprocess = postprocess
|
378
317
|
self.allow_replacement = allow_replacement
|
379
|
-
self.
|
380
|
-
if self.postprocess:
|
381
|
-
assert bootstrap is not False
|
382
|
-
self.bootstrap = True
|
383
|
-
elif isinstance(bootstrap, type):
|
384
|
-
self.bootstrap_class = bootstrap
|
385
|
-
self.bootstrap = True
|
386
|
-
else:
|
387
|
-
self.bootstrap = bootstrap
|
318
|
+
self.bootstrap = bootstrap
|
388
319
|
self.name = name
|
320
|
+
self.shortname = name or f"__OVLD{self.id}"
|
389
321
|
self.__name__ = name
|
390
322
|
self._defns = {}
|
391
323
|
self._locked = False
|
392
324
|
self.mixins = []
|
393
325
|
self.add_mixins(*mixins)
|
394
|
-
self.ocls = _fresh(
|
395
|
-
self._make_signature()
|
326
|
+
self.ocls = _fresh(OvldCall)
|
396
327
|
|
397
328
|
@property
|
398
329
|
def defns(self):
|
@@ -402,6 +333,42 @@ class _Ovld:
|
|
402
333
|
defns.update(self._defns)
|
403
334
|
return defns
|
404
335
|
|
336
|
+
@property
|
337
|
+
def __doc__(self):
|
338
|
+
if not self._compiled:
|
339
|
+
self.compile()
|
340
|
+
|
341
|
+
docs = [fn.__doc__ for fn in self.defns.values() if fn.__doc__]
|
342
|
+
if len(docs) == 1:
|
343
|
+
maindoc = docs[0]
|
344
|
+
else:
|
345
|
+
maindoc = f"Ovld with {len(self.defns)} methods."
|
346
|
+
|
347
|
+
doc = f"{maindoc}\n\n"
|
348
|
+
for fn in self.defns.values():
|
349
|
+
fndef = inspect.signature(fn)
|
350
|
+
fdoc = fn.__doc__
|
351
|
+
if not fdoc or fdoc == maindoc:
|
352
|
+
doc += f"{self.__name__}{fndef}\n\n"
|
353
|
+
else:
|
354
|
+
if not fdoc.strip(" ").endswith("\n"):
|
355
|
+
fdoc += "\n"
|
356
|
+
fdoc = textwrap.indent(fdoc, " " * 4)
|
357
|
+
doc += f"{self.__name__}{fndef}\n{fdoc}\n"
|
358
|
+
return doc
|
359
|
+
|
360
|
+
@property
|
361
|
+
def __signature__(self):
|
362
|
+
if not self._compiled:
|
363
|
+
self.compile()
|
364
|
+
|
365
|
+
sig = inspect.signature(self._dispatch)
|
366
|
+
if not self.argument_analysis.is_method:
|
367
|
+
sig = inspect.Signature(
|
368
|
+
[v for k, v in sig.parameters.items() if k != "self"]
|
369
|
+
)
|
370
|
+
return sig
|
371
|
+
|
405
372
|
def lock(self):
|
406
373
|
self._locked = True
|
407
374
|
|
@@ -414,40 +381,21 @@ class _Ovld:
|
|
414
381
|
for mixin in mixins:
|
415
382
|
if self.linkback:
|
416
383
|
mixin.children.append(self)
|
417
|
-
if mixin._defns:
|
418
|
-
|
419
|
-
if self.bootstrap is None:
|
420
|
-
self.bootstrap = mixin.bootstrap
|
421
|
-
assert mixin.bootstrap is self.bootstrap
|
422
|
-
if mixin._dispatch:
|
423
|
-
self._dispatch = mixin._dispatch
|
384
|
+
if mixin._defns and self.bootstrap is None:
|
385
|
+
self.bootstrap = mixin.bootstrap
|
424
386
|
self.mixins += mixins
|
425
387
|
|
426
|
-
def _sig_string(self, type_tuple):
|
427
|
-
def clsname(cls):
|
428
|
-
if cls is object:
|
429
|
-
return "*"
|
430
|
-
elif is_type_of_type(cls):
|
431
|
-
arg = clsname(cls.__args__[0])
|
432
|
-
return f"type[{arg}]"
|
433
|
-
elif hasattr(cls, "__name__"):
|
434
|
-
return cls.__name__
|
435
|
-
else:
|
436
|
-
return repr(cls)
|
437
|
-
|
438
|
-
return ", ".join(map(clsname, type_tuple))
|
439
|
-
|
440
388
|
def _key_error(self, key, possibilities=None):
|
441
|
-
typenames =
|
389
|
+
typenames = sigstring(key)
|
442
390
|
if not possibilities:
|
443
|
-
return
|
391
|
+
return TypeError(
|
444
392
|
f"No method in {self} for argument types [{typenames}]"
|
445
393
|
)
|
446
394
|
else:
|
447
395
|
hlp = ""
|
448
396
|
for p, prio, spc in possibilities:
|
449
397
|
hlp += f"* {p.__name__} (priority: {prio}, specificity: {list(spc)})\n"
|
450
|
-
return
|
398
|
+
return TypeError(
|
451
399
|
f"Ambiguous resolution in {self} for"
|
452
400
|
f" argument types [{typenames}]\n"
|
453
401
|
f"Candidates are:\n{hlp}"
|
@@ -458,58 +406,15 @@ class _Ovld:
|
|
458
406
|
"""Rename this Ovld."""
|
459
407
|
self.name = name
|
460
408
|
self.__name__ = name
|
461
|
-
self._make_signature()
|
462
|
-
|
463
|
-
def _make_signature(self):
|
464
|
-
"""Make the __doc__ and __signature__."""
|
465
|
-
|
466
|
-
def modelA(*args, **kwargs): # pragma: no cover
|
467
|
-
pass
|
468
409
|
|
469
|
-
|
470
|
-
pass
|
471
|
-
|
472
|
-
seen = set()
|
473
|
-
doc = (
|
474
|
-
f"{self.maindoc}\n"
|
475
|
-
if self.maindoc
|
476
|
-
else f"Ovld with {len(self.defns)} methods.\n\n"
|
477
|
-
)
|
478
|
-
for key, fn in self.defns.items():
|
479
|
-
if fn in seen:
|
480
|
-
continue
|
481
|
-
seen.add(fn)
|
482
|
-
fndef = inspect.signature(fn)
|
483
|
-
fdoc = fn.__doc__
|
484
|
-
if not fdoc or fdoc == self.maindoc:
|
485
|
-
doc += f" ``{self.__name__}{fndef}``\n\n"
|
486
|
-
else:
|
487
|
-
if not fdoc.strip(" ").endswith("\n"):
|
488
|
-
fdoc += "\n"
|
489
|
-
fdoc = textwrap.indent(fdoc, " " * 8)
|
490
|
-
doc += f" ``{self.__name__}{fndef}``\n{fdoc}\n"
|
491
|
-
self.__doc__ = doc
|
492
|
-
if self.bootstrap:
|
493
|
-
self.__signature__ = inspect.signature(modelB)
|
494
|
-
else:
|
495
|
-
self.__signature__ = inspect.signature(modelA)
|
496
|
-
|
497
|
-
def _set_attrs_from(self, fn, dispatch=False):
|
410
|
+
def _set_attrs_from(self, fn):
|
498
411
|
"""Inherit relevant attributes from the function."""
|
499
412
|
if self.bootstrap is None:
|
500
|
-
|
501
|
-
params = list(sign.parameters.values())
|
502
|
-
if not dispatch:
|
503
|
-
if params and params[0].name == "self":
|
504
|
-
self.bootstrap = True
|
505
|
-
else:
|
506
|
-
self.bootstrap = False
|
413
|
+
self.bootstrap = arg0_is_self(fn)
|
507
414
|
|
508
415
|
if self.name is None:
|
509
416
|
self.name = f"{fn.__module__}.{fn.__qualname__}"
|
510
|
-
self.
|
511
|
-
if self.maindoc and not self.maindoc.strip(" ").endswith("\n"):
|
512
|
-
self.maindoc += "\n"
|
417
|
+
self.shortname = fn.__name__
|
513
418
|
self.__name__ = fn.__name__
|
514
419
|
self.__qualname__ = fn.__qualname__
|
515
420
|
self.__module__ = fn.__module__
|
@@ -533,57 +438,51 @@ class _Ovld:
|
|
533
438
|
for mixin in self.mixins:
|
534
439
|
if self not in mixin.children:
|
535
440
|
mixin.lock()
|
536
|
-
self._compiled = True
|
537
|
-
self.map = self.mapper(key_error=self._key_error)
|
538
441
|
|
539
442
|
cls = type(self)
|
540
443
|
if self.name is None:
|
541
|
-
self.name = self.__name__ = f"ovld{id
|
444
|
+
self.name = self.__name__ = f"ovld{self.id}"
|
542
445
|
|
543
446
|
name = self.__name__
|
447
|
+
self.map = MultiTypeMap(name=name, key_error=self._key_error)
|
544
448
|
|
545
449
|
# Replace the appropriate functions by their final behavior
|
546
450
|
for method in dir(cls):
|
547
451
|
value = getattr(cls, method)
|
548
452
|
repl = getattr(value, "_replace_by", None)
|
549
453
|
if repl:
|
550
|
-
if self.bootstrap and value._alt:
|
551
|
-
repl = value._alt
|
552
454
|
repl = self._maybe_rename(repl)
|
553
455
|
setattr(cls, method, repl)
|
554
456
|
|
555
457
|
target = self.ocls if self.bootstrap else cls
|
556
|
-
if self._dispatch:
|
557
|
-
target.__call__ = self._dispatch
|
558
458
|
|
559
|
-
|
560
|
-
|
459
|
+
anal = ArgumentAnalyzer()
|
460
|
+
for key, fn in list(self.defns.items()):
|
461
|
+
anal.add(fn)
|
462
|
+
self.argument_analysis = anal
|
463
|
+
dispatch = generate_dispatch(anal)
|
464
|
+
self._dispatch = dispatch
|
465
|
+
target.__call__ = rename_function(dispatch, f"{name}.dispatch")
|
561
466
|
|
562
467
|
for key, fn in list(self.defns.items()):
|
563
468
|
self.register_signature(key, fn)
|
564
469
|
|
565
|
-
|
566
|
-
"""Set a dispatch function."""
|
567
|
-
if self._dispatch is not None:
|
568
|
-
raise TypeError(f"dispatch for {self} is already set")
|
569
|
-
self._dispatch = dispatch
|
570
|
-
self._set_attrs_from(dispatch, dispatch=True)
|
571
|
-
return self
|
470
|
+
self._compiled = True
|
572
471
|
|
472
|
+
@_compile_first
|
573
473
|
def resolve(self, *args):
|
574
474
|
"""Find the correct method to call for the given arguments."""
|
575
475
|
return self.map[tuple(map(self.map.transform, args))]
|
576
476
|
|
577
|
-
def register_signature(self,
|
477
|
+
def register_signature(self, sig, orig_fn):
|
578
478
|
"""Register a function for the given signature."""
|
579
|
-
|
580
|
-
|
581
|
-
orig_fn, f"{self.__name__}[{self._sig_string(sig)}]"
|
479
|
+
fn = adapt_function(
|
480
|
+
orig_fn, self, f"{self.__name__}[{sigstring(sig.types)}]"
|
582
481
|
)
|
583
482
|
# We just need to keep the Conformer pointer alive for jurigged
|
584
483
|
# to find it, if jurigged is used with ovld
|
585
484
|
fn._conformer = Conformer(self, orig_fn, fn)
|
586
|
-
self.map.register(sig,
|
485
|
+
self.map.register(sig, fn)
|
587
486
|
return self
|
588
487
|
|
589
488
|
def register(self, fn=None, priority=0):
|
@@ -595,60 +494,17 @@ class _Ovld:
|
|
595
494
|
def _register(self, fn, priority):
|
596
495
|
"""Register a function."""
|
597
496
|
|
598
|
-
def _normalize_type(t, force_tuple=False):
|
599
|
-
origin = getattr(t, "__origin__", None)
|
600
|
-
if UnionType and isinstance(t, UnionType):
|
601
|
-
return _normalize_type(t.__args__)
|
602
|
-
elif origin is type:
|
603
|
-
return (t,) if force_tuple else t
|
604
|
-
elif origin is typing.Union:
|
605
|
-
return _normalize_type(t.__args__)
|
606
|
-
elif origin is not None:
|
607
|
-
raise TypeError(
|
608
|
-
f"ovld does not accept generic types except type, Union or Optional, not {t}"
|
609
|
-
)
|
610
|
-
elif isinstance(t, tuple):
|
611
|
-
return tuple(_normalize_type(t2) for t2 in t)
|
612
|
-
elif force_tuple:
|
613
|
-
return (t,)
|
614
|
-
else:
|
615
|
-
return t
|
616
|
-
|
617
497
|
self._attempt_modify()
|
618
498
|
|
619
499
|
self._set_attrs_from(fn)
|
620
500
|
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
"The first argument of the function must be named `self`"
|
628
|
-
)
|
629
|
-
argnames = argnames[1:]
|
630
|
-
|
631
|
-
typelist = []
|
632
|
-
for i, name in enumerate(argnames):
|
633
|
-
t = ann.get(name, None)
|
634
|
-
if t is None:
|
635
|
-
typelist.append(object)
|
636
|
-
else:
|
637
|
-
typelist.append(t)
|
638
|
-
|
639
|
-
max_pos = len(argnames)
|
640
|
-
req_pos = max_pos - len(argspec.defaults or ())
|
641
|
-
|
642
|
-
typelist_tups = tuple(
|
643
|
-
_normalize_type(t, force_tuple=True) for t in typelist
|
644
|
-
)
|
645
|
-
for tl in itertools.product(*typelist_tups):
|
646
|
-
sig = (tuple(tl), req_pos, max_pos, bool(argspec.varargs), priority)
|
647
|
-
if not self.allow_replacement and sig in self._defns:
|
648
|
-
raise TypeError(f"There is already a method for {tl}")
|
649
|
-
self._defns[(*sig,)] = fn
|
501
|
+
sig = replace(Signature.extract(fn), priority=priority)
|
502
|
+
if not self.allow_replacement and sig in self._defns:
|
503
|
+
raise TypeError(
|
504
|
+
f"There is already a method for {sigstring(sig.types)}"
|
505
|
+
)
|
506
|
+
self._defns[sig] = fn
|
650
507
|
|
651
|
-
self._make_signature()
|
652
508
|
self._update()
|
653
509
|
return self
|
654
510
|
|
@@ -664,13 +520,7 @@ class _Ovld:
|
|
664
520
|
for child in self.children:
|
665
521
|
child._update()
|
666
522
|
|
667
|
-
def copy(
|
668
|
-
self,
|
669
|
-
dispatch=MISSING,
|
670
|
-
postprocess=None,
|
671
|
-
mixins=[],
|
672
|
-
linkback=False,
|
673
|
-
):
|
523
|
+
def copy(self, mixins=[], linkback=False):
|
674
524
|
"""Create a copy of this Ovld.
|
675
525
|
|
676
526
|
New functions can be registered to the copy without affecting the
|
@@ -678,13 +528,11 @@ class _Ovld:
|
|
678
528
|
"""
|
679
529
|
return _fresh(_Ovld)(
|
680
530
|
bootstrap=self.bootstrap,
|
681
|
-
dispatch=self._dispatch if dispatch is MISSING else dispatch,
|
682
531
|
mixins=[self, *mixins],
|
683
|
-
postprocess=postprocess or self.postprocess,
|
684
532
|
linkback=linkback,
|
685
533
|
)
|
686
534
|
|
687
|
-
def variant(self, fn=None, **kwargs):
|
535
|
+
def variant(self, fn=None, priority=0, **kwargs):
|
688
536
|
"""Decorator to create a variant of this Ovld.
|
689
537
|
|
690
538
|
New functions can be registered to the variant without affecting the
|
@@ -692,73 +540,58 @@ class _Ovld:
|
|
692
540
|
"""
|
693
541
|
ov = self.copy(**kwargs)
|
694
542
|
if fn is None:
|
695
|
-
return ov.register
|
543
|
+
return partial(ov.register, priority=priority)
|
696
544
|
else:
|
697
|
-
ov.register(fn)
|
545
|
+
ov.register(fn, priority=priority)
|
698
546
|
return ov
|
699
547
|
|
700
|
-
@_compile_first
|
701
|
-
def get_map(self):
|
702
|
-
return self.map
|
703
|
-
|
704
548
|
@_compile_first
|
705
549
|
def __get__(self, obj, cls):
|
706
550
|
if obj is None:
|
707
551
|
return self
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
552
|
+
key = self.shortname
|
553
|
+
rval = obj.__dict__.get(key, None)
|
554
|
+
if rval is None:
|
555
|
+
obj.__dict__[key] = rval = self.ocls(self, obj)
|
556
|
+
return rval
|
713
557
|
|
714
558
|
@_compile_first
|
715
559
|
def __getitem__(self, t):
|
716
560
|
if not isinstance(t, tuple):
|
717
561
|
t = (t,)
|
718
|
-
assert not self.bootstrap
|
719
562
|
return self.map[t]
|
720
563
|
|
721
564
|
@_compile_first
|
722
565
|
@_setattrs(rename="dispatch")
|
723
|
-
def __call__(self, *args
|
566
|
+
def __call__(self, *args): # pragma: no cover
|
724
567
|
"""Call the overloaded function.
|
725
568
|
|
726
|
-
This
|
727
|
-
|
728
|
-
If bootstrap is False and a dispatch function is provided, it
|
729
|
-
replaces this function.
|
569
|
+
This should be replaced by an auto-generated function.
|
730
570
|
"""
|
731
571
|
key = tuple(map(self.map.transform, args))
|
732
572
|
method = self.map[key]
|
733
|
-
return method(*args
|
573
|
+
return method(*args)
|
734
574
|
|
735
575
|
@_compile_first
|
736
576
|
@_setattrs(rename="next")
|
737
|
-
def next(self, *args
|
577
|
+
def next(self, *args):
|
738
578
|
"""Call the next matching method after the caller, in terms of priority or specificity."""
|
739
579
|
fr = sys._getframe(1)
|
740
580
|
key = (fr.f_code, *map(self.map.transform, args))
|
741
581
|
method = self.map[key]
|
742
|
-
return method(*args
|
743
|
-
|
744
|
-
@__call__.setalt
|
745
|
-
@_setattrs(rename="entry")
|
746
|
-
def __ovldcall__(self, *args, **kwargs):
|
747
|
-
"""Call the overloaded function.
|
748
|
-
|
749
|
-
This version of __call__ is used when bootstrap is True. This function is
|
750
|
-
only called once at the entry point: recursive calls will will be to
|
751
|
-
OvldCall.__call__.
|
752
|
-
"""
|
753
|
-
ovc = self.__get__(BOOTSTRAP, None)
|
754
|
-
res = ovc(*args, **kwargs)
|
755
|
-
if self.postprocess:
|
756
|
-
res = self.postprocess(self, res)
|
757
|
-
return res
|
582
|
+
return method(*args)
|
758
583
|
|
759
584
|
def __repr__(self):
|
760
585
|
return f"<Ovld {self.name or hex(id(self))}>"
|
761
586
|
|
587
|
+
@_compile_first
|
588
|
+
def display_methods(self):
|
589
|
+
self.map.display_methods()
|
590
|
+
|
591
|
+
@_compile_first
|
592
|
+
def display_resolution(self, *args, **kwargs):
|
593
|
+
self.map.display_resolution(*args, **kwargs)
|
594
|
+
|
762
595
|
|
763
596
|
def is_ovld(x):
|
764
597
|
"""Return whether the argument is an ovld function/method."""
|
@@ -768,47 +601,43 @@ def is_ovld(x):
|
|
768
601
|
class OvldCall:
|
769
602
|
"""Context for an Ovld call."""
|
770
603
|
|
771
|
-
def __init__(self,
|
604
|
+
def __init__(self, ovld, bind_to):
|
772
605
|
"""Initialize an OvldCall."""
|
773
|
-
self.
|
774
|
-
self.
|
775
|
-
self.obj =
|
606
|
+
self.ovld = ovld
|
607
|
+
self.map = ovld.map
|
608
|
+
self.obj = bind_to
|
776
609
|
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
610
|
+
@property
|
611
|
+
def __name__(self):
|
612
|
+
return self.ovld.__name__
|
613
|
+
|
614
|
+
@property
|
615
|
+
def __doc__(self):
|
616
|
+
return self.ovld.__doc__
|
617
|
+
|
618
|
+
@property
|
619
|
+
def __signature__(self):
|
620
|
+
return self.ovld.__signature__
|
782
621
|
|
783
|
-
def next(self, *args
|
622
|
+
def next(self, *args):
|
784
623
|
"""Call the next matching method after the caller, in terms of priority or specificity."""
|
785
624
|
fr = sys._getframe(1)
|
786
625
|
key = (fr.f_code, *map(self.map.transform, args))
|
787
626
|
method = self.map[key]
|
788
|
-
return method(self.obj, *args
|
789
|
-
|
790
|
-
def super(self, *args, **kwargs):
|
791
|
-
"""Use the parent ovld's method for this call."""
|
792
|
-
pmap = self._parent.get_map()
|
793
|
-
method = pmap[tuple(map(pmap.transform, args))]
|
794
|
-
return method.__get__(self.obj)(*args, **kwargs)
|
627
|
+
return method(self.obj, *args)
|
795
628
|
|
796
629
|
def resolve(self, *args):
|
797
630
|
"""Find the right method to call for the given arguments."""
|
798
|
-
return self[tuple(map(self.map.transform, args))]
|
799
|
-
|
800
|
-
def call(self, *args):
|
801
|
-
"""Call the right method for the given arguments."""
|
802
|
-
return self[tuple(map(self.map.transform, args))](*args)
|
631
|
+
return self.map[tuple(map(self.map.transform, args))].__get__(self.obj)
|
803
632
|
|
804
|
-
def __call__(self, *args
|
633
|
+
def __call__(self, *args): # pragma: no cover
|
805
634
|
"""Call this overloaded function.
|
806
635
|
|
807
|
-
|
636
|
+
This should be replaced by an auto-generated function.
|
808
637
|
"""
|
809
638
|
key = tuple(map(self.map.transform, args))
|
810
639
|
method = self.map[key]
|
811
|
-
return method(self.obj, *args
|
640
|
+
return method(self.obj, *args)
|
812
641
|
|
813
642
|
|
814
643
|
def Ovld(*args, **kwargs):
|
@@ -823,7 +652,7 @@ def extend_super(fn):
|
|
823
652
|
plus this definition and others with the same name.
|
824
653
|
"""
|
825
654
|
if not is_ovld(fn):
|
826
|
-
fn = ovld(fn)
|
655
|
+
fn = ovld(fn, fresh=True)
|
827
656
|
fn._extend_super = True
|
828
657
|
return fn
|
829
658
|
|
@@ -835,29 +664,45 @@ class ovld_cls_dict(dict):
|
|
835
664
|
"""
|
836
665
|
|
837
666
|
def __init__(self, bases):
|
838
|
-
self.
|
667
|
+
self._bases = bases
|
839
668
|
|
840
669
|
def __setitem__(self, attr, value):
|
670
|
+
prev = None
|
841
671
|
if attr in self:
|
842
672
|
prev = self[attr]
|
673
|
+
if inspect.isfunction(prev):
|
674
|
+
prev = ovld(prev, fresh=True)
|
675
|
+
elif not is_ovld(prev): # pragma: no cover
|
676
|
+
prev = None
|
843
677
|
elif is_ovld(value) and getattr(value, "_extend_super", False):
|
844
|
-
|
845
|
-
|
846
|
-
|
678
|
+
mixins = []
|
679
|
+
for base in self._bases:
|
680
|
+
if (candidate := getattr(base, attr, None)) is not None:
|
681
|
+
if is_ovld(candidate) or inspect.isfunction(candidate):
|
682
|
+
mixins.append(candidate)
|
683
|
+
if mixins:
|
684
|
+
prev, *others = mixins
|
685
|
+
if is_ovld(prev):
|
686
|
+
prev = prev.copy()
|
687
|
+
else:
|
688
|
+
prev = ovld(prev, fresh=True)
|
689
|
+
for other in others:
|
690
|
+
if is_ovld(other):
|
691
|
+
prev.add_mixins(other)
|
692
|
+
else:
|
693
|
+
prev.register(other)
|
847
694
|
else:
|
848
695
|
prev = None
|
849
696
|
|
850
697
|
if prev is not None:
|
851
|
-
if
|
852
|
-
prev
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
prev.register(value)
|
860
|
-
value = prev
|
698
|
+
if is_ovld(value) and prev is not value:
|
699
|
+
if prev.name is None:
|
700
|
+
prev.rename(value.name)
|
701
|
+
prev.add_mixins(value)
|
702
|
+
value = prev
|
703
|
+
elif inspect.isfunction(value):
|
704
|
+
prev.register(value)
|
705
|
+
value = prev
|
861
706
|
|
862
707
|
super().__setitem__(attr, value)
|
863
708
|
|
@@ -905,19 +750,31 @@ class OvldBase(metaclass=OvldMC):
|
|
905
750
|
|
906
751
|
|
907
752
|
def _find_overload(fn, **kwargs):
|
908
|
-
|
909
|
-
|
753
|
+
fr = sys._getframe(1) # We typically expect to get to frame 3.
|
754
|
+
while fr and fn.__code__ not in fr.f_code.co_consts:
|
755
|
+
# We are basically searching for the function's code object in the stack.
|
756
|
+
# When a class/function A is nested in a class/function B, the former's
|
757
|
+
# code object is in the latter's co_consts. If ovld is used as a decorator,
|
758
|
+
# on A, then necessarily we are inside the execution of B, so B should be
|
759
|
+
# on the stack and we should be able to find A's code object in there.
|
760
|
+
fr = fr.f_back
|
761
|
+
|
762
|
+
if not fr:
|
763
|
+
raise UsageError("@ovld only works as a decorator.")
|
764
|
+
|
765
|
+
dispatch = fr.f_locals.get(fn.__name__, None)
|
766
|
+
|
910
767
|
if dispatch is None:
|
911
768
|
dispatch = _fresh(_Ovld)(**kwargs)
|
769
|
+
elif not is_ovld(dispatch): # pragma: no cover
|
770
|
+
raise TypeError("@ovld requires Ovld instance")
|
912
771
|
elif kwargs: # pragma: no cover
|
913
772
|
raise TypeError("Cannot configure an overload that already exists")
|
914
|
-
if not is_ovld(dispatch): # pragma: no cover
|
915
|
-
raise TypeError("@ovld requires Ovld instance")
|
916
773
|
return dispatch
|
917
774
|
|
918
775
|
|
919
776
|
@keyword_decorator
|
920
|
-
def ovld(fn, priority=0, **kwargs):
|
777
|
+
def ovld(fn, priority=0, fresh=False, **kwargs):
|
921
778
|
"""Overload a function.
|
922
779
|
|
923
780
|
Overloading is based on the function name.
|
@@ -931,71 +788,29 @@ def ovld(fn, priority=0, **kwargs):
|
|
931
788
|
|
932
789
|
Arguments:
|
933
790
|
fn: The function to register.
|
934
|
-
|
935
|
-
|
936
|
-
postprocess: A function to call on the return value. It is not called
|
937
|
-
after recursive calls.
|
791
|
+
priority: The priority of the function in the resolution order.
|
792
|
+
fresh: Whether to create a new ovld or try to reuse an existing one.
|
938
793
|
mixins: A list of Ovld instances that contribute functions to this
|
939
794
|
Ovld.
|
940
|
-
type_error: The error type to raise when no function can be found to
|
941
|
-
dispatch to (default: TypeError).
|
942
795
|
name: Optional name for the Ovld. If not provided, it will be
|
943
796
|
gotten automatically from the first registered function or
|
944
797
|
dispatch.
|
945
|
-
mapper: Class implementing a mapping interface from a tuple of
|
946
|
-
types to a handler (default: MultiTypeMap).
|
947
798
|
linkback: Whether to keep a pointer in the parent mixins to this
|
948
799
|
ovld so that updates can be propagated. (default: False)
|
949
800
|
"""
|
950
|
-
|
801
|
+
if fresh:
|
802
|
+
dispatch = _fresh(_Ovld)(**kwargs)
|
803
|
+
else:
|
804
|
+
dispatch = _find_overload(fn, **kwargs)
|
951
805
|
return dispatch.register(fn, priority=priority)
|
952
806
|
|
953
807
|
|
954
|
-
@keyword_decorator
|
955
|
-
def ovld_dispatch(dispatch, **kwargs):
|
956
|
-
"""Overload a function using the decorated function as a dispatcher.
|
957
|
-
|
958
|
-
The dispatch is the entry point for the function and receives a `self`
|
959
|
-
which is an Ovld or OvldCall instance, and the rest of the arguments.
|
960
|
-
It may call `self.resolve(arg1, arg2, ...)` to get the right method to
|
961
|
-
call.
|
962
|
-
|
963
|
-
The decorator optionally takes keyword arguments, *only* on the first
|
964
|
-
use.
|
965
|
-
|
966
|
-
Arguments:
|
967
|
-
dispatch: The function to use as the entry point. It must find the
|
968
|
-
function to dispatch to and call it.
|
969
|
-
postprocess: A function to call on the return value. It is not called
|
970
|
-
after recursive calls.
|
971
|
-
mixins: A list of Ovld instances that contribute functions to this
|
972
|
-
Ovld.
|
973
|
-
type_error: The error type to raise when no function can be found to
|
974
|
-
dispatch to (default: TypeError).
|
975
|
-
name: Optional name for the Ovld. If not provided, it will be
|
976
|
-
gotten automatically from the first registered function or
|
977
|
-
dispatch.
|
978
|
-
mapper: Class implementing a mapping interface from a tuple of
|
979
|
-
types to a handler (default: MultiTypeMap).
|
980
|
-
linkback: Whether to keep a pointer in the parent mixins to this
|
981
|
-
ovld so that updates can be propagated. (default: False)
|
982
|
-
"""
|
983
|
-
ov = _find_overload(dispatch, **kwargs)
|
984
|
-
return ov.dispatch(dispatch)
|
985
|
-
|
986
|
-
|
987
|
-
ovld.dispatch = ovld_dispatch
|
988
|
-
|
989
|
-
|
990
808
|
__all__ = [
|
991
|
-
"MultiTypeMap",
|
992
809
|
"Ovld",
|
993
810
|
"OvldBase",
|
994
811
|
"OvldCall",
|
995
812
|
"OvldMC",
|
996
|
-
"TypeMap",
|
997
813
|
"extend_super",
|
998
814
|
"is_ovld",
|
999
815
|
"ovld",
|
1000
|
-
"ovld_dispatch",
|
1001
816
|
]
|