diff --git a/Lib/test/test_linenumbers.py b/Lib/test/test_linenumbers.py new file mode 100644 index 0000000..b713bdb --- /dev/null +++ b/Lib/test/test_linenumbers.py @@ -0,0 +1,138 @@ +""" +Tests for attributing code to line numbers +""" + +import sys +import traceback +import unittest + + +def boom(*args, **kwargs): + raise RuntimeError() + +def dummy(*args, **kwargs): + pass + + +class TestPEP380Operation(unittest.TestCase): + def expect_line(self, f, expected_relative_line, depth=1): + """Expect an exception in f at the given line offset.""" + try: + f() + except Exception: + frames = traceback.extract_tb(sys.exc_info()[2]) + line = frames[depth][1] + relative_line = line - f.__code__.co_firstlineno + self.assertEqual(relative_line, expected_relative_line) + else: + self.fail("expected an exception") + + def test_call(self): + def f(): + boom( + 1, + x=2, + ) + self.expect_line(f, 1) + + def f(): + boom \ + ( + 1, + x=2, + ) + self.expect_line(f, 1) # want 2 + + def f(): + dummy( + 1, + boom(), + x=3, + ) + self.expect_line(f, 3) + + def test_comprehensions(self): + def f(): + [ + boom() + for x in range(2) + if x + ] + self.expect_line(f, 2, depth=2) + + def f(): + { + x: boom() + for x in range(2) + if x + } + self.expect_line(f, 2, depth=2) + + def f(): + { + boom() + for x in range(2) + if x + } + self.expect_line(f, 2, depth=2) + + def test_operators(self): + class C: + def __neg__(self): + boom() + + def __add__(self, other): + boom() + + def __rsub__(self, other): + boom() + + def __imul__(self, other): + boom() + + def __lt__(self, other): + boom() + + def f(): + x = ( + - + C()) + self.expect_line(f, 3) # want 2 + + def f(): + x = (C() + + + None + ) + self.expect_line(f, 3) # want 2 + + def f(): + x = (None + - + C() + ) + self.expect_line(f, 3) # want 2 + + def f(): + x = C() + x \ + *= ( + C()) + self.expect_line(f, 4) # want 3 + + def f(): + c = C() + (c + is + c + < + C() + ) + self.expect_line(f, 6) # want 5 + + # TODO getitem, getattr + # TODO with, for/else, try/except/else/finally + # TODO classdef bases + +if __name__ == '__main__': + unittest.main() diff --git a/Python/compile.c b/Python/compile.c index 568bdfe..2e0d933 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -196,6 +196,7 @@ static int compiler_with(struct compiler *, stmt_ty, int); static int compiler_async_with(struct compiler *, stmt_ty, int); static int compiler_async_for(struct compiler *, stmt_ty); static int compiler_call_helper(struct compiler *c, Py_ssize_t n, + expr_ty source_loc, asdl_seq *args, asdl_seq *keywords); static int compiler_try_except(struct compiler *, stmt_ty); @@ -842,8 +843,6 @@ static void compiler_set_lineno(struct compiler *c, int off) { basicblock *b; - if (c->u->u_lineno_set) - return; c->u->u_lineno_set = 1; b = c->u->u_curblock; b->b_instr[off].i_lineno = c->u->u_lineno; @@ -1825,7 +1824,7 @@ compiler_class(struct compiler *c, stmt_ty s) ADDOP_O(c, LOAD_CONST, s->v.ClassDef.name, consts); /* 5. generate the rest of the code for the call */ - if (!compiler_call_helper(c, 2, + if (!compiler_call_helper(c, 2, NULL, s->v.ClassDef.bases, s->v.ClassDef.keywords)) return 0; @@ -3180,7 +3179,7 @@ static int compiler_call(struct compiler *c, expr_ty e) { VISIT(c, expr, e->v.Call.func); - return compiler_call_helper(c, 0, + return compiler_call_helper(c, 0, e->v.Call.func, e->v.Call.args, e->v.Call.keywords); } @@ -3264,6 +3263,7 @@ compiler_formatted_value(struct compiler *c, expr_ty e) static int compiler_call_helper(struct compiler *c, Py_ssize_t n, /* Args already pushed */ + expr_ty source_loc, asdl_seq *args, asdl_seq *keywords) { @@ -3361,6 +3361,11 @@ compiler_call_helper(struct compiler *c, assert(nkw < 1<<24); n |= nkw << 8; + if (source_loc) { + c->u->u_lineno = source_loc->lineno; + c->u->u_col_offset = source_loc->col_offset; + } + switch (code) { case 0: ADDOP_I(c, CALL_FUNCTION, n); @@ -3846,10 +3851,8 @@ compiler_visit_expr(struct compiler *c, expr_ty e) /* If expr e has a different line number than the last expr/stmt, set a new line number for the next instruction. */ - if (e->lineno > c->u->u_lineno) { - c->u->u_lineno = e->lineno; - c->u->u_lineno_set = 0; - } + c->u->u_lineno = e->lineno; + c->u->u_lineno_set = 0; /* Updating the column offset is always harmless. */ c->u->u_col_offset = e->col_offset; switch (e->kind) {