[pypy-commit] pypy py3.7: implement https://bugs.python.org/issue30579
cfbolz
pypy.commits at gmail.com
Wed Jan 22 08:15:02 EST 2020
Author: Carl Friedrich Bolz-Tereick <cfbolz at gmx.de>
Branch: py3.7
Changeset: r98568:10615e618c98
Date: 2020-01-22 13:56 +0100
http://bitbucket.org/pypy/pypy/changeset/10615e618c98/
Log: implement https://bugs.python.org/issue30579
Allow the instantiation and modification of tracebacks, which some
frameworks (examples in the issue are trio and jinja2) would really
like to do
diff --git a/pypy/interpreter/pytraceback.py b/pypy/interpreter/pytraceback.py
--- a/pypy/interpreter/pytraceback.py
+++ b/pypy/interpreter/pytraceback.py
@@ -1,5 +1,14 @@
+import sys
+
+from pypy.interpreter.gateway import unwrap_spec
from pypy.interpreter import baseobjspace
-from pypy.interpreter.error import OperationError
+from pypy.interpreter.error import oefmt, OperationError
+
+# for some strange reason CPython allows the setting of the lineno to be
+# negative. I am not sure why that is useful, but let's use the most negative
+# value as a sentinel to denote the default behaviour "please take the lineno
+# from the frame and lasti"
+LINENO_NOT_COMPUTED = -sys.maxint-1
def offset2lineno(c, stopat):
# even position in lnotab denote byte increments, odd line increments.
@@ -30,18 +39,53 @@
* 'tb_next'
"""
- def __init__(self, space, frame, lasti, next):
+ def __init__(self, space, frame, lasti, next, lineno=LINENO_NOT_COMPUTED):
self.space = space
self.frame = frame
self.lasti = lasti
self.next = next
+ self.lineno = lineno
def get_lineno(self):
- return offset2lineno(self.frame.pycode, self.lasti)
+ if self.lineno == LINENO_NOT_COMPUTED:
+ self.lineno = offset2lineno(self.frame.pycode, self.lasti)
+ return self.lineno
- def descr_tb_lineno(self, space):
+ def descr_get_tb_lineno(self, space):
return space.newint(self.get_lineno())
+ def descr_set_tb_lineno(self, space, w_lineno):
+ self.lineno = space.int_w(w_lineno)
+
+ def descr_get_tb_lasti(self, space):
+ return space.newint(self.lasti)
+
+ def descr_set_tb_lasti(self, space, w_lasti):
+ self.lasti = space.int_w(w_lasti)
+
+ def descr_get_next(self, space):
+ return self.next
+
+ def descr_set_next(self, space, w_next):
+ newnext = space.interp_w(PyTraceback, w_next, can_be_None=True)
+ # check for loops
+ curr = newnext
+ while curr is not None:
+ if curr is self:
+ raise oefmt(space.w_ValueError, 'traceback loop detected')
+ curr = curr.next
+ self.next = newnext
+
+ @staticmethod
+ @unwrap_spec(lasti=int, lineno=int)
+ def descr_new(space, w_subtype, w_next, w_frame, lasti, lineno):
+ from pypy.interpreter.pyframe import PyFrame
+ w_next = space.interp_w(PyTraceback, w_next, can_be_None=True)
+ w_frame = space.interp_w(PyFrame, w_frame)
+ traceback = space.allocate_instance(PyTraceback, w_subtype)
+ PyTraceback.__init__(traceback, space, w_frame, lasti, w_next, lineno)
+ return traceback
+
def descr__reduce__(self, space):
from pypy.interpreter.mixedmodule import MixedModule
w_mod = space.getbuiltinmodule('_pickle_support')
@@ -53,17 +97,19 @@
self.frame,
space.newint(self.lasti),
self.next,
+ space.newint(self.lineno)
]
nt = space.newtuple
return nt([new_inst, nt(tup_base), nt(tup_state)])
def descr__setstate__(self, space, w_args):
from pypy.interpreter.pyframe import PyFrame
- args_w = space.unpackiterable(w_args)
- w_frame, w_lasti, w_next = args_w
+ args_w = space.unpackiterable(w_args, 4)
+ w_frame, w_lasti, w_next, w_lineno = args_w
self.frame = space.interp_w(PyFrame, w_frame)
self.lasti = space.int_w(w_lasti)
self.next = space.interp_w(PyTraceback, w_next, can_be_None=True)
+ self.lineno = space.int_w(w_lineno)
def descr__dir__(self, space):
return space.newlist([space.newtext(n) for n in
diff --git a/pypy/interpreter/test/apptest_traceback.py b/pypy/interpreter/test/apptest_traceback.py
new file mode 100644
--- /dev/null
+++ b/pypy/interpreter/test/apptest_traceback.py
@@ -0,0 +1,65 @@
+import sys
+from pytest import raises
+from types import TracebackType
+
+def ve():
+ raise ValueError
+
+def get_tb():
+ try:
+ ve()
+ except ValueError as e:
+ return e.__traceback__
+
+def test_mutation():
+ tb = get_tb()
+
+ # allowed
+ tb.tb_next = None
+ assert tb.tb_next is None
+
+ tb2 = get_tb()
+ tb.tb_next = tb2
+ assert tb.tb_next is tb2
+
+ with raises(TypeError):
+ tb.tb_next = "rabc"
+
+ # loops are forbidden
+ with raises(ValueError):
+ tb2.tb_next = tb
+
+ with raises(ValueError):
+ tb.tb_next = tb
+
+ tb.tb_lasti = 1233
+ assert tb.tb_lasti == 1233
+ with raises(TypeError):
+ tb.tb_lasti = "abc"
+
+ tb.tb_lineno = 1233
+ assert tb.tb_lineno == 1233
+ with raises(TypeError):
+ tb.tb_lineno = "abc"
+
+
+def test_construct():
+ frame = sys._getframe()
+ tb = get_tb()
+ tb2 = TracebackType(tb, frame, 1, 2)
+ assert tb2.tb_next is tb
+ assert tb2.tb_frame is frame
+ assert tb2.tb_lasti == 1
+ assert tb2.tb_lineno == 2
+
+ tb2 = TracebackType(tb, frame, 1, -1)
+ assert tb2.tb_next is tb
+ assert tb2.tb_frame is frame
+ assert tb2.tb_lasti == 1
+ assert tb2.tb_lineno == -1
+
+def test_can_subclass():
+ with raises(TypeError):
+ class TB(TracebackType):
+ pass
+
diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py
--- a/pypy/interpreter/typedef.py
+++ b/pypy/interpreter/typedef.py
@@ -813,14 +813,15 @@
PyTraceback.typedef = TypeDef("traceback",
__reduce__ = interp2app(PyTraceback.descr__reduce__),
+ __new__ = interp2app(PyTraceback.descr_new),
__setstate__ = interp2app(PyTraceback.descr__setstate__),
__dir__ = interp2app(PyTraceback.descr__dir__),
tb_frame = interp_attrproperty_w('frame', cls=PyTraceback),
- tb_lasti = interp_attrproperty('lasti', cls=PyTraceback, wrapfn="newint"),
- tb_lineno = GetSetProperty(PyTraceback.descr_tb_lineno),
- tb_next = interp_attrproperty_w('next', cls=PyTraceback),
+ tb_lasti = GetSetProperty(PyTraceback.descr_get_tb_lasti, PyTraceback.descr_set_tb_lasti),
+ tb_lineno = GetSetProperty(PyTraceback.descr_get_tb_lineno, PyTraceback.descr_set_tb_lineno),
+ tb_next = GetSetProperty(PyTraceback.descr_get_next, PyTraceback.descr_set_next),
)
-assert not PyTraceback.typedef.acceptable_as_base_class # no __new__
+PyTraceback.typedef.acceptable_as_base_class = False
GeneratorIterator.typedef = TypeDef("generator",
__repr__ = interp2app(GeneratorIterator.descr__repr__),
More information about the pypy-commit
mailing list