Skip to content

Commit 95b6acf

Browse files
bpo-34876: Change the lineno of the AST for decorated function and class. (GH-9731)
It was overridden by the lineno of the first decorator. Now it is the lineno of 'def' or 'class'.
1 parent b83d917 commit 95b6acf

File tree

7 files changed

+2560
-2491
lines changed

7 files changed

+2560
-2491
lines changed

Lib/test/test_ast.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ def to_tuple(t):
124124
"{*{1, 2}, 3}",
125125
# Asynchronous comprehensions
126126
"async def f():\n [i async for b in c]",
127+
# Decorated FunctionDef
128+
"@deco1\n@deco2()\ndef f(): pass",
129+
# Decorated AsyncFunctionDef
130+
"@deco1\n@deco2()\nasync def f(): pass",
131+
# Decorated ClassDef
132+
"@deco1\n@deco2()\nclass C: pass",
127133
]
128134

129135
# These are compiled through "single"
@@ -203,13 +209,16 @@ def _assertTrueorder(self, ast_node, parent_pos):
203209
return
204210
if isinstance(ast_node, (ast.expr, ast.stmt, ast.excepthandler)):
205211
node_pos = (ast_node.lineno, ast_node.col_offset)
206-
self.assertTrue(node_pos >= parent_pos)
212+
self.assertGreaterEqual(node_pos, parent_pos)
207213
parent_pos = (ast_node.lineno, ast_node.col_offset)
208214
for name in ast_node._fields:
209215
value = getattr(ast_node, name)
210216
if isinstance(value, list):
217+
first_pos = parent_pos
218+
if value and name == 'decorator_list':
219+
first_pos = (value[0].lineno, value[0].col_offset)
211220
for child in value:
212-
self._assertTrueorder(child, parent_pos)
221+
self._assertTrueorder(child, first_pos)
213222
elif value is not None:
214223
self._assertTrueorder(value, parent_pos)
215224

@@ -1289,6 +1298,9 @@ def main():
12891298
('Module', [('Expr', (1, 0), ('Dict', (1, 0), [None, ('Constant', (1, 10), 2)], [('Dict', (1, 3), [('Constant', (1, 4), 1)], [('Constant', (1, 6), 2)]), ('Constant', (1, 12), 3)]))]),
12901299
('Module', [('Expr', (1, 0), ('Set', (1, 0), [('Starred', (1, 1), ('Set', (1, 2), [('Constant', (1, 3), 1), ('Constant', (1, 6), 2)]), ('Load',)), ('Constant', (1, 10), 3)]))]),
12911300
('Module', [('AsyncFunctionDef', (1, 0), 'f', ('arguments', [], None, [], [], None, []), [('Expr', (2, 1), ('ListComp', (2, 2), ('Name', (2, 2), 'i', ('Load',)), [('comprehension', ('Name', (2, 14), 'b', ('Store',)), ('Name', (2, 19), 'c', ('Load',)), [], 1)]))], [], None)]),
1301+
('Module', [('FunctionDef', (3, 0), 'f', ('arguments', [], None, [], [], None, []), [('Pass', (3, 9))], [('Name', (1, 1), 'deco1', ('Load',)), ('Call', (2, 0), ('Name', (2, 1), 'deco2', ('Load',)), [], [])], None)]),
1302+
('Module', [('AsyncFunctionDef', (3, 0), 'f', ('arguments', [], None, [], [], None, []), [('Pass', (3, 15))], [('Name', (1, 1), 'deco1', ('Load',)), ('Call', (2, 0), ('Name', (2, 1), 'deco2', ('Load',)), [], [])], None)]),
1303+
('Module', [('ClassDef', (3, 0), 'C', [], [], [('Pass', (3, 9))], [('Name', (1, 1), 'deco1', ('Load',)), ('Call', (2, 0), ('Name', (2, 1), 'deco2', ('Load',)), [], [])])]),
12921304
]
12931305
single_results = [
12941306
('Interactive', [('Expr', (1, 0), ('BinOp', (1, 0), ('Constant', (1, 0), 1), ('Add',), ('Constant', (1, 2), 2)))]),

Lib/test/test_trace.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,19 @@ def traced_caller_list_comprehension():
7575
mylist = [traced_doubler(i) for i in range(k)]
7676
return mylist
7777

78+
def traced_decorated_function():
79+
def decorator1(f):
80+
return f
81+
def decorator_fabric():
82+
def decorator2(f):
83+
return f
84+
return decorator2
85+
@decorator1
86+
@decorator_fabric()
87+
def func():
88+
pass
89+
func()
90+
7891

7992
class TracedClass(object):
8093
def __init__(self, x):
@@ -172,6 +185,24 @@ def test_trace_list_comprehension(self):
172185
}
173186
self.assertEqual(self.tracer.results().counts, expected)
174187

188+
def test_traced_decorated_function(self):
189+
self.tracer.runfunc(traced_decorated_function)
190+
191+
firstlineno = get_firstlineno(traced_decorated_function)
192+
expected = {
193+
(self.my_py_filename, firstlineno + 1): 1,
194+
(self.my_py_filename, firstlineno + 2): 1,
195+
(self.my_py_filename, firstlineno + 3): 1,
196+
(self.my_py_filename, firstlineno + 4): 1,
197+
(self.my_py_filename, firstlineno + 5): 1,
198+
(self.my_py_filename, firstlineno + 6): 1,
199+
(self.my_py_filename, firstlineno + 7): 1,
200+
(self.my_py_filename, firstlineno + 8): 1,
201+
(self.my_py_filename, firstlineno + 9): 1,
202+
(self.my_py_filename, firstlineno + 10): 1,
203+
(self.my_py_filename, firstlineno + 11): 1,
204+
}
205+
self.assertEqual(self.tracer.results().counts, expected)
175206

176207
def test_linear_methods(self):
177208
# XXX todo: later add 'static_method_linear' and 'class_method_linear'
@@ -189,6 +220,7 @@ def test_linear_methods(self):
189220
}
190221
self.assertEqual(tracer.results().counts, expected)
191222

223+
192224
class TestRunExecCounts(unittest.TestCase):
193225
"""A simple sanity test of line-counting, via runctx (exec)"""
194226
def setUp(self):
@@ -263,6 +295,18 @@ def test_inst_method_calling(self):
263295
}
264296
self.assertEqual(self.tracer.results().calledfuncs, expected)
265297

298+
def test_traced_decorated_function(self):
299+
self.tracer.runfunc(traced_decorated_function)
300+
301+
expected = {
302+
self.filemod + ('traced_decorated_function',): 1,
303+
self.filemod + ('decorator_fabric',): 1,
304+
self.filemod + ('decorator2',): 1,
305+
self.filemod + ('decorator1',): 1,
306+
self.filemod + ('func',): 1,
307+
}
308+
self.assertEqual(self.tracer.results().calledfuncs, expected)
309+
266310

267311
class TestCallers(unittest.TestCase):
268312
"""White-box testing of callers tracing"""
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
The *lineno* and *col_offset* attributes of the AST for decorated function
2+
and class refer now to the position of the corresponding ``def``, ``async
3+
def`` and ``class`` instead of the position of the first decorator. This
4+
leads to more correct line reporting in tracing. This is the only case when
5+
the position of child AST nodes can preceed the position of the parent AST
6+
node.

Python/ast.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,12 +1659,6 @@ ast_for_decorated(struct compiling *c, const node *n)
16591659
} else if (TYPE(CHILD(n, 1)) == async_funcdef) {
16601660
thing = ast_for_async_funcdef(c, CHILD(n, 1), decorator_seq);
16611661
}
1662-
/* we count the decorators in when talking about the class' or
1663-
* function's line number */
1664-
if (thing) {
1665-
thing->lineno = LINENO(n);
1666-
thing->col_offset = n->n_col_offset;
1667-
}
16681662
return thing;
16691663
}
16701664

Python/compile.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,6 +1950,7 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async)
19501950
Py_ssize_t i, funcflags;
19511951
int annotations;
19521952
int scope_type;
1953+
int firstlineno;
19531954

19541955
if (is_async) {
19551956
assert(s->kind == AsyncFunctionDef_kind);
@@ -1976,6 +1977,11 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async)
19761977
if (!compiler_decorators(c, decos))
19771978
return 0;
19781979

1980+
firstlineno = s->lineno;
1981+
if (asdl_seq_LEN(decos)) {
1982+
firstlineno = ((expr_ty)asdl_seq_GET(decos, 0))->lineno;
1983+
}
1984+
19791985
funcflags = compiler_default_arguments(c, args);
19801986
if (funcflags == -1) {
19811987
return 0;
@@ -1989,7 +1995,7 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async)
19891995
funcflags |= 0x04;
19901996
}
19911997

1992-
if (!compiler_enter_scope(c, name, scope_type, (void *)s, s->lineno)) {
1998+
if (!compiler_enter_scope(c, name, scope_type, (void *)s, firstlineno)) {
19931999
return 0;
19942000
}
19952001

@@ -2032,12 +2038,17 @@ compiler_class(struct compiler *c, stmt_ty s)
20322038
{
20332039
PyCodeObject *co;
20342040
PyObject *str;
2035-
int i;
2041+
int i, firstlineno;
20362042
asdl_seq* decos = s->v.ClassDef.decorator_list;
20372043

20382044
if (!compiler_decorators(c, decos))
20392045
return 0;
20402046

2047+
firstlineno = s->lineno;
2048+
if (asdl_seq_LEN(decos)) {
2049+
firstlineno = ((expr_ty)asdl_seq_GET(decos, 0))->lineno;
2050+
}
2051+
20412052
/* ultimately generate code for:
20422053
<name> = __build_class__(<func>, <name>, *<bases>, **<keywords>)
20432054
where:
@@ -2052,7 +2063,7 @@ compiler_class(struct compiler *c, stmt_ty s)
20522063

20532064
/* 1. compile the class body into a code object */
20542065
if (!compiler_enter_scope(c, s->v.ClassDef.name,
2055-
COMPILER_SCOPE_CLASS, (void *)s, s->lineno))
2066+
COMPILER_SCOPE_CLASS, (void *)s, firstlineno))
20562067
return 0;
20572068
/* this block represents what we do in the new scope */
20582069
{

0 commit comments

Comments
 (0)