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