Skip to content
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

ENH: Implement support for with-blocks. #59

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ssanderson
Copy link

I missed the presentation tonight at Boston Python, but a colleague pointed me at this repository.

This PR is a work in progress toward support for with-blocks.

The basic idea here is rewrite the ast of the with-block into two
statements and a try-except, which we already know how to oneline-ify

In general, we can rewrite a with block of the form:

    with <expr> as <ctxname>:
        <body>

as

    __anonymous = <expr>
    <ctxname> = __anonymous.__enter__()
    try:
        <body>
    except:
        if not __anonymous.__exit__(*sys.exc_info()):
            raise
    else:
        __anonymous.__exit__(None, None, None)

Once we've rewritten the AST, we should be able to just delegate to existing the existing functionality for sequencing statements and handling try-except blocks.

This mostly works as-is, but for reasons I don't quite understand, statements after the with-block aren't being executed. I suspect that I'm not correctly doing whatever work is needed to sequence the with-block with subsequent statements. Any guidance here would be appreciated.

The basic idea here is rewrite the ast of the with-block into two
statements and try-except, which we already know how to oneline-ify

In general, we can rewrite a with block of the form:

    with <expr> as <ctxname>:
        <body>
as
    __anonymous = <expr>
    <ctxname> = __anonymous.__enter__()
    try:
        <body>
    except:
        if not __anonymous.__exit__(*sys.exc_info()):
            raise
    else:
        __anonymous.__exit__(None, None, None)
@@ -177,7 +180,10 @@ def var(self, name):

def store_var(self, name):
name = self.mangle(name)
sym = self.table.lookup(name)
try:
sym = self.table.lookup(name)
Copy link
Author

Choose a reason for hiding this comment

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

These were needed because I'm inserting assignments to names that weren't in the original source, which means they aren't in the symbol table.

@andersk
Copy link
Collaborator

andersk commented Nov 18, 2015

Thanks for looking at this.

Sequencing subsequent statements is the purpose of the {after} template parameter used by all the existing statement translators (except the terminating statements break, continue, raise, and return).

Have you read PEP 343? It gives a slightly different translation of the with statement. The most important difference is the finally: block, which ensures that __exit__ is called even if the body jumps out of the with: block using break, continue, or return. A more subtle difference is when the __exit__ method is looked up—this matters because __getattr__/__getattribute__ could have side effects, and we’ve tried to be careful about preserving evaluation order even to this level of detail (tests/eval_order.py).

In general, I think it would be better to factor out the try…except translation code into functions that can be reused without literally generating and re-parsing fake ast nodes. You’ve already discovered some of the reasons. For example, your var and store_var kludge will make bugs elsewhere harder to find—those KeyErrors are really important for making sure we stay meticulously synchronized with the symtable module by visiting child namespaces in the same order (tests/symtable_torture.py).

Such refactoring could also tremendously simplify the resulting translated code. For example, the sys.exc_info() call could be eliminated because the __exit__ handler used for catching exceptions already gets passed this information.

@ssanderson
Copy link
Author

@andersk thanks for the thorough read-through. Responses to notes in line:

Have you read PEP 343? It gives a slightly different translation of the with statement. The most important difference is the finally: block, which ensures that exit is called even if the body jumps out of the with: block using break, continue, or return.

Ah, that's a good point. I hadn't considered jumping out of the with block from a loop.

A more subtle difference is when the exit method is looked up—this matters because getattr/getattribute could have side effects, and we’ve tried to be careful about preserving evaluation order even to this level of detail

Makes sense as well.

In general, I think it would be better to factor out the try…except translation code into functions that can be reused without literally generating and re-parsing fake ast nodes.

I'm not sure what you mean here. Are you just suggesting that we replace the big nested AST literals with easier-to-read/change functions? Or do you think the strategy of doing AST-level writing itself is the wrong way to do this. My experience has been that it's significantly easier to do source-to-source transformations on AST trees than on raw strings

@andersk
Copy link
Collaborator

andersk commented Nov 23, 2015

I’m explaining our reasons to avoid AST-level rewriting, yes. Perhaps things would be different if symtable worked on AST input—though even without such external considerations, I find it better to refactor an AST-to-AST translation

translate(ast.Foo(args)) = translate(ast.Bar(f(args)))
translate(ast.Bar(args)) = some output

as something more like

translate(ast.Foo(args)) = translateBar(f(args))
translate(ast.Bar(args)) = translateBar(args)
translateBar(args) = some output

Some examples of this pattern already in use are assignment_component and comprehension_code. (Also, we aren’t working on raw strings, we’re working on the T class.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants