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/core.py CHANGED
@@ -2,10 +2,23 @@
2
2
 
3
3
  import inspect
4
4
  import itertools
5
- import math
5
+ import sys
6
6
  import textwrap
7
7
  import typing
8
- from types import FunctionType
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
- try:
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 TypeMap(dict):
37
- """Represents a mapping from types to handlers.
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
- def register(self, obj_t, handler):
54
- """Register a handler for the given object type."""
55
- if isinstance(obj_t, str):
56
- obj_t = eval(obj_t, getattr(handler[0], "__globals__", {}))
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
- def __missing__(self, obj_t):
67
- """Get the handler for the given type.
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
- The result is cached so that the normal dict getitem will find it
70
- the next time getitem is called.
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
- class MultiTypeMap(dict):
92
- """Represents a mapping from tuples of types to handlers.
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
- Handler A, registered for types (A1, A2, ..., An), is more specific than
100
- handler B, registered for types (B1, B2, ..., Bn), if there exists n such
101
- that An is more specific than Bn, and for all n, either An == Bn or An is
102
- more specific than Bn. An is more specific than Bn if An is a direct or
103
- indirect subclass of Bn.
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
- In other words, [int, object] is more specific than [object, object] and
106
- less specific than [int, int], but it is neither less specific nor more
107
- specific than [object, int] (which means there is an ambiguity).
108
- """
77
+ @cached_property
78
+ def is_complex(self):
79
+ return isinstance(self.ann, GenericAlias)
109
80
 
110
- def __init__(self, key_error=KeyError):
111
- self.maps = {}
112
- self.empty = MISSING
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
- entry = (handler, amin, amax, vararg)
143
- if not obj_t_tup:
144
- self.empty = entry
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
- for i, cls in enumerate(obj_t_tup):
147
- tm = self.maps.setdefault(i, TypeMap())
148
- tm.register(cls, entry)
149
- if vararg:
150
- tm = self.maps.setdefault(-1, TypeMap())
151
- tm.register(object, entry)
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
- def __missing__(self, obj_t_tup):
154
- specificities = {}
155
- candidates = None
156
- nargs = len(obj_t_tup)
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
- if not obj_t_tup:
159
- if self.empty is MISSING:
160
- raise self.key_error(obj_t_tup, ())
161
- else:
162
- return self.empty[0]
163
-
164
- for i, cls in enumerate(obj_t_tup):
165
- try:
166
- results = self.maps[i][cls]
167
- except KeyError:
168
- results = {}
169
-
170
- results = {
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
- ((result, _),) = results
227
- self[obj_t_tup] = result
228
- return result
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 _fresh(t):
232
- """Returns a new subclass of type t.
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
- @keyword_decorator
241
- def _setattrs(fn, **kwargs):
242
- for k, v in kwargs.items():
243
- setattr(fn, k, v)
244
- return fn
245
-
246
-
247
- @keyword_decorator
248
- def _compile_first(fn, rename=None):
249
- def deco(self, *args, **kwargs):
250
- self.compile()
251
- method = getattr(self, fn.__name__)
252
- assert method is not deco
253
- return method(*args, **kwargs)
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 setalt(alt):
256
- deco._alt = alt
257
- return None
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
- deco.setalt = setalt
260
- deco._replace_by = fn
261
- deco._alt = None
262
- deco._rename = rename
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.bootstrap_class = OvldCall
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(self.bootstrap_class)
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
- assert mixin.bootstrap is not None
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 = self._sig_string(key)
395
+ typenames = sigstring(key)
383
396
  if not possibilities:
384
- raise self.type_error(
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
- raise self.type_error(
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" + hlp
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
- def modelA(*args, **kwargs): # pragma: no cover
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
- sign = inspect.signature(fn)
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.maindoc = fn.__doc__
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(self)}"
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._dispatch:
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
- # Rename the dispatch
502
- if self._dispatch:
503
- self._dispatch = rename_function(self._dispatch, f"{name}.dispatch")
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
- def dispatch(self, dispatch):
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, key, orig_fn):
483
+ def register_signature(self, sig, orig_fn):
521
484
  """Register a function for the given signature."""
522
- sig, min, max, vararg = key
523
- fn = rename_function(
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, (min, max, vararg), fn)
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
- def _normalize_type(t, force_tuple=False):
536
- origin = getattr(t, "__origin__", None)
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
- ann = fn.__annotations__
559
- argspec = inspect.getfullargspec(fn)
560
- argnames = argspec.args
561
- if self.bootstrap:
562
- if argnames[0] != "self":
563
- raise TypeError(
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
- if self.initial_state is None or isinstance(self.initial_state, dict):
652
- state = self.initial_state
653
- else:
654
- state = self.initial_state()
655
- return self.ocls(
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, **kwargs):
572
+ def __call__(self, *args): # pragma: no cover
672
573
  """Call the overloaded function.
673
574
 
674
- This version of __call__ is used when bootstrap is False.
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, **kwargs)
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
- This version of __call__ is used when bootstrap is True. It creates an
689
- OvldCall instance to contain the state. This function is only called
690
- once at the entry point: recursive calls will will be to
691
- OvldCall.__call__.
692
- """
693
- ovc = self.__get__(BOOTSTRAP, None)
694
- res = ovc(*args, **kwargs)
695
- if self.postprocess:
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, map, state, bind_to, super=None):
610
+ def __init__(self, ovld, bind_to):
712
611
  """Initialize an OvldCall."""
713
- self.map = map
714
- self._parent = super
715
- if state is not None:
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
- def __getitem__(self, t):
720
- """Find the right method to call given a tuple of types."""
721
- if not isinstance(t, tuple):
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
- def super(self, *args, **kwargs):
726
- """Use the parent ovld's method for this call."""
727
- pmap = self._parent.get_map()
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
- def resolve(self, *args):
732
- """Find the right method to call for the given arguments."""
733
- return self[tuple(map(self.map.transform, args))]
624
+ @property
625
+ def __signature__(self):
626
+ return self.ovld.__signature__
734
627
 
735
- def call(self, *args):
736
- """Call the right method for the given arguments."""
737
- return self[tuple(map(self.map.transform, args))](*args)
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 with_state(self, **state):
740
- """Return a new OvldCall using the given state."""
741
- return type(self)(self.map, state, BOOTSTRAP)
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, **kwargs):
639
+ def __call__(self, *args): # pragma: no cover
744
640
  """Call this overloaded function.
745
641
 
746
- If a dispatch function is provided, it replaces this function.
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, **kwargs)
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._mock = type("MockSuper", bases, {})
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
- prev = getattr(self._mock, attr, None)
784
- if is_ovld(prev):
785
- prev = prev.copy()
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 inspect.isfunction(prev):
791
- prev = ovld(prev)
792
-
793
- if is_ovld(prev):
794
- if is_ovld(value):
795
- prev.add_mixins(value)
796
- value = prev
797
- elif inspect.isfunction(value):
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
- mod = __import__(fn.__module__, fromlist="_")
848
- dispatch = getattr(mod, fn.__qualname__, None)
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
- dispatch: A function to use as the entry point. It must find the
874
- function to dispatch to and call it.
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
- ov = _find_overload(dispatch, **kwargs)
927
- return ov.dispatch(dispatch)
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
- return type(co)(
981
- co.co_argcount,
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
  ]