Skip to content

bpo-42246: Partial implementation of PEP 626. #23113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Nov 12, 2020
28 changes: 21 additions & 7 deletions Include/cpython/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct PyCodeObject {
Py_ssize_t *co_cell2arg; /* Maps cell vars which are arguments. */
PyObject *co_filename; /* unicode (where it was loaded from) */
PyObject *co_name; /* unicode (name, for reference) */
PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) See
PyObject *co_linetable; /* string (encoding addr<->lineno mapping) See
Objects/lnotab_notes.txt for details. */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
PyObject *co_weakreflist; /* to support weakrefs to code objects */
Expand Down Expand Up @@ -135,16 +135,18 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno);
PyAPI_FUNC(int) PyCode_Addr2Line(PyCodeObject *, int);

/* for internal use only */
typedef struct _addr_pair {
int ap_lower;
int ap_upper;
} PyAddrPair;
typedef struct _line_offsets {
int ar_start;
int ar_end;
int ar_line;
int ar_computed_line;
char *lo_next;
} PyCodeAddressRange;

/* Update *bounds to describe the first and one-past-the-last instructions in the
same line as lasti. Return the number of that line.
*/
PyAPI_FUNC(int) _PyCode_CheckLineNumber(PyCodeObject* co,
int lasti, PyAddrPair *bounds);
PyAPI_FUNC(int) _PyCode_CheckLineNumber(int lasti, PyCodeAddressRange *bounds);

/* Create a comparable key used to compare constants taking in account the
* object type. It is used to make sure types are not coerced (e.g., float and
Expand All @@ -163,3 +165,15 @@ PyAPI_FUNC(int) _PyCode_GetExtra(PyObject *code, Py_ssize_t index,
void **extra);
PyAPI_FUNC(int) _PyCode_SetExtra(PyObject *code, Py_ssize_t index,
void *extra);

/** API for initializing the line number table. */
int _PyCode_InitAddressRange(PyCodeObject* co, PyCodeAddressRange *bounds);

/** Out of process API for initializing the line number table. */
void PyLineTable_InitAddressRange(char *linetable, int firstlineno, PyCodeAddressRange *range);

/** API for traversing the line number table. */
int PyLineTable_NextAddressRange(PyCodeAddressRange *range);
int PyLineTable_PreviousAddressRange(PyCodeAddressRange *range);


33 changes: 8 additions & 25 deletions Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,32 +449,15 @@ def findlabels(code):
def findlinestarts(code):
"""Find the offsets in a byte code which are start of lines in the source.

Generate pairs (offset, lineno) as described in Python/compile.c.

Generate pairs (offset, lineno)
"""
byte_increments = code.co_lnotab[0::2]
line_increments = code.co_lnotab[1::2]
bytecode_len = len(code.co_code)

lastlineno = None
lineno = code.co_firstlineno
addr = 0
for byte_incr, line_incr in zip(byte_increments, line_increments):
if byte_incr:
if lineno != lastlineno:
yield (addr, lineno)
lastlineno = lineno
addr += byte_incr
if addr >= bytecode_len:
# The rest of the lnotab byte offsets are past the end of
# the bytecode, so the lines were optimized away.
return
if line_incr >= 0x80:
# line_increments is an array of 8-bit signed integers
line_incr -= 0x100
lineno += line_incr
if lineno != lastlineno:
yield (addr, lineno)
lastline = None
for start, end, line in code.co_lines():
if line is not None and line != lastline:
lastline = line
yield start, line
return


class Bytecode:
"""The bytecode operations of a piece of code
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def func2():
("co_cellvars", ("cellvar",)),
("co_filename", "newfilename"),
("co_name", "newname"),
("co_lnotab", code2.co_lnotab),
("co_linetable", code2.co_linetable),
):
with self.subTest(attr=attr, value=value):
new_code = code.replace(**{attr: value})
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ def test_indentation(self):
def test_leading_newlines(self):
s256 = "".join(["\n"] * 256 + ["spam"])
co = compile(s256, 'fn', 'exec')
self.assertEqual(co.co_firstlineno, 257)
self.assertEqual(co.co_lnotab, bytes())
self.assertEqual(co.co_firstlineno, 1)
self.assertEqual(list(co.co_lines()), [(0, 4, 257), (4, 8, None)])

def test_literals_with_leading_zeroes(self):
for arg in ["077787", "0xj", "0x.", "0e", "090000000000000",
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_setup_annotations_line(self):
with open(ann_module.__file__) as f:
txt = f.read()
co = compile(txt, ann_module.__file__, 'exec')
self.assertEqual(co.co_firstlineno, 3)
self.assertEqual(co.co_firstlineno, 1)
except OSError:
pass

Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1645,9 +1645,10 @@ def test_errors_in_command(self):
'debug doesnotexist',
'c',
])
stdout, _ = self.run_pdb_script('', commands + '\n')
stdout, _ = self.run_pdb_script('pass', commands + '\n')

self.assertEqual(stdout.splitlines()[1:], [
'-> pass',
'(Pdb) *** SyntaxError: unexpected EOF while parsing',

'(Pdb) ENTERING RECURSIVE DEBUGGER',
Expand Down
18 changes: 14 additions & 4 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,7 @@ def ireturn_example():
(2, 'line'),
(3, 'line'),
(4, 'line'),
(6, 'line'),
(6, 'return')]
(4, 'return')]

# Tight loop with while(1) example (SF #765624)
def tightloop_example():
Expand Down Expand Up @@ -602,6 +601,17 @@ def run(tracer):
self.compare_events(doit_async.__code__.co_firstlineno,
tracer.events, events)

def test_21_repeated_pass(self):
def func():
pass
pass

self.run_and_compare(func,
[(0, 'call'),
(1, 'line'),
(2, 'line'),
(2, 'return')])

def test_loop_in_try_except(self):
# https://bugs.python.org/issue41670

Expand Down Expand Up @@ -766,7 +776,7 @@ def trace(self, frame, event, arg):
if (self.firstLine is None and frame.f_code == self.code and
event == 'line'):
self.firstLine = frame.f_lineno - 1
if (event == self.event and self.firstLine and
if (event == self.event and self.firstLine is not None and
frame.f_lineno == self.firstLine + self.jumpFrom):
f = frame
while f is not None and f.f_code != self.code:
Expand Down Expand Up @@ -1540,7 +1550,7 @@ def test_jump_to_firstlineno(self):
""", "<fake module>", "exec")
class fake_function:
__code__ = code
tracer = JumpTracer(fake_function, 2, 0)
tracer = JumpTracer(fake_function, 4, 1)
sys.settrace(tracer.trace)
namespace = {"output": []}
exec(code, namespace)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improved accuracy of line tracing events and f_lineno attribute of Frame
objects. See PEP 626 for details.
26 changes: 13 additions & 13 deletions Objects/clinic/codeobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading