Skip to content

Commit 25dcdbb

Browse files
Don't use AssignPiece for for-in loop "in" clauses. (#1485)
Don't use AssignPiece for for-in loop "in" clauses. One of the reasons that AssignPiece is so hairy is because it handles both splitting after the operator ("=", ":", etc.) and before "in". Splitting out for-in loops to their own Piece class lets us get rid of a little of that complexity. Also, I thought that was the only reason that the operator is a separate Piece. That's actually not true because we also need the LHS to be its own piece for applyConstraints(). Even so, I still think it's a little easier to understand with for-in loops being their own piece. I also slightly tweaked the formatting of for-in loops. In the rare case where the left side block splits, we now indent it and split at "in". This is consistent with the old style. I don't have a strong preference, but I think it looks a little neater this way. Co-authored-by: Nate Bosch <[email protected]>
1 parent 222f670 commit 25dcdbb

File tree

5 files changed

+128
-56
lines changed

5 files changed

+128
-56
lines changed

lib/src/front_end/ast_node_visitor.dart

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,8 +1204,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
12041204

12051205
@override
12061206
void visitMapLiteralEntry(MapLiteralEntry node) {
1207-
writeAssignment(node.key, node.separator, node.value,
1208-
spaceBeforeOperator: false);
1207+
writeAssignment(node.key, node.separator, node.value);
12091208
}
12101209

12111210
@override
@@ -1220,8 +1219,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
12201219

12211220
@override
12221221
void visitMapPatternEntry(MapPatternEntry node) {
1223-
writeAssignment(node.key, node.separator, node.value,
1224-
spaceBeforeOperator: false);
1222+
writeAssignment(node.key, node.separator, node.value);
12251223
}
12261224

12271225
@override
@@ -1271,8 +1269,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
12711269

12721270
@override
12731271
void visitNamedExpression(NamedExpression node) {
1274-
writeAssignment(node.name.label, node.name.colon, node.expression,
1275-
spaceBeforeOperator: false);
1272+
writeAssignment(node.name.label, node.name.colon, node.expression);
12761273
}
12771274

12781275
@override

lib/src/front_end/piece_factory.dart

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -473,10 +473,7 @@ mixin PieceFactory {
473473
// statement or element.
474474
forPartsPiece = pieces.build(() {
475475
pieces.token(leftParenthesis);
476-
writeAssignment(
477-
variable, forEachParts.inKeyword, forEachParts.iterable,
478-
splitBeforeOperator: true,
479-
leftHandSideContext: NodeContext.forLoopVariable);
476+
writeForIn(variable, forEachParts.inKeyword, forEachParts.iterable);
480477
pieces.token(rightParenthesis);
481478
});
482479

@@ -491,10 +488,7 @@ mixin PieceFactory {
491488
pieces.token(keyword);
492489
pieces.space();
493490

494-
writeAssignment(
495-
pattern, forEachParts.inKeyword, forEachParts.iterable,
496-
splitBeforeOperator: true,
497-
leftHandSideContext: NodeContext.forLoopVariable);
491+
writeForIn(pattern, forEachParts.inKeyword, forEachParts.iterable);
498492
});
499493
pieces.token(rightParenthesis);
500494
});
@@ -1299,11 +1293,6 @@ mixin PieceFactory {
12991293
/// * Expression (`=>`) function body
13001294
/// * Named argument or named record field (`:`)
13011295
/// * Map entry (`:`)
1302-
/// * For-in loop iterator (`in`)
1303-
///
1304-
/// If [splitBeforeOperator] is `true`, then puts [operator] at the beginning
1305-
/// of the next line when it splits. Otherwise, puts the operator at the end
1306-
/// of the preceding line.
13071296
///
13081297
/// If [canBlockSplitLeft] is `true`, then the left-hand operand supports
13091298
/// being block-formatted without indenting it farther, like:
@@ -1313,9 +1302,7 @@ mixin PieceFactory {
13131302
/// ] = list;
13141303
void writeAssignment(
13151304
AstNode leftHandSide, Token operator, AstNode rightHandSide,
1316-
{bool splitBeforeOperator = false,
1317-
bool includeComma = false,
1318-
bool spaceBeforeOperator = true,
1305+
{bool includeComma = false,
13191306
bool canBlockSplitLeft = false,
13201307
NodeContext leftHandSideContext = NodeContext.none}) {
13211308
// If an operand can have block formatting, then a newline in it doesn't
@@ -1345,25 +1332,38 @@ mixin PieceFactory {
13451332
var leftPiece = nodePiece(leftHandSide, context: leftHandSideContext);
13461333

13471334
var operatorPiece = pieces.build(() {
1348-
if (spaceBeforeOperator) pieces.space();
1335+
if (operator.type != TokenType.COLON) pieces.space();
13491336
pieces.token(operator);
1350-
if (splitBeforeOperator) pieces.space();
13511337
});
13521338

13531339
var rightPiece = nodePiece(rightHandSide,
1354-
commaAfter: includeComma,
1355-
context:
1356-
splitBeforeOperator ? NodeContext.none : NodeContext.assignment);
1340+
commaAfter: includeComma, context: NodeContext.assignment);
13571341

13581342
pieces.add(AssignPiece(
13591343
left: leftPiece,
13601344
operatorPiece,
13611345
rightPiece,
1362-
splitBeforeOperator: splitBeforeOperator,
13631346
canBlockSplitLeft: canBlockSplitLeft,
13641347
canBlockSplitRight: canBlockSplitRight));
13651348
}
13661349

1350+
/// Writes a [Piece] for the `<variable> in <expression>` part of a for-in
1351+
/// loop.
1352+
void writeForIn(AstNode leftHandSide, Token inKeyword, Expression sequence) {
1353+
var leftPiece =
1354+
nodePiece(leftHandSide, context: NodeContext.forLoopVariable);
1355+
1356+
var sequencePiece = pieces.build(() {
1357+
// Put the `in` at the beginning of the sequence.
1358+
pieces.token(inKeyword);
1359+
pieces.space();
1360+
pieces.visit(sequence);
1361+
});
1362+
1363+
pieces.add(ForInPiece(leftPiece, sequencePiece,
1364+
canBlockSplitSequence: sequence.canBlockSplit));
1365+
}
1366+
13671367
/// Writes a piece for a parameter-like constructor: Either a simple formal
13681368
/// parameter or a record type field, which is syntactically similar to a
13691369
/// parameter.

lib/src/piece/assign.dart

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import '../back_end/code_writer.dart';
55
import '../constants.dart';
66
import 'piece.dart';
77

8-
/// A piece for any construct where `=` is followed by an expression: variable
9-
/// initializer, assignment, constructor initializer, etc.
8+
/// A piece for an assignment-like construct where an operator is followed by
9+
/// an expression but where the left side of the operator isn't also an
10+
/// expression. Used for:
1011
///
11-
/// This piece is also used for map entries and named arguments where `:` is
12-
/// followed by an expression or element because those also want to support the
13-
/// "block-like" formatting of delimited expressions on the right, and for the
14-
/// `in` clause in for-in loops.
12+
/// - Assignment (`=`, `+=`, etc.)
13+
/// - Named arguments (`:`)
14+
/// - Map entries (`:`)
15+
/// - Record fields (`:`)
16+
/// - Expression function bodies (`=>`)
1517
///
1618
/// These constructs can be formatted four ways:
1719
///
@@ -76,25 +78,21 @@ class AssignPiece extends Piece {
7678
/// Split at the operator.
7779
static const State _atOperator = State(3);
7880

79-
/// The left-hand side of the operation. Includes the operator unless it is
80-
/// `in`.
81+
/// The left-hand side of the operation.
8182
final Piece? _left;
8283

83-
// TODO(perf): Most AssignPieces don't allow splitting between [_left] and
84-
// [_operator]. Also, in the common case where the AssignPiece is for a named
85-
// argument, then both [_left] and [_operator] will be simple CodePieces. If
86-
// we used a single piece for both, they can often be concatenated into a
87-
// single [CodePiece]. We only store [_operator] separately for for-in loops.
88-
// Consider handling those with a separate Piece class and merging [_left]
89-
// and [_operator] in this one.
84+
// TODO(rnystrom): If it wasn't for the need to constrain [_left] to split
85+
// in [applyConstraints()], we could write the operator into the same piece
86+
// as [_left]. In the common case where the AssignPiece is for a named
87+
// argument, the name and `:` would then end up in a single atomic
88+
// [CodePiece].
9089

90+
/// The `=` or other operator.
9191
final Piece _operator;
9292

9393
/// The right-hand side of the operation.
9494
final Piece _right;
9595

96-
final bool _splitBeforeOperator;
97-
9896
/// If `true`, then the left side supports being block-formatted, like:
9997
///
10098
/// var [
@@ -113,11 +111,9 @@ class AssignPiece extends Piece {
113111

114112
AssignPiece(this._operator, this._right,
115113
{Piece? left,
116-
bool splitBeforeOperator = false,
117114
bool canBlockSplitLeft = false,
118115
bool canBlockSplitRight = false})
119116
: _left = left,
120-
_splitBeforeOperator = splitBeforeOperator,
121117
_canBlockSplitLeft = canBlockSplitLeft,
122118
_canBlockSplitRight = canBlockSplitRight;
123119

@@ -130,10 +126,16 @@ class AssignPiece extends Piece {
130126
_atOperator,
131127
];
132128

133-
/// Apply constraints between how the parameters may split and how the
134-
/// initializers may split.
135129
@override
136130
void applyConstraints(State state, Constrain constrain) {
131+
// Force the left side to block split when in that state.
132+
//
133+
// Otherwise, the solver may instead leave it unsplit and then split the
134+
// right side incorrectly as in:
135+
//
136+
// (x, y) = longOperand2 +
137+
// longOperand2 +
138+
// longOperand3;
137139
if (state == _blockSplitLeft) constrain(_left!, State.split);
138140
}
139141

@@ -172,13 +174,10 @@ class AssignPiece extends Piece {
172174
}
173175

174176
void _writeOperator(CodeWriter writer, {bool split = false}) {
175-
if (_splitBeforeOperator) writer.splitIf(split);
176-
177177
writer.pushIndent(Indent.expression);
178178
writer.format(_operator);
179179
writer.popIndent();
180-
181-
if (!_splitBeforeOperator) writer.splitIf(split);
180+
writer.splitIf(split);
182181
}
183182

184183
void _writeRight(CodeWriter writer,

lib/src/piece/for.dart

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,78 @@ class ForPiece extends Piece {
8080
callback(_body);
8181
}
8282
}
83+
84+
/// A piece for the `<variable> in <expression>` part of a for-in loop.
85+
///
86+
/// Can be formatted two ways:
87+
///
88+
/// [State.unsplit] No split at all:
89+
///
90+
/// for (var x in y) ...
91+
///
92+
/// This state also allows splitting the sequence expression if it can be block
93+
/// formatted:
94+
///
95+
/// for (var i in [
96+
/// element1,
97+
/// element2,
98+
/// element3,
99+
/// ];
100+
///
101+
/// [State.split] Split at the `in` operator and allow expression splitting on
102+
/// either side. Allows:
103+
///
104+
/// for (var (longVariable &&
105+
/// anotherVariable)
106+
/// in longOperand +
107+
/// anotherOperand) {
108+
/// ...
109+
/// }
110+
class ForInPiece extends Piece {
111+
/// The variable or pattern initialized with each loop iteration.
112+
final Piece _variable;
113+
114+
/// The `in` keyword followed by the sequence expression.
115+
final Piece _sequence;
116+
117+
/// If `true` then the sequence expression supports being block-formatted,
118+
/// like:
119+
///
120+
/// for (var e in [
121+
/// element1,
122+
/// element2,
123+
/// ]) {
124+
/// // ...
125+
/// }
126+
final bool _canBlockSplitSequence;
127+
128+
ForInPiece(this._variable, this._sequence,
129+
{bool canBlockSplitSequence = false})
130+
: _canBlockSplitSequence = canBlockSplitSequence;
131+
132+
@override
133+
List<State> get additionalStates => const [State.split];
134+
135+
@override
136+
void format(CodeWriter writer, State state) {
137+
// When splitting at `in`, both operands may split or not and will be
138+
// indented if they do.
139+
if (state == State.split) writer.pushIndent(Indent.expression);
140+
141+
writer.format(_variable, allowNewlines: state == State.split);
142+
143+
writer.splitIf(state == State.split);
144+
145+
// Always allow block-splitting the sequence if it supports it.
146+
writer.format(_sequence,
147+
allowNewlines: _canBlockSplitSequence || state == State.split);
148+
149+
if (state == State.split) writer.popIndent();
150+
}
151+
152+
@override
153+
void forEachChild(void Function(Piece piece) callback) {
154+
callback(_variable);
155+
callback(_sequence);
156+
}
157+
}

test/tall/statement/for_in.stmt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,10 @@ for (var (longIdentifier &&
113113
for (var [longIdentifier, anotherLongOne] in obj) {;}
114114
<<<
115115
for (var [
116-
longIdentifier,
117-
anotherLongOne,
118-
] in obj) {
116+
longIdentifier,
117+
anotherLongOne,
118+
]
119+
in obj) {
119120
;
120121
}
121122
>>> With pattern, split in value.

0 commit comments

Comments
 (0)