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/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Assertion rewriting with a structured AssertionError."""
|
|
2
|
+
|
|
3
|
+
from __future__ import absolute_import, division, print_function
|
|
4
|
+
|
|
5
|
+
from ._entry import report_annotated
|
|
6
|
+
from ._error import AnnotatedAssertion, AssertSite, annotated
|
|
7
|
+
from ._hook import install, uninstall
|
|
8
|
+
from ._rewrite import rewrite_asserts, rewrite_source
|
|
9
|
+
|
|
10
|
+
# the public name, in tracebacks and pickles
|
|
11
|
+
AnnotatedAssertion.__module__ = __name__
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AnnotatedAssertion",
|
|
15
|
+
"AssertSite",
|
|
16
|
+
"annotated",
|
|
17
|
+
"install",
|
|
18
|
+
"report_annotated",
|
|
19
|
+
"rewrite_asserts",
|
|
20
|
+
"rewrite_source",
|
|
21
|
+
"uninstall",
|
|
22
|
+
]
|
cot_assert/_entry.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Making AnnotatedAssertion explanations visible from translated programs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import absolute_import, division, print_function
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from ._error import AnnotatedAssertion
|
|
8
|
+
from ._rpy import we_are_translated
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def report_annotated(func):
|
|
12
|
+
"""Wrap a translated entry point to print an escaping AnnotatedAssertion.
|
|
13
|
+
|
|
14
|
+
Uncaught, RPython aborts with just the class name. The wrapper writes
|
|
15
|
+
the explanation to stderr first and re-raises, so the abort still
|
|
16
|
+
happens. On the host the traceback already shows it; nothing is written.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def entry_point(*args):
|
|
20
|
+
try:
|
|
21
|
+
return func(*args)
|
|
22
|
+
# RPython rejects ``except`` on AssertionError or any subclass
|
|
23
|
+
except Exception as exc:
|
|
24
|
+
if we_are_translated() and isinstance(exc, AnnotatedAssertion):
|
|
25
|
+
os.write(2, exc.render_plain() + "\n")
|
|
26
|
+
raise
|
|
27
|
+
|
|
28
|
+
entry_point.__name__ = "report_annotated_" + func.__name__
|
|
29
|
+
return entry_point
|
cot_assert/_error.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""The assertion error raised by rewritten asserts.
|
|
2
|
+
|
|
3
|
+
Everything in this module that is not marked host-only is in the RPython
|
|
4
|
+
subset: fields keep one type per attribute, and host-only paths sit behind
|
|
5
|
+
``we_are_translated()``, which the flow space folds to True.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import absolute_import, division, print_function
|
|
9
|
+
|
|
10
|
+
from ._rpy import specialize, we_are_translated
|
|
11
|
+
from ._values import value
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AssertSite(object):
|
|
15
|
+
"""Static description of one assert statement, prebuilt by the rewriter."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, source, path_labels, shape=None, paths=None):
|
|
18
|
+
self.source = source
|
|
19
|
+
# One label list per failure path: a path that skipped a short-circuit
|
|
20
|
+
# operand has no value for it. Lists, not tuples: prebuilt tuples of
|
|
21
|
+
# different lengths do not unify in RPython, lists of str do.
|
|
22
|
+
self.path_labels = path_labels
|
|
23
|
+
# Host only, never read by translated code, so never annotated:
|
|
24
|
+
# the expression tree and per-path records the renderer walks.
|
|
25
|
+
# See _rewrite.AssertRewriter.
|
|
26
|
+
self.shape = shape
|
|
27
|
+
self.paths = paths
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AnnotatedAssertion(AssertionError):
|
|
31
|
+
"""An AssertionError that carries the values its explanation is built from.
|
|
32
|
+
|
|
33
|
+
Translated, ``values`` holds str; on the host it holds the objects
|
|
34
|
+
themselves until ``finalize`` renders them and drops them.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Unpatched RPython takes every AssertionError subclass for a built-in
|
|
38
|
+
# exception: it skips __init__ and allows no attributes but the ones
|
|
39
|
+
# listed here. Code that has to translate there builds instances with
|
|
40
|
+
# ``annotated()``; see docs/design/rpython.md.
|
|
41
|
+
_attrs_ = ["msg", "site", "path", "values", "labels", "rendered"]
|
|
42
|
+
|
|
43
|
+
def __init__(self, msg=None, site=None, path=0, values=None):
|
|
44
|
+
if not we_are_translated():
|
|
45
|
+
AssertionError.__init__(self)
|
|
46
|
+
self.init_fields(msg, site, path, values)
|
|
47
|
+
|
|
48
|
+
def init_fields(self, msg, site, path, values):
|
|
49
|
+
self.msg = msg
|
|
50
|
+
self.site = site
|
|
51
|
+
self.path = path
|
|
52
|
+
if values is None:
|
|
53
|
+
values = []
|
|
54
|
+
self.values = values
|
|
55
|
+
self.labels = []
|
|
56
|
+
self.rendered = None
|
|
57
|
+
|
|
58
|
+
@specialize.argtype(2)
|
|
59
|
+
def annotate(self, label, obj):
|
|
60
|
+
"""Attach a labelled value; the label follows the site's labels."""
|
|
61
|
+
self.labels.append(label)
|
|
62
|
+
self.values.append(value(obj))
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def all_labels(self):
|
|
66
|
+
if self.site is None:
|
|
67
|
+
return self.labels
|
|
68
|
+
return self.site.path_labels[self.path] + self.labels
|
|
69
|
+
|
|
70
|
+
def render_plain(self):
|
|
71
|
+
"""The explanation from str values, as translated code can build it."""
|
|
72
|
+
lines = []
|
|
73
|
+
if self.msg is not None:
|
|
74
|
+
lines.append(self.msg)
|
|
75
|
+
if self.site is not None:
|
|
76
|
+
lines.append("assert " + self.site.source)
|
|
77
|
+
labels = self.all_labels()
|
|
78
|
+
i = 0
|
|
79
|
+
while i < len(labels) and i < len(self.values):
|
|
80
|
+
lines.append(" where " + labels[i] + " = " + self.values[i])
|
|
81
|
+
i += 1
|
|
82
|
+
return "\n".join(lines)
|
|
83
|
+
|
|
84
|
+
def render(self):
|
|
85
|
+
"""Host only: the explanation text."""
|
|
86
|
+
if self.rendered is not None:
|
|
87
|
+
return self.rendered
|
|
88
|
+
from ._render import render
|
|
89
|
+
|
|
90
|
+
return render(self)
|
|
91
|
+
|
|
92
|
+
def finalize(self, mode="notes"):
|
|
93
|
+
"""Host only: render once and drop the tracked values.
|
|
94
|
+
|
|
95
|
+
``mode="notes"`` adds the explanation as a PEP 678 note,
|
|
96
|
+
``mode="message"`` puts it into ``args`` for hosts that do not show
|
|
97
|
+
notes (pytest 4.6, Python < 3.11 tracebacks).
|
|
98
|
+
"""
|
|
99
|
+
if mode not in ("notes", "message"):
|
|
100
|
+
raise ValueError("unknown finalize mode: %r" % (mode,))
|
|
101
|
+
if self.values is None:
|
|
102
|
+
return self
|
|
103
|
+
text = self.render()
|
|
104
|
+
# keep only text: nothing that holds on to the tested objects or
|
|
105
|
+
# needs this package's classes to unpickle into a meaningful state
|
|
106
|
+
self.rendered = text
|
|
107
|
+
self.values = None
|
|
108
|
+
self.site = None
|
|
109
|
+
if self.msg is not None:
|
|
110
|
+
self.msg = str(self.msg)
|
|
111
|
+
if mode == "notes":
|
|
112
|
+
_add_note(self, text)
|
|
113
|
+
else:
|
|
114
|
+
self.args = (text,)
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
def __reduce__(self):
|
|
118
|
+
# BaseException's reduce passes args to __init__, which reads them
|
|
119
|
+
# as msg/site/...; restore args and state directly instead
|
|
120
|
+
return _unpickle, (type(self), self.args, self.__dict__)
|
|
121
|
+
|
|
122
|
+
def __str__(self):
|
|
123
|
+
if we_are_translated():
|
|
124
|
+
return self.render_plain()
|
|
125
|
+
if self.args:
|
|
126
|
+
return str(self.args[0])
|
|
127
|
+
if self.values is None:
|
|
128
|
+
# finalized into notes: the note carries the explanation
|
|
129
|
+
return self.msg if self.msg is not None else ""
|
|
130
|
+
return self.render()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def annotated(msg=None, site=None, path=0, values=None):
|
|
134
|
+
"""Build an AnnotatedAssertion; the constructor for RPython code."""
|
|
135
|
+
exc = AnnotatedAssertion()
|
|
136
|
+
exc.init_fields(msg, site, path, values)
|
|
137
|
+
return exc
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _unpickle(cls, args, state):
|
|
141
|
+
exc = cls.__new__(cls)
|
|
142
|
+
exc.args = args
|
|
143
|
+
exc.__dict__.update(state)
|
|
144
|
+
return exc
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _add_note(exc, text):
|
|
148
|
+
add_note = getattr(exc, "add_note", None)
|
|
149
|
+
if add_note is not None:
|
|
150
|
+
add_note(text)
|
|
151
|
+
else:
|
|
152
|
+
notes = getattr(exc, "__notes__", None)
|
|
153
|
+
if notes is None:
|
|
154
|
+
notes = exc.__notes__ = []
|
|
155
|
+
notes.append(text)
|
cot_assert/_hook.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Import hook that rewrites the asserts of selected modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import absolute_import, division, print_function
|
|
4
|
+
|
|
5
|
+
import fnmatch
|
|
6
|
+
import hashlib
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from ._rewrite import rewrite_source
|
|
11
|
+
|
|
12
|
+
PY2 = sys.version_info[0] == 2
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _rewriter_digest():
|
|
16
|
+
"""Changes whenever the code that shapes rewritten modules changes.
|
|
17
|
+
|
|
18
|
+
Cached bytecode is only valid for the rewriter that produced it; hashing
|
|
19
|
+
the sources avoids having to remember a version bump.
|
|
20
|
+
"""
|
|
21
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
22
|
+
digest = hashlib.sha1()
|
|
23
|
+
for name in ("_rewrite.py", "_unparse.py"):
|
|
24
|
+
try:
|
|
25
|
+
with open(os.path.join(here, name), "rb") as f:
|
|
26
|
+
digest.update(f.read())
|
|
27
|
+
except (IOError, OSError):
|
|
28
|
+
return "unknown"
|
|
29
|
+
return digest.hexdigest()[:10]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
CACHE_TAG = "cot_assert-" + _rewriter_digest()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _Matcher(object):
|
|
36
|
+
def __init__(self, patterns):
|
|
37
|
+
self.patterns = list(patterns)
|
|
38
|
+
|
|
39
|
+
def __call__(self, fullname):
|
|
40
|
+
if fullname == "cot_assert" or fullname.startswith("cot_assert."):
|
|
41
|
+
return False
|
|
42
|
+
for pattern in self.patterns:
|
|
43
|
+
if fnmatch.fnmatchcase(fullname, pattern):
|
|
44
|
+
return True
|
|
45
|
+
# a package name selects its submodules too
|
|
46
|
+
if fullname.startswith(pattern + "."):
|
|
47
|
+
return True
|
|
48
|
+
# a wildcard pattern like "test_*" matches the last dotted component
|
|
49
|
+
last = fullname.rpartition(".")[2]
|
|
50
|
+
if _is_glob(pattern) and "." not in pattern:
|
|
51
|
+
if fnmatch.fnmatchcase(last, pattern):
|
|
52
|
+
return True
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _is_glob(pattern):
|
|
57
|
+
return any(char in pattern for char in "*?[")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def install(match):
|
|
61
|
+
"""Rewrite asserts in modules imported from now on whose name matches.
|
|
62
|
+
|
|
63
|
+
``match`` is a list of module names (selecting their submodules too) or
|
|
64
|
+
fnmatch patterns; a wildcard pattern without dots also matches the last
|
|
65
|
+
component of a dotted name. Returns the hook; ``uninstall(hook)`` removes it.
|
|
66
|
+
"""
|
|
67
|
+
hook = RewriteHook(match)
|
|
68
|
+
sys.meta_path.insert(0, hook)
|
|
69
|
+
return hook
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def uninstall(hook):
|
|
73
|
+
try:
|
|
74
|
+
sys.meta_path.remove(hook)
|
|
75
|
+
except ValueError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if not PY2:
|
|
80
|
+
import importlib.machinery
|
|
81
|
+
import importlib.util
|
|
82
|
+
import marshal
|
|
83
|
+
|
|
84
|
+
class RewriteHook(object):
|
|
85
|
+
def __init__(self, match):
|
|
86
|
+
self.matches = _Matcher(match)
|
|
87
|
+
|
|
88
|
+
def find_spec(self, fullname, path=None, target=None):
|
|
89
|
+
if not self.matches(fullname):
|
|
90
|
+
return None
|
|
91
|
+
spec = importlib.machinery.PathFinder.find_spec(fullname, path)
|
|
92
|
+
if spec is None or not isinstance(
|
|
93
|
+
spec.loader, importlib.machinery.SourceFileLoader
|
|
94
|
+
):
|
|
95
|
+
return None
|
|
96
|
+
spec.loader = RewritingLoader(fullname, spec.origin)
|
|
97
|
+
return spec
|
|
98
|
+
|
|
99
|
+
def invalidate_caches(self):
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
class RewritingLoader(importlib.machinery.SourceFileLoader):
|
|
103
|
+
"""A source loader whose code has its asserts rewritten.
|
|
104
|
+
|
|
105
|
+
It keeps its own bytecode cache next to the regular one; the regular
|
|
106
|
+
``__pycache__`` entry holds unrewritten code and must not be used.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def get_code(self, fullname):
|
|
110
|
+
path = self.get_filename(fullname)
|
|
111
|
+
st = os.stat(path)
|
|
112
|
+
cache = cache_path(path)
|
|
113
|
+
code = _read_cache(cache, st)
|
|
114
|
+
if code is None:
|
|
115
|
+
code = rewrite_source(self.get_data(path), path)
|
|
116
|
+
if not sys.dont_write_bytecode:
|
|
117
|
+
_write_cache(cache, st, code)
|
|
118
|
+
return code
|
|
119
|
+
|
|
120
|
+
def cache_path(source_path):
|
|
121
|
+
directory, filename = os.path.split(source_path)
|
|
122
|
+
stem = filename.rpartition(".")[0]
|
|
123
|
+
tag = sys.implementation.cache_tag
|
|
124
|
+
return os.path.join(
|
|
125
|
+
directory, "__pycache__", "%s.%s-%s.pyc" % (stem, tag, CACHE_TAG)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def _header(st):
|
|
129
|
+
return (
|
|
130
|
+
importlib.util.MAGIC_NUMBER
|
|
131
|
+
+ (0).to_bytes(4, "little")
|
|
132
|
+
+ (int(st.st_mtime) & 0xFFFFFFFF).to_bytes(4, "little")
|
|
133
|
+
+ (st.st_size & 0xFFFFFFFF).to_bytes(4, "little")
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def _read_cache(cache, st):
|
|
137
|
+
try:
|
|
138
|
+
with open(cache, "rb") as f:
|
|
139
|
+
data = f.read()
|
|
140
|
+
except OSError:
|
|
141
|
+
return None
|
|
142
|
+
header = _header(st)
|
|
143
|
+
if data[: len(header)] != header:
|
|
144
|
+
return None
|
|
145
|
+
try:
|
|
146
|
+
return marshal.loads(data[len(header) :])
|
|
147
|
+
except (EOFError, ValueError, TypeError):
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
def _write_cache(cache, st, code):
|
|
151
|
+
data = _header(st) + marshal.dumps(code)
|
|
152
|
+
tmp = "%s.%d" % (cache, os.getpid())
|
|
153
|
+
try:
|
|
154
|
+
os.makedirs(os.path.dirname(cache), exist_ok=True)
|
|
155
|
+
with open(tmp, "wb") as f:
|
|
156
|
+
f.write(data)
|
|
157
|
+
os.replace(tmp, cache)
|
|
158
|
+
except OSError:
|
|
159
|
+
# read-only trees just go without a cache
|
|
160
|
+
try:
|
|
161
|
+
os.unlink(tmp)
|
|
162
|
+
except OSError:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
else:
|
|
166
|
+
import imp
|
|
167
|
+
|
|
168
|
+
class RewriteHook(object):
|
|
169
|
+
"""PEP 302 finder and loader; Python 2 goes without a disk cache."""
|
|
170
|
+
|
|
171
|
+
def __init__(self, match):
|
|
172
|
+
self.matches = _Matcher(match)
|
|
173
|
+
self._found = {}
|
|
174
|
+
self._sources = {}
|
|
175
|
+
|
|
176
|
+
def find_module(self, fullname, path=None):
|
|
177
|
+
if not self.matches(fullname):
|
|
178
|
+
return None
|
|
179
|
+
name = fullname.rpartition(".")[2]
|
|
180
|
+
try:
|
|
181
|
+
fd, pathname, (_, _, kind) = imp.find_module(name, path)
|
|
182
|
+
except ImportError:
|
|
183
|
+
return None
|
|
184
|
+
if fd is not None:
|
|
185
|
+
fd.close()
|
|
186
|
+
is_package = kind == imp.PKG_DIRECTORY
|
|
187
|
+
if is_package:
|
|
188
|
+
pathname = os.path.join(pathname, "__init__.py")
|
|
189
|
+
if not os.path.isfile(pathname):
|
|
190
|
+
return None
|
|
191
|
+
elif kind != imp.PY_SOURCE:
|
|
192
|
+
return None
|
|
193
|
+
self._found[fullname] = (pathname, is_package)
|
|
194
|
+
return self
|
|
195
|
+
|
|
196
|
+
def load_module(self, fullname):
|
|
197
|
+
if fullname in sys.modules:
|
|
198
|
+
return sys.modules[fullname]
|
|
199
|
+
pathname, is_package = self._found.pop(fullname)
|
|
200
|
+
with open(pathname, "rb") as f:
|
|
201
|
+
code = rewrite_source(f.read(), pathname)
|
|
202
|
+
module = imp.new_module(fullname)
|
|
203
|
+
module.__file__ = pathname
|
|
204
|
+
module.__loader__ = self
|
|
205
|
+
if is_package:
|
|
206
|
+
module.__path__ = [os.path.dirname(pathname)]
|
|
207
|
+
module.__package__ = fullname
|
|
208
|
+
else:
|
|
209
|
+
module.__package__ = fullname.rpartition(".")[0]
|
|
210
|
+
self._sources[fullname] = pathname
|
|
211
|
+
sys.modules[fullname] = module
|
|
212
|
+
try:
|
|
213
|
+
exec(code, module.__dict__)
|
|
214
|
+
except BaseException:
|
|
215
|
+
sys.modules.pop(fullname, None)
|
|
216
|
+
raise
|
|
217
|
+
return sys.modules[fullname]
|
|
218
|
+
|
|
219
|
+
def is_package(self, fullname):
|
|
220
|
+
return os.path.basename(self._sources[fullname]) == "__init__.py"
|
|
221
|
+
|
|
222
|
+
def get_source(self, fullname):
|
|
223
|
+
with open(self._sources[fullname], "rb") as f:
|
|
224
|
+
return f.read()
|
cot_assert/_llexc.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Host view of an AnnotatedAssertion raised under RPython's llinterpreter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import absolute_import, division, print_function
|
|
4
|
+
|
|
5
|
+
from ._error import AnnotatedAssertion, AssertSite
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def from_llexception(interp, exc):
|
|
9
|
+
"""The AnnotatedAssertion inside an ``LLException``, or None.
|
|
10
|
+
|
|
11
|
+
``interp`` is the ``LLInterpreter`` that raised it; its rtyper knows the
|
|
12
|
+
low-level layout of the class.
|
|
13
|
+
"""
|
|
14
|
+
from rpython.rtyper.annlowlevel import hlstr
|
|
15
|
+
from rpython.rtyper.lltypesystem import lltype
|
|
16
|
+
from rpython.rtyper.rclass import getinstancerepr
|
|
17
|
+
|
|
18
|
+
klass, inst = exc.args[0], exc.args[1]
|
|
19
|
+
rtyper = interp.typer
|
|
20
|
+
bookkeeper = rtyper.annotator.bookkeeper
|
|
21
|
+
try:
|
|
22
|
+
classdef = bookkeeper.getuniqueclassdef(AnnotatedAssertion)
|
|
23
|
+
except Exception:
|
|
24
|
+
return None
|
|
25
|
+
r_inst = getinstancerepr(rtyper, classdef)
|
|
26
|
+
vtable = r_inst.rclass.getvtable()
|
|
27
|
+
if not _is_subclass(klass, vtable):
|
|
28
|
+
return None
|
|
29
|
+
inst = lltype.cast_pointer(r_inst.lowleveltype, inst)
|
|
30
|
+
|
|
31
|
+
def text(ll):
|
|
32
|
+
# a field that only ever holds one constant is Void, and reads back
|
|
33
|
+
# as that host constant
|
|
34
|
+
if ll is None or isinstance(ll, str):
|
|
35
|
+
return ll
|
|
36
|
+
return hlstr(ll) if ll else None
|
|
37
|
+
|
|
38
|
+
def strlist(ll):
|
|
39
|
+
if not ll:
|
|
40
|
+
return []
|
|
41
|
+
return [text(ll.ll_getitem_fast(i)) for i in range(ll.ll_length())]
|
|
42
|
+
|
|
43
|
+
result = AnnotatedAssertion(
|
|
44
|
+
text(inst.inst_msg),
|
|
45
|
+
_host_site(rtyper, inst.inst_site),
|
|
46
|
+
inst.inst_path,
|
|
47
|
+
strlist(inst.inst_values),
|
|
48
|
+
)
|
|
49
|
+
result.labels = strlist(inst.inst_labels)
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _is_subclass(klass, vtable):
|
|
54
|
+
# RPython vtables carry a [min, max] id range for subclass checks
|
|
55
|
+
low, high = vtable.subclassrange_min, vtable.subclassrange_max
|
|
56
|
+
return low <= klass.subclassrange_min < high
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _host_site(rtyper, ll_site):
|
|
60
|
+
"""The prebuilt host AssertSite behind a low-level one.
|
|
61
|
+
|
|
62
|
+
Sites are prebuilt constants, so the host object exists; its fields that
|
|
63
|
+
translated code never reads are not in the low-level struct at all.
|
|
64
|
+
"""
|
|
65
|
+
from rpython.rtyper.lltypesystem import lltype
|
|
66
|
+
from rpython.rtyper.rclass import getinstancerepr
|
|
67
|
+
|
|
68
|
+
if not ll_site:
|
|
69
|
+
return None
|
|
70
|
+
classdef = rtyper.annotator.bookkeeper.getuniqueclassdef(AssertSite)
|
|
71
|
+
r_site = getinstancerepr(rtyper, classdef)
|
|
72
|
+
for host, ll in r_site.iprebuiltinstances.items():
|
|
73
|
+
if lltype.cast_pointer(lltype.typeOf(ll_site), ll) == ll_site:
|
|
74
|
+
return host
|
|
75
|
+
raise LookupError("no prebuilt AssertSite for %r" % (ll_site,))
|