Skip to content

Commit 2aaddd0

Browse files
authored
Use a more general mechanism for tracking constraints between rules. (#1122)
* Use a more general mechanism for tracking constraints between rules. Rules were initially decoupled from each other. Then I added _implies to handle subexpressions forcing outer expressions to split. Then I added some object-oriented "constrain()" and "addConstrainedRules()" API so that Rules could dynamically add other ad-hoc constraints. At this point, it's better to just have a single general mechanism for expressing constraints between rules that can subsume both of those. This does that. I think the resulting code is simpler. It is also easier to extend for other kinds of constraints in the future. * Add a missing argument constraint. This (interestingly) was not caught by any existing tests, but did show up when I ran the formatter on a big corpus. Added a test to catch it in the future. * Apply review feedback.
1 parent 5ffaab7 commit 2aaddd0

File tree

8 files changed

+209
-271
lines changed

8 files changed

+209
-271
lines changed

lib/src/argument_list_visitor.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,10 @@ class ArgumentSublist {
389389
// Only count the blocks in the positional rule.
390390
var leadingBlocks = math.min(_leadingBlocks, _positional.length);
391391
var trailingBlocks = math.max(_trailingBlocks - _named.length, 0);
392-
var rule = PositionalRule(_blockRule, leadingBlocks, trailingBlocks);
392+
var rule = PositionalRule(_blockRule,
393+
argumentCount: _positional.length,
394+
leadingCollections: leadingBlocks,
395+
trailingCollections: trailingBlocks);
393396
_visitArguments(visitor, _positional, rule);
394397

395398
return rule;
@@ -406,7 +409,7 @@ class ArgumentSublist {
406409

407410
// Let the positional args force the named ones to split.
408411
if (positionalRule != null) {
409-
positionalRule.setNamedArgsRule(namedRule);
412+
positionalRule.addNamedArgsConstraints(namedRule);
410413
}
411414

412415
_visitArguments(visitor, _named, namedRule);

lib/src/call_chain_visitor.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ class CallChainVisitor {
186186

187187
if (splitOnTarget) {
188188
if (_properties.length > 1) {
189-
_propertyRule = PositionalRule(null, 0, 0);
189+
_propertyRule = PositionalRule(null, argumentCount: _properties.length);
190190
_visitor.builder.startLazyRule(_propertyRule);
191191
} else {
192192
_enableRule(lazy: true);
@@ -202,7 +202,7 @@ class CallChainVisitor {
202202
_properties.single.write(this);
203203
} else if (_properties.length > 1) {
204204
if (!splitOnTarget) {
205-
_propertyRule = PositionalRule(null, 0, 0);
205+
_propertyRule = PositionalRule(null, argumentCount: _properties.length);
206206
_visitor.builder.startRule(_propertyRule);
207207
}
208208

@@ -377,7 +377,7 @@ class CallChainVisitor {
377377

378378
// If the properties split, force the calls to split too.
379379
var rule = Rule();
380-
_propertyRule?.setNamedArgsRule(rule);
380+
_propertyRule?.addNamedArgsConstraints(rule);
381381

382382
if (lazy) {
383383
_visitor.builder.startLazyRule(rule);

lib/src/chunk_builder.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ class ChunkBuilder {
462462
// See if any of the rules that contain this one care if it splits.
463463
for (var outer in _rules) {
464464
if (!outer.splitsOnInnerRules) continue;
465-
rule.imply(outer);
465+
rule.constrainWhenSplit(outer);
466466
}
467467
_rules.add(rule);
468468
}

lib/src/rule/argument.dart

Lines changed: 65 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ abstract class ArgumentRule extends Rule {
99
/// The chunks prior to each positional argument.
1010
final List<Chunk?> _arguments = [];
1111

12-
/// The rule used to split collections in the argument list, if any.
13-
Rule? _collectionRule;
14-
1512
/// The number of leading collection arguments.
1613
///
1714
/// This and [_trailingCollections] cannot both be positive. If every
@@ -34,20 +31,7 @@ abstract class ArgumentRule extends Rule {
3431
@override
3532
bool get splitsOnInnerRules => _trackInnerRules;
3633

37-
ArgumentRule(this._collectionRule, this._leadingCollections,
38-
this._trailingCollections);
39-
40-
@override
41-
void addConstrainedRules(Set<Rule> rules) {
42-
super.addConstrainedRules(rules);
43-
if (_collectionRule != null) rules.add(_collectionRule!);
44-
}
45-
46-
@override
47-
void forgetUnusedRules() {
48-
super.forgetUnusedRules();
49-
if (_collectionRule?.index == null) _collectionRule = null;
50-
}
34+
ArgumentRule._(this._leadingCollections, this._trailingCollections);
5135

5236
/// Remembers [chunk] as containing the split that occurs right before an
5337
/// argument in the list.
@@ -86,17 +70,47 @@ abstract class ArgumentRule extends Rule {
8670
/// splits before all of the non-collection arguments, but does not split
8771
/// before the collections, so that they can split internally.
8872
class PositionalRule extends ArgumentRule {
89-
/// If there are named arguments following these positional ones, this will
90-
/// be their rule.
91-
Rule? _namedArgsRule;
92-
9373
/// Creates a new rule for a positional argument list.
9474
///
95-
/// If [_collectionRule] is given, it is the rule used to split the collection
96-
/// arguments in the list.
97-
PositionalRule(
98-
Rule? collectionRule, int leadingCollections, int trailingCollections)
99-
: super(collectionRule, leadingCollections, trailingCollections);
75+
/// [argumentCount] is the number of arguments that will be added to the rule
76+
/// by later calls to [beforeArgument()].
77+
///
78+
/// If [collectionRule] is given, it is the rule used to split the collection
79+
/// arguments in the list. It must be provided if [leadingCollections] or
80+
/// [trailingCollections] is non-zero.
81+
PositionalRule(Rule? collectionRule,
82+
{required int argumentCount,
83+
int leadingCollections = 0,
84+
int trailingCollections = 0})
85+
: super._(leadingCollections, trailingCollections) {
86+
if (collectionRule != null) {
87+
// Don't split inside collections if there are leading collections and
88+
// we split before the first argument.
89+
if (leadingCollections > 0) {
90+
addConstraint(1, collectionRule, Rule.unsplit);
91+
}
92+
93+
// If we're only splitting before the non-collection arguments, the
94+
// intent is to split inside the collections, so force that here.
95+
if (leadingCollections > 0 || trailingCollections > 0) {
96+
addConstraint(argumentCount + 1, collectionRule, 1);
97+
}
98+
99+
// Split before a single argument. If it's in the middle of the collection
100+
// arguments, don't allow them to split.
101+
for (var argument = 0; argument < leadingCollections; argument++) {
102+
var value = argumentCount - argument + 1;
103+
addConstraint(value, collectionRule, Rule.unsplit);
104+
}
105+
106+
for (var argument = argumentCount - trailingCollections;
107+
argument < argumentCount;
108+
argument++) {
109+
var value = argumentCount - argument + 1;
110+
addConstraint(value, collectionRule, Rule.unsplit);
111+
}
112+
}
113+
}
100114

101115
@override
102116
int get numValues {
@@ -118,18 +132,6 @@ class PositionalRule extends ArgumentRule {
118132
return result;
119133
}
120134

121-
@override
122-
void addConstrainedRules(Set<Rule> rules) {
123-
super.addConstrainedRules(rules);
124-
if (_namedArgsRule != null) rules.add(_namedArgsRule!);
125-
}
126-
127-
@override
128-
void forgetUnusedRules() {
129-
super.forgetUnusedRules();
130-
if (_namedArgsRule?.index == null) _namedArgsRule = null;
131-
}
132-
133135
@override
134136
bool isSplitAtValue(int value, Chunk chunk) {
135137
// Split only before the first argument. Keep the entire argument list
@@ -162,78 +164,19 @@ class PositionalRule extends ArgumentRule {
162164
return true;
163165
}
164166

165-
/// Remembers that [rule] is the [Rule] immediately following this positional
166-
/// positional argument list.
167+
/// Builds any constraints from this positional argument rule onto the [rule]
168+
/// used for the subsequent named arguments in the same argument list.
167169
///
168-
/// This is normally a [NamedRule] but [PositionalRule] is also used for the
169-
/// property accesses at the beginning of a call chain, in which case this
170+
/// The [rule] is normally a [NamedRule] but [PositionalRule] is also used for
171+
/// the property accesses at the beginning of a call chain, in which case this
170172
/// is just a [SimpleRule].
171-
void setNamedArgsRule(Rule rule) {
172-
_namedArgsRule = rule;
173-
}
174-
175-
/// Constrains the named argument list to at least move to the next line if
176-
/// there are any splits in the positional arguments. Prevents things like:
177-
///
178-
/// function(
179-
/// argument,
180-
/// argument, named: argument);
181-
@override
182-
int? constrain(int value, Rule other) {
183-
var constrained = super.constrain(value, other);
184-
if (constrained != null) return constrained;
173+
void addNamedArgsConstraints(Rule rule) {
174+
// If the positional args are one-per-line, the named args are too.
175+
constrainWhenFullySplit(rule);
185176

186-
// Handle the relationship between the positional and named args.
187-
if (other == _namedArgsRule) {
188-
// If the positional args are one-per-line, the named args are too.
189-
if (value == fullySplitValue) return _namedArgsRule!.fullySplitValue;
190-
191-
// Otherwise, if there is any split in the positional arguments, don't
192-
// allow the named arguments on the same line as them.
193-
if (value != 0) return -1;
194-
}
195-
196-
// Decide how to constrain the collection rule.
197-
if (other != _collectionRule) return null;
198-
199-
// If all of the collections are in the named arguments, [_collectionRule]
200-
// will not be null, but we don't have to handle it.
201-
if (_leadingCollections == 0 && _trailingCollections == 0) return null;
202-
203-
// If we aren't splitting any args, we can split the collection.
204-
if (value == Rule.unsplit) return null;
205-
206-
// Split only before the first argument.
207-
if (value == 1) {
208-
if (_leadingCollections > 0) {
209-
// We are splitting before a collection, so don't let it split
210-
// internally.
211-
return Rule.unsplit;
212-
} else {
213-
// The split is outside of the collections so they can split or not.
214-
return null;
215-
}
216-
}
217-
218-
// Split before a single argument. If it's in the middle of the collection
219-
// arguments, don't allow them to split.
220-
if (value <= _arguments.length) {
221-
var argument = _arguments.length - value + 1;
222-
if (argument < _leadingCollections ||
223-
argument >= _arguments.length - _trailingCollections) {
224-
return Rule.unsplit;
225-
}
226-
227-
return null;
228-
}
229-
230-
// Only split before the non-collection arguments. This case only comes into
231-
// play when we do want to split the collection, so force that here.
232-
if (value == _arguments.length + 1) return 1;
233-
234-
// Split before all of the arguments, even the collections. We'll allow
235-
// them to split but indent their bodies if they do.
236-
return null;
177+
// Otherwise, if there is any split in the positional arguments, don't
178+
// allow the named arguments on the same line as them.
179+
addRangeConstraint(1, fullySplitValue, rule, Rule.mustSplit);
237180
}
238181

239182
@override
@@ -249,9 +192,23 @@ class NamedRule extends ArgumentRule {
249192
@override
250193
int get numValues => 3;
251194

195+
/// Creates a new rule for a named argument list.
196+
///
197+
/// [argumentCount] is the number of arguments that will be added to the rule
198+
/// by later calls to [beforeArgument()].
199+
///
200+
/// If [collectionRule] is given, it is the rule used to split the collection
201+
/// arguments in the list. It must be provided if [leadingCollections] or
202+
/// [trailingCollections] is non-zero.
252203
NamedRule(
253204
Rule? collectionRule, int leadingCollections, int trailingCollections)
254-
: super(collectionRule, leadingCollections, trailingCollections);
205+
: super._(leadingCollections, trailingCollections) {
206+
if (leadingCollections > 0 || trailingCollections > 0) {
207+
// Split only before the first argument. Don't allow the collections to
208+
// split.
209+
addConstraint(1, collectionRule!, Rule.unsplit);
210+
}
211+
}
255212

256213
@override
257214
bool isSplitAtValue(int value, Chunk chunk) {
@@ -262,30 +219,6 @@ class NamedRule extends ArgumentRule {
262219
return true;
263220
}
264221

265-
@override
266-
int? constrain(int value, Rule other) {
267-
var constrained = super.constrain(value, other);
268-
if (constrained != null) return constrained;
269-
270-
// Decide how to constrain the collection rule.
271-
if (other != _collectionRule) return null;
272-
273-
// If all of the collections are in the named arguments, [_collectionRule]
274-
// will not be null, but we don't have to handle it.
275-
if (_leadingCollections == 0 && _trailingCollections == 0) return null;
276-
277-
// If we aren't splitting any args, we can split the collection.
278-
if (value == Rule.unsplit) return null;
279-
280-
// Split only before the first argument. Don't allow the collections to
281-
// split.
282-
if (value == 1) return Rule.unsplit;
283-
284-
// Split before all of the arguments, even the collections. We'll allow
285-
// them to split but indent their bodies if they do.
286-
return null;
287-
}
288-
289222
@override
290223
String toString() => 'Named${super.toString()}';
291224
}

lib/src/rule/metadata.dart

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)