ovld 0.4.3__py3-none-any.whl → 0.4.5__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/recode.py CHANGED
@@ -7,8 +7,7 @@ from functools import reduce
7
7
  from itertools import count
8
8
  from types import CodeType, FunctionType
9
9
 
10
- from .dependent import DependentType
11
- from .utils import Unusable, UsageError
10
+ from .utils import MISSING, NameDatabase, Unusable, UsageError, subtler_type
12
11
 
13
12
  recurse = Unusable(
14
13
  "recurse() can only be used from inside an @ovld-registered function."
@@ -18,24 +17,8 @@ call_next = Unusable(
18
17
  )
19
18
 
20
19
 
21
- dispatch_template = """
22
- from ovld.utils import MISSING
23
-
24
- def __DISPATCH__(self, {args}):
25
- {inits}
26
- {body}
27
- {call}
28
- """
29
-
30
-
31
- call_template = """
32
- method = self.map[({lookup})]
33
- return method({posargs})
34
- """
35
-
36
-
37
20
  def instantiate_code(symbol, code, inject={}):
38
- virtual_file = f"<ovld{hash(code)}>"
21
+ virtual_file = f"<ovld:{abs(hash(code)):x}>"
39
22
  linecache.cache[virtual_file] = (None, None, splitlines(code), virtual_file)
40
23
  code = compile(source=code, filename=virtual_file, mode="exec")
41
24
  glb = {**inject}
@@ -55,7 +38,22 @@ def instantiate_code(symbol, code, inject={}):
55
38
  # return rval
56
39
 
57
40
 
58
- def generate_dispatch(arganal):
41
+ dispatch_template = """
42
+ def __WRAP_DISPATCH__(OVLD):
43
+ def __DISPATCH__({args}):
44
+ {body}
45
+
46
+ return __DISPATCH__
47
+ """
48
+
49
+
50
+ call_template = """
51
+ {mvar} = OVLD.map[({lookup})]
52
+ return {mvar}({posargs})
53
+ """
54
+
55
+
56
+ def generate_dispatch(ov, arganal):
59
57
  def join(li, sep=", ", trail=False):
60
58
  li = [x for x in li if x]
61
59
  rval = sep.join(li)
@@ -63,19 +61,35 @@ def generate_dispatch(arganal):
63
61
  rval += ","
64
62
  return rval
65
63
 
66
- spr, spo, pr, po, kr, ko = arganal.compile()
64
+ arganal.compile()
65
+
66
+ spr = arganal.strict_positional_required
67
+ spo = arganal.strict_positional_optional
68
+ pr = arganal.positional_required
69
+ po = arganal.positional_optional
70
+ kr = arganal.keyword_required
71
+ ko = arganal.keyword_optional
67
72
 
68
73
  inits = set()
69
74
 
70
75
  kwargsstar = ""
71
76
  targsstar = ""
72
77
 
73
- args = []
78
+ args = ["self" if arganal.is_method else ""]
74
79
  body = [""]
75
- posargs = ["self.obj" if arganal.is_method else ""]
80
+ posargs = ["self" if arganal.is_method else ""]
76
81
  lookup = []
77
82
 
78
83
  i = 0
84
+ ndb = NameDatabase(default_name="INJECT")
85
+
86
+ def lookup_for(x):
87
+ return ndb[arganal.lookup_for(x)]
88
+
89
+ for name in spr + spo + pr + po + kr:
90
+ ndb.register(name)
91
+
92
+ mv = ndb.gensym(desired_name="method")
79
93
 
80
94
  for name in spr + spo:
81
95
  if name in spr:
@@ -83,10 +97,10 @@ def generate_dispatch(arganal):
83
97
  else:
84
98
  args.append(f"{name}=MISSING")
85
99
  posargs.append(name)
86
- lookup.append(f"{arganal.lookup_for(i)}({name})")
100
+ lookup.append(f"{lookup_for(i)}({name})")
87
101
  i += 1
88
102
 
89
- if len(po) <= 1:
103
+ if len(po) <= 1 and (spr or spo):
90
104
  # If there are more than one non-strictly positional optional arguments,
91
105
  # then all positional arguments are strictly positional, because if e.g.
92
106
  # x and y are optional we want x==MISSING to imply that y==MISSING, but
@@ -99,7 +113,7 @@ def generate_dispatch(arganal):
99
113
  else:
100
114
  args.append(f"{name}=MISSING")
101
115
  posargs.append(name)
102
- lookup.append(f"{arganal.lookup_for(i)}({name})")
116
+ lookup.append(f"{lookup_for(i)}({name})")
103
117
  i += 1
104
118
 
105
119
  if len(po) > 1:
@@ -109,14 +123,9 @@ def generate_dispatch(arganal):
109
123
  args.append("*")
110
124
 
111
125
  for name in kr:
112
- lookup_fn = (
113
- "self.map.transform"
114
- if name in arganal.complex_transforms
115
- else "type"
116
- )
117
126
  args.append(f"{name}")
118
127
  posargs.append(f"{name}={name}")
119
- lookup.append(f"({name!r}, {lookup_fn}({name}))")
128
+ lookup.append(f"({name!r}, {lookup_for(name)}({name}))")
120
129
 
121
130
  for name in ko:
122
131
  args.append(f"{name}=MISSING")
@@ -126,9 +135,7 @@ def generate_dispatch(arganal):
126
135
  inits.add("TARGS = []")
127
136
  body.append(f"if {name} is not MISSING:")
128
137
  body.append(f" KWARGS[{name!r}] = {name}")
129
- body.append(
130
- f" TARGS.append(({name!r}, {arganal.lookup_for(name)}({name})))"
131
- )
138
+ body.append(f" TARGS.append(({name!r}, {lookup_for(name)}({name})))")
132
139
 
133
140
  posargs.append(kwargsstar)
134
141
  lookup.append(targsstar)
@@ -136,6 +143,7 @@ def generate_dispatch(arganal):
136
143
  fullcall = call_template.format(
137
144
  lookup=join(lookup, trail=True),
138
145
  posargs=join(posargs),
146
+ mvar=mv,
139
147
  )
140
148
 
141
149
  calls = []
@@ -145,35 +153,26 @@ def generate_dispatch(arganal):
145
153
  call = call_template.format(
146
154
  lookup=join(lookup[: req + i], trail=True),
147
155
  posargs=join(posargs[: req + i + 1]),
156
+ mvar=mv,
148
157
  )
149
- call = textwrap.indent(call, " ")
158
+ call = textwrap.indent(call, " ")
150
159
  calls.append(f"\nif {arg} is MISSING:{call}")
151
160
  calls.append(fullcall)
152
161
 
162
+ lines = [*inits, *body, textwrap.indent("".join(calls), " ")]
153
163
  code = dispatch_template.format(
154
- inits=join(inits, sep="\n "),
155
164
  args=join(args),
156
- body=join(body, sep="\n "),
157
- call=textwrap.indent("".join(calls), " "),
165
+ body=join(lines, sep="\n ").lstrip(),
158
166
  )
159
- return instantiate_code("__DISPATCH__", code)
160
-
161
-
162
- class GenSym:
163
- def __init__(self, prefix):
164
- self.prefix = prefix
165
- self.count = count()
166
- self.variables = {}
167
-
168
- def add(self, value):
169
- if isinstance(value, (int, float, str)):
170
- return repr(value)
171
- id = f"{self.prefix}{next(self.count)}"
172
- self.variables[id] = value
173
- return id
167
+ wr = instantiate_code(
168
+ "__WRAP_DISPATCH__", code, inject={"MISSING": MISSING, **ndb.variables}
169
+ )
170
+ return wr(ov)
174
171
 
175
172
 
176
173
  def generate_dependent_dispatch(tup, handlers, next_call, slf, name, err, nerr):
174
+ from .dependent import generate_checking_code, is_dependent
175
+
177
176
  def to_dict(tup):
178
177
  return dict(
179
178
  entry if isinstance(entry, tuple) else (i, entry)
@@ -187,14 +186,14 @@ def generate_dependent_dispatch(tup, handlers, next_call, slf, name, err, nerr):
187
186
  return f"ARG{x}" if isinstance(x, int) else f"{x}={x}"
188
187
 
189
188
  def codegen(typ, arg):
190
- cg = typ.codegen()
189
+ cg = generate_checking_code(typ)
191
190
  return cg.template.format(
192
- arg=arg, **{k: gen.add(v) for k, v in cg.substitutions.items()}
191
+ arg=arg, **{k: ndb[v] for k, v in cg.substitutions.items()}
193
192
  )
194
193
 
195
194
  tup = to_dict(tup)
196
195
  handlers = [(h, to_dict(types)) for h, types in handlers]
197
- gen = GenSym(prefix="INJECT")
196
+ ndb = NameDatabase(default_name="INJECT")
198
197
  conjs = []
199
198
 
200
199
  exclusive = False
@@ -227,7 +226,7 @@ def generate_dependent_dispatch(tup, handlers, next_call, slf, name, err, nerr):
227
226
  exclusive = getattr(focus, "exclusive_type", False)
228
227
 
229
228
  for i, (h, types) in enumerate(handlers):
230
- relevant = [k for k in tup if isinstance(types[k], DependentType)]
229
+ relevant = [k for k in tup if is_dependent(types[k])]
231
230
  if len(relevant) > 1:
232
231
  # The keyexpr method only works if there is only one condition to check.
233
232
  keyexpr = keyed = None
@@ -238,12 +237,15 @@ def generate_dependent_dispatch(tup, handlers, next_call, slf, name, err, nerr):
238
237
  conj = "True"
239
238
  conjs.append(conj)
240
239
 
240
+ if len(handlers) == 1:
241
+ exclusive = True
242
+
241
243
  argspec = ", ".join(argname(x) for x in tup)
242
244
  argcall = ", ".join(argprovide(x) for x in tup)
243
245
 
244
246
  body = []
245
247
  if keyexpr:
246
- body.append(f"HANDLER = {gen.add(keyed)}.get({keyexpr}, FALLTHROUGH)")
248
+ body.append(f"HANDLER = {ndb[keyed]}.get({keyexpr}, FALLTHROUGH)")
247
249
  body.append(f"return HANDLER({slf}{argcall})")
248
250
 
249
251
  elif exclusive:
@@ -263,12 +265,12 @@ def generate_dependent_dispatch(tup, handlers, next_call, slf, name, err, nerr):
263
265
  body.append("elif SUMMATION == 0:")
264
266
  body.append(f" return FALLTHROUGH({slf}{argcall})")
265
267
  body.append("else:")
266
- body.append(f" raise {gen.add(err)}")
268
+ body.append(f" raise {ndb[err]}")
267
269
 
268
270
  body_text = textwrap.indent("\n".join(body), " ")
269
271
  code = f"def __DEPENDENT_DISPATCH__({slf}{argspec}):\n{body_text}"
270
272
 
271
- inject = gen.variables
273
+ inject = ndb.variables
272
274
  for i, (h, types) in enumerate(handlers):
273
275
  inject[f"HANDLER{i}"] = h
274
276
 
@@ -366,12 +368,19 @@ def rename_function(fn, newname):
366
368
 
367
369
  class NameConverter(ast.NodeTransformer):
368
370
  def __init__(
369
- self, anal, recurse_sym, call_next_sym, ovld_mangled, code_mangled
371
+ self,
372
+ anal,
373
+ recurse_sym,
374
+ call_next_sym,
375
+ ovld_mangled,
376
+ map_mangled,
377
+ code_mangled,
370
378
  ):
371
379
  self.analysis = anal
372
380
  self.recurse_sym = recurse_sym
373
381
  self.call_next_sym = call_next_sym
374
382
  self.ovld_mangled = ovld_mangled
383
+ self.map_mangled = map_mangled
375
384
  self.code_mangled = code_mangled
376
385
  self.count = count()
377
386
 
@@ -400,18 +409,16 @@ class NameConverter(ast.NodeTransformer):
400
409
  tmp = f"__TMP{next(self.count)}_"
401
410
 
402
411
  def _make_lookup_call(key, arg):
412
+ name = (
413
+ "__SUBTLER_TYPE"
414
+ if self.analysis.lookup_for(key) is subtler_type
415
+ else "type"
416
+ )
403
417
  value = ast.NamedExpr(
404
418
  target=ast.Name(id=f"{tmp}{key}", ctx=ast.Store()),
405
419
  value=self.visit(arg),
406
420
  )
407
- if self.analysis.lookup_for(key) == "self.map.transform":
408
- func = ast.Attribute(
409
- value=ast.Name(id=f"{tmp}M", ctx=ast.Load()),
410
- attr="transform",
411
- ctx=ast.Load(),
412
- )
413
- else:
414
- func = ast.Name(id="type", ctx=ast.Load())
421
+ func = ast.Name(id=name, ctx=ast.Load())
415
422
  return ast.Call(
416
423
  func=func,
417
424
  args=[value],
@@ -438,14 +445,7 @@ class NameConverter(ast.NodeTransformer):
438
445
  if cn:
439
446
  type_parts.insert(0, ast.Name(id=self.code_mangled, ctx=ast.Load()))
440
447
  method = ast.Subscript(
441
- value=ast.NamedExpr(
442
- target=ast.Name(id=f"{tmp}M", ctx=ast.Store()),
443
- value=ast.Attribute(
444
- value=ast.Name(id=self.ovld_mangled, ctx=ast.Load()),
445
- attr="map",
446
- ctx=ast.Load(),
447
- ),
448
- ),
448
+ value=ast.Name(id=self.map_mangled, ctx=ast.Load()),
449
449
  slice=ast.Tuple(
450
450
  elts=type_parts,
451
451
  ctx=ast.Load(),
@@ -453,19 +453,14 @@ class NameConverter(ast.NodeTransformer):
453
453
  ctx=ast.Load(),
454
454
  )
455
455
  if self.analysis.is_method:
456
- method = ast.Call(
457
- func=ast.Attribute(
458
- value=method,
459
- attr="__get__",
460
- ctx=ast.Load(),
461
- ),
462
- args=[ast.Name(id="self", ctx=ast.Load())],
463
- keywords=[],
464
- )
456
+ selfarg = [ast.Name(id="self", ctx=ast.Load())]
457
+ else:
458
+ selfarg = []
465
459
 
466
460
  new_node = ast.Call(
467
461
  func=method,
468
- args=[
462
+ args=selfarg
463
+ + [
469
464
  ast.Name(id=f"{tmp}{i}", ctx=ast.Load())
470
465
  for i, arg in enumerate(node.args)
471
466
  ],
@@ -498,7 +493,10 @@ def adapt_function(fn, ovld, newname):
498
493
  """Create a copy of the function with a different name."""
499
494
  rec_syms = list(
500
495
  _search_names(
501
- fn.__code__, (recurse, ovld), fn.__globals__, fn.__closure__
496
+ fn.__code__,
497
+ (recurse, ovld, ovld.dispatch),
498
+ fn.__globals__,
499
+ fn.__closure__,
502
500
  )
503
501
  )
504
502
  cn_syms = list(
@@ -540,6 +538,7 @@ def closure_wrap(tree, fname, names):
540
538
 
541
539
  def recode(fn, ovld, recurse_sym, call_next_sym, newname):
542
540
  ovld_mangled = f"___OVLD{ovld.id}"
541
+ map_mangled = f"___MAP{ovld.id}"
543
542
  code_mangled = f"___CODE{next(_current)}"
544
543
  try:
545
544
  src = inspect.getsource(fn)
@@ -556,6 +555,7 @@ def recode(fn, ovld, recurse_sym, call_next_sym, newname):
556
555
  recurse_sym=recurse_sym,
557
556
  call_next_sym=call_next_sym,
558
557
  ovld_mangled=ovld_mangled,
558
+ map_mangled=map_mangled,
559
559
  code_mangled=code_mangled,
560
560
  ).visit(tree)
561
561
  new.body[0].decorator_list = []
@@ -579,6 +579,8 @@ def recode(fn, ovld, recurse_sym, call_next_sym, newname):
579
579
  new_fn.__kwdefaults__ = fn.__kwdefaults__
580
580
  new_fn.__annotations__ = fn.__annotations__
581
581
  new_fn = rename_function(new_fn, newname)
582
- new_fn.__globals__[ovld_mangled] = ovld
582
+ new_fn.__globals__["__SUBTLER_TYPE"] = subtler_type
583
+ new_fn.__globals__[ovld_mangled] = ovld.dispatch
584
+ new_fn.__globals__[map_mangled] = ovld.map
583
585
  new_fn.__globals__[code_mangled] = new_fn.__code__
584
586
  return new_fn
ovld/typemap.py CHANGED
@@ -1,27 +1,12 @@
1
1
  import inspect
2
2
  import math
3
- import typing
4
3
  from dataclasses import dataclass
5
4
  from itertools import count
6
5
  from types import CodeType
7
6
 
8
- from .dependent import DependentType
9
7
  from .mro import sort_types
10
8
  from .recode import generate_dependent_dispatch
11
- from .utils import MISSING
12
-
13
-
14
- class GenericAliasMC(type):
15
- def __instancecheck__(cls, obj):
16
- return hasattr(obj, "__origin__")
17
-
18
-
19
- class GenericAlias(metaclass=GenericAliasMC):
20
- pass
21
-
22
-
23
- def is_type_of_type(t):
24
- return getattr(t, "__origin__", None) is type
9
+ from .utils import MISSING, subtler_type
25
10
 
26
11
 
27
12
  class TypeMap(dict):
@@ -123,16 +108,6 @@ class MultiTypeMap(dict):
123
108
  self.all = {}
124
109
  self.errors = {}
125
110
 
126
- def transform(self, obj):
127
- if isinstance(obj, GenericAlias):
128
- return type[obj]
129
- elif obj is typing.Any:
130
- return type[object]
131
- elif isinstance(obj, type):
132
- return type[obj]
133
- else:
134
- return type(obj)
135
-
136
111
  def mro(self, obj_t_tup):
137
112
  specificities = {}
138
113
  candidates = None
@@ -229,6 +204,8 @@ class MultiTypeMap(dict):
229
204
  sig: A Signature object.
230
205
  handler: A function to handle the tuple.
231
206
  """
207
+ from .dependent import is_dependent
208
+
232
209
  self.clear()
233
210
 
234
211
  obj_t_tup = sig.types
@@ -240,8 +217,7 @@ class MultiTypeMap(dict):
240
217
  self.tiebreaks[handler] = sig.tiebreak
241
218
  self.type_tuples[handler] = obj_t_tup
242
219
  self.dependent[handler] = any(
243
- isinstance(t[1] if isinstance(t, tuple) else t, DependentType)
244
- for t in obj_t_tup
220
+ is_dependent(t[1] if isinstance(t, tuple) else t) for t in obj_t_tup
245
221
  )
246
222
 
247
223
  for i, cls in enumerate(obj_t_tup):
@@ -266,19 +242,21 @@ class MultiTypeMap(dict):
266
242
  print(f"{'':{width-2}} @ {co.co_filename}:{co.co_firstlineno}")
267
243
 
268
244
  def display_resolution(self, *args, **kwargs):
245
+ from .dependent import is_dependent
246
+
269
247
  def dependent_match(tup, args):
270
248
  for t, a in zip(tup, args):
271
249
  if isinstance(t, tuple):
272
250
  t = t[1]
273
251
  a = a[1]
274
- if isinstance(t, DependentType) and not t.check(a):
252
+ if is_dependent(t) and not isinstance(a, t):
275
253
  return False
276
254
  return True
277
255
 
278
256
  message = "No method will be called."
279
257
  argt = [
280
- *map(self.transform, args),
281
- *[(k, self.transform(v)) for k, v in kwargs.items()],
258
+ *map(subtler_type, args),
259
+ *[(k, subtler_type(v)) for k, v in kwargs.items()],
282
260
  ]
283
261
  finished = False
284
262
  rank = 1