Skip to content

Commit a5533c3

Browse files
authored
Fix a crash and some formatting bugs in cascades. (#1410)
I did a test run of the new formatter on the Flutter repo and it hit some crashes in cascades where a cascade section contains a method chain like `foo..a.b`. While investigating that, I realized that the existing code for formatting cascades tries to reuse the regular method chain formatting code entirely unnecessarily. This fixes the crash, and the formatting, and is simpler for handling cascades.
1 parent 860410d commit a5533c3

File tree

3 files changed

+56
-27
lines changed

3 files changed

+56
-27
lines changed

lib/src/front_end/chain_builder.dart

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,19 @@ class ChainBuilder {
7272
if (_root case CascadeExpression cascade) {
7373
_visitTarget(cascade.target);
7474

75+
// When [_root] is a cascade, the chain is the series of cascade sections.
7576
for (var section in cascade.cascadeSections) {
76-
_unwrapCall(section);
77+
var piece = _visitor.nodePiece(section);
78+
79+
var callType = switch (section) {
80+
MethodInvocation(argumentList: var args)
81+
when args.arguments.canSplit(args.rightParenthesis) =>
82+
CallType.splittableCall,
83+
MethodInvocation() => CallType.unsplittableCall,
84+
_ => CallType.property,
85+
};
86+
87+
_calls.add(ChainCall(piece, callType));
7788
}
7889
} else {
7990
_unwrapCall(_root);
@@ -160,9 +171,6 @@ class ChainBuilder {
160171
/// Given [expression], which is the expression for some call chain, traverses
161172
/// the selectors to fill in the list of [_calls].
162173
///
163-
/// If [_root] is a [CascadeSection], then this is called once for each
164-
/// section in the cascade.
165-
///
166174
/// Otherwise, it's a method chain, and this recursively calls itself for the
167175
/// targets to unzip and flatten the nested selector expressions. Then it
168176
/// initializes [_target] with the innermost subexpression that isn't a part
@@ -176,10 +184,8 @@ class ChainBuilder {
176184
/// .baz[0][1]
177185
/// .bang()
178186
void _unwrapCall(Expression expression) {
179-
var isCascade = _root is CascadeExpression;
180-
181187
switch (expression) {
182-
case Expression(looksLikeStaticCall: true) when !isCascade:
188+
case Expression(looksLikeStaticCall: true):
183189
// Don't include things that look like static method or constructor
184190
// calls in the call chain because that tends to split up named
185191
// constructors from their class.
@@ -191,8 +197,8 @@ class ChainBuilder {
191197
expression.operator, expression.rightHandSide);
192198
_calls.add(ChainCall(piece, CallType.property));
193199

194-
case MethodInvocation(:var target) when isCascade || target != null:
195-
if (target != null) _unwrapCall(target);
200+
case MethodInvocation(:var target?):
201+
_unwrapCall(target);
196202

197203
var callPiece = _visitor.buildPiece((b) {
198204
b.token(expression.operator);
@@ -206,8 +212,8 @@ class ChainBuilder {
206212
_calls.add(ChainCall(callPiece,
207213
canSplit ? CallType.splittableCall : CallType.unsplittableCall));
208214

209-
case PropertyAccess(:var target):
210-
if (target != null) _unwrapCall(target);
215+
case PropertyAccess(:var target?):
216+
_unwrapCall(target);
211217

212218
var piece = _visitor.buildPiece((b) {
213219
b.token(expression.operator);
@@ -217,7 +223,7 @@ class ChainBuilder {
217223
_calls.add(ChainCall(piece, CallType.property));
218224

219225
case PrefixedIdentifier(:var prefix):
220-
if (!isCascade) _unwrapCall(prefix);
226+
_unwrapCall(prefix);
221227

222228
var piece = _visitor.buildPiece((b) {
223229
b.token(expression.period);
@@ -236,19 +242,6 @@ class ChainBuilder {
236242
});
237243
});
238244

239-
case IndexExpression() when isCascade && _calls.isEmpty:
240-
// An index expression as the first cascade section should be part of
241-
// the cascade chain and not part of the target, as in:
242-
//
243-
// foo
244-
// ..[index]
245-
// ..another();
246-
//
247-
// For non-cascade method chains, we keep leave the index as part of
248-
// the target since the method chain doesn't begin until the first `.`.
249-
var piece = _visitor.createIndexExpression(null, expression);
250-
_calls.add(ChainCall(piece, CallType.property));
251-
252245
case IndexExpression():
253246
_unwrapPostfix(expression.target!, (target) {
254247
return _visitor.createIndexExpression(target, expression);
@@ -264,7 +257,7 @@ class ChainBuilder {
264257

265258
default:
266259
// Otherwise, it isn't a selector so we've reached the target.
267-
if (!isCascade) _visitTarget(expression);
260+
_visitTarget(expression);
268261
}
269262
}
270263

test/invocation/cascade.stmt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,26 @@ object?..[index]..method()..[index]=value;
224224
object
225225
?..[index]
226226
..method()
227-
..[index] = value;
227+
..[index] = value;
228+
>>> Property chain in cascade.
229+
object..first.second.third
230+
..fourth.fifth.sixth;
231+
<<<
232+
object
233+
..first.second.third
234+
..fourth.fifth.sixth;
235+
>>> Split property chain in cascade.
236+
object..firstProperty.secondProperty.thirdProperty
237+
..fourthProperty.fifthProperty.sixthProperty;
238+
<<<
239+
object
240+
..firstProperty
241+
.secondProperty
242+
.thirdProperty
243+
..fourthProperty
244+
.fifthProperty
245+
.sixthProperty;
246+
>>> Cascade chained property access setter.
247+
object..a.b = value;
248+
<<<
249+
object..a.b = value;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
>>> Unitialized late variable on cascade containing method chains.
2+
main() {
3+
htmlElement
4+
..style.width = '100%'
5+
..style.height = '100%'
6+
..classList.add(_kClassName);
7+
}
8+
<<<
9+
main() {
10+
htmlElement
11+
..style.width = '100%'
12+
..style.height = '100%'
13+
..classList.add(_kClassName);
14+
}

0 commit comments

Comments
 (0)