8000 bpo-33211: Change line number of decorated nodes back to def by emmatyping · Pull Request #6460 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-33211: Change line number of decorated nodes back to def #6460

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

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8000
33 changes: 32 additions & 1 deletion Lib/test/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import dis
import os
import sys
import textwrap
import unittest
import weakref

Expand Down Expand Up @@ -43,12 +44,36 @@ def to_tuple(t):
"def f(**kwargs): pass",
# FunctionDef with all kind of args and docstring
"def f(a, b=1, c=None, d=[], e={}, *args, f=42, **kwargs): 'doc for f()'",
# Decorated FunctionDef
textwrap.dedent("""
@deco
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you use textwrap.dedent() it would clearer if add an indentation of the inner code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please increase the indentation of these fragments.

def f():...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: add a space after :. And maybe use pass?

"""),
# Multiple decorators on a FunctionDef
textwrap.dedent("""
@deco1
@deco2()
def f():...
"""),
# ClassDef
"class C:pass",
# ClassDef with docstring
"class C: 'docstring for class C'",
# ClassDef, new style class
"class C(object): pass",
# Decorated ClassDef
textwrap.dedent("""
@foo
class A:
pass
"""),
# Multiple decorators on a ClassDef
textwrap.dedent("""
@foo1
@bar(4)
class A:
pass
"""),
# Return
"def f():return 1",
# Delete
Expand Down Expand Up @@ -207,7 +232,9 @@ def _assertTrueorder(self, ast_node, parent_pos):
parent_pos = (ast_node.lineno, ast_node.col_offset)
for name in ast_node._fields:
value = getattr(ast_node, name)
if isinstance(value, list):
# Since decorators are stored as an attribute of a FunctionDef
# they don't follow the true order of the syntax.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a test that all decorators are before a FunctionDef?

if isinstance(value, list) and name != 'decorator_list':
for child in value:
self._assertTrueorder(child, parent_pos)
elif value is not None:
Expand Down Expand Up @@ -1133,9 +1160,13 @@ def main():
('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], ('arg', (1, 7), 'args', None), [], [], None, []), [('Pass', (1, 14))], [], None, None)], None),
8000 ('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], None, [], [], ('arg', (1, 8), 'kwargs', None), []), [('Pass', (1, 17))], [], None, None)], None),
('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [('arg', (1, 6), 'a', None), ('arg', (1, 9), 'b', None), ('arg', (1, 14), 'c', None), ('arg', (1, 22), 'd', None), ('arg', (1, 28), 'e', None)], ('arg', (1, 35), 'args', None), [('arg', (1, 41), 'f', None)], [('Num', (1, 43), 42)], ('arg', (1, 49), 'kwargs', None), [('Num', (1, 11), 1), ('NameConstant', (1, 16), None), ('List', (1, 24), [], ('Load',)), ('Dict', (1, 30), [], [])]), [], [], None, 'doc for f()')], None),
('Module', [('FunctionDef', (3, 0), 'f', ('arguments', [], None, [], [], None, []), [('Expr', (3, 8), ('Ellipsis', (3, 8)))], [('Name', (2, 1), 'deco', ('Load',))], None, None)], None),
('Module', [('FunctionDef', (4, 0), 'f', ('arguments', [], None, [], [], None, []), [('Expr', (4, 8), ('Ellipsis', (4, 8)))], [('Name', (2, 1), 'deco1', ('Load',)), ('Call', (3, 0), ('Name', (3, 1), 'deco2', ('Load',)), [], [])], None, None)], None),
('Module', [('ClassDef', (1, 0), 'C', [], [], [('Pass', (1, 8))], [], None)], None),
('Module', [('ClassDef', (1, 0), 'C', [], [], [], [], 'docstring for class C')], None),
('Module', [('ClassDef', (1, 0), 'C', [('Name', (1, 8), 'object', ('Load',))], [], [('Pass', (1, 17))], [], None)], None),
('Module', [('ClassDef', (3, 0), 'A', [], [], [('Pass', (4, 4))], [('Name', (2, 1), 'foo', ('Load',))], None)], None),
('Module', [('ClassDef', (4, 0), 'A', [], [], [('Pass', (5, 4))], [('Name', (2, 1), 'foo1', ('Load',)), ('Call', (3, 1), ('Name', (3, 1), 'bar', ('Load',)), [('Num', (3, 5), 4)], [])], None)], None),
('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], None, [], [], None, []), [('Return', (1, 8), ('Num', (1, 15), 1))], [], None, None)], None),
('Module', [('Delete', (1, 0), [('Name', (1, 4), 'v', ('Del',))])], None),
('Module', [('Assign', (1, 0), [('Name', (1, 0), 'v', ('Store',))], ('Num', (1, 4), 1))], None),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Change the line number of decorated defs back to the def or class token in the
AST, not the first decorator. Have the change of line number happen in the
compiler.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add "Patch by *yourname"." And add your name in Misc/ACKS.

16 changes: 5 additions & 11 deletions Python/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1672,7 +1672,6 @@ static stmt_ty
ast_for_decorated(struct compiling *c, const node *n)
{
/* decorated: decorators (classdef | funcdef | async_funcdef) */
stmt_ty thing = NULL;
asdl_seq *decorator_seq = NULL;

REQ(n, decorated);
Expand All @@ -1686,19 +1685,14 @@ ast_for_decorated(struct compiling *c, const node *n)
TYPE(CHILD(n, 1)) == classdef);

if (TYPE(CHILD(n, 1)) == funcdef) {
thing = ast_for_funcdef(c, CHILD(n, 1), decorator_seq);
return ast_for_funcdef(c, CHILD(n, 1), decorator_seq);
} else if (TYPE(CHILD(n, 1)) == classdef) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually now you can remove all elses.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd think so, sadly MSVC warns that not all code paths return if there isn't an else.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even with Py_UNREACHABLE()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, with the Py_UNREACHABLE() it is happy. Do you want something like this?

    if (TYPE(CHILD(n, 1)) == funcdef) {
        return ast_for_funcdef(c, CHILD(n, 1), decorator_seq);
    } else if (TYPE(CHILD(n, 1)) == classdef) {
        return ast_for_classdef(c, CHILD(n, 1), decorator_seq);
    } else if (TYPE(CHILD(n, 1)) == async_funcdef) {
        return ast_for_async_funcdef(c, CHILD(n, 1), decorator_seq);
    }
    Py_UNREACHABLE();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to remove other elses too.

thing = ast_for_classdef(c, CHILD(n, 1), decorator_seq);
return ast_for_classdef(c, CHILD(n, 1), decorator_seq);
} else if (TYPE(CHILD(n, 1)) == async_funcdef) {
thing = ast_for_async_funcdef(c, CHILD(n, 1), decorator_seq);
}
/* we count the decorators in when talking about the class' or
* function's line number */
if (thing) {
thing->lineno = LINENO(n);
thing->col_offset = n->n_col_offset;
return ast_for_async_funcdef(c, CHILD(n, 1), decorator_seq);
} else {
Py_UNREACHABLE();
}
return thing;
}

static expr_ty
Expand Down
30 changes: 28 additions & 2 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,26 @@ compiler_default_arguments(struct compiler *c, arguments_ty args)
return funcflags;
}

static int corrected_firstlineno(struct compiler *c, stmt_ty s,
asdl_seq * decos)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why weird indentation on a separate line?

Copy link
Member Author
@emmatyping emmatyping Apr 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think patchcheck messed up some of this up, sorry for the noise.

On the Py_UNREACHABLE() line, I originally had double tab indentation (I forgot to change Visual Studio's settings), and its seemed to change that to triple groups of 4 spaces, so I'm guessing when I ran patchcheck it caused other problems. I'll manually verify in future.

{
/* To keep the ability to get the relevant source of a decorated item
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Us 4 spaces for indentation.

using inspect.getsource, we need to keep the first line number
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Align wrapped lines to "To keep", not to "/*".

of the code object as the first line number of the first decorator.
This used to be done via the AST, but it is better to hide this
internally.
*/
if (asdl_seq_LEN(decos) > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

> 0 is not needed. Using just asdl_seq_LEN(decos) in the boolean context is common.

expr_ty first_decorator = asdl_seq_GET(decos, 0);
c->u->u_firstlineno = first_decorator->lineno;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems c->u still refers to the outer scope.

Add a regression test for these cases:

def f():
    @deco
    def g(): pass
class A:
    @deco
    def m(self): pass

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems c->u still refers to the outer scope.

I'm probably missing something, but the generated AST is:

('Module',
 [('ClassDef',
   (1, 0),
   'A',
   [],
   [],
   [('FunctionDef',
     (3, 4),
     'm',
     ('arguments', [('arg', (3, 10), 'self', None)], None, [], [], None, []),
     [('Pass', (3, 17))],
     [('Name', (2, 5), 'deco', ('Load',))],
     None,
     None)],
   [],
   None)],
 None)

which seems correct. Perhaps I am misunderstanding?

return first_decorator->lineno;
}
else {
return s->lineno;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This empty line is unnecessary.

}

static int
compiler_function(struct compiler *c, stmt_ty s, int is_async)
{
Expand Down Expand Up @@ -1988,6 +2008,10 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async)
if (!compiler_decorators(c, decos))
return 0;


Copy link
Member
@ilevkivskyi ilevkivskyi Apr 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many empty lines.

int first_lineno = corrected_firstlineno(c, s, decos);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is misleading, shift left.



Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many empty lines

funcflags = compiler_default_arguments(c, args);
if (funcflags == -1) {
return 0;
Expand All @@ -2001,7 +2025,7 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async)
funcflags |= 0x04;
}

if (!compiler_enter_scope(c, name, scope_type, (void *)s, s->lineno)) {
if (!compiler_enter_scope(c, name, scope_type, (void *)s, first_lineno)) {
return 0;
}

Expand Down Expand Up @@ -2050,6 +2074,8 @@ compiler_class(struct compiler *c, stmt_ty s)
if (!compiler_decorators(c, decos))
return 0;

int first_lineno = corrected_firstlineno(c, s, decos);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong indentation.


/* ultimately generate code for:
<name> = __build_class__(<func>, <name>, *<bases>, **<keywords>)
where:
Expand All @@ -2064,7 +2090,7 @@ compiler_class(struct compiler *c, stmt_ty s)

/* 1. compile the class body into a code object */
if (!compiler_enter_scope(c, s->v.ClassDef.name,
COMPILER_SCOPE_CLASS, (void *)s, s->lineno))
COMPILER_SCOPE_CLASS, (void *)s, first_lineno))
return 0;
/* this block represents what we do in the new scope */
{
Expand Down
Loading
0