Fix cast and retype implementations. (#88)

* Fix cast and retype implementations.

* Fix and add a few tests.

* Address feedbakc.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cab5521..e5a8b71 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 1.15.0
+
+* Fixed bugs where `QueueList`, `MapKeySet`, and `MapValueSet` did not adhere to
+  the contract laid out by `List.cast`, `Set.cast` and `Map.cast` respectively.
+  The returned instances of these methods now correctly forward to the existing
+  instance instead of always creating a new copy.
+
 ## 1.14.8
 
 * Deprecated `Delegating{Name}.typed` static methods in favor of the new Dart 2
diff --git a/lib/src/queue_list.dart b/lib/src/queue_list.dart
index cab687a..dcfee7c 100644
--- a/lib/src/queue_list.dart
+++ b/lib/src/queue_list.dart
@@ -10,6 +10,21 @@
 // that are redundant with ListMixin. Remove or simplify it when issue 21330 is
 // fixed.
 class QueueList<E> extends Object with ListMixin<E> implements Queue<E> {
+  /// Adapts [source] to be a `QueueList<T>`.
+  ///
+  /// Any time the class would produce an element that is not a [T], the element
+  /// access will throw.
+  ///
+  /// Any time a [T] value is attempted stored into the adapted class, the store
+  /// will throw unless the value is also an instance of [S].
+  ///
+  /// If all accessed elements of [source] are actually instances of [T] and if
+  /// all elements stored in the returned  are actually instance of [S],
+  /// then the returned instance can be used as a `QueueList<T>`.
+  static QueueList<T> _castFrom<S, T>(QueueList<S> source) {
+    return new _CastQueueList<S, T>(source);
+  }
+
   static const int _INITIAL_CAPACITY = 8;
   List<E> _table;
   int _head;
@@ -31,6 +46,9 @@
     _table = new List<E>(initialCapacity);
   }
 
+  // An internal constructor for use by _CastQueueList.
+  QueueList._();
+
   /// Create a queue initially containing the elements of [source].
   factory QueueList.from(Iterable<E> source) {
     if (source is List) {
@@ -81,13 +99,14 @@
   }
 
   QueueList<T> cast<T>() {
-    if (this is QueueList<T>) {
-      return this as QueueList<T>;
+    QueueList<Object> self = this;
+    if (self is QueueList<T>) {
+      return self;
     }
-    return new QueueList<T>.from(_table.cast<T>());
+    return retype<T>();
   }
 
-  QueueList<T> retype<T>() => new QueueList<T>.from(_table.retype<T>());
+  QueueList<T> retype<T>() => QueueList._castFrom<E, T>(this);
 
   String toString() => IterableBase.iterableToFullString(this, "{", "}");
 
@@ -230,3 +249,17 @@
     _head = 0;
   }
 }
+
+class _CastQueueList<S, T> extends QueueList<T> {
+  final QueueList<S> _delegate;
+
+  _CastQueueList(this._delegate) : super._() {
+    _table = _delegate._table.cast<T>();
+  }
+
+  int get _head => _delegate._head;
+  set _head(int value) => _delegate._head = value;
+
+  int get _tail => _delegate._tail;
+  set _tail(int value) => _delegate._tail = value;
+}
diff --git a/lib/src/wrappers.dart b/lib/src/wrappers.dart
index cfe97c7..cead501 100644
--- a/lib/src/wrappers.dart
+++ b/lib/src/wrappers.dart
@@ -475,7 +475,12 @@
 
   Iterable<E> get _base => _baseMap.keys;
 
-  Set<T> cast<T>() => _baseMap.keys.toSet().cast<T>();
+  Set<T> cast<T>() {
+    if (this is MapKeySet<T>) {
+      return this as MapKeySet<T>;
+    }
+    return Set.castFrom<E, T>(this);
+  }
 
   bool contains(Object element) => _baseMap.containsKey(element);
 
@@ -513,7 +518,7 @@
   E lookup(Object element) =>
       throw new UnsupportedError("MapKeySet doesn't support lookup().");
 
-  Set<T> retype<T>() => _baseMap.keys.toSet().retype<T>();
+  Set<T> retype<T>() => Set.castFrom<E, T>(this);
 
   /// Returns a new set which contains all the elements of [this] and [other].
   ///
@@ -561,7 +566,12 @@
 
   Iterable<V> get _base => _baseMap.values;
 
-  Set<T> cast<T>() => _baseMap.values.toSet().cast<T>();
+  Set<T> cast<T>() {
+    if (this is Set<T>) {
+      return this as Set<T>;
+    }
+    return Set.castFrom<V, T>(this);
+  }
 
   bool contains(Object element) {
     if (element != null && element is! V) return false;
@@ -659,7 +669,7 @@
   void retainWhere(bool test(V element)) =>
       removeWhere((element) => !test(element));
 
-  Set<T> retype<T>() => toSet().retype<T>();
+  Set<T> retype<T>() => Set.castFrom<V, T>(this);
 
   /// Returns a new set which contains all the elements of [this] and [other].
   ///
diff --git a/pubspec.yaml b/pubspec.yaml
index 300fb9c..5f9ca88 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: collection
-version: 1.14.7
+version: 1.15.0
 author: Dart Team <misc@dartlang.org>
 description: Collections and utilities functions and classes related to collections.
 homepage: https://www.github.com/dart-lang/collection
diff --git a/test/queue_list_test.dart b/test/queue_list_test.dart
index d2e7014..ed26a65 100644
--- a/test/queue_list_test.dart
+++ b/test/queue_list_test.dart
@@ -258,6 +258,42 @@
     expect(queue.cast<Pattern>(), same(queue));
   }, skip: isDart2 ? false : 'Requires a Dart2 runtime');
 
+  test("cast does not throw on mutation when the type is valid", () {
+    var patternQueue = new QueueList<Pattern>()..addAll(['a', 'b']);
+    var stringQueue = patternQueue.cast<String>();
+    stringQueue.addAll(['c', 'd']);
+    expect(
+      stringQueue,
+      const isInstanceOf<QueueList<String>>(),
+      reason: 'Expected QueueList<String>, got ${stringQueue.runtimeType}',
+      skip: isDart2 ? false : 'Cast does nothing in Dart1',
+    );
+
+    expect(
+      stringQueue,
+      ['a', 'b', 'c', 'd'],
+      skip: isDart2 ? false : 'Cast does nothing in Dart1',
+    );
+
+    expect(patternQueue, stringQueue, reason: 'Should forward to original');
+  });
+
+  test("cast throws on mutation when the type is not valid", () {
+    QueueList<Object> stringQueue = new QueueList<String>();
+    var numQueue = stringQueue.cast<num>();
+    expect(
+      numQueue,
+      const isInstanceOf<QueueList<num>>(),
+      reason: 'Expected QueueList<num>, got ${numQueue.runtimeType}',
+      skip: isDart2 ? false : 'Cast does nothing in Dart1',
+    );
+    expect(
+      () => numQueue.add(1),
+      throwsCastError,
+      skip: isDart2 ? false : 'In Dart1 a TypeError is not thrown',
+    );
+  });
+
   test("retype returns a new QueueList", () {
     var queue = new QueueList<String>();
     expect(queue.retype<Pattern>(), isNot(same(queue)));