| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import '../chunk.dart'; |
| import '../constants.dart'; |
| import '../fast_hash.dart'; |
| |
| /// A constraint that determines the different ways a related set of chunks may |
| /// be split. |
| class Rule extends FastHash { |
| /// The rule used for dummy chunks. |
| static final Rule dummy = Rule.hard(); |
| |
| /// Rule value that splits no chunks. |
| /// |
| /// Every rule is required to treat this value as fully unsplit. |
| static const unsplit = 0; |
| |
| /// Rule constraint value that means "any value as long as something splits". |
| /// |
| /// It disallows [unsplit] but allows any other value. |
| static const mustSplit = -1; |
| |
| /// Rule constraint that means the rule must split to its fully split value. |
| /// |
| /// This is used instead of the actual full split value because at the point |
| /// that the constraint is added, the Rule may not have all of the chunks it |
| /// needs to correctly calculate [numValues]. |
| static const _fullSplitConstraint = -2; |
| |
| /// The number of different states this rule can be in. |
| /// |
| /// Each state determines which set of chunks using this rule are split and |
| /// which aren't. Values range from zero to one minus this. Value zero |
| /// always means "no chunks are split" and increasing values by convention |
| /// mean increasingly undesirable splits. |
| /// |
| /// By default, a rule has two values: fully unsplit and fully split. |
| int get numValues => 2; |
| |
| /// The rule value that forces this rule into its maximally split state. |
| /// |
| /// By convention, this is the highest of the range of allowed values. |
| int get fullySplitValue => numValues - 1; |
| |
| int get cost => _cost; |
| final int _cost; |
| |
| /// During line splitting [LineSplitter] sets this to the index of this |
| /// rule in its list of rules. |
| int? index; |
| |
| /// If `true`, the rule has been "hardened" meaning it's been placed into a |
| /// permanent "must fully split" state. |
| bool get isHardened => _isHardened; |
| bool _isHardened = false; |
| |
| /// The constraints that this rule implies about other rule values. |
| /// |
| /// In many cases, if a split occurs inside an expression, surrounding rules |
| /// also want to split too. For example, a split in the middle of an argument |
| /// forces the entire argument list to also split. |
| /// |
| /// Also, there are sometimes more bespoke constraints between rules. For |
| /// example, positional and named arguments in an argument list each have |
| /// their own rules, but the way the positional arguments split restricts the |
| /// way the named rules are allowed to split. |
| /// |
| /// This tracks those relationships. Each key is a rule whose values can be |
| /// constrained by this rule. The entry values are a list of [_Constraint] |
| /// objects. Each specifies that when this rule has a value within a certain |
| /// range, the constrained rule's value must be a certain given value. |
| final Map<Rule, List<_Constraint>> _constraints = {}; |
| |
| /// Whether this rule cares about rules that it contains. |
| /// |
| /// If `true` then inner rules will constrain this one and force it to split |
| /// when they split. Otherwise, it can split independently of any contained |
| /// rules. |
| bool get splitsOnInnerRules => true; |
| |
| Rule([this._cost = Cost.normal]); |
| |
| /// Creates a new rule that is already fully split. |
| Rule.hard() : _cost = 0 { |
| // Set the cost to zero since it will always be applied, so there's no |
| // point in penalizing it. |
| // |
| // Also, this avoids doubled counting in literal blocks where there is both |
| // a split in the outer chunk containing the block and the inner hard split |
| // between the elements or statements. |
| harden(); |
| } |
| |
| /// Fixes this rule into a "fully split" state. |
| void harden() { |
| _isHardened = true; |
| } |
| |
| /// Returns `true` if [chunk] should split when this rule has [value]. |
| bool isSplit(int value, Chunk chunk) { |
| if (_isHardened) return true; |
| |
| if (value == Rule.unsplit) return false; |
| |
| // Let the subclass decide. |
| return isSplitAtValue(value, chunk); |
| } |
| |
| /// Subclasses can override this to determine which values split which chunks. |
| /// |
| /// By default, this assumes every chunk splits. |
| bool isSplitAtValue(int value, Chunk chunk) => true; |
| |
| /// Given that this rule has [value], determine if [other]'s value should be |
| /// constrained. |
| /// |
| /// Allows relationships between rules like "if I split, then this should |
| /// split too". Returns a non-negative value to force [other] to take that |
| /// value. Returns -1 to allow [other] to take any non-zero value. Returns |
| /// `null` to not constrain other. |
| int? constrain(int value, Rule other) { |
| // By default, any containing rule will be fully split if this one is split. |
| if (value == Rule.unsplit) return null; |
| |
| var constrained = _constraints[other]; |
| if (constrained == null) return null; |
| |
| for (var constraint in constrained) { |
| if (value >= constraint.min && value <= constraint.max) { |
| if (constraint.otherValue == _fullSplitConstraint) { |
| return other.fullySplitValue; |
| } |
| |
| return constraint.otherValue; |
| } |
| } |
| |
| return null; |
| } |
| |
| /// When this rule has [value], constrains [other] to [otherValue]. |
| void addConstraint(int value, Rule other, int otherValue) { |
| addRangeConstraint(value, value, other, otherValue); |
| } |
| |
| /// When this rule's value is between [min] and [max] (inclusive), constrains |
| /// [other] to [otherValue]. |
| void addRangeConstraint(int min, int max, Rule other, int otherValue) { |
| _constraints |
| .putIfAbsent(other, () => []) |
| .add(_Constraint(min, max, otherValue)); |
| } |
| |
| /// Constrains [other] to its fully split value when this rule is split in |
| /// any way. |
| void constrainWhenSplit(Rule other) { |
| // We want the constraint to apply to any non-zero value, so use an |
| // arbitrary but sufficiently large number. |
| addRangeConstraint(1, 100000, other, _fullSplitConstraint); |
| } |
| |
| /// Constrains [other] to its fully split value when this rule is fully split. |
| void constrainWhenFullySplit(Rule other) { |
| addConstraint(fullySplitValue, other, _fullSplitConstraint); |
| } |
| |
| /// Discards constraints on any rule that doesn't have an index. |
| /// |
| /// This is called by [LineSplitter] after it has indexed all of the in-use |
| /// rules. A rule may end up with a constraint on a rule that's no longer |
| /// used by any chunk. This can happen if the rule gets hardened, or if it |
| /// simply never got used by a chunk. For example, a rule for splitting an |
| /// empty list of metadata annotations. |
| /// |
| /// This removes all of those. |
| void forgetUnusedRules() { |
| _constraints.removeWhere((rule, _) => rule.index == null); |
| |
| // Clear the cached ones too. |
| _allConstrainedRules = null; |
| } |
| |
| /// The other [Rule]s that this rule places immediate constraints on. |
| Iterable<Rule> get constrainedRules => _constraints.keys; |
| |
| /// The transitive closure of all of the rules this rule places constraints |
| /// on, directly or indirectly, including itself. |
| Set<Rule> get allConstrainedRules { |
| var rules = _allConstrainedRules; |
| if (rules != null) return rules; |
| |
| rules = {}; |
| _traverseConstraints(rules, this); |
| _allConstrainedRules = rules; |
| return rules; |
| } |
| |
| /// Traverses the constraint graph of [rule] adding everything to [rules]. |
| void _traverseConstraints(Set<Rule> rules, Rule rule) { |
| if (rules.contains(rule)) return; |
| |
| rules.add(rule); |
| for (var rule in rule.constrainedRules) { |
| _traverseConstraints(rules, rule); |
| } |
| } |
| |
| Set<Rule>? _allConstrainedRules; |
| |
| @override |
| String toString() => '$id'; |
| } |
| |
| /// A rule that may contain splits which don't force it to split. |
| class SplitContainingRule extends Rule { |
| /// If true, then inner rules that are written will force this rule to split. |
| /// |
| /// Temporarily disabled while writing collection arguments so that they can |
| /// be multi-line without forcing the whole argument list to split. |
| bool _trackInnerRules = true; |
| |
| /// Don't split when an inner collection rule splits. |
| @override |
| bool get splitsOnInnerRules => _trackInnerRules; |
| |
| /// Disables tracking inner rules while a collection argument is written. |
| void disableSplitOnInnerRules() { |
| assert(_trackInnerRules == true); |
| _trackInnerRules = false; |
| } |
| |
| /// Re-enables tracking inner rules. |
| void enableSplitOnInnerRules() { |
| assert(_trackInnerRules == false); |
| _trackInnerRules = true; |
| } |
| } |
| |
| /// Describes a value constraint that one [Rule] places on another rule's |
| /// values. |
| /// |
| /// If the first rule's selected value is within [min], [max] (inclusive), then |
| /// the other rule's value is forced to be [otherValue]. |
| class _Constraint { |
| /// The minimum of the range of values that rule can have to enable the |
| /// constraint. |
| final int min; |
| |
| /// The maximum of the range of values that rule can have to enable the |
| /// constraint. |
| final int max; |
| |
| /// When this constraint applies, then this is the value the other rule must |
| /// have. |
| /// |
| /// If this is [_fullSplitConstraint], then forces the other rule to its |
| /// fully split value. We don't just eagerly store the fully split value in |
| /// here because some rules incrementally build the list of Chunks that are |
| /// used to determine the the number of values the rule can take and thus its |
| /// fully split value isn't known when the rule is created and its |
| /// constraints are wired up. In particular, when using [TypeArgumentRule] |
| /// for the elements in a collection literal with comments used to control |
| /// splitting, it's not easy to eagerly calculate the number of values each |
| /// rule will end up having. |
| final int otherValue; |
| |
| _Constraint(this.min, this.max, this.otherValue); |
| } |