Search code examples
pythonabstract-syntax-tree

compile() from AST ignores line numbers


How do I specify line numbers in AST? I've tried ast.increment_lineno, but compile() pays no attention to the changed line numbers:

>>> import ast
>>> example_tree=ast.parse("print('Hello, World!')")
>>> ast.increment_lineno(example_tree, 10)
>>> compile(example_tree,"somefile.py","exec")
<code object <module> at 0x7fb84501a4a0, file "somefile.py", line 1>

I am pretty sure that there should be line 11. What's wrong?


Solution

  • Code parsed with mode='exec' (the default) becomes an ast.Module object, which does not have a lineno attribute. [Note 1]

    As the documentation for module ast indicates, only stmt and expr subclasses have token position attributes (lineno, col_offset, end_lineno and end_col_offset). ast.increment_lineno modifies those nodes, but the Module node has no location attributes so it is always considered to start at line 1.

    If you disassemble the code compiled from the modified AST, you'll find that the compiled opcodes do indicate the incremented line numbers, except for the RESUME opcode at the very beginning, which continues to have line number 0. So backtraces and debugging on the compiled code should show the line numbers you expect.

    While researching the answer, I noticed that ast.increment_lineno does not change the line numbers in the Module's list of TypeIgnore objects. I think that's a bug, so I reported it. But that's not relevant to your question.

    Tools I used to construct this answer:

    import ast, dis
    # Sample code
    code = """
    def hello():
      print('Hello, world')
    """
    tree = ast.parse(code)
    ast.increment_lineno(tree, 10)
    # View the AST, neatly formatted and including attributes
    print(ast.dump(tree, include_attributes=True, indent=2))
    # Compile the code
    code = compile(tree, 'example.py', mode='exec')
    # Look at the disassembly
    print(dis.dis(code))
    

    Notes

    1. See the first entry in the "abstract grammar", which shows that the attributes for ast.Module are body, which is a list of objects derived from ast.stmt, and type_ignores, which is a list of ast.TypeIgnore objects, although the type_ignores list is empty unless you use type_comments=True when parsing.