Make the VersionUnion class public.

This is the first of several pub_semver changes to support my solver
work.

R=rnystrom@google.com

Review URL: https://codereview.chromium.org//2029263003 .
diff --git a/pkgs/pub_semver/CHANGELOG.md b/pkgs/pub_semver/CHANGELOG.md
index 78c85e7..dc169fb 100644
--- a/pkgs/pub_semver/CHANGELOG.md
+++ b/pkgs/pub_semver/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 1.3.0
+
+* Make the `VersionUnion` class public. This was previously used internally to
+  implement `new VersionConstraint.unionOf()` and `VersionConstraint.union()`.
+  Now it's public so you can use it too.
+
 # 1.2.4
 
 * Fix all remaining strong mode warnings.
diff --git a/pkgs/pub_semver/lib/pub_semver.dart b/pkgs/pub_semver/lib/pub_semver.dart
index fd2320e..bfb7d8b 100644
--- a/pkgs/pub_semver/lib/pub_semver.dart
+++ b/pkgs/pub_semver/lib/pub_semver.dart
@@ -5,3 +5,4 @@
 export 'src/version.dart';
 export 'src/version_constraint.dart';
 export 'src/version_range.dart';
+export 'src/version_union.dart';
diff --git a/pkgs/pub_semver/lib/src/version_constraint.dart b/pkgs/pub_semver/lib/src/version_constraint.dart
index f222b51..9f55da0 100644
--- a/pkgs/pub_semver/lib/src/version_constraint.dart
+++ b/pkgs/pub_semver/lib/src/version_constraint.dart
@@ -6,6 +6,7 @@
 import 'version.dart';
 import 'version_range.dart';
 import 'version_union.dart';
+import 'utils.dart';
 
 /// A [VersionConstraint] is a predicate that can determine whether a given
 /// version is valid or not.
@@ -172,8 +173,44 @@
   /// It allows any versions that any of those constraints allows. If
   /// [constraints] is empty, this returns a constraint that allows no versions.
   factory VersionConstraint.unionOf(
-          Iterable<VersionConstraint> constraints) =>
-      VersionUnion.create(constraints);
+          Iterable<VersionConstraint> constraints) {
+    var flattened = constraints.expand((constraint) {
+      if (constraint.isEmpty) return [];
+      if (constraint is VersionUnion) return constraint.ranges;
+      return [constraint];
+    }).toList();
+
+    if (flattened.isEmpty) return VersionConstraint.empty;
+
+    if (flattened.any((constraint) => constraint.isAny)) {
+      return VersionConstraint.any;
+    }
+
+    // Only allow Versions and VersionRanges here so we can more easily reason
+    // about everything in [flattened]. _EmptyVersions and VersionUnions are
+    // filtered out above.
+    for (var constraint in flattened) {
+      if (constraint is VersionRange) continue;
+      throw new ArgumentError('Unknown VersionConstraint type $constraint.');
+    }
+
+    flattened.sort(compareMax);
+
+    var merged = <VersionRange>[];
+    for (var constraint in flattened) {
+      // Merge this constraint with the previous one, but only if they touch.
+      if (merged.isEmpty ||
+          (!merged.last.allowsAny(constraint) &&
+              !areAdjacent(merged.last, constraint))) {
+        merged.add(constraint);
+      } else {
+        merged[merged.length - 1] = merged.last.union(constraint);
+      }
+    }
+
+    if (merged.length == 1) return merged.single;
+    return new VersionUnion.fromRanges(merged);
+  }
 
   /// Returns `true` if this constraint allows no versions.
   bool get isEmpty;
diff --git a/pkgs/pub_semver/lib/src/version_range.dart b/pkgs/pub_semver/lib/src/version_range.dart
index 668b9b2..145151f 100644
--- a/pkgs/pub_semver/lib/src/version_range.dart
+++ b/pkgs/pub_semver/lib/src/version_range.dart
@@ -124,7 +124,7 @@
     if (other is Version) return allows(other);
 
     if (other is VersionUnion) {
-      return other.constraints.every((constraint) => allowsAll(constraint));
+      return other.ranges.every((constraint) => allowsAll(constraint));
     }
 
     if (other is VersionRange) {
@@ -151,7 +151,7 @@
     if (other is Version) return allows(other);
 
     if (other is VersionUnion) {
-      return other.constraints.any((constraint) => allowsAny(constraint));
+      return other.ranges.any((constraint) => allowsAny(constraint));
     }
 
     if (other is VersionRange) {
diff --git a/pkgs/pub_semver/lib/src/version_union.dart b/pkgs/pub_semver/lib/src/version_union.dart
index 5d52564..dbdadcf 100644
--- a/pkgs/pub_semver/lib/src/version_union.dart
+++ b/pkgs/pub_semver/lib/src/version_union.dart
@@ -9,8 +9,8 @@
 import 'version_constraint.dart';
 import 'version_range.dart';
 
-/// A (package-private) version constraint representing a union of multiple
-/// disjoint version constraints.
+/// A version constraint representing a union of multiple disjoint version
+/// ranges.
 ///
 /// An instance of this will only be created if the version can't be represented
 /// as a non-compound value.
@@ -23,101 +23,63 @@
   /// * Its contents are disjoint and non-adjacent. In other words, for any two
   ///   constraints next to each other in the list, there's some version between
   ///   those constraints that they don't match.
-  final List<VersionRange> constraints;
+  final List<VersionRange> ranges;
 
   bool get isEmpty => false;
 
   bool get isAny => false;
 
-  /// Returns the union of [constraints].
+  /// Creates a union from a list of ranges with no pre-processing.
   ///
-  /// This ensures that an actual [VersionUnion] is only returned if necessary.
-  /// It also takes care of sorting and merging the constraints to ensure that
-  /// they're disjoint.
-  static VersionConstraint create(Iterable<VersionConstraint> constraints) {
-    var flattened = constraints.expand((constraint) {
-      if (constraint.isEmpty) return [];
-      if (constraint is VersionUnion) return constraint.constraints;
-      return [constraint];
-    }).toList();
-
-    if (flattened.isEmpty) return VersionConstraint.empty;
-
-    if (flattened.any((constraint) => constraint.isAny)) {
-      return VersionConstraint.any;
-    }
-
-    // Only allow Versions and VersionRanges here so we can more easily reason
-    // about everything in [flattened]. _EmptyVersions and VersionUnions are
-    // filtered out above.
-    for (var constraint in flattened) {
-      if (constraint is VersionRange) continue;
-      throw new ArgumentError('Unknown VersionConstraint type $constraint.');
-    }
-
-    flattened.sort(compareMax);
-
-    var merged = <VersionRange>[];
-    for (var constraint in flattened) {
-      // Merge this constraint with the previous one, but only if they touch.
-      if (merged.isEmpty ||
-          (!merged.last.allowsAny(constraint) &&
-              !areAdjacent(merged.last, constraint))) {
-        merged.add(constraint);
-      } else {
-        merged[merged.length - 1] = merged.last.union(constraint);
-      }
-    }
-
-    if (merged.length == 1) return merged.single;
-    return new VersionUnion._(merged);
-  }
-
-  VersionUnion._(this.constraints);
+  /// It's up to the caller to ensure that the invariants described in [ranges]
+  /// are maintained. They are not verified by this constructor. To
+  /// automatically ensure that they're maintained, use [new
+  /// VersionConstraint.unionOf] instead.
+  VersionUnion.fromRanges(this.ranges);
 
   bool allows(Version version) =>
-      constraints.any((constraint) => constraint.allows(version));
+      ranges.any((constraint) => constraint.allows(version));
 
   bool allowsAll(VersionConstraint other) {
-    var ourConstraints = constraints.iterator;
-    var theirConstraints = _constraintsFor(other).iterator;
+    var ourRanges = ranges.iterator;
+    var theirRanges = _rangesFor(other).iterator;
 
-    // Because both lists of constraints are ordered by minimum version, we can
+    // Because both lists of ranges are ordered by minimum version, we can
     // safely move through them linearly here.
-    ourConstraints.moveNext();
-    theirConstraints.moveNext();
-    while (ourConstraints.current != null && theirConstraints.current != null) {
-      if (ourConstraints.current.allowsAll(theirConstraints.current)) {
-        theirConstraints.moveNext();
+    ourRanges.moveNext();
+    theirRanges.moveNext();
+    while (ourRanges.current != null && theirRanges.current != null) {
+      if (ourRanges.current.allowsAll(theirRanges.current)) {
+        theirRanges.moveNext();
       } else {
-        ourConstraints.moveNext();
+        ourRanges.moveNext();
       }
     }
 
-    // If our constraints have allowed all of their constraints, we'll have
-    // consumed all of them.
-    return theirConstraints.current == null;
+    // If our ranges have allowed all of their ranges, we'll have consumed all
+    // of them.
+    return theirRanges.current == null;
   }
 
   bool allowsAny(VersionConstraint other) {
-    var ourConstraints = constraints.iterator;
-    var theirConstraints = _constraintsFor(other).iterator;
+    var ourRanges = ranges.iterator;
+    var theirRanges = _rangesFor(other).iterator;
 
-    // Because both lists of constraints are ordered by minimum version, we can
+    // Because both lists of ranges are ordered by minimum version, we can
     // safely move through them linearly here.
-    ourConstraints.moveNext();
-    theirConstraints.moveNext();
-    while (ourConstraints.current != null && theirConstraints.current != null) {
-      if (ourConstraints.current.allowsAny(theirConstraints.current)) {
+    ourRanges.moveNext();
+    theirRanges.moveNext();
+    while (ourRanges.current != null && theirRanges.current != null) {
+      if (ourRanges.current.allowsAny(theirRanges.current)) {
         return true;
       }
 
       // Move the constraint with the higher max value forward. This ensures
       // that we keep both lists in sync as much as possible.
-      if (compareMax(ourConstraints.current, theirConstraints.current) < 0) {
-        ourConstraints.moveNext();
+      if (compareMax(ourRanges.current, theirRanges.current) < 0) {
+        ourRanges.moveNext();
       } else {
-        theirConstraints.moveNext();
+        theirRanges.moveNext();
       }
     }
 
@@ -125,43 +87,42 @@
   }
 
   VersionConstraint intersect(VersionConstraint other) {
-    var ourConstraints = constraints.iterator;
-    var theirConstraints = _constraintsFor(other).iterator;
+    var ourRanges = ranges.iterator;
+    var theirRanges = _rangesFor(other).iterator;
 
-    // Because both lists of constraints are ordered by minimum version, we can
+    // Because both lists of ranges are ordered by minimum version, we can
     // safely move through them linearly here.
-    var newConstraints = <VersionRange>[];
-    ourConstraints.moveNext();
-    theirConstraints.moveNext();
-    while (ourConstraints.current != null && theirConstraints.current != null) {
-      var intersection = ourConstraints.current
-          .intersect(theirConstraints.current);
+    var newRanges = <VersionRange>[];
+    ourRanges.moveNext();
+    theirRanges.moveNext();
+    while (ourRanges.current != null && theirRanges.current != null) {
+      var intersection = ourRanges.current
+          .intersect(theirRanges.current);
 
-      if (!intersection.isEmpty) newConstraints.add(intersection);
+      if (!intersection.isEmpty) newRanges.add(intersection);
 
       // Move the constraint with the higher max value forward. This ensures
       // that we keep both lists in sync as much as possible, and that large
-      // constraints have a chance to match multiple small constraints that they
-      // contain.
-      if (compareMax(ourConstraints.current, theirConstraints.current) < 0) {
-        ourConstraints.moveNext();
+      // ranges have a chance to match multiple small ranges that they contain.
+      if (compareMax(ourRanges.current, theirRanges.current) < 0) {
+        ourRanges.moveNext();
       } else {
-        theirConstraints.moveNext();
+        theirRanges.moveNext();
       }
     }
 
-    if (newConstraints.isEmpty) return VersionConstraint.empty;
-    if (newConstraints.length == 1) return newConstraints.single;
+    if (newRanges.isEmpty) return VersionConstraint.empty;
+    if (newRanges.length == 1) return newRanges.single;
 
-    return new VersionUnion._(newConstraints);
+    return new VersionUnion.fromRanges(newRanges);
   }
 
-  /// Returns [constraint] as a list of constraints.
+  /// Returns [constraint] as a list of ranges.
   ///
-  /// This is used to normalize constraints of various types.
-  List<VersionRange> _constraintsFor(VersionConstraint constraint) {
+  /// This is used to normalize ranges of various types.
+  List<VersionRange> _rangesFor(VersionConstraint constraint) {
     if (constraint.isEmpty) return [];
-    if (constraint is VersionUnion) return constraint.constraints;
+    if (constraint is VersionUnion) return constraint.ranges;
     if (constraint is VersionRange) return [constraint];
     throw new ArgumentError('Unknown VersionConstraint type $constraint.');
   }
@@ -171,10 +132,10 @@
 
   bool operator ==(other) {
     if (other is! VersionUnion) return false;
-    return const ListEquality().equals(constraints, other.constraints);
+    return const ListEquality().equals(ranges, other.ranges);
   }
 
-  int get hashCode => const ListEquality().hash(constraints);
+  int get hashCode => const ListEquality().hash(ranges);
 
-  String toString() => constraints.join(" or ");
+  String toString() => ranges.join(" or ");
 }
diff --git a/pkgs/pub_semver/pubspec.yaml b/pkgs/pub_semver/pubspec.yaml
index a026e25..53ef4d3 100644
--- a/pkgs/pub_semver/pubspec.yaml
+++ b/pkgs/pub_semver/pubspec.yaml
@@ -1,5 +1,5 @@
 name: pub_semver
-version: 1.2.4
+version: 1.3.0-dev
 author: Dart Team <misc@dartlang.org>
 description: >
  Versions and version constraints implementing pub's versioning policy. This