-
Notifications
You must be signed in to change notification settings - Fork 232
Upgrade the version solver's algorithm #1758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright (c) 2017, 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 '../package_name.dart'; | ||
import 'incompatibility.dart'; | ||
import 'term.dart'; | ||
|
||
/// A term in a [PartialSolution] that tracks some additional metadata. | ||
class Assignment extends Term { | ||
/// The number of decisions at or before this in the [PartialSolution] that | ||
/// contains it. | ||
final int decisionLevel; | ||
|
||
/// The index of this assignment in [PartialSolution.assignments]. | ||
final int index; | ||
|
||
/// The incompatibility that caused this assignment to be derived, or `null` | ||
/// if the assignment isn't a derivation. | ||
final Incompatibility cause; | ||
|
||
Assignment( | ||
PackageName package, bool isPositive, this.decisionLevel, this.index, | ||
{this.cause}) | ||
: super(package, isPositive); | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Copyright (c) 2017, 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 '../package_name.dart'; | ||
import 'term.dart'; | ||
|
||
/// A set of mutually-incompatible terms. | ||
/// | ||
/// See https://github.com/dart-lang/pub/tree/master/doc/solver.md#incompatibility. | ||
class Incompatibility { | ||
/// The mutually-incompatibile terms. | ||
final List<Term> terms; | ||
|
||
/// Creates an incompatibility with [terms]. | ||
/// | ||
/// This normalizes [terms] so that each package has at most one term | ||
/// referring to it. | ||
factory Incompatibility(List<Term> terms) { | ||
if (terms.length == 1 || | ||
// Short-circuit in the common case of a two-term incompatibility with | ||
// two different packages (for example, a dependency). | ||
(terms.length == 2 && | ||
terms.first.package.name != terms.last.package.name)) { | ||
return new Incompatibility._(terms); | ||
} | ||
|
||
// Coalesce multiple terms about the same package if possible. | ||
var byName = <String, Map<PackageRef, Term>>{}; | ||
for (var term in terms) { | ||
var byRef = byName.putIfAbsent(term.package.name, () => {}); | ||
var ref = term.package.toRef(); | ||
if (byRef.containsKey(ref)) { | ||
byRef[ref] = byRef[ref].intersect(term); | ||
|
||
// If we have two terms that refer to the same package but have a null | ||
// intersection, they're mutually exclusive, making this incompatibility | ||
// irrelevant, since we already know that mutually exclusive version | ||
// ranges are incompatible. We should never derive an irrelevant | ||
// incompatibility. | ||
assert(byRef[ref] != null); | ||
} else { | ||
byRef[ref] = term; | ||
} | ||
} | ||
|
||
return new Incompatibility._(byName.values.expand((byRef) { | ||
// If there are any positive terms for a given package, we can discard | ||
// any negative terms. | ||
var positiveTerms = | ||
byRef.values.where((term) => term.isPositive).toList(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not strictly, but it avoids re-doing work when we call |
||
if (positiveTerms.isNotEmpty) return positiveTerms; | ||
|
||
return byRef.values; | ||
}).toList()); | ||
} | ||
|
||
Incompatibility._(this.terms); | ||
|
||
String toString() { | ||
if (terms.length == 1) { | ||
var term = terms.single; | ||
return "${term.package.toTerseString()} is " | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice if If we do want both, maybe move the old behavior to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it makes more sense to have the default behavior—which is more likely to come up during debugging and so on—use the more verbose output that provides full information about what's going on. |
||
"${term.isPositive ? 'forbidden' : 'required'}"; | ||
} | ||
|
||
if (terms.length == 2) { | ||
var term1 = terms.first; | ||
var term2 = terms.last; | ||
if (term1.isPositive != term2.isPositive) { | ||
var positive = (term1.isPositive ? term1 : term2).package; | ||
var negative = (term1.isPositive ? term2 : term1).package; | ||
return "if ${positive.toTerseString()} then ${negative.toTerseString()}"; | ||
} else if (term1.isPositive) { | ||
return "${term1.package.toTerseString()} is incompatible with " | ||
"${term2.package.toTerseString()}"; | ||
} else { | ||
return "either ${term1.package.toTerseString()} or " | ||
"${term2.package.toTerseString()}"; | ||
} | ||
} | ||
|
||
var positive = <String>[]; | ||
var negative = <String>[]; | ||
for (var term in terms) { | ||
(term.isPositive ? positive : negative).add(term.package.toTerseString()); | ||
} | ||
|
||
if (positive.isNotEmpty && negative.isNotEmpty) { | ||
return "if ${positive.join(' and ')} then ${negative.join(' and ')}"; | ||
} else if (positive.isNotEmpty) { | ||
return "one of ${positive.join(' or ')} must be false"; | ||
} else { | ||
return "one of ${negative.join(' or ')} must be true"; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still useful? Could we just make it do what
toTerseString()
does and only have the one method?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we do use
PackageName.toString()
in some places where it makes sense to provide full information about the package names.