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

Fix edge cases triggered by newlines in arrow functions #1217

Merged
merged 1 commit into from
Apr 13, 2017

Conversation

vjeux
Copy link
Contributor

@vjeux vjeux commented Apr 12, 2017

This one is pretty crazy. In #927, I changed

concat(["(", join(concat([", "]), printed), ")"]),

into

concat(["(", join(concat([", "]), printedLastArgExpanded), ")"]),

which makes the example in #1203 look ugly. The crazy thing is that JSON.stringify(printed) === JSON.stringify(printedLastArgExpanded). So they are deep equal but not the same object. In a non-mutative world, this should cause any problem, but we actually mutate those to propagate breaks.

In the break propagation, we only looked at the first one in case of a conditional group. But, because the two were the same object then it also applied to the second one and happened to be the correct behavior! When I changed that piece of code to be two distinct objects, it no longer worked by accident and caused a bunch of weird issues where breaks didn't propagate correctly.

The solution for this case is to propagate the breaks on all the conditions. I'm pretty sure that this is the expected behavior, we want to deeply recurse in all of them, we don't propagate it up the boundary anyway.

The other use case for traverseInDoc() is findInDoc(), right now it searches for the first conditional group but it seems very arbitrary. I changed it to not search on any and none of the tests are failing, so I think it's safe(tm). If it triggers weird behavior, then it'll at least be expected and not randomly explode at us if we move groups around.

I tried to go through all the conditions for findInDoc() but it triggers a few failures (the output look bad). I'm not really sure why. https://gist.github.com/vjeux/5fb7566cc3d65974817d512d1ef6abe1

Fix #1203

@jlongster
Copy link
Member

Yeah, this is a good solution. I had deferred thinking about this and never got around to it. It was a known issue, and it's hard to know if we can traverse all the conditions or not. But here this should work, because as you said it doesn't propagate them across the conditional group anyway, so it doesn't affect anything outside the group.

findInDoc is different. willBreak should tell you statically if something is going to break (which uses findInDoc). How do you know which condition is going to be chosen at compile time? You don't, so you can't just traverse in all of the conditions looking for a breakParent, there'd be way too many false positives. It was intentional to only search the first condition because the idea was that it was the most collapsed form, and if that breaks, than all of them probably are. But I think you're right; the gist is that breakParent that inside a conditional group doesn't really make much sense anyway, since we don't propagate it.

So willBreak doesn't need to descend into conditional groups, but getFirstString probably should read the first group. Yes, it's a little arbitrary, but it should always return the next real string (and the actual strings are probably the same in all conditions of a group).

I changed it to not search on any and none of the tests are failing

I don't see that change in the PR? getFirstString should still traverse...

I tried to go through all the conditions for findInDoc() but it triggers a few failures

What do you mean by conditions?

src/doc-utils.js Outdated
} else if (doc.type === "group" && doc.expandedStates) {
if (shouldTraverseConditionalGroups) {
doc.expandedStates.forEach(traverseDocRec);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't call traverseDocRec(doc.contents); if we're not in shouldTraverseConditionalGroups

Copy link
Member

Choose a reason for hiding this comment

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

But it traverses into contents in a later branch which is the first group of a conditional group

Copy link
Member

Choose a reason for hiding this comment

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

Just to clarify, as my above comment is wrong, I see that it will enter only this branch if it's a conditional group. But expandedStates are all of the conditions, including the first one. contents happens to point to the first one for convenience.

@vjeux
Copy link
Contributor Author

vjeux commented Apr 13, 2017

getFirstString should still traverse...

Here are the only three callsites of getFirstString:

function isCurlyBracket(doc) {
  const str = getFirstString(doc);
  return str === "{" || str === "{}";
}

function isEmptyBlock(doc) {
  const str = getFirstString(doc);
  return str === "{}";
}

case "BlockStatement": {
  var naked = path.call(function(bodyPath) {
    return printStatementSequence(bodyPath, options, print);
  }, "body");
  const hasContent = getFirstString(naked);

Do you know why those are reading the docs instead of checking the AST?

@jlongster
Copy link
Member

Do you know why those are reading the docs instead of checking the AST?

I think it's just more straight-forward. But if there aren't enough uses of it we can try that.

@vjeux
Copy link
Contributor Author

vjeux commented Apr 13, 2017

I removed isCurlyBracket and isEmptyBlock in #1221. I'm looking into hasContent now.

@vjeux
Copy link
Contributor Author

vjeux commented Apr 13, 2017

Turns out, removing the last callsite of getFirstString was trivial: #1222

So, we can kill getFirstString (after those two PRs are merged) and not worry about this edge case in findInDoc :)

@vjeux
Copy link
Contributor Author

vjeux commented Apr 13, 2017

I tried to go through all the conditions for findInDoc() but it triggers a few failures
What do you mean by conditions?

All the expandedStates. Basically, not adding a flag and always iterating through all the expandedStates.

@jlongster
Copy link
Member

With your other PRs merged, are you going to get rid of findInDoc and make traverseDoc always recurse into conditional groups?

@vjeux
Copy link
Contributor Author

vjeux commented Apr 13, 2017

I just got rid of getFirstString. The other two calls of findInDoc are isLineNext which is used by JSX and willBreak which is used everywhere. Trying to change them to recurse through all the conditional groups end up breaking a few tests, so I can't do that.

https://gist.github.com/vjeux/5fb7566cc3d65974817d512d1ef6abe1

@vjeux
Copy link
Contributor Author

vjeux commented Apr 13, 2017

One thing I can do is to leave the behavior of going through the first one. This way it's a less risky change.

This one is pretty crazy. In prettier#927, I changed

```js
concat(["(", join(concat([", "]), printed), ")"]),
```

into

```js
concat(["(", join(concat([", "]), printedLastArgExpanded), ")"]),
```

which makes the example in prettier#1203 look ugly. The crazy thing is that `JSON.stringify(printed) === JSON.stringify(printedLastArgExpanded)`. So they are deep equal but not the same object. In a non-mutative world, this should cause any problem, but we actually mutate those to propagate breaks.

In the break propagation, we only looked at the first one in case of a conditional group. But, because the two were the same object then it also applied to the second one and happened to be the correct behavior! When I changed that piece of code to be two distinct objects, it no longer worked by accident and caused a bunch of weird issues where breaks didn't propagate correctly.

The solution for this case is to propagate the breaks on all the conditions. I'm pretty sure that this is the expected behavior, we want to deeply recurse in all of them, we don't propagate it up the boundary anyway.

The other use case for `traverseInDoc()` is `findInDoc()`, right now it searches for the first conditional group but it seems very arbitrary. I changed it to not search on any and none of the tests are failing, so I think it's safe(tm). If it triggers weird behavior, then it'll at least be expected and not randomly explode at us if we move groups around.

I tried to go through all the conditions for `findInDoc()` but it triggers a few failures (the output look bad). I'm not really sure why. https://gist.github.com/vjeux/5fb7566cc3d65974817d512d1ef6abe1

Fix prettier#1203
@vjeux vjeux merged commit fe7ebc0 into prettier:master Apr 13, 2017
vjeux added a commit to vjeux/prettier that referenced this pull request Apr 16, 2017
This is the second part of the fix for the performance regression seen in prettier#1250. In prettier#1217, for correctness reasons, we're now traversing all the conditional groups. This means that we're now in O(n^2). But, in practice, many of those groups are === between each others. So we only need to recurse through one of the instances to know if it's going to break.

This makes the first example go from not terminating to being instant. The second one going from not terminating to taking ~1s. We can also make it instant by tweaking the printing phase, but that's for another PR.
vjeux added a commit to vjeux/prettier that referenced this pull request Apr 18, 2017
This is the second part of the fix for the performance regression seen in prettier#1250. In prettier#1217, for correctness reasons, we're now traversing all the conditional groups. This means that we're now in O(n^2). But, in practice, many of those groups are === between each others. So we only need to recurse through one of the instances to know if it's going to break.

This makes the first example go from not terminating to being instant. The second one going from not terminating to taking ~1s. We can also make it instant by tweaking the printing phase, but that's for another PR.
vjeux added a commit that referenced this pull request Apr 18, 2017
This is the second part of the fix for the performance regression seen in #1250. In #1217, for correctness reasons, we're now traversing all the conditional groups. This means that we're now in O(n^2). But, in practice, many of those groups are === between each others. So we only need to recurse through one of the instances to know if it's going to break.

This makes the first example go from not terminating to being instant. The second one going from not terminating to taking ~1s. We can also make it instant by tweaking the printing phase, but that's for another PR.
@lock lock bot added the locked-due-to-inactivity Please open a new issue and fill out the template instead of commenting. label Jan 20, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Jan 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
locked-due-to-inactivity Please open a new issue and fill out the template instead of commenting.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants