Skip to content

Commit d382969

Browse files
authored
Merge pull request #1836 from dart-lang/feature.solver.refactor
Refactor some solver stuff
2 parents c4d896f + c04710e commit d382969

File tree

6 files changed

+119
-160
lines changed

6 files changed

+119
-160
lines changed

lib/src/package_name.dart

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ class PackageId extends PackageName {
161161
bool operator ==(other) =>
162162
other is PackageId && samePackage(other) && other.version == version;
163163

164+
/// Returns a [PackageRange] that allows only [version] of this package.
165+
PackageRange toRange() => withConstraint(version);
166+
164167
String toString([PackageDetail detail]) {
165168
detail ??= PackageDetail.defaults;
166169
if (isMagic) return name;
@@ -242,12 +245,11 @@ class PackageRange extends PackageName {
242245
if (isMagic) return name;
243246

244247
var buffer = new StringBuffer(name);
245-
if (detail.showVersion ??
246-
!constraint.isAny || (source is! PathSource && source is! GitSource)) {
248+
if (detail.showVersion ?? _showVersionConstraint) {
247249
buffer.write(" $constraint");
248250
}
249251

250-
if (detail.showSource ?? source is! HostedSource) {
252+
if (!isRoot && (detail.showSource ?? source is! HostedSource)) {
251253
buffer.write(" from $source");
252254
if (detail.showDescription) {
253255
buffer.write(" ${source.formatDescription(description)}");
@@ -261,6 +263,15 @@ class PackageRange extends PackageName {
261263
return buffer.toString();
262264
}
263265

266+
/// Whether to include the version constraint in [toString] by default.
267+
bool get _showVersionConstraint {
268+
if (isRoot) return false;
269+
if (!constraint.isAny) return true;
270+
if (source is PathSource) return false;
271+
if (source is GitSource) return false;
272+
return true;
273+
}
274+
264275
/// Returns a new [PackageRange] with [features] merged with [this.features].
265276
PackageRange withFeatures(Map<String, FeatureDependency> features) {
266277
if (features.isEmpty) return this;

lib/src/solver/assignment.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,17 @@ class Assignment extends Term {
1919
/// if the assignment isn't a derivation.
2020
final Incompatibility cause;
2121

22-
Assignment(
23-
PackageName package, bool isPositive, this.decisionLevel, this.index,
24-
{this.cause})
22+
/// Whether this assignment is a decision, as opposed to a derivation.
23+
bool get isDecision => cause == null;
24+
25+
/// Creates a decision: a speculative assignment of a single package version.
26+
Assignment.decision(PackageId package, this.decisionLevel, this.index)
27+
: cause = null,
28+
super(package.toRange(), true);
29+
30+
/// Creates a derivation: an assignment that's automatically propagated from
31+
/// incompatibilities.
32+
Assignment.derivation(PackageRange package, bool isPositive, this.cause,
33+
this.decisionLevel, this.index)
2534
: super(package, isPositive);
2635
}

lib/src/solver/package_lister.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,12 @@ class PackageLister {
155155
_listedLockedVersion = true;
156156
if (!_matchesDartSdkConstraint(pubspec)) {
157157
return [
158-
new Incompatibility(
159-
[new Term(id, true)], new SdkCause(pubspec.dartSdkConstraint))
158+
new Incompatibility([new Term(id.toRange(), true)],
159+
new SdkCause(pubspec.dartSdkConstraint))
160160
];
161161
} else if (!_matchesFlutterSdkConstraint(pubspec)) {
162162
return [
163-
new Incompatibility([new Term(id, true)],
163+
new Incompatibility([new Term(id.toRange(), true)],
164164
new SdkCause(pubspec.flutterSdkConstraint, flutter: true))
165165
];
166166
} else {
@@ -171,7 +171,7 @@ class PackageLister {
171171
: pubspec.dependencies;
172172
return dependencies.values
173173
.map((range) => new Incompatibility(
174-
[new Term(id, true), new Term(range, false)],
174+
[new Term(id.toRange(), true), new Term(range, false)],
175175
IncompatibilityCause.dependency))
176176
.toList();
177177
}

lib/src/solver/partial_solution.dart

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,36 @@ import 'term.dart';
1616
class PartialSolution {
1717
/// The assignments that have been made so far, in the order they were
1818
/// assigned.
19-
final assignments = <Assignment>[];
19+
final _assignments = <Assignment>[];
20+
21+
/// The decisions made for each package.
22+
final _decisions = <String, PackageId>{};
2023

2124
/// The intersection of all positive [Assignment]s for each package, minus any
2225
/// negative [Assignment]s that refer to that package.
2326
///
24-
/// This is derived from [assignments].
25-
final positive = <String, Term>{};
27+
/// This is derived from [_assignments].
28+
final _positive = <String, Term>{};
2629

2730
/// The union of all negative [Assignment]s for each package.
2831
///
2932
/// If a package has any positive [Assignment]s, it doesn't appear in this
3033
/// map.
3134
///
32-
/// This is derived from [assignments].
33-
final negative = <String, Map<PackageRef, Term>>{};
35+
/// This is derived from [_assignments].
36+
final _negative = <String, Map<PackageRef, Term>>{};
37+
38+
/// Returns all the decisions that have been made in this partial solution.
39+
Iterable<PackageId> get decisions => _decisions.values;
3440

35-
// The current decision level—that is, the number of decisions in
36-
// [assignments].
37-
int get decisionLevel => _decisionLevel;
38-
var _decisionLevel = 0;
41+
/// Returns all [PackageRange]s that have been assigned but are not yet
42+
/// satisfied.
43+
Iterable<PackageRange> get unsatisfied => _positive.values
44+
.where((term) => !_decisions.containsKey(term.package.name))
45+
.map((term) => term.package);
46+
47+
// The current decision level—that is, the length of [decisions].
48+
int get decisionLevel => _decisions.length;
3949

4050
/// The number of distinct solutions that have been attempted so far.
4151
int get attemptedSolutions => _attemptedSolutions;
@@ -44,27 +54,29 @@ class PartialSolution {
4454
/// Whether the solver is currently backtracking.
4555
var _backtracking = false;
4656

47-
/// Adds an assignment of [package] to [isPositive] to [assignments].
48-
///
49-
/// If [decision] is `true`, this is a decision (a speculative assignment
50-
/// rather than one that's automatically propagated from incompatibilities)
51-
/// and the [decisionLevel] should be incremented.
52-
void assign(PackageName package, bool isPositive,
53-
{Incompatibility cause, bool decision: false}) {
54-
if (decision) {
55-
// When we make a new decision after backtracking, count an additional
56-
// attempted solution. If we backtrack multiple times in a row, though, we
57-
// only want to count one, since we haven't actually started attempting a
58-
// new solution.
59-
if (_backtracking) _attemptedSolutions++;
60-
_backtracking = false;
61-
_decisionLevel++;
62-
}
57+
/// Adds an assignment of [package] as a decision and increments the
58+
/// [decisionLevel].
59+
void decide(PackageId package) {
60+
// When we make a new decision after backtracking, count an additional
61+
// attempted solution. If we backtrack multiple times in a row, though, we
62+
// only want to count one, since we haven't actually started attempting a
63+
// new solution.
64+
if (_backtracking) _attemptedSolutions++;
65+
_backtracking = false;
66+
_decisions[package.name] = package;
67+
_assign(
68+
new Assignment.decision(package, decisionLevel, _assignments.length));
69+
}
70+
71+
/// Adds an assignment of [package] as a derivation.
72+
void derive(PackageName package, bool isPositive, Incompatibility cause) {
73+
_assign(new Assignment.derivation(
74+
package, isPositive, cause, decisionLevel, _assignments.length));
75+
}
6376

64-
var assignment = new Assignment(
65-
package, isPositive, _decisionLevel, assignments.length,
66-
cause: cause);
67-
assignments.add(assignment);
77+
/// Adds [assignment] to [_assignments] and [_positive] or [_negative].
78+
void _assign(Assignment assignment) {
79+
_assignments.add(assignment);
6880
_register(assignment);
6981
}
7082

@@ -74,56 +86,55 @@ class PartialSolution {
7486
_backtracking = true;
7587

7688
var packages = new Set<String>();
77-
while (assignments.last.decisionLevel > decisionLevel) {
78-
var removed = assignments.removeLast();
89+
while (_assignments.last.decisionLevel > decisionLevel) {
90+
var removed = _assignments.removeLast();
7991
packages.add(removed.package.name);
92+
if (removed.isDecision) _decisions.remove(removed.package.name);
8093
}
81-
_decisionLevel = decisionLevel;
8294

83-
// Re-compute [positive] and [negative] for the packages that were removed.
95+
// Re-compute [_positive] and [_negative] for the packages that were removed.
8496
for (var package in packages) {
85-
positive.remove(package);
86-
negative.remove(package);
97+
_positive.remove(package);
98+
_negative.remove(package);
8799
}
88100

89-
for (var assignment in assignments) {
101+
for (var assignment in _assignments) {
90102
if (packages.contains(assignment.package.name)) {
91103
_register(assignment);
92104
}
93105
}
94106
}
95107

96-
/// Registers [assignment] in [positive] or [negative].
108+
/// Registers [assignment] in [_positive] or [_negative].
97109
void _register(Assignment assignment) {
98110
var name = assignment.package.name;
99-
var oldPositive = positive[name];
111+
var oldPositive = _positive[name];
100112
if (oldPositive != null) {
101-
positive[name] = oldPositive.intersect(assignment);
113+
_positive[name] = oldPositive.intersect(assignment);
102114
return;
103115
}
104116

105117
var ref = assignment.package.toRef();
106-
var negativeByRef = negative[name];
118+
var negativeByRef = _negative[name];
107119
var oldNegative = negativeByRef == null ? null : negativeByRef[ref];
108120
var term =
109121
oldNegative == null ? assignment : assignment.intersect(oldNegative);
110122

111123
if (term.isPositive) {
112-
negative.remove(name);
113-
positive[name] = term;
124+
_negative.remove(name);
125+
_positive[name] = term;
114126
} else {
115-
negative.putIfAbsent(name, () => {})[ref] = term;
127+
_negative.putIfAbsent(name, () => {})[ref] = term;
116128
}
117129
}
118130

119-
/// Returns the first entry in [assignments] such that the sublist of
120-
/// assignments up to and including that entry collectively satisfies
121-
/// [term].
131+
/// Returns the first [Assignment] in this solution such that the sublist of
132+
/// assignments up to and including that entry collectively satisfies [term].
122133
///
123134
/// Throws a [StateError] if [term] isn't satisfied by [this].
124135
Assignment satisfier(Term term) {
125136
Term assignedTerm;
126-
for (var assignment in assignments) {
137+
for (var assignment in _assignments) {
127138
if (assignment.package.name != term.package.name) continue;
128139

129140
if (!assignment.package.isRoot &&
@@ -149,20 +160,20 @@ class PartialSolution {
149160

150161
/// Returns whether [this] satisfies [other].
151162
///
152-
/// That is, whether [other] must be true given that [assignments] are all
153-
/// true.
163+
/// That is, whether [other] must be true given the assignments in this
164+
/// partial solution.
154165
bool satisfies(Term term) => relation(term) == SetRelation.subset;
155166

156167
/// Returns the relationship between the package versions allowed by all
157168
/// assignments in [this] and those allowed by [term].
158169
SetRelation relation(Term term) {
159-
var positive = this.positive[term.package.name];
170+
var positive = _positive[term.package.name];
160171
if (positive != null) return positive.relation(term);
161172

162173
// If there are no assignments related to [term], that means the
163174
// assignments allow any version of any package, which is a superset of
164175
// [term].
165-
var byRef = this.negative[term.package.name];
176+
var byRef = _negative[term.package.name];
166177
if (byRef == null) return SetRelation.overlapping;
167178

168179
// not foo from git is a superset of foo from hosted

0 commit comments

Comments
 (0)