ovld 0.5.8__tar.gz → 0.5.10__tar.gz

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.
Files changed (69) hide show
  1. {ovld-0.5.8 → ovld-0.5.10}/PKG-INFO +1 -1
  2. {ovld-0.5.8 → ovld-0.5.10}/pyproject.toml +1 -1
  3. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/core.py +82 -60
  4. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/medley.py +10 -5
  5. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/mro.py +12 -3
  6. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/signatures.py +4 -5
  7. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/typemap.py +4 -2
  8. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/types.py +3 -3
  9. ovld-0.5.10/src/ovld/version.py +1 -0
  10. {ovld-0.5.8 → ovld-0.5.10}/tests/test_abc.py +8 -4
  11. {ovld-0.5.8 → ovld-0.5.10}/tests/test_dependent.py +18 -4
  12. {ovld-0.5.8 → ovld-0.5.10}/tests/test_medley.py +31 -0
  13. {ovld-0.5.8 → ovld-0.5.10}/tests/test_mro.py +12 -0
  14. {ovld-0.5.8 → ovld-0.5.10}/tests/test_ovld/test_doc.txt +1 -2
  15. {ovld-0.5.8 → ovld-0.5.10}/tests/test_ovld/test_doc2.txt +1 -2
  16. {ovld-0.5.8 → ovld-0.5.10}/tests/test_ovld/test_method_doc.txt +1 -2
  17. {ovld-0.5.8 → ovld-0.5.10}/tests/test_ovld.py +19 -17
  18. {ovld-0.5.8 → ovld-0.5.10}/uv.lock +120 -81
  19. ovld-0.5.8/src/ovld/version.py +0 -1
  20. {ovld-0.5.8 → ovld-0.5.10}/.github/workflows/publish.yml +0 -0
  21. {ovld-0.5.8 → ovld-0.5.10}/.github/workflows/python-package.yml +0 -0
  22. {ovld-0.5.8 → ovld-0.5.10}/.gitignore +0 -0
  23. {ovld-0.5.8 → ovld-0.5.10}/.python-version +0 -0
  24. {ovld-0.5.8 → ovld-0.5.10}/.readthedocs.yaml +0 -0
  25. {ovld-0.5.8 → ovld-0.5.10}/LICENSE +0 -0
  26. {ovld-0.5.8 → ovld-0.5.10}/README.md +0 -0
  27. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/__init__.py +0 -0
  28. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/common.py +0 -0
  29. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/conftest.py +0 -0
  30. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/test_add.py +0 -0
  31. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/test_ast.py +0 -0
  32. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/test_calc.py +0 -0
  33. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/test_fib.py +0 -0
  34. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/test_multer.py +0 -0
  35. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/test_regexp.py +0 -0
  36. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/test_trivial.py +0 -0
  37. {ovld-0.5.8 → ovld-0.5.10}/benchmarks/test_tweaknum.py +0 -0
  38. {ovld-0.5.8 → ovld-0.5.10}/docs/codegen.md +0 -0
  39. {ovld-0.5.8 → ovld-0.5.10}/docs/compare.md +0 -0
  40. {ovld-0.5.8 → ovld-0.5.10}/docs/dependent.md +0 -0
  41. {ovld-0.5.8 → ovld-0.5.10}/docs/features.md +0 -0
  42. {ovld-0.5.8 → ovld-0.5.10}/docs/index.md +0 -0
  43. {ovld-0.5.8 → ovld-0.5.10}/docs/medley.md +0 -0
  44. {ovld-0.5.8 → ovld-0.5.10}/docs/types.md +0 -0
  45. {ovld-0.5.8 → ovld-0.5.10}/docs/usage.md +0 -0
  46. {ovld-0.5.8 → ovld-0.5.10}/mkdocs.yml +0 -0
  47. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/__init__.py +0 -0
  48. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/abc.py +0 -0
  49. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/codegen.py +0 -0
  50. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/dependent.py +0 -0
  51. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/py.typed +0 -0
  52. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/recode.py +0 -0
  53. {ovld-0.5.8 → ovld-0.5.10}/src/ovld/utils.py +0 -0
  54. {ovld-0.5.8 → ovld-0.5.10}/tests/__init__.py +0 -0
  55. {ovld-0.5.8 → ovld-0.5.10}/tests/modules/gingerbread.py +0 -0
  56. {ovld-0.5.8 → ovld-0.5.10}/tests/test_codegen/test_dataclass_gen.txt +0 -0
  57. {ovld-0.5.8 → ovld-0.5.10}/tests/test_codegen/test_method.txt +0 -0
  58. {ovld-0.5.8 → ovld-0.5.10}/tests/test_codegen/test_method_metaclass.txt +0 -0
  59. {ovld-0.5.8 → ovld-0.5.10}/tests/test_codegen/test_method_per_instance.txt +0 -0
  60. {ovld-0.5.8 → ovld-0.5.10}/tests/test_codegen/test_simple.txt +0 -0
  61. {ovld-0.5.8 → ovld-0.5.10}/tests/test_codegen/test_variant_generation.txt +0 -0
  62. {ovld-0.5.8 → ovld-0.5.10}/tests/test_codegen.py +0 -0
  63. {ovld-0.5.8 → ovld-0.5.10}/tests/test_examples.py +0 -0
  64. {ovld-0.5.8 → ovld-0.5.10}/tests/test_global.py +0 -0
  65. {ovld-0.5.8 → ovld-0.5.10}/tests/test_ovld/test_display.txt +0 -0
  66. {ovld-0.5.8 → ovld-0.5.10}/tests/test_ovld/test_display_more.txt +0 -0
  67. {ovld-0.5.8 → ovld-0.5.10}/tests/test_typemap.py +0 -0
  68. {ovld-0.5.8 → ovld-0.5.10}/tests/test_types.py +0 -0
  69. {ovld-0.5.8 → ovld-0.5.10}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ovld
3
- Version: 0.5.8
3
+ Version: 0.5.10
4
4
  Summary: Overloading Python functions
5
5
  Project-URL: Homepage, https://ovld.readthedocs.io/en/latest/
6
6
  Project-URL: Documentation, https://ovld.readthedocs.io/en/latest/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ovld"
3
- version = "0.5.8"
3
+ version = "0.5.10"
4
4
  description = "Overloading Python functions"
5
5
  authors = [
6
6
  { name = "Olivier Breuleux", email = "breuleux@gmail.com" }
@@ -25,6 +25,21 @@ from .utils import (
25
25
  subtler_type,
26
26
  )
27
27
 
28
+ _orig_getdoc = inspect.getdoc
29
+
30
+
31
+ def _getdoc(fn):
32
+ if hasattr(fn, "__calculate_doc__"):
33
+ if inspect.ismethod(fn):
34
+ fn = fn.__func__
35
+ fn.__doc__ = fn.__calculate_doc__()
36
+ del fn.__calculate_doc__
37
+ return _orig_getdoc(fn)
38
+
39
+
40
+ inspect.getdoc = _getdoc
41
+
42
+
28
43
  _current_id = itertools.count()
29
44
 
30
45
 
@@ -72,8 +87,6 @@ class Ovld:
72
87
  dispatch.
73
88
  linkback: Whether to keep a pointer in the parent mixins to this
74
89
  ovld so that updates can be propagated. (default: False)
75
- allow_replacement: Allow replacing a method by another with the
76
- same signature. (default: True)
77
90
  """
78
91
 
79
92
  def __init__(
@@ -82,50 +95,43 @@ class Ovld:
82
95
  mixins=[],
83
96
  name=None,
84
97
  linkback=False,
85
- allow_replacement=True,
86
98
  ):
87
99
  """Initialize an Ovld."""
88
100
  self.id = next(_current_id)
89
101
  self.specialization_self = MISSING
90
102
  self._compiled = False
103
+ self._signatures = None
104
+ self._argument_analysis = None
91
105
  self.linkback = linkback
92
106
  self.children = []
93
- self.allow_replacement = allow_replacement
94
107
  self.name = name
95
108
  self.shortname = name or f"__OVLD{self.id}"
96
109
  self.__name__ = name
97
- self._defns = {}
110
+ self._regs = {}
98
111
  self._locked = False
99
112
  self.mixins = []
100
- self.argument_analysis = ArgumentAnalyzer()
101
113
  self.dispatch = bootstrap_dispatch(self, name=self.shortname)
102
114
  self.add_mixins(*mixins)
103
115
 
104
- @property
105
- def defns(self):
106
- defns = {}
116
+ def regs(self):
107
117
  for mixin in self.mixins:
108
- defns.update(mixin.defns)
109
- defns.update(self._defns)
110
- return defns
118
+ yield from mixin.regs()
119
+ yield from self._regs.items()
111
120
 
112
- def analyze_arguments(self):
113
- self.argument_analysis = ArgumentAnalyzer()
114
- for key, fn in list(self.defns.items()):
115
- self.argument_analysis.add(fn)
116
- self.argument_analysis.compile()
117
- return self.argument_analysis
121
+ def empty(self):
122
+ return not self._regs and all(m.empty() for m in self.mixins)
118
123
 
119
124
  def mkdoc(self):
125
+ fns = [f for f, _ in self.regs()]
120
126
  try:
121
- docs = [fn.__doc__ for fn in self.defns.values() if fn.__doc__]
127
+ docs = [fn.__doc__ for fn in fns if fn.__doc__]
122
128
  if len(docs) == 1:
123
129
  maindoc = docs[0]
124
130
  else:
125
- maindoc = f"Ovld with {len(self.defns)} methods."
131
+ maindoc = f"Ovld with {len(fns)} methods."
126
132
 
127
133
  doc = f"{maindoc}\n\n"
128
- for fn in self.defns.values():
134
+ for fn in fns:
129
135
  fndef = inspect.signature(fn)
130
136
  fdoc = fn.__doc__
131
137
  if not fdoc or fdoc == maindoc:
@@ -192,17 +198,41 @@ class Ovld:
192
198
  def __set_name__(self, inst, name):
193
199
  self.rename(name)
194
200
 
195
- def _set_attrs_from(self, fn):
196
- """Inherit relevant attributes from the function."""
197
- if self.name is None:
198
- self.__qualname__ = fn.__qualname__
199
- self.__module__ = fn.__module__
200
- self.rename(f"{fn.__module__}.{fn.__qualname__}", fn.__name__)
201
-
202
201
  def ensure_compiled(self):
203
202
  if not self._compiled:
204
203
  self.compile()
205
204
 
205
+ @property
206
+ def signatures(self):
207
+ if self._signatures is None:
208
+ regs = {}
209
+ for fn, priority in self.regs():
210
+ ss = self.specialization_self
211
+ cgf = getattr(ss, "_ovld_codegen_fields", ())
212
+ lcl = {f: getattr(ss, f) for f in cgf}
213
+ sig = replace(Signature.extract(fn, lcl), priority=priority)
214
+
215
+ def _set(sig, fn):
216
+ if sig in regs:
217
+ # Push down the existing handler with a lower tiebreak
218
+ msig = replace(sig, tiebreak=sig.tiebreak - 1)
219
+ _set(msig, regs[sig])
220
+ regs[sig] = fn
221
+
222
+ _set(sig, fn)
223
+ self._signatures = regs
224
+ return self._signatures
225
+
226
+ @property
227
+ def argument_analysis(self):
228
+ if self._argument_analysis is None:
229
+ aa = ArgumentAnalyzer()
230
+ for sig in self.signatures:
231
+ aa.add(sig)
232
+ aa.compile()
233
+ self._argument_analysis = aa
234
+ return self._argument_analysis
235
+
206
236
  def compile(self):
207
237
  """Finalize this overload.
208
238
 
@@ -223,7 +253,9 @@ class Ovld:
223
253
  name = self.__name__
224
254
  self.map = MultiTypeMap(name=name, key_error=self._key_error, ovld=self)
225
255
 
226
- self.analyze_arguments()
256
+ for sig, fn in list(self.signatures.items()):
257
+ self.register_signature(sig, fn)
258
+
227
259
  dispatch = generate_dispatch(self, self.argument_analysis)
228
260
  self.dispatch.__code__ = rename_code(dispatch.__code__, self.shortname)
229
261
  self.dispatch.__kwdefaults__ = dispatch.__kwdefaults__
@@ -231,10 +263,7 @@ class Ovld:
231
263
  self.dispatch.__defaults__ = dispatch.__defaults__
232
264
  self.dispatch.__globals__.update(dispatch.__globals__)
233
265
  self.dispatch.map = self.map
234
- self.dispatch.__doc__ = self.mkdoc()
235
-
236
- for key, fn in list(self.defns.items()):
237
- self.register_signature(key, fn)
266
+ self.dispatch.__generate_doc__ = self.mkdoc
238
267
 
239
268
  self._compiled = True
240
269
 
@@ -275,42 +304,35 @@ class Ovld:
275
304
  def _register(self, fn, priority):
276
305
  """Register a function."""
277
306
 
278
- self._attempt_modify()
279
-
280
- self._set_attrs_from(fn)
281
-
282
- sig = replace(Signature.extract(fn), priority=priority)
283
- if not self.allow_replacement and sig in self._defns:
284
- raise TypeError(f"There is already a method for {sigstring(sig.types)}")
307
+ if not isinstance(priority, tuple):
308
+ priority = (priority,)
285
309
 
286
- def _set(sig, fn):
287
- if sig in self._defns:
288
- # Push down the existing handler with a lower tiebreak
289
- msig = replace(sig, tiebreak=sig.tiebreak - 1)
290
- _set(msig, self._defns[sig])
291
- self._defns[sig] = fn
292
-
293
- _set(sig, fn)
310
+ self._attempt_modify()
311
+ if self.name is None:
312
+ self.__qualname__ = fn.__qualname__
313
+ self.__module__ = fn.__module__
314
+ self.rename(f"{fn.__module__}.{fn.__qualname__}", fn.__name__)
315
+ self._regs[fn] = priority
294
316
 
295
- self._update()
317
+ self.invalidate()
296
318
  return self
297
319
 
298
320
  def unregister(self, fn):
299
321
  """Unregister a function."""
300
322
  self._attempt_modify()
301
- self._defns = {sig: f for sig, f in self._defns.items() if f is not fn}
302
- self._update()
303
-
304
- def _update(self):
305
- self.reset()
323
+ del self._regs[fn]
324
+ self.invalidate()
325
+
326
+ def invalidate(self):
327
+ self._signatures = None
328
+ self._argument_analysis = None
329
+ if self._compiled:
330
+ self._compiled = False
331
+ self.dispatch.__code__ = self.dispatch.first_entry.__code__
306
332
  for child in self.children:
307
- child._update()
333
+ child.invalidate()
308
334
  if hasattr(self, "dispatch"):
309
- self.dispatch.__doc__ = self.mkdoc()
310
-
311
- def reset(self):
312
- self._compiled = False
313
- self.dispatch.__code__ = self.dispatch.first_entry.__code__
335
+ self.dispatch.__calculate_doc__ = self.mkdoc
314
336
 
315
337
  def copy(self, mixins=[], linkback=False):
316
338
  """Create a copy of this Ovld.
@@ -123,9 +123,9 @@ class BuildOvld(Combiner):
123
123
  for f, arg in self.pending:
124
124
  f(arg)
125
125
  self.pending.clear()
126
- if not self.ovld.defns:
126
+ if self.ovld.empty():
127
127
  return ABSENT
128
- self.ovld.reset()
128
+ self.ovld.invalidate()
129
129
  return self.ovld.dispatch
130
130
 
131
131
  def copy(self):
@@ -337,15 +337,20 @@ _meld_classes_cache = {}
337
337
 
338
338
 
339
339
  def meld_classes(classes):
340
+ def key(cls):
341
+ return getattr(cls, "_ovld_specialization_parent", None) or cls
342
+
340
343
  medleys = {}
341
344
  for cls in classes:
342
- medleys.update({x: True for x in getattr(cls, "_ovld_medleys", [cls])})
345
+ medleys.update({key(x): x for x in getattr(cls, "_ovld_medleys", [cls])})
343
346
  for cls in classes:
347
+ cls = key(cls)
344
348
  if not hasattr(cls, "_ovld_medleys"):
345
349
  for base in cls.mro():
346
350
  if base is not cls and base in medleys:
347
351
  del medleys[base]
348
- medleys = tuple(medleys)
352
+
353
+ medleys = tuple(medleys.values())
349
354
  if len(medleys) == 1:
350
355
  return medleys[0]
351
356
 
@@ -372,7 +377,7 @@ def meld_classes(classes):
372
377
 
373
378
  result = make_dataclass(
374
379
  cls_name="+".join(sorted(c.__name__ for c in medleys)),
375
- bases=tuple(medleys),
380
+ bases=medleys,
376
381
  fields=dc_fields,
377
382
  kw_only=True,
378
383
  namespace=merged,
@@ -118,8 +118,8 @@ def typeorder(t1, t2):
118
118
  ords = [typeorder(a1, a2) for a1, a2 in zip(args1, args2)]
119
119
  return Order.merge(ords)
120
120
 
121
- if not isinstance(t1, type) or not isinstance(t2, type): # pragma: no cover
122
- return Order.SAME
121
+ if not isinstance(t1, type) or not isinstance(t2, type):
122
+ return Order.NONE
123
123
 
124
124
  sx = issubclass(t1, t2)
125
125
  sy = issubclass(t2, t1)
@@ -134,6 +134,15 @@ def typeorder(t1, t2):
134
134
  return Order.NONE
135
135
 
136
136
 
137
+ def _find_ann(main, others):
138
+ if main in others:
139
+ return True
140
+ elif isinstance(main, type):
141
+ return any(isinstance(x, main) for x in others)
142
+ else:
143
+ return False
144
+
145
+
137
146
  def subclasscheck(t1, t2):
138
147
  """Check whether t1 is a "subclass" of t2."""
139
148
  if t1 == t2 or t2 is Any:
@@ -165,7 +174,7 @@ def subclasscheck(t1, t2):
165
174
  if o1 is Annotated and o2 is Annotated:
166
175
  t1, *a1 = get_args(t1)
167
176
  t2, *a2 = get_args(t2)
168
- return subclasscheck(t1, t2) and any(ann in a2 for ann in a1)
177
+ return subclasscheck(t1, t2) and any(_find_ann(main, a1) for main in a2)
169
178
 
170
179
  if o1 is Annotated:
171
180
  return t2 is Annotated
@@ -30,7 +30,7 @@ class LazySignature(inspect.Signature):
30
30
 
31
31
  @property
32
32
  def parameters(self):
33
- anal = self.ovld.analyze_arguments()
33
+ anal = self.ovld.argument_analysis
34
34
  parameters = []
35
35
  if anal.is_method:
36
36
  parameters.append(
@@ -117,7 +117,7 @@ class Signature:
117
117
  arginfo: list[Arginfo] = field(default_factory=list, hash=False, compare=False)
118
118
 
119
119
  @classmethod
120
- def extract(cls, fn):
120
+ def extract(cls, fn, lcl={}):
121
121
  typelist = []
122
122
  sig = inspect.signature(fn)
123
123
  max_pos = 0
@@ -135,7 +135,7 @@ class Signature:
135
135
  is_method = True
136
136
  continue
137
137
  pos = nm = None
138
- ann = normalize_type(param.annotation, fn)
138
+ ann = normalize_type(param.annotation, fn, lcl)
139
139
  if param.kind is inspect._POSITIONAL_ONLY:
140
140
  pos = i - is_method
141
141
  typelist.append(ann)
@@ -188,9 +188,8 @@ class ArgumentAnalyzer:
188
188
  self.is_method = None
189
189
  self.done = False
190
190
 
191
- def add(self, fn):
191
+ def add(self, sig):
192
192
  self.done = False
193
- sig = Signature.extract(fn)
194
193
  self.complex_transforms.update(arg.canonical for arg in sig.arginfo if arg.is_complex)
195
194
  for arg in sig.arginfo:
196
195
  if arg.position is not None:
@@ -242,7 +242,8 @@ class MultiTypeMap(dict):
242
242
  self.maps[-1].register(object, entry)
243
243
 
244
244
  def display_methods(self):
245
- for h, prio in sorted(self.priorities.items(), key=lambda kv: -kv[1]):
245
+ for h, prio in sorted(self.priorities.items(), key=lambda kv: kv[1], reverse=True):
246
+ prio = ":".join(map(str, prio))
246
247
  prio = f"[{prio}]"
247
248
  width = 6
248
249
  print(f"{prio:{width}} \033[1m{h.__name__}\033[0m")
@@ -306,7 +307,8 @@ class MultiTypeMap(dict):
306
307
  color = "\033[1;32m"
307
308
  rank += 1
308
309
  spec = ".".join(map(str, c.specificity))
309
- lvl = f"[{c.priority}:{spec}]"
310
+ prios = ":".join(map(str, c.priority))
311
+ lvl = f"[{prios}:{spec}]"
310
312
  width = 2 * len(args) + 6
311
313
  print(f"{color}{bullet} {lvl:{width}} {handler.__name__}")
312
314
  co = handler.__code__
@@ -25,7 +25,7 @@ def eval_annotation(t, ctx, locals, catch=False):
25
25
  else: # pragma: no cover
26
26
  glb = {}
27
27
  return eval(t, glb, locals)
28
- else:
28
+ else: # pragma: no cover
29
29
  return t
30
30
  except Exception: # pragma: no cover
31
31
  if catch:
@@ -43,11 +43,11 @@ class TypeNormalizer:
43
43
  else:
44
44
  self.generic_handlers.register(generic, handler)
45
45
 
46
- def __call__(self, t, fn):
46
+ def __call__(self, t, fn, lcl={}):
47
47
  from .dependent import DependentType
48
48
 
49
49
  if isinstance(t, str):
50
- t = eval_annotation(t, fn, {})
50
+ t = eval_annotation(t, fn, lcl)
51
51
 
52
52
  if t is type:
53
53
  t = type[object]
@@ -0,0 +1 @@
1
+ version = "0.5.10"
@@ -30,16 +30,20 @@ def test_literal():
30
30
  assert f(4) == "nah"
31
31
 
32
32
 
33
+ def f_i(i):
34
+ def f(x: Literal[i]):
35
+ return x * x
36
+
37
+ return f
38
+
39
+
33
40
  def test_many_literals():
34
41
  f = Ovld()
35
42
 
36
43
  n = 10
37
44
 
38
45
  for i in range(n):
39
-
40
- @ovld
41
- def f(x: Literal[i]):
42
- return x * x
46
+ f.register(f_i(i))
43
47
 
44
48
  for i in range(n):
45
49
  assert f(i) == i * i
@@ -337,11 +337,25 @@ def test_keyed_plus_other():
337
337
  # Tests a failure mode of generating dispatch code like HANDLER = HANDLERS[x]
338
338
  # if you also have to check a condition on y.
339
339
 
340
- for i in range(10):
340
+ @ovld
341
+ def f(x: Literal[0], y: Bounded[0, 100]):
342
+ return "yes"
341
343
 
342
- @ovld
343
- def f(x: Literal[i], y: Bounded[0, 100]):
344
- return "yes"
344
+ @ovld
345
+ def f(x: Literal[1], y: Bounded[0, 100]):
346
+ return "yes"
347
+
348
+ @ovld
349
+ def f(x: Literal[2], y: Bounded[0, 100]):
350
+ return "yes"
351
+
352
+ @ovld
353
+ def f(x: Literal[3], y: Bounded[0, 100]):
354
+ return "yes"
355
+
356
+ @ovld
357
+ def f(x: Literal[4], y: Bounded[0, 100]):
358
+ return "yes"
345
359
 
346
360
  @ovld
347
361
  def f(x: int, y: int):
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import sys
2
4
  from dataclasses import field, replace
3
5
  from typing import Counter
@@ -7,6 +9,7 @@ import pytest
7
9
  from ovld import recurse
8
10
  from ovld.codegen import Code, Lambda, code_generator
9
11
  from ovld.core import ovld
12
+ from ovld.dependent import Regexp
10
13
  from ovld.medley import (
11
14
  BuildOvld,
12
15
  ChainAll,
@@ -541,3 +544,31 @@ def test_default_factory_inheritance():
541
544
 
542
545
  lu = Lust()
543
546
  assert lu.xs == []
547
+
548
+
549
+ class Monkey(Medley):
550
+ regex: CodegenParameter[str] = "monk[iey]+"
551
+
552
+ def hi(self, x: Regexp[regex]):
553
+ return "hello!"
554
+
555
+ def hi(self, x: str):
556
+ return "goodbye!"
557
+
558
+
559
+ def test_configure_dependent():
560
+ m1 = Monkey()
561
+ m2 = Monkey(regex="go+rilla")
562
+
563
+ assert m1.hi("monki") == "hello!"
564
+ assert m1.hi("monkeeeee") == "hello!"
565
+ assert m1.hi("goorilla") == "goodbye!"
566
+
567
+ assert m2.hi("monki") == "goodbye!"
568
+ assert m2.hi("monkeeeee") == "goodbye!"
569
+ assert m2.hi("goorilla") == "hello!"
570
+
571
+
572
+ def test_configure_dependent_pileon():
573
+ m = Monkey(regex="chimpanze+") + Monkey(regex="go+rilla")
574
+ assert m.hi("goorilla") == "hello!"
@@ -9,6 +9,7 @@ from ovld.mro import Order, subclasscheck, typeorder
9
9
  from ovld.types import (
10
10
  All,
11
11
  Dataclass,
12
+ HasMethod,
12
13
  Intersection,
13
14
  Order,
14
15
  Union,
@@ -150,6 +151,12 @@ def test_subclasscheck_anntype():
150
151
  assert not subclasscheck(type[int], type[Annotated[int, "hello"]])
151
152
 
152
153
 
154
+ def test_subclasscheck_anntype_type():
155
+ assert subclasscheck(type[Annotated[int, "hello"]], type[Annotated[int, str]])
156
+ assert subclasscheck(type[Annotated[int, str]], type[Annotated[object, str]])
157
+ assert not subclasscheck(type[Annotated[int, "hello"]], type[Annotated[int, int]])
158
+
159
+
153
160
  @pytest.mark.skipif(
154
161
  sys.version_info < (3, 12), reason="Python 3.11 is more strict on Annotated"
155
162
  )
@@ -164,6 +171,11 @@ def test_typeorder_any():
164
171
  assert typeorder(Any, Any) is Order.SAME
165
172
 
166
173
 
174
+ def test_typeorder_exotic():
175
+ assert typeorder(HasMethod["meth"], Annotated) is Order.NONE
176
+ assert typeorder(Annotated, HasMethod["meth"]) is Order.NONE
177
+
178
+
167
179
  @dataclass(frozen=True)
168
180
  class Anno:
169
181
  name: str
@@ -9,5 +9,4 @@ mushroom(x: str)
9
9
  Naming mushrooms.
10
10
 
11
11
  mushroom(x: str, y: object, *, beauty, bigness)
12
- Whatever.
13
-
12
+ Whatever.
@@ -6,5 +6,4 @@ mushroom(x: int)
6
6
 
7
7
  mushroom(x: str)
8
8
 
9
- mushroom(x: str, y: object, *, beauty, bigness)
10
-
9
+ mushroom(x: str, y: object, *, beauty, bigness)
@@ -6,5 +6,4 @@ rise(self, x: int)
6
6
 
7
7
  rise(self, x: str)
8
8
 
9
- rise(self, x: str, y: object, *, beauty, bigness)
10
-
9
+ rise(self, x: str, y: object, *, beauty, bigness)
@@ -809,6 +809,20 @@ def test_priority():
809
809
  assert f([1, "x"]) == ["TOP", [["TOP", 2], ["TOP", "BOTTOM"]]]
810
810
 
811
811
 
812
+ def test_priority_tuple():
813
+ f = Ovld()
814
+
815
+ @f.register(priority=(1, 2))
816
+ def f(x: object):
817
+ return ["TOP", call_next(x)]
818
+
819
+ @f.register(priority=(1, 1))
820
+ def f(x: object):
821
+ return "BOTTOM"
822
+
823
+ assert f(123) == ["TOP", "BOTTOM"]
824
+
825
+
812
826
  def test_resolve_for_values():
813
827
  f = Ovld()
814
828
 
@@ -1146,18 +1160,6 @@ def test_replacement():
1146
1160
  assert f(5) == 2
1147
1161
 
1148
1162
 
1149
- def test_disallow_replacement():
1150
- @ovld(allow_replacement=False)
1151
- def f(x: int):
1152
- pass
1153
-
1154
- with pytest.raises(TypeError):
1155
-
1156
- @f.register
1157
- def f2(x: int):
1158
- pass
1159
-
1160
-
1161
1163
  def test_unregister():
1162
1164
  @ovld
1163
1165
  def f(xs: list):
@@ -1819,12 +1821,12 @@ def test_doc(file_regression):
1819
1821
  return None
1820
1822
 
1821
1823
  doc = f"{mushroom.__name__}{inspect.signature(mushroom)}\n\n"
1822
- doc += mushroom.__doc__
1824
+ doc += inspect.getdoc(mushroom)
1823
1825
 
1824
1826
  file_regression.check(doc)
1825
1827
 
1826
- assert mushroom.__doc__ == mushroom.__ovld__.__doc__
1827
- assert mushroom.__signature__ == mushroom.__ovld__.__signature__
1828
+ assert inspect.getdoc(mushroom) == inspect.getdoc(mushroom.__ovld__)
1829
+ assert inspect.signature(mushroom) == inspect.signature(mushroom.__ovld__)
1828
1830
 
1829
1831
 
1830
1832
  def test_doc2(file_regression):
@@ -1841,7 +1843,7 @@ def test_doc2(file_regression):
1841
1843
  def mushroom(x: str, y: object, *, beauty, bigness):
1842
1844
  return None
1843
1845
 
1844
- doc = mushroom.__doc__
1846
+ doc = inspect.getdoc(mushroom)
1845
1847
  doc = f"{mushroom.__name__}{inspect.signature(mushroom)}\n\n" + doc
1846
1848
 
1847
1849
  file_regression.check(doc)
@@ -1863,7 +1865,7 @@ def test_method_doc(file_regression):
1863
1865
  return None
1864
1866
 
1865
1867
  mushroom = Mushroom()
1866
- doc = mushroom.rise.__doc__
1868
+ doc = inspect.getdoc(mushroom.rise)
1867
1869
  doc = f"{mushroom.rise.__name__}{inspect.signature(mushroom.rise)}\n\n" + doc
1868
1870
 
1869
1871
  file_regression.check(doc)