Skip to content

Commit 15fef68

Browse files
authored
Support eagerly pinning a piece to a specific state. (#1291)
* Support eagerly pinning a piece to a specific state. The BlockPiece had some bespoke support for being in an "always split" configuration that overrode the two different ways it could be formatted. While working on conditional expressions, I found myself needing something similar to InfixPiece. Instead of adding similar code to that and likely other pieces in the future, I figured it made more sense to bake support for locking a piece into a specific state directly in the Piece base class. * Revamp how pieces enumerate their states. - Rename State.initial to `State.unsplit` to describe what it represents. (This is a little bit of a misnomer for SequencePiece where there is still a hard newline between each child piece, but it's clearer for all of the other pieces.) - Piece subclasses implement Piece.additionalStates to enumerate the possible set of states, if they support more than just `State.unsplit`. - Have Piece.states return only the pinned state if there is one and otherwise return `State.unsplit` followed by `additionalStates`. - Also reorganized the format code in InfixPiece to have less copy/paste. * Re-add comment on unsplit block pieces.
1 parent ee3dd8a commit 15fef68

File tree

14 files changed

+85
-75
lines changed

14 files changed

+85
-75
lines changed

lib/src/back_end/solution.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ class PieceStateSet {
2626
PieceStateSet._(this._pieces, this._pieceStates);
2727

2828
/// The state this solution selects for [piece].
29-
State pieceState(Piece piece) => _pieceStates[piece] ?? State.initial;
29+
///
30+
/// If no state has been selected, defaults to the first state.
31+
State pieceState(Piece piece) => _pieceStates[piece] ?? piece.states.first;
3032

3133
/// Gets the first piece that doesn't have a state selected yet, or `null` if
3234
/// all pieces have selected states.
@@ -86,9 +88,6 @@ class Solution implements Comparable<Solution> {
8688
if (piece == null) return const [];
8789

8890
return [
89-
// All pieces support a default state.
90-
Solution(root, pageWidth, _state.cloneWith(piece, State.initial)),
91-
9291
for (var state in piece.states)
9392
Solution(root, pageWidth, _state.cloneWith(piece, state))
9493
];

lib/src/back_end/solver.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ import 'solution.dart';
1414
/// possible states, so it isn't feasible to brute force. There are a few
1515
/// techniques we use to avoid that:
1616
///
17-
/// - All pieces default to being in [State.initial]. Every piece is
18-
/// implemented such that that state has no line splits (or only mandatory
19-
/// ones) and zero cost. Thus, it tries solutions with a minimum number of
20-
/// line splits first.
17+
/// - The initial state for each piece has no line splits or only mandatory
18+
/// ones. Thus, it tries solutions with a minimum number of line splits
19+
/// first.
2120
///
2221
/// - Solutions are explored in priority order. We explore solutions with the
2322
/// the lowest cost first. This way, as soon as we find a solution with no

lib/src/piece/assign.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'piece.dart';
1414
///
1515
/// These constructs can be formatted three ways:
1616
///
17-
/// [State.initial] No split at all:
17+
/// [State.unsplit] No split at all:
1818
///
1919
/// ```
2020
/// var x = 123;
@@ -60,13 +60,14 @@ class AssignPiece extends Piece {
6060
: _isValueDelimited = isValueDelimited;
6161

6262
@override
63-
List<State> get states => [if (_isValueDelimited) _insideValue, _atEquals];
63+
List<State> get additionalStates =>
64+
[if (_isValueDelimited) _insideValue, _atEquals];
6465

6566
@override
6667
void format(CodeWriter writer, State state) {
6768
// A split in either child piece forces splitting after the "=" unless it's
6869
// a delimited expression.
69-
if (state == State.initial) writer.setAllowNewlines(false);
70+
if (state == State.unsplit) writer.setAllowNewlines(false);
7071

7172
// Don't indent a split delimited expression.
7273
if (state != _insideValue) writer.setIndent(Indent.expression);

lib/src/piece/block.dart

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,22 @@ class BlockPiece extends Piece {
1919
/// The closing delimiter.
2020
final Piece rightBracket;
2121

22-
/// Whether the block should always split its contents.
23-
///
24-
/// True for most blocks, but false for enums and blocks containing only
25-
/// inline block comments.
26-
final bool _alwaysSplit;
27-
22+
/// If [alwaysSplit] is true, then the block should always split its contents.
23+
/// This is true for most blocks, but false for enums and blocks containing
24+
/// only inline block comments.
2825
BlockPiece(this.leftBracket, this.contents, this.rightBracket,
29-
{bool alwaysSplit = true})
30-
: _alwaysSplit = alwaysSplit;
26+
{bool alwaysSplit = true}) {
27+
if (alwaysSplit) pin(State.split);
28+
}
3129

3230
@override
33-
List<State> get states => _alwaysSplit ? const [] : const [State.split];
31+
List<State> get additionalStates => const [State.split];
3432

3533
@override
3634
void format(CodeWriter writer, State state) {
3735
writer.format(leftBracket);
3836

39-
if (_alwaysSplit || state == State.split) {
37+
if (state == State.split) {
4038
if (contents.isNotEmpty) {
4139
writer.newline(indent: Indent.block);
4240
writer.format(contents);

lib/src/piece/do_while.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ class DoWhilePiece extends Piece {
1212

1313
DoWhilePiece(this._body, this._condition);
1414

15-
@override
16-
List<State> get states => const [];
17-
1815
@override
1916
void format(CodeWriter writer, State state) {
2017
writer.setIndent(Indent.none);

lib/src/piece/function.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class FunctionTypePiece extends Piece {
1818
FunctionTypePiece(this._returnType, this._signature);
1919

2020
@override
21-
List<State> get states => const [State.split];
21+
List<State> get additionalStates => const [State.split];
2222

2323
@override
2424
void format(CodeWriter writer, State state) {

lib/src/piece/if.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ class IfPiece extends Piece {
3030

3131
/// If there is at least one else or else-if clause, then it always splits.
3232
@override
33-
List<State> get states => _isUnbracedIfThen ? const [State.split] : const [];
33+
List<State> get additionalStates => [if (_isUnbracedIfThen) State.split];
3434

3535
@override
3636
void format(CodeWriter writer, State state) {
3737
if (_isUnbracedIfThen) {
3838
// A split in the condition or statement forces moving the entire
3939
// statement to the next line.
40-
writer.setAllowNewlines(state != State.initial);
40+
writer.setAllowNewlines(state != State.unsplit);
4141

4242
var section = _sections.single;
4343
writer.format(section.header);

lib/src/piece/import.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import 'piece.dart';
1313
///
1414
/// Combinators can be split like so:
1515
///
16-
/// [State.initial] All on one line:
16+
/// [State.unsplit] All on one line:
1717
///
1818
/// ```
1919
/// import 'animals.dart' show Ant, Bat hide Cat, Dog;
@@ -109,7 +109,7 @@ class ImportPiece extends Piece {
109109
}
110110

111111
@override
112-
List<State> get states => [
112+
List<State> get additionalStates => [
113113
_beforeCombinators,
114114
if (_combinators.length > 1) ...[
115115
_firstCombinator,
@@ -126,13 +126,13 @@ class ImportPiece extends Piece {
126126

127127
if (_combinators.isNotEmpty) {
128128
_combinators[0].format(writer,
129-
splitKeyword: state != State.initial,
129+
splitKeyword: state != State.unsplit,
130130
splitNames: state == _firstCombinator || state == State.split);
131131
}
132132

133133
if (_combinators.length > 1) {
134134
_combinators[1].format(writer,
135-
splitKeyword: state != State.initial,
135+
splitKeyword: state != State.unsplit,
136136
splitNames: state == _secondCombinator || state == State.split);
137137
}
138138
}

lib/src/piece/infix.dart

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,19 @@ class InfixPiece extends Piece {
2222
InfixPiece(this.operands);
2323

2424
@override
25-
List<State> get states => const [State.split];
25+
List<State> get additionalStates => const [State.split];
2626

2727
@override
2828
void format(CodeWriter writer, State state) {
29-
switch (state) {
30-
case State.initial:
31-
writer.setAllowNewlines(false);
32-
for (var i = 0; i < operands.length; i++) {
33-
writer.format(operands[i]);
34-
35-
if (i < operands.length - 1) writer.space();
36-
}
29+
if (state == State.unsplit) {
30+
writer.setAllowNewlines(false);
31+
} else {
32+
writer.setNesting(Indent.expression);
33+
}
3734

38-
case State.split:
39-
writer.setNesting(Indent.expression);
40-
for (var i = 0; i < operands.length; i++) {
41-
writer.format(operands[i]);
42-
if (i < operands.length - 1) writer.newline();
43-
}
35+
for (var i = 0; i < operands.length; i++) {
36+
writer.format(operands[i]);
37+
if (i < operands.length - 1) writer.splitIf(state == State.split);
4438
}
4539
}
4640

lib/src/piece/list.dart

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,12 @@ class ListPiece extends Piece {
5555
this._isTypeList);
5656

5757
@override
58-
List<State> get states {
59-
// Don't split between an empty pair of brackets.
60-
if (_arguments.isEmpty) return const [];
61-
62-
// Type lists are more expensive to split.
63-
if (_isTypeList) return const [_splitTypes];
64-
65-
return const [State.split];
66-
}
58+
List<State> get additionalStates => [
59+
if (_isTypeList)
60+
_splitTypes // Type lists are more expensive to split.
61+
else if (_arguments.isNotEmpty)
62+
State.split // Don't split between an empty pair of brackets.
63+
];
6764

6865
@override
6966
void format(CodeWriter writer, State state) {
@@ -78,7 +75,7 @@ class ListPiece extends Piece {
7875
// });
7976
// ```
8077
switch (state) {
81-
case State.initial:
78+
case State.unsplit:
8279
// All arguments on one line with no trailing comma.
8380
writer.setAllowNewlines(false);
8481
for (var i = 0; i < _arguments.length; i++) {

0 commit comments

Comments
 (0)