Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Summary Black supports for layouts when it comes to breaking binary expressions: ```rust #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum BinaryLayout { /// Put each operand on their own line if either side expands Default, /// Try to expand the left to make it fit. Add parentheses if the left or right don't fit. /// ///```python /// [ /// a, /// b /// ] & c ///``` ExpandLeft, /// Try to expand the right to make it fix. Add parentheses if the left or right don't fit. /// /// ```python /// a & [ /// b, /// c /// ] /// ``` ExpandRight, /// Both the left and right side can be expanded. Try in the following order: /// * expand the right side /// * expand the left side /// * expand both sides /// /// to make the expression fit /// /// ```python /// [ /// a, /// b /// ] & [ /// c, /// d /// ] /// ``` ExpandRightThenLeft, } ``` Our current implementation only handles `ExpandRight` and `Default` correctly. `ExpandLeft` turns out to be surprisingly hard. This PR adds a new `BestFittingMode` parameter to `BestFitting` to support `ExpandLeft`. There are 3 variants that `ExpandLeft` must support: **Variant 1**: Everything fits on the line (easy) ```python [a, b] + c ``` **Variant 2**: Left breaks, but right fits on the line. Doesn't need parentheses ```python [ a, b ] + c ``` **Variant 3**: The left breaks, but there's still not enough space for the right hand side. Parenthesize the whole expression: ```python ( [ a, b ] + ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ) ``` Solving Variant 1 and 2 on their own is straightforward The printer gives us this behavior by nesting right inside of the group of left: ``` group(&format_args![ if_group_breaks(&text("(")), soft_block_indent(&group(&format_args![ left, soft_line_break_or_space(), op, space(), group(&right) ])), if_group_breaks(&text(")")) ]) ``` The fundamental problem is that the outer group, which adds the parentheses, always breaks if the left side breaks. That means, we end up with ```python ( [ a, b ] + c ) ``` which is not what we want (we only want parentheses if the right side doesn't fit). Okay, so nesting groups don't work because of the outer parentheses. Sequencing groups doesn't work because it results in a right-to-left breaking which is the opposite of what we want. Could we use best fitting? Almost! ``` best_fitting![ // All flat format_args![left, space(), op, space(), right], // Break left format_args!(group(&left).should_expand(true), space(), op, space(), right], // Break all format_args![ text("("), block_indent!(&format_args![ left, hard_line_break(), op, space() right ]) ] ] ``` I hope I managed to write this up correctly. The problem is that the printer never reaches the 3rd variant because the second variant always fits: * The `group(&left).should_expand(true)` changes the group so that all `soft_line_breaks` are turned into hard line breaks. This is necessary because we want to test if the content fits if we break after the `[`. * Now, the whole idea of `best_fitting` is that you can pretend that some content fits on the line when it actually does not. The way this works is that the printer **only** tests if all the content of the variant **up to** the first line break fits on the line (we insert that line break by using `should_expand(true))`. The printer doesn't care whether the rest `a\n, b\n ] + c` all fits on (multiple?) lines. Why does breaking right work but not breaking the left? The difference is that we can make the decision whether to parenthesis the expression based on the left expression. We can't do this for breaking left because the decision whether to insert parentheses or not would depend on a lookahead: will the right side break. We simply don't know this yet when printing the parentheses (it would work for the right parentheses but not for the left and indent). What we kind of want here is to tell the printer: Look, what comes here may or may not fit on a single line but we don't care. Simply test that what comes **after** fits on a line. This PR adds a new `BestFittingMode` that has a new `AllLines` option that gives us the desired behavior of testing all content and not just up to the first line break. ## Test Plan I added a new example to `BestFitting::with_mode`
- Loading branch information