diff --git a/lib/src/solver/deducer.dart b/lib/src/solver/deducer.dart index efc26d1e9ee4d7f085b265d0d09d47f5fdd4d3eb..5b67c1446a520ea77ccb78e6280302150845d352 100644 --- a/lib/src/solver/deducer.dart +++ b/lib/src/solver/deducer.dart @@ -132,6 +132,9 @@ class Deducer { } void _requiredIntoDependencies(Required fact) { + // TODO: remove dependencies from/onto packages with the same name but + // non-matching sources. + // Dependencies whose depender is exactly [fact.dep], grouped by the names // of packages they depend on. var matchingByAllowed = <String, Set<Dependency>>{}; @@ -204,50 +207,13 @@ class Deducer { // Trim any dependencies whose allowed versions aren't covered by [fact]. We // can even create [Disallowed]s if the dependencies are entirely - // unsatisfiable. + // unsatisfiable. See [_requiredAndAllowed] for details. for (var dependency in _dependenciesByAllowed[ref].toList()) { - var intersection = fact.dep.constraint.intersect( - dependency.allowed.constraint); - if (intersection == dependency.allowed.constraint) continue; + var result = _requiredAndAllowed(fact, dependency); + if (result == dependency) continue; _removeDependency(dependency); - if (intersection.isEmpty) { - // If there are no valid versions covered by both [dependency.allowed] - // and [fact], then this dependency can never be satisfied and the - // depender should be disallowed entirely. For example, if - // - // * a [0, 1) is required (fact) - // * b [0, 1) depends on a [1, 2) (dependency) - // - // we can remove [dependency] and add - // - // * b [0, 1) is disallowed - _fromCurrent.add(new Disallowed(dependency.depender, [dependency, fact])); - } else if (intersection != fact.dep.constraint) { - // If some but not all packages covered by [dependency.allowed] are - // covered by [fact], replace [dependency] with one with a narrower - // constraint. For example, if - // - // * a [0, 2) is required (fact) - // * b [0, 1) depends on a [1, 3) (dependency) - // - // we can remove [dependency] and add - // - // * b [0, 1) depends on a [1, 2) - _fromCurrent.add(new Dependency( - dependency.depender, - dependency.allowed.withConstraint(intersection), - [dependency, fact])); - } else { - // If [intersection] is exactly [fact.dep.constraint], then this - // dependency adds no information in addition to [fact], so it can be - // discarded entirely. For example, if - // - // * a [0, 1) is required (fact) - // * b [0, 1) depends on a [0, 1) (dependency) - // - // we can throw away [dependency]. - } + if (result != null) _fromCurrent.add(result); } } @@ -664,41 +630,16 @@ class Deducer { } } - /// Trim [fact]'s allowed version if it's not covered by a requirement. + /// Trim [fact]'s allowed version if it's not covered by a requirement. See + /// [_requiredAndAllowed] for details. required = _required[fact.allowed.name]; - if (required != null) { - var intersection = _intersectDeps(required.dep, fact.allowed); + if (required == null) return fact; - if (intersection == null) { - // If the intersection is empty, then [fact] can never be satisfied and - // we should convert it into a [Disallowed]. For example, if - // - // * a [0, 1) depends on b [0, 1) (fact) - // * b [1, 2) is required (required) - // - // we can remove [fact] and add - // - // * a [0, 1) is disallowed - // - // Add to [_toProcess] because [_fromCurrent] will get discarded when we - // return `null`. - _toProcess.add(new Disallowed(fact.depender, [required, fact])); - return null; - } else if (intersection != fact.allowed.constraint) { - // If only a subset of [fact.allowed] is required, we can trim [fact]. - // For example, if - // - // * a [0, 1) depends on b [0, 2) (fact) - // * b [1, 3) is required (required) - // - // we can remove [fact] and add - // - // * a [0, 1) depends on b [1, 2) - fact = new Dependency(fact.depender, intersection, [required, fact]); - } - } + var result = _requiredAndAllowed(required, fact); + if (result is! Disallowed) return result as Dependency; - return fact; + _toProcess.add(result); + return null; } // Resolves [required] and [disallowed], which should refer to the same @@ -718,9 +659,63 @@ class Deducer { required.dep.withConstraint(difference), [required, disallowed]); } - void _removeDependency(Dependency dependency); + /// Resolves [required] and [dependency.allowed], which should refer to the + /// same package. + /// + /// Returns a new fact to replace [dependency] (either a [Disallowed] or a + /// [Dependency]), or `null` if the dependency is irrelevant. + Fact _requiredAndAllowed(Required required, Dependency dependency) { + assert(required.dep.name == dependency.allowed.name); - void _removeDisallowed(Disallowed disallowed); + var intersection = _intersectDeps(required.dep, dependency.allowed); + if (intersection == null) { + // If there are no versions covered by both [dependency.allowed] and + // [required], then this dependency can never be satisfied and the + // depender should be disallowed entirely. For example, if + // + // * a [0, 1) is required (required) + // * b [0, 1) depends on a [1, 2) (dependency) + // + // we can remove [dependency] and add + // + // * b [0, 1) is disallowed + return new Disallowed(dependency.depender, [dependency, required]); + } else if (intersection.constraint == required.dep.constraint) { + // If [intersection] is exactly [required.dep], then this dependency adds + // no information in addition to [required], so it can be discarded + // entirely. For example, if + // + // * a [0, 1) is required (required) + // * b [0, 1) depends on a [0, 2) (dependency) + // + // we can throw away [dependency]. + return null; + } else if (intersection.constraint == dependency.allowed.constraint) { + // If [intersection] is exactly [dependency.allowed.constraint], then + // [dependency] can be preserved as-is. For example, if + // + // * a [0, 2) is required (required) + // * b [0, 1) depends on a [0, 1) (dependency) + // + // there are no changes to be made. + return dependency; + } else { + // If some but not all packages covered by [dependency.allowed] are + // covered by [required], replace [dependency] with one with a narrower + // constraint. For example, if + // + // * a [0, 2) is required (required) + // * b [0, 1) depends on a [1, 3) (dependency) + // + // we can remove [dependency] and add + // + // * b [0, 1) depends on a [1, 2) + return new Dependency( + dependency.depender, intersection, [dependency, required]); + } + } + + void _removeDependency(Dependency dependency); /// If [dependencies]' dependers cover all of [depender], returns the union of /// their allowed constraints.