-
-
Notifications
You must be signed in to change notification settings - Fork 32k
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few comments. Also watch out, this PR will get merge conflicts pretty often.
Python/ast.c
Outdated
thing->col_offset = n->n_col_offset; | ||
return ast_for_async_funcdef(c, CHILD(n, 1), decorator_seq); | ||
} else { | ||
return NULL; // should never ever happen |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe then assert 0
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Py_UNREACHABLE()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still not fixed.
Python/compile.c
Outdated
// fixup decorators to be the first line number internally. | ||
if (asdl_seq_LEN(decos) > 0) { | ||
expr_ty first_decorator = asdl_seq_GET(decos, 0); | ||
c->u->u_firstlineno = first_decorator->lineno; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this is needed? It would be a bit awkward if we externally show everywhere line of def
as a first line while internally always use the decorator line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, I think it may be OK if you give a good comment with an explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to maintain the first line number of the code object to be the line number of the first decorator so that inspect.getsource
will work correctly. The other alternative would be to change this, so that code objects point to both the line number of the def and the first source line number, but I doubt that would be useful, or worth any additional overhead that would cause. I will add a more detailed comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about classes? Does they need correcting the first line number?
Please add test for AST line numbers.
Python/ast.c
Outdated
thing->col_offset = n->n_col_offset; | ||
return ast_for_async_funcdef(c, CHILD(n, 1), decorator_seq); | ||
} else { | ||
return NULL; // should never ever happen |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Py_UNREACHABLE()
Lib/test/test_dis.py
Outdated
@@ -65,7 +65,7 @@ def cm(cls, x): | |||
8 STORE_ATTR 0 (x) | |||
10 LOAD_CONST 0 (None) | |||
12 RETURN_VALUE | |||
""" % (_C.cm.__code__.co_firstlineno + 2,) | |||
""" % (_C.cm.__code__.co_firstlineno + 1,) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this is changed? I thought this PR shouldn't change __code__.co_firstlineno
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for pointing this out. I realized that my changes weren't actually updating the first line number in the compiler! I've fixed that.
Yes, I just added the same change that happens in
I am planning on doing so. I want things to pass existing tests in CI first however. |
Python/ast.c
Outdated
thing->col_offset = n->n_col_offset; | ||
return ast_for_async_funcdef(c, CHILD(n, 1), decorator_seq); | ||
} else { | ||
return NULL; // should never ever happen |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still not fixed.
Okay, looks like test_dis and test_inspect passed, so I will get to writing new tests for decorated nodes in test_ast. Done! Ready for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is almost ready.
Python/compile.c
Outdated
@@ -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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Python/compile.c
Outdated
@@ -1988,6 +2008,10 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async) | |||
if (!compiler_decorators(c, decos)) | |||
return 0; | |||
|
|||
|
|||
int first_lineno = corrected_firstlineno(c, s, decos); |
There was a problem hiding this comment.
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.
Python/compile.c
Outdated
@@ -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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong indentation.
Python/compile.c
Outdated
|
||
int first_lineno = corrected_firstlineno(c, s, decos); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too many empty lines
Python/compile.c
Outdated
@@ -1988,6 +2008,10 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async) | |||
if (!compiler_decorators(c, decos)) | |||
return 0; | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too many empty lines.
Python/compile.c
Outdated
else { | ||
return s->lineno; | ||
} | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be worth to add a note about this change in the "Whats New" document.
@@ -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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Lib/test/test_ast.py
Outdated
# Decorated FunctionDef | ||
textwrap.dedent(""" | ||
@deco | ||
def f():... |
There was a problem hiding this comment.
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
?
Lib/test/test_ast.py
Outdated
@@ -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. |
There was a problem hiding this comment.
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?
@@ -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. |
There was a problem hiding this comment.
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.
Python/ast.c
Outdated
@@ -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) { |
There was a problem hiding this comment.
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 else
s.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even with Py_UNREACHABLE()
?
There was a problem hiding this comment.
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();
There was a problem hiding this comment.
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 else
s too.
Python/compile.c
Outdated
static int corrected_firstlineno(struct compiler *c, stmt_ty s, | ||
asdl_seq * decos) | ||
{ | ||
/* To keep the ability to get the relevant source of a decorated item |
There was a problem hiding this comment.
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.
Python/compile.c
Outdated
asdl_seq * decos) | ||
{ | ||
/* To keep the ability to get the relevant source of a decorated item | ||
using inspect.getsource, we need to keep the first line number |
There was a problem hiding this comment.
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 "/*".
Python/compile.c
Outdated
This used to be done via the AST, but it is better to hide this | ||
internally. | ||
*/ | ||
if (asdl_seq_LEN(decos) > 0) { |
There was a problem hiding this comment.
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.
Python/compile.c
Outdated
*/ | ||
if (asdl_seq_LEN(decos) > 0) { | ||
expr_ty first_decorator = asdl_seq_GET(decos, 0); | ||
c->u->u_firstlineno = first_decorator->lineno; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generated AST is correct, the generated code is wrong. Look at f.__code__.co_firstlineno
in
def f():
@deco
def g(): pass
and
def f():
@deco
class A: pass
Python/ast.c
Outdated
@@ -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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even with Py_UNREACHABLE()
?
Thank you for catching this. I've added tests to both test_ast and test_inspect to make sure the AST and inspect.getsource don't regress. Also, I believe I have responded to all of your feedback. Thank you! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why there are changes in Python/importlib.h
and Python/importlib_external.h
? I thought the purpose of this PR is changing line numbers in AST, but keeping the bytecode unchanged.
@@ -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 |
There was a problem hiding this comment.
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.
Python/compile.c
Outdated
// modify first line number for decorated nodes | ||
if (asdl_seq_LEN(decos)) { | ||
expr_ty first_decorator = asdl_seq_GET(decos, 0); | ||
c->u->u_firstlineno = first_decorator->lineno; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is not it set in compiler_enter_scope()
?
Python/compile.c
Outdated
// modify first line number for decorated nodes | ||
if (asdl_seq_LEN(decos)) { | ||
expr_ty first_decorator = asdl_seq_GET(decos, 0); | ||
c->u->u_firstlineno = first_decorator->lineno; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is not it set in compiler_enter_scope()
?
Yes, that is concerning. I think I will take a closer look at what is going on. W.R.T your comments on So based on these I think I need to figure out what is actually going on before I proceed, I still have much to learn about the compiler's intricacies. |
Um, it seems you still need to act on the comment from 5 days ago? |
Ah, yes I haven't pushed anything because I haven't found out why the generated bytecode is different yet. I will once I figure things out more. |
Sorry this is temorarily stalled until I can get my computer working again... |
@serhiy-storchaka What do you think? Is it ready to be merged? I don't suppose this can make it into 3.7, since IIUC the breakage is pretty noticeable. |
If I understand it correctly, the bytecode shouldn't be affected by this PR. But the frozen bytecode is changed, and I don't understand the cause. Either there is a bug in the code generation introduced by this PR, or my understanding is wrong. I like this PR in general and hope Ethan and me will found the cause. |
Yes, this is correct. I am going to try to spend more time today trying to understand why it changes the bytecode. |
@ethanhs |
@ilevkivskyi Serhiy asked me to see if there was anything useful in this PR that could be pulled out in the original issue https://bugs.python.org/issue33211#msg330181 (He accidentally redid this work on his own). I don't see anything that would be beneficial that is in this PR and not in #9731, so I will close this. |
This change moves the patching of decorated defs from the publicly used AST to the private bytecode.
This has the advantage that decorated defs in the ast keep their line numbers correctly, but
inspect.getsource
should still work.https://bugs.python.org/issue33211