cot-assert 0.1.0__py2.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.
- cot_assert/__init__.py +22 -0
- cot_assert/_entry.py +29 -0
- cot_assert/_error.py +155 -0
- cot_assert/_hook.py +224 -0
- cot_assert/_llexc.py +75 -0
- cot_assert/_render.py +311 -0
- cot_assert/_rewrite.py +495 -0
- cot_assert/_rpy.py +32 -0
- cot_assert/_runtime.py +12 -0
- cot_assert/_unparse.py +180 -0
- cot_assert/_values.py +30 -0
- cot_assert/pytest_plugin.py +140 -0
- cot_assert-0.1.0.dist-info/METADATA +69 -0
- cot_assert-0.1.0.dist-info/RECORD +17 -0
- cot_assert-0.1.0.dist-info/WHEEL +5 -0
- cot_assert-0.1.0.dist-info/entry_points.txt +2 -0
- cot_assert-0.1.0.dist-info/licenses/LICENSE +373 -0
cot_assert/_rewrite.py
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
"""Rewrite assert statements to raise AnnotatedAssertion.
|
|
2
|
+
|
|
3
|
+
The emitted code is kept inside what the RPython flow space accepts and to
|
|
4
|
+
plain bytecode: every sub-expression lands in its own temporary (one type
|
|
5
|
+
each), short-circuits become nested ``if``s, temporaries are never reset to
|
|
6
|
+
None, and the failure branch is a single call per exit that gets the values
|
|
7
|
+
evaluated on that path.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import absolute_import, division, print_function
|
|
11
|
+
|
|
12
|
+
import ast
|
|
13
|
+
import copy
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from ._unparse import binop, cmpop, unaryop, unparse
|
|
17
|
+
|
|
18
|
+
PY2 = sys.version_info[0] == 2
|
|
19
|
+
|
|
20
|
+
RUNTIME = "@cot_rt"
|
|
21
|
+
DONT_REWRITE = ("COT_DONT_REWRITE", "PYTEST_DONT_REWRITE")
|
|
22
|
+
|
|
23
|
+
if PY2:
|
|
24
|
+
_NAME_CONSTANTS = ("True", "False", "None")
|
|
25
|
+
_CONST_NODES = (ast.Num, ast.Str)
|
|
26
|
+
else:
|
|
27
|
+
_NAME_CONSTANTS = ()
|
|
28
|
+
_CONST_NODES = (ast.Constant,)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def rewrite_source(source, filename="<rewritten>"):
|
|
32
|
+
"""Parse, rewrite and compile module source; returns a code object."""
|
|
33
|
+
tree = compile(source, filename, "exec", ast.PyCF_ONLY_AST, True)
|
|
34
|
+
rewrite_asserts(tree)
|
|
35
|
+
# dont_inherit: this module's __future__ flags must not leak into the
|
|
36
|
+
# rewritten one
|
|
37
|
+
return compile(tree, filename, "exec", 0, True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def rewrite_asserts(module):
|
|
41
|
+
"""Rewrite the asserts of an ``ast.Module`` in place."""
|
|
42
|
+
if _is_disabled(module):
|
|
43
|
+
return
|
|
44
|
+
ModuleRewriter().run(module)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _is_disabled(module):
|
|
48
|
+
doc = _docstring(module)
|
|
49
|
+
return doc is not None and any(marker in doc for marker in DONT_REWRITE)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _docstring(module):
|
|
53
|
+
if not module.body:
|
|
54
|
+
return None
|
|
55
|
+
first = module.body[0]
|
|
56
|
+
if not isinstance(first, ast.Expr):
|
|
57
|
+
return None
|
|
58
|
+
value = first.value
|
|
59
|
+
if PY2:
|
|
60
|
+
return value.s if isinstance(value, ast.Str) else None
|
|
61
|
+
if isinstance(value, ast.Constant) and isinstance(value.value, str):
|
|
62
|
+
return value.value
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# -- node construction that works on both AST flavours ----------------------
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _const(value):
|
|
70
|
+
if not PY2:
|
|
71
|
+
return ast.Constant(value)
|
|
72
|
+
if value is None or isinstance(value, bool):
|
|
73
|
+
return ast.Name(repr(value), ast.Load())
|
|
74
|
+
if isinstance(value, (int, float)):
|
|
75
|
+
return ast.Num(value)
|
|
76
|
+
return ast.Str(value)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _literal(value):
|
|
80
|
+
"""AST for nested tuples and lists of str, int, bool and None."""
|
|
81
|
+
return ast.parse(repr(value), mode="eval").body
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _name(id_, store=False):
|
|
85
|
+
return ast.Name(id_, ast.Store() if store else ast.Load())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _attr(value, attr):
|
|
89
|
+
return ast.Attribute(value, attr, ast.Load())
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _call(func, args):
|
|
93
|
+
if PY2:
|
|
94
|
+
return ast.Call(func, args, [], None, None)
|
|
95
|
+
return ast.Call(func, args, [])
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _raise(exc):
|
|
99
|
+
if PY2:
|
|
100
|
+
return ast.Raise(exc, None, None)
|
|
101
|
+
return ast.Raise(exc, None)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _not(expr):
|
|
105
|
+
return ast.UnaryOp(ast.Not(), expr)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _is_constant(node):
|
|
109
|
+
if isinstance(node, _CONST_NODES):
|
|
110
|
+
return True
|
|
111
|
+
if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.USub, ast.UAdd)):
|
|
112
|
+
return _is_constant(node.operand)
|
|
113
|
+
return isinstance(node, ast.Name) and node.id in _NAME_CONSTANTS
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# -- scopes -------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _function_locals(func):
|
|
120
|
+
"""Names local to ``func``: its parameters and everything it binds."""
|
|
121
|
+
names = set()
|
|
122
|
+
args = func.args
|
|
123
|
+
if PY2:
|
|
124
|
+
for arg in args.args:
|
|
125
|
+
names.update(_target_names(arg))
|
|
126
|
+
else:
|
|
127
|
+
for arg in getattr(args, "posonlyargs", []) + args.args + args.kwonlyargs:
|
|
128
|
+
names.add(arg.arg)
|
|
129
|
+
for star in (args.vararg, args.kwarg):
|
|
130
|
+
if star is not None:
|
|
131
|
+
names.add(star if PY2 else star.arg)
|
|
132
|
+
declared_global = set()
|
|
133
|
+
for node in _walk_scope(func.body):
|
|
134
|
+
if isinstance(node, ast.Name) and not isinstance(node.ctx, ast.Load):
|
|
135
|
+
names.add(node.id)
|
|
136
|
+
elif isinstance(node, (ast.Global,) + _NONLOCAL):
|
|
137
|
+
declared_global.update(node.names)
|
|
138
|
+
elif isinstance(node, (ast.Import, ast.ImportFrom)):
|
|
139
|
+
for alias in node.names:
|
|
140
|
+
names.add((alias.asname or alias.name).split(".")[0])
|
|
141
|
+
elif isinstance(node, _SCOPES):
|
|
142
|
+
names.add(node.name)
|
|
143
|
+
elif isinstance(node, ast.ExceptHandler) and isinstance(node.name, str):
|
|
144
|
+
names.add(node.name)
|
|
145
|
+
return names - declared_global
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
_NONLOCAL = () if PY2 else (ast.Nonlocal,)
|
|
149
|
+
_SCOPES = (ast.FunctionDef, ast.ClassDef) + (() if PY2 else (ast.AsyncFunctionDef,))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _target_names(node):
|
|
153
|
+
return [n.id for n in ast.walk(node) if isinstance(n, ast.Name)]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _walk_scope(body):
|
|
157
|
+
"""Walk statements without descending into nested scopes."""
|
|
158
|
+
todo = list(body)
|
|
159
|
+
while todo:
|
|
160
|
+
node = todo.pop()
|
|
161
|
+
yield node
|
|
162
|
+
if isinstance(node, _SCOPES + (ast.Lambda,)):
|
|
163
|
+
continue
|
|
164
|
+
todo.extend(ast.iter_child_nodes(node))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# -- the rewriter ------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ModuleRewriter(object):
|
|
171
|
+
def __init__(self):
|
|
172
|
+
self.sites = []
|
|
173
|
+
self.counter = 0
|
|
174
|
+
|
|
175
|
+
def run(self, module):
|
|
176
|
+
self.rewrite_body(module.body, None)
|
|
177
|
+
if self.sites:
|
|
178
|
+
module.body[_insert_position(module.body) : 0] = self.preamble()
|
|
179
|
+
ast.fix_missing_locations(module)
|
|
180
|
+
|
|
181
|
+
def preamble(self):
|
|
182
|
+
stmts = [
|
|
183
|
+
ast.ImportFrom("cot_assert", [ast.alias("_runtime", RUNTIME)], 0),
|
|
184
|
+
]
|
|
185
|
+
for name, source, path_labels, shape, paths in self.sites:
|
|
186
|
+
site = _call(
|
|
187
|
+
_attr(_name(RUNTIME), "AssertSite"),
|
|
188
|
+
[
|
|
189
|
+
_const(source),
|
|
190
|
+
_literal(path_labels),
|
|
191
|
+
_literal(shape),
|
|
192
|
+
_literal(paths),
|
|
193
|
+
],
|
|
194
|
+
)
|
|
195
|
+
stmts.append(ast.Assign([_name(name, store=True)], site))
|
|
196
|
+
for stmt in stmts:
|
|
197
|
+
stmt.lineno = 1
|
|
198
|
+
stmt.col_offset = 0
|
|
199
|
+
return stmts
|
|
200
|
+
|
|
201
|
+
def fresh(self, prefix):
|
|
202
|
+
self.counter += 1
|
|
203
|
+
return "@%s%d" % (prefix, self.counter)
|
|
204
|
+
|
|
205
|
+
def rewrite_body(self, body, local_names):
|
|
206
|
+
"""Rewrite asserts in a statement list; ``local_names`` None = module."""
|
|
207
|
+
i = 0
|
|
208
|
+
while i < len(body):
|
|
209
|
+
stmt = body[i]
|
|
210
|
+
if isinstance(stmt, ast.Assert):
|
|
211
|
+
new = AssertRewriter(self, local_names).rewrite(stmt)
|
|
212
|
+
body[i : i + 1] = new
|
|
213
|
+
i += len(new)
|
|
214
|
+
continue
|
|
215
|
+
self.rewrite_children(stmt, local_names)
|
|
216
|
+
i += 1
|
|
217
|
+
|
|
218
|
+
def rewrite_children(self, stmt, local_names):
|
|
219
|
+
if isinstance(stmt, ast.FunctionDef) or (
|
|
220
|
+
not PY2 and isinstance(stmt, ast.AsyncFunctionDef)
|
|
221
|
+
):
|
|
222
|
+
self.rewrite_body(stmt.body, _function_locals(stmt))
|
|
223
|
+
return
|
|
224
|
+
if isinstance(stmt, ast.ClassDef):
|
|
225
|
+
self.rewrite_body(stmt.body, None)
|
|
226
|
+
return
|
|
227
|
+
for field in ("body", "orelse", "finalbody"):
|
|
228
|
+
block = getattr(stmt, field, None)
|
|
229
|
+
if isinstance(block, list):
|
|
230
|
+
self.rewrite_body(block, local_names)
|
|
231
|
+
# except handlers and match cases hold their own statement lists
|
|
232
|
+
for child in getattr(stmt, "handlers", []) + getattr(stmt, "cases", []):
|
|
233
|
+
self.rewrite_body(child.body, local_names)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _insert_position(body):
|
|
237
|
+
pos = 0
|
|
238
|
+
if _docstring_node(body):
|
|
239
|
+
pos = 1
|
|
240
|
+
while (
|
|
241
|
+
pos < len(body)
|
|
242
|
+
and isinstance(body[pos], ast.ImportFrom)
|
|
243
|
+
and body[pos].module == "__future__"
|
|
244
|
+
):
|
|
245
|
+
pos += 1
|
|
246
|
+
return pos
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _docstring_node(body):
|
|
250
|
+
if not body or not isinstance(body[0], ast.Expr):
|
|
251
|
+
return False
|
|
252
|
+
value = body[0].value
|
|
253
|
+
if PY2:
|
|
254
|
+
return isinstance(value, ast.Str)
|
|
255
|
+
return isinstance(value, ast.Constant) and isinstance(value.value, str)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class AssertRewriter(object):
|
|
259
|
+
"""Rewrites a single assert statement.
|
|
260
|
+
|
|
261
|
+
Besides the code, it records what the failure explanation needs:
|
|
262
|
+
|
|
263
|
+
- slots: every tracked value gets a slot number and a label;
|
|
264
|
+
- paths: per failure exit, the slots evaluated on the way there (in the
|
|
265
|
+
order their values are passed) and the marks that tell which boolean
|
|
266
|
+
operands ran and which comparisons are known to have failed;
|
|
267
|
+
- shape: a tree of tuples mirroring the expression, which the host
|
|
268
|
+
renders the way pytest's rewriter formats its explanations.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
def __init__(self, module, local_names):
|
|
272
|
+
self.module = module
|
|
273
|
+
self.local_names = local_names
|
|
274
|
+
self.site_name = module.fresh("cot_site")
|
|
275
|
+
self.labels = []
|
|
276
|
+
self.paths = []
|
|
277
|
+
self.node_ids = 0
|
|
278
|
+
|
|
279
|
+
def rewrite(self, stmt):
|
|
280
|
+
self.msg = stmt.msg
|
|
281
|
+
block = []
|
|
282
|
+
shape = self.check(stmt.test, block, _Path())
|
|
283
|
+
path_labels = [[self.labels[slot] for slot in slots] for slots, _ in self.paths]
|
|
284
|
+
self.module.sites.append(
|
|
285
|
+
(self.site_name, unparse(stmt.test), path_labels, shape, self.paths)
|
|
286
|
+
)
|
|
287
|
+
for new in block:
|
|
288
|
+
ast.copy_location(new, stmt)
|
|
289
|
+
return block
|
|
290
|
+
|
|
291
|
+
def new_id(self):
|
|
292
|
+
self.node_ids += 1
|
|
293
|
+
return self.node_ids
|
|
294
|
+
|
|
295
|
+
# -- failure exits --
|
|
296
|
+
|
|
297
|
+
def fail(self, path):
|
|
298
|
+
number = len(self.paths)
|
|
299
|
+
self.paths.append((list(path.slots), sorted(path.marks)))
|
|
300
|
+
values = ast.List(
|
|
301
|
+
[_call(_attr(_name(RUNTIME), "v"), [expr]) for expr in path.exprs],
|
|
302
|
+
ast.Load(),
|
|
303
|
+
)
|
|
304
|
+
msg = copy.deepcopy(self.msg) if self.msg is not None else _const(None)
|
|
305
|
+
return _raise(
|
|
306
|
+
_call(
|
|
307
|
+
_attr(_name(RUNTIME), "fail"),
|
|
308
|
+
[_name(self.site_name), _const(number), values, msg],
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def fail_unless(self, test, block, path):
|
|
313
|
+
block.append(ast.If(_not(test), [self.fail(path)], []))
|
|
314
|
+
|
|
315
|
+
# -- statement level: fall through when true, raise when false --
|
|
316
|
+
|
|
317
|
+
def check(self, expr, block, path):
|
|
318
|
+
"""Emit the test for ``expr`` into ``block``; return its shape."""
|
|
319
|
+
if isinstance(expr, ast.BoolOp):
|
|
320
|
+
node = self.new_id()
|
|
321
|
+
is_or = isinstance(expr.op, ast.Or)
|
|
322
|
+
shapes = []
|
|
323
|
+
last = len(expr.values) - 1
|
|
324
|
+
for i, value in enumerate(expr.values):
|
|
325
|
+
path.marks.add(("bool", node, i))
|
|
326
|
+
if is_or and i < last:
|
|
327
|
+
# a false operand of ``or`` moves on to the next one
|
|
328
|
+
test, shape = self.cond(value, block, path)
|
|
329
|
+
inner = []
|
|
330
|
+
block.append(ast.If(_not(test), inner, []))
|
|
331
|
+
block = inner
|
|
332
|
+
shapes.append(shape)
|
|
333
|
+
self.mark_false(shape, path)
|
|
334
|
+
else:
|
|
335
|
+
shapes.append(self.check(value, block, path))
|
|
336
|
+
return ("boolop", node, is_or, shapes)
|
|
337
|
+
if isinstance(expr, ast.Compare) and len(expr.ops) > 1:
|
|
338
|
+
node = self.new_id()
|
|
339
|
+
left, left_shape = self.track(expr.left, block, path)
|
|
340
|
+
operands = [left_shape]
|
|
341
|
+
for i, (op, comparator) in enumerate(zip(expr.ops, expr.comparators)):
|
|
342
|
+
right, right_shape = self.track(comparator, block, path)
|
|
343
|
+
operands.append(right_shape)
|
|
344
|
+
test = ast.Compare(left, [op], [right])
|
|
345
|
+
path.marks.add(("cmp", node, i))
|
|
346
|
+
block.append(ast.If(_not(test), [self.fail(path)], []))
|
|
347
|
+
path.marks.discard(("cmp", node, i))
|
|
348
|
+
left = right
|
|
349
|
+
return ("compare", node, [cmpop(op) for op in expr.ops], operands, None)
|
|
350
|
+
test, shape = self.cond(expr, block, path)
|
|
351
|
+
self.mark_false(shape, path)
|
|
352
|
+
self.fail_unless(test, block, path)
|
|
353
|
+
self.unmark_false(shape, path)
|
|
354
|
+
return shape
|
|
355
|
+
|
|
356
|
+
def mark_false(self, shape, path):
|
|
357
|
+
if shape[0] == "compare" and shape[4] is None:
|
|
358
|
+
path.marks.add(("cmp", shape[1], 0))
|
|
359
|
+
|
|
360
|
+
def unmark_false(self, shape, path):
|
|
361
|
+
if shape[0] == "compare" and shape[4] is None:
|
|
362
|
+
path.marks.discard(("cmp", shape[1], 0))
|
|
363
|
+
|
|
364
|
+
def cond(self, expr, block, path):
|
|
365
|
+
"""An expression to test, its operands already evaluated into block."""
|
|
366
|
+
if isinstance(expr, ast.Compare) and len(expr.ops) == 1:
|
|
367
|
+
left, left_shape = self.track(expr.left, block, path)
|
|
368
|
+
right, right_shape = self.track(expr.comparators[0], block, path)
|
|
369
|
+
shape = (
|
|
370
|
+
"compare",
|
|
371
|
+
self.new_id(),
|
|
372
|
+
[cmpop(expr.ops[0])],
|
|
373
|
+
[left_shape, right_shape],
|
|
374
|
+
None,
|
|
375
|
+
)
|
|
376
|
+
return ast.Compare(left, expr.ops, [right]), shape
|
|
377
|
+
return self.track(expr, block, path)
|
|
378
|
+
|
|
379
|
+
# -- expression level: evaluate once, remember the value --
|
|
380
|
+
|
|
381
|
+
def slot(self, expr, label, path):
|
|
382
|
+
slot = len(self.labels)
|
|
383
|
+
self.labels.append(label)
|
|
384
|
+
path.slots.append(slot)
|
|
385
|
+
path.exprs.append(expr)
|
|
386
|
+
return slot
|
|
387
|
+
|
|
388
|
+
def track(self, expr, block, path):
|
|
389
|
+
"""Evaluate ``expr`` into ``block``; return (value expression, shape)."""
|
|
390
|
+
if _is_constant(expr):
|
|
391
|
+
return expr, ("const", unparse(expr))
|
|
392
|
+
label = unparse(expr)
|
|
393
|
+
if isinstance(expr, ast.Name):
|
|
394
|
+
if self.local_names is None or expr.id in self.local_names:
|
|
395
|
+
return expr, ("name", self.slot(_name(expr.id), label, path), expr.id)
|
|
396
|
+
return expr, ("text", expr.id)
|
|
397
|
+
new, build = self.rebuild(expr, block, path)
|
|
398
|
+
temp = self.module.fresh("cot")
|
|
399
|
+
block.append(ast.Assign([_name(temp, store=True)], new))
|
|
400
|
+
slot = self.slot(_name(temp), label, path)
|
|
401
|
+
return _name(temp), build(slot)
|
|
402
|
+
|
|
403
|
+
def rebuild(self, expr, block, path):
|
|
404
|
+
"""``expr`` over tracked sub-values, and a shape builder taking its slot."""
|
|
405
|
+
if isinstance(expr, ast.Attribute) and isinstance(expr.ctx, ast.Load):
|
|
406
|
+
value, value_shape = self.track(expr.value, block, path)
|
|
407
|
+
attr = expr.attr
|
|
408
|
+
return _attr(value, attr), lambda slot: ("attr", slot, value_shape, attr)
|
|
409
|
+
if isinstance(expr, ast.BinOp):
|
|
410
|
+
left, left_shape = self.track(expr.left, block, path)
|
|
411
|
+
right, right_shape = self.track(expr.right, block, path)
|
|
412
|
+
sym = binop(expr.op)
|
|
413
|
+
return (
|
|
414
|
+
ast.BinOp(left, expr.op, right),
|
|
415
|
+
lambda slot: ("binop", sym, left_shape, right_shape),
|
|
416
|
+
)
|
|
417
|
+
if isinstance(expr, ast.UnaryOp):
|
|
418
|
+
operand, operand_shape = self.track(expr.operand, block, path)
|
|
419
|
+
pattern = unaryop(expr.op) + "%s"
|
|
420
|
+
return (
|
|
421
|
+
ast.UnaryOp(expr.op, operand),
|
|
422
|
+
lambda slot: ("unary", pattern, operand_shape),
|
|
423
|
+
)
|
|
424
|
+
if isinstance(expr, ast.Compare) and len(expr.ops) == 1:
|
|
425
|
+
test, shape = self.cond(expr, block, path)
|
|
426
|
+
return test, lambda slot: shape[:4] + (slot,)
|
|
427
|
+
if isinstance(expr, ast.Call):
|
|
428
|
+
return self.rebuild_call(expr, block, path)
|
|
429
|
+
if isinstance(expr, ast.Subscript) and isinstance(expr.ctx, ast.Load):
|
|
430
|
+
value, _ = self.track(expr.value, block, path)
|
|
431
|
+
return ast.Subscript(value, expr.slice, ast.Load()), _repr_shape
|
|
432
|
+
return expr, _repr_shape
|
|
433
|
+
|
|
434
|
+
def rebuild_call(self, call, block, path):
|
|
435
|
+
func = call.func
|
|
436
|
+
if isinstance(func, ast.Attribute):
|
|
437
|
+
# keep the method call shape: a bound method in a temporary is
|
|
438
|
+
# something RPython would have to annotate as a value
|
|
439
|
+
obj, obj_shape = self.track(func.value, block, path)
|
|
440
|
+
func_shape = ("method", obj_shape, func.attr)
|
|
441
|
+
func = _attr(obj, func.attr)
|
|
442
|
+
else:
|
|
443
|
+
func, func_shape = self.track(func, block, path)
|
|
444
|
+
args = []
|
|
445
|
+
arg_shapes = []
|
|
446
|
+
for arg in call.args:
|
|
447
|
+
if not PY2 and isinstance(arg, ast.Starred):
|
|
448
|
+
value, shape = self.track(arg.value, block, path)
|
|
449
|
+
args.append(ast.Starred(value, ast.Load()))
|
|
450
|
+
arg_shapes.append(("*", shape))
|
|
451
|
+
else:
|
|
452
|
+
value, shape = self.track(arg, block, path)
|
|
453
|
+
args.append(value)
|
|
454
|
+
arg_shapes.append(("", shape))
|
|
455
|
+
starargs = kwargs = None
|
|
456
|
+
if PY2 and call.starargs is not None:
|
|
457
|
+
starargs, shape = self.track(call.starargs, block, path)
|
|
458
|
+
arg_shapes.append(("*", shape))
|
|
459
|
+
keywords = []
|
|
460
|
+
for kw in call.keywords:
|
|
461
|
+
value, shape = self.track(kw.value, block, path)
|
|
462
|
+
keywords.append(ast.keyword(kw.arg, value))
|
|
463
|
+
arg_shapes.append(("**" if kw.arg is None else kw.arg + "=", shape))
|
|
464
|
+
if PY2 and call.kwargs is not None:
|
|
465
|
+
kwargs, shape = self.track(call.kwargs, block, path)
|
|
466
|
+
arg_shapes.append(("**", shape))
|
|
467
|
+
if PY2:
|
|
468
|
+
new = ast.Call(func, args, keywords, starargs, kwargs)
|
|
469
|
+
else:
|
|
470
|
+
new = ast.Call(func, args, keywords)
|
|
471
|
+
return new, lambda slot: ("call", slot, func_shape, arg_shapes)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _repr_shape(slot):
|
|
475
|
+
return ("repr", slot)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
class _Path(object):
|
|
479
|
+
"""What has been evaluated along the code path being emitted.
|
|
480
|
+
|
|
481
|
+
Failure exits only ever come after the code of everything they report,
|
|
482
|
+
and short-circuits only nest, so one growing record per assert is enough.
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
def __init__(self):
|
|
486
|
+
self.slots = []
|
|
487
|
+
self.exprs = []
|
|
488
|
+
self.marks = set()
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def load_source(source, name="rewritten"):
|
|
492
|
+
"""Rewrite and execute module source; return its namespace."""
|
|
493
|
+
namespace = {"__name__": name}
|
|
494
|
+
exec(rewrite_source(source, "<%s>" % name), namespace)
|
|
495
|
+
return namespace
|
cot_assert/_rpy.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""RPython primitives, or inert stand-ins when rpython is not importable.
|
|
2
|
+
|
|
3
|
+
Code in the RPython subset imports these from here, never from rpython
|
|
4
|
+
directly, so the package works on hosts without an rpython checkout.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import absolute_import, division, print_function
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from rpython.rlib.objectmodel import specialize, we_are_translated
|
|
11
|
+
from rpython.rlib.rfloat import DTSF_ADD_DOT_0, formatd
|
|
12
|
+
|
|
13
|
+
def float_repr(x):
|
|
14
|
+
# str() of an RPython float is "%f"; this matches the host's repr
|
|
15
|
+
return formatd(x, "r", 0, DTSF_ADD_DOT_0)
|
|
16
|
+
|
|
17
|
+
except ImportError:
|
|
18
|
+
float_repr = repr
|
|
19
|
+
|
|
20
|
+
def we_are_translated():
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
class _Specialize(object):
|
|
24
|
+
def __getattr__(self, name):
|
|
25
|
+
def decorator_factory(*args, **kwargs):
|
|
26
|
+
return lambda func: func
|
|
27
|
+
|
|
28
|
+
return decorator_factory
|
|
29
|
+
|
|
30
|
+
specialize = _Specialize()
|
|
31
|
+
|
|
32
|
+
__all__ = ["float_repr", "specialize", "we_are_translated"]
|
cot_assert/_runtime.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Names the rewritten code calls; imported into rewritten modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import absolute_import, division, print_function
|
|
4
|
+
|
|
5
|
+
from ._error import AssertSite, annotated
|
|
6
|
+
from ._values import value as v
|
|
7
|
+
|
|
8
|
+
__all__ = ["AssertSite", "fail", "v"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def fail(site, path, values, msg):
|
|
12
|
+
return annotated(msg, site, path, values)
|