Skip to content
This repository was archived by the owner on Oct 1, 2019. It is now read-only.

Improve nested if printing #20

Closed
wants to merge 2 commits into from
Closed

Conversation

patrick91
Copy link
Member

@patrick91 patrick91 commented Jan 14, 2018

Currently transforms this:

if foo:
    foo()
elif z:
    if bar:
        bar()
    if baz:
        baz()
    elif x:
        bax()
else:
    if bar:
        bar()
    if baz:
        baz()
    elif x:
        bax()

to this, which is broken:

if foo:
  foo()
elif z:
  if bar:
    bar()

  if baz:
    baz()
  elif x:
    bax()
else:
  elif bar:
    bar()
  elif baz:
    baz()
  elif x:
    bax()

Almost there :)

return concat(path.map(printElseBody, "orelse"));
}
return concat([
hardline,
"else:",
concat(path.map(printElseBody, "orelse"))
indent(concat(path.map(printElseBody, "orelse")))
Copy link
Contributor

Choose a reason for hiding this comment

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

Just don't use printElseBody here and you should be good. You only need special-case handling for the elif case.

@taion
Copy link
Contributor

taion commented Jan 14, 2018

I will say that both this and the multi-with thing in Python 2 make me understand better what yapf does.

The Python AST has a much looser mapping to the tokens than the JS AST does. The following pieces of code are not just equivalent – they're actually syntactically identical:

if foo:
    foo()
elif bar:
    bar()
else:
    baz()

if foo:
    foo()
else:
    if bar:
        bar()
    else:
        baz()

The same is not true of the analogous code in JS.

Fortunately asttokens means we do actually have the token information available here.

@taion
Copy link
Contributor

taion commented Jan 14, 2018

Calling it "fucked up" is a bit much, though. It's an AST. It doesn't have to reflect all the syntactic sugar used in the code.

@patrick91
Copy link
Member Author

@taion thanks for that I noticed it as well, anyway, before seeing your comment I've tried a different approach, can you have a look at the code now? :)

I've tested with your example code and it prints always the same:

if foo:
  foo()
elif bar:
  bar()
else:
  baz()

I'm not sure if we should change this :)

@@ -189,10 +150,29 @@ function printIf(path, print) {
indent(concat([hardline, printBody(path, print)]))
];

const orElse = printOrElse(path);
while (n.orelse && n.orelse.length === 1 && n.orelse[0].ast_type === "If") {
n = n.orelse[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe just something like:

parts.push(concat(path.map(printElif, 'orelse')));

and then have printElif deal with this if clause, or even like a printIfBodyAndOrElse

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure I'm understanding what you mean here

if (n.orelse && n.orelse.length > 0) {
parts.push(hardline, "else:");

for (let i = 0; i < n.orelse.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can't you just do

parts.push(indent(concat(path.map(print, 'orelse'))));

Copy link
Member Author

Choose a reason for hiding this comment

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

for some reason it doesn't work, it is like it is missing the indent call:

if foo:
  foo()
elif bar:
  bar()
else:baz()

Copy link
Contributor

Choose a reason for hiding this comment

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

you need the hardline

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we doing a manual for loop here rather than just re-using our printBody function, like we do the other times in this function?

@taion
Copy link
Contributor

taion commented Jan 14, 2018

I opened #22 to track some of these ambiguities. I don't think I've ever intentionally written an else: with only a single if in there, so this is probably fine, but it's worth thinking about.

@patrick91
Copy link
Member Author

@taion thanks for that, pretty helpful! I'll have a look at your comments soon (I'm running out of battery)

@patrick91
Copy link
Member Author

@FuegoFro would mind having a look at this one? I think your input will be super helpful :)

@patrick91 patrick91 requested a review from FuegoFro February 20, 2018 21:54
Copy link
Collaborator

@FuegoFro FuegoFro left a comment

Choose a reason for hiding this comment

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

Agree that this is a super interesting problem and a look at how Python's AST handles this! I made some suggestions inline :) The main concern I have is that this is going to morph the code into something that's syntactically different but parses/desugars to the same AST.

if bar:
bar()
if baz:
baz()
Copy link
Collaborator

Choose a reason for hiding this comment

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

It'd be great to see some tests that have elif in them! Maybe something like

if foo:
    foo()
elif bar:
    bar()
else:
    if baz:
        baz()
    elif garply:
        garply()
    else:
        qux()

Maybe also a simple case like:

if a:
    a()
else:
    if b:
        b()

My guess is that to make the above cases work (and not, for instance, collapse the end of the second case into elif b) we're going to need to modify the printer function to use n.source (probably looking at what it starts with) to determine if the orelse is an else or elif.

if (n.orelse && n.orelse.length > 0) {
parts.push(hardline, "else:");

for (let i = 0; i < n.orelse.length; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we doing a manual for loop here rather than just re-using our printBody function, like we do the other times in this function?

@@ -189,10 +150,29 @@ function printIf(path, print) {
indent(concat([hardline, printBody(path, print)]))
];

const orElse = printOrElse(path);
while (n.orelse && n.orelse.length === 1 && n.orelse[0].ast_type === "If") {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: wouldn't hurt to have a comment or two in here :) Maybe something along the lines of this?

# "Handle the nested `elif`, which are represented as `else: if...` in the AST."

Up to you though :)

@@ -189,10 +150,29 @@ function printIf(path, print) {
indent(concat([hardline, printBody(path, print)]))
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: If you wanted, you could update this function to use the indentConcat function, which reduces the number of parens you need :D

@patrick91 patrick91 mentioned this pull request May 5, 2018
@patrick91
Copy link
Member Author

Closing in favour of #85

@patrick91 patrick91 closed this May 5, 2018
@patrick91 patrick91 deleted the feature/fix-nested-multi-if branch May 5, 2018 03:18
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants