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

update chain handling of long lines and block-like parents #3884

Closed
wants to merge 13 commits into from

Conversation

calebcartwright
Copy link
Member

@calebcartwright calebcartwright commented Oct 25, 2019

Resolves #3863, resolves #2970, and, I believe, resolves #3157 as well.

Apologies that this PR is on the larger side, though worth noting that it mostly consists of test files. As I'm sure folks are well aware, chains are tricky 😄, so I included a lot of tests for my own sanity (happy to remove anything that seems duplicative/unnecessary).

I also had to change my approach quite a few times while working on this and didn't do a great job of squashing commits along the way so I'd probably advise against trying to review this commit-by-commit. Instead I'd suggest reviewing the files associated with the update for the two referenced issues, and I've included a summary of the respective changes below for each issue accordingly.

Issue 3863 - handling of long chain elements

Currently, rustfmt will fail to format a chain if that chain contains one or more elements that exceeds max_width which has surfaced in a few issues (#3863 and rust-lang/rustup#2097 for example).

This PR updates the handling of chains to ensure that rustfmt will still be able to format the chain even if there's a long item. There's one edge case (when indent_style is Visual and the long chain item is the chain's parent) that's not perfect, but IMO is still an improvement over not being able to format the chain at all.

Files associated with this update:

  • src/chains.rs (all the changes except the fn format_chain_parent_inner() function that starts on line 730)
  • src/expr.rs (See my inline comment in this file as this change may not be necessary/helpful)
  • tests/source/chains_long_item_block.rs
  • tests/source/chains_long_item_visual.rs
  • tests/target/chains_long_item_block.rs
  • tests/target/chains_long_item_block.rs

Issue 3157 - configurability of chain indents for chains with block-like parents

The current state is described in far better detail in #3157, but the gist is that when the chain parent is block-like, then the chain elements are not indented like they are with a simple chain parent.

Based on that thread, my sense is that there's some subjective/stylistic-preferences around the indentation flavors. As such the approach I've proposed in this PR leverages two new configuration options (with the default indent behavior being rustfmt's current style/approach for these types of chains) to give users some flexibility and to support the requested formatting in #3157

Files associated with this update:

  • Configurations.md
  • src/chains.rs (just the fn format_chain_parent_inner() function that starts on line 730)
  • src/config/mod.rs
  • src/config/options.rs
  • tests/source/chains_multiline_parent_always.rs
  • tests/source/chains_multiline_parent_never.rs
  • tests/source/chains_multiline_parent_simple_calls.rs
  • tests/source/chains_multiline_parent_tuples_and_simple_calls.rs
  • tests/target/chains_multiline_parent_always.rs
  • tests/target/chains_multiline_parent_never.rs
  • tests/target/chains_multiline_parent_simple_calls.rs
  • tests/target/chains_multiline_parent_tuples_and_simple_calls.rs

Configurations.md Outdated Show resolved Hide resolved
src/chains.rs Outdated Show resolved Hide resolved
src/chains.rs Outdated Show resolved Hide resolved
(Some(orig_rhs), _) => Some(format!(" {}", orig_rhs)),
(Some(orig_rhs), _) => {
let prefix = if orig_rhs.starts_with('\n') { "" } else { " " };
Some(format!("{}{}", prefix, orig_rhs))
Copy link
Member Author

@calebcartwright calebcartwright Oct 25, 2019

Choose a reason for hiding this comment

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

While working on this I tried a few different things to get the formatting to look right (especially for chains with long elements when using the Visual indent style). This change was added to handle some of those approaches that started with a newline in order to prevent the error[internal]: left behind trailing whitespace error from occurring.

The ultimate approach I went with does not need this, so I can remove this check if folks would prefer (though I do wonder if it may help with some other/unrelated trailing whitespace errors)

Configurations.md Outdated Show resolved Hide resolved
src/chains.rs Outdated Show resolved Hide resolved
@calebcartwright calebcartwright changed the title support wrapping call chains with long elements [WIP] support wrapping call chains with long elements Oct 29, 2019
@calebcartwright calebcartwright marked this pull request as ready for review October 29, 2019 17:36
@calebcartwright calebcartwright changed the title [WIP] support wrapping call chains with long elements [WIP] update chain handling of long lines and block-like parents Oct 31, 2019
@calebcartwright calebcartwright changed the title [WIP] update chain handling of long lines and block-like parents update chain handling of long lines and block-like parents Nov 20, 2019
@calebcartwright
Copy link
Member Author

I think this is probably finally ready for review whenever folks get a chance, apologies it took so long!

@@ -389,6 +389,255 @@ fn example() {
}
```

## `chains_block_parent_indent_children`
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 always have such a hard time coming up with config names 😄 If this approach seems viable for handling the scenario in #3157 (and it should indeed be configurable) then this should probably be renamed to something more succinct

// Whether one or more of the inner chain items exceed the max width
has_long_inner_item: bool,
// Whether the final chain item exceeds the max width
has_long_tail: bool,
Copy link
Member Author

Choose a reason for hiding this comment

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

instead of a single has_long flag, these fields are used to have a more detailed view of where the chain has a long element based on how chains are formatted (parent and tail are formatted separately)

wrap_str(rewrite, max_width, shape).is_none()
}

fn format_chain_parent_inner(
Copy link
Member Author

Choose a reason for hiding this comment

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

This entire function (which runs to line 832) is to support #3157 (indentation for chains with block-like parents), all the other changes in this file are to address #3863 (long chain elements)

).foo().bar().baz();
}

fn long_parent() {
Copy link
Member Author

Choose a reason for hiding this comment

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

While working on this I discovered quite a variety of chain scenarios (position of long element in the chain, rhs of an assignment vs. free, etc.) that had to be explicitly handled, so there's test cases for each of those scenarios

@@ -0,0 +1,167 @@
// rustfmt-indent_style: Visual

fn long_parent() {
Copy link
Member Author

Choose a reason for hiding this comment

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

This scenario was the most difficult to deal with (Visual for indent_style with the long element being the chain parent).

As I mentioned in the PR description this is definitely not perfect, but probably still better than not formatting the chains at all. I felt like this approach was the better of the evils, as doing the typical Visual indent with this scenario would basically result in the entire chain body being off screen (as all the child elements would be shifted to the right of the end of the long parent).

In the future, it might be possible to leverage some of the heurstics added here to improve upon this scenario (for example checking whether the first child is long, etc.) but this feels like quite an edge case to me and I need a break from chains for a bit 😁

@calebcartwright
Copy link
Member Author

@topecongiro - I'm going to close this and then open a new PR with the changes. that should make it easier to review, provide a cleaner commit history, and allow me to avoid dealing with all the merge conflicts

@superhawk610
Copy link

@calebcartwright any updates? #3157 linked here but I wasn't sure if anything else had been done since.

@calebcartwright
Copy link
Member Author

Sorry @superhawk610, but no updates. There's some other discussions and decisions that will impact this relative to major version upgrades and the stability guarantee which need to settle first so we don't end up with divergent chain formatting implementations that are each massive.

Will take another pass after some of those other items are resolved, but TBH this is still a ways out from being included in a released version of rustfmt.

@superhawk610
Copy link

Makes sense. Thanks for working on this! 🙂

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