Add type-asserting wrapper constructors.

Closes #25

R=lrn@google.com

Review URL: https://codereview.chromium.org//1840573002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9bca49a..cde274e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,10 @@
-## 1.4.2
+## 1.5.0
+
+* Add `DelegatingIterable.typed()`, `DelegatingList.typed()`,
+  `DelegatingSet.typed()`, `DelegatingMap.typed()`, and
+  `DelegatingQueue.typed()` static methods. These wrap untyped instances of
+  these classes with the correct type parameter, and assert the types of values
+  as they're accessed.
 
 * Fix the types for `binarySearch()` and `lowerBound()` so they no longer
   require all arguments to be comparable.
diff --git a/lib/src/typed_wrappers.dart b/lib/src/typed_wrappers.dart
new file mode 100644
index 0000000..1dcb3b7
--- /dev/null
+++ b/lib/src/typed_wrappers.dart
@@ -0,0 +1,349 @@
+// Copyright (c) 2016, 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 "dart:collection";
+import "dart:math" as math;
+
+import "wrappers.dart";
+
+typedef F _UnaryFunction<E, F>(E argument);
+
+/// The base class for delegating, type-asserting iterables.
+///
+/// Subclasses can provide a [_base] that should be delegated to. Unlike
+/// [TypeSafeIterable], this allows the base to be created on demand.
+abstract class _TypeSafeIterableBase<E> implements Iterable<E> {
+  /// The base iterable to which operations are delegated.
+  Iterable get _base;
+
+  _TypeSafeIterableBase();
+
+  bool any(bool test(E element)) => _base.any(_validate(test));
+
+  bool contains(Object element) => _base.contains(element);
+
+  E elementAt(int index) => _base.elementAt(index) as E;
+
+  bool every(bool test(E element)) => _base.every(_validate(test));
+
+  Iterable/*<T>*/ expand/*<T>*/(Iterable/*<T>*/ f(E element)) =>
+      _base.expand(_validate(f));
+
+  E get first => _base.first as E;
+
+  E firstWhere(bool test(E element), {E orElse()}) =>
+      _base.firstWhere(_validate(test), orElse: orElse) as E;
+
+  /*=T*/ fold/*<T>*/(
+          /*=T*/ initialValue,
+          /*=T*/ combine(/*=T*/ previousValue, E element)) =>
+      _base.fold(initialValue,
+          (previousValue, element) => combine(previousValue, element as E));
+
+  void forEach(void f(E element)) => _base.forEach(_validate(f));
+
+  bool get isEmpty => _base.isEmpty;
+
+  bool get isNotEmpty => _base.isNotEmpty;
+
+  Iterator<E> get iterator => _base.map((element) => element as E).iterator;
+
+  String join([String separator = ""]) => _base.join(separator);
+
+  E get last => _base.last as E;
+
+  E lastWhere(bool test(E element), {E orElse()}) =>
+      _base.lastWhere(_validate(test), orElse: orElse) as E;
+
+  int get length => _base.length;
+
+  Iterable/*<T>*/ map/*<T>*/(/*=T*/ f(E element)) => _base.map(_validate(f));
+
+  E reduce(E combine(E value, E element)) =>
+      _base.reduce((value, element) => combine(value as E, element as E)) as E;
+
+  E get single => _base.single as E;
+
+  E singleWhere(bool test(E element)) =>
+    _base.singleWhere(_validate(test)) as E;
+
+  Iterable<E> skip(int n) => new TypeSafeIterable<E>(_base.skip(n));
+
+  Iterable<E> skipWhile(bool test(E value)) =>
+      new TypeSafeIterable<E>(_base.skipWhile(_validate(test)));
+
+  Iterable<E> take(int n) => new TypeSafeIterable<E>(_base.take(n));
+
+  Iterable<E> takeWhile(bool test(E value)) =>
+      new TypeSafeIterable<E>(_base.takeWhile(_validate(test)));
+
+  List<E> toList({bool growable: true}) =>
+      new TypeSafeList<E>(_base.toList(growable: growable));
+
+  Set<E> toSet() => new TypeSafeSet<E>(_base.toSet());
+
+  Iterable<E> where(bool test(E element)) =>
+      new TypeSafeIterable<E>(_base.where(_validate(test)));
+
+  String toString() => _base.toString();
+
+  /// Returns a version of [function] that asserts that its argument is an
+  /// instance of `E`.
+  _UnaryFunction/*<dynamic, F>*/ _validate/*<F>*/(/*=F*/ function(E value)) =>
+      (value) => function(value as E);
+}
+
+/// An [Iterable] that asserts the types of values in a base iterable.
+///
+/// This is instantiated using [DelegatingIterable.typed].
+class TypeSafeIterable<E> extends _TypeSafeIterableBase<E>
+    implements DelegatingIterable<E> {
+  final Iterable _base;
+
+  TypeSafeIterable(Iterable base) : _base = base;
+}
+
+/// A [List] that asserts the types of values in a base list.
+///
+/// This is instantiated using [DelegatingList.typed].
+class TypeSafeList<E> extends TypeSafeIterable<E> implements DelegatingList<E> {
+  TypeSafeList(List base) : super(base);
+
+  /// A [List]-typed getter for [_base].
+  List get _listBase => _base;
+
+  E operator [](int index) => _listBase[index] as E;
+
+  void operator []=(int index, E value) {
+    _listBase[index] = value;
+  }
+
+  void add(E value) {
+    _listBase.add(value);
+  }
+
+  void addAll(Iterable<E> iterable) {
+    _listBase.addAll(iterable);
+  }
+
+  Map<int, E> asMap() => new TypeSafeMap<int, E>(_listBase.asMap());
+
+  void clear() {
+    _listBase.clear();
+  }
+
+  void fillRange(int start, int end, [E fillValue]) {
+    _listBase.fillRange(start, end, fillValue);
+  }
+
+  Iterable<E> getRange(int start, int end) =>
+      new TypeSafeIterable<E>(_listBase.getRange(start, end));
+
+  int indexOf(E element, [int start = 0]) => _listBase.indexOf(element, start);
+
+  void insert(int index, E element) {
+    _listBase.insert(index, element);
+  }
+
+  void insertAll(int index, Iterable<E> iterable) {
+    _listBase.insertAll(index, iterable);
+  }
+
+  int lastIndexOf(E element, [int start]) =>
+      _listBase.lastIndexOf(element, start);
+
+  void set length(int newLength) {
+    _listBase.length = newLength;
+  }
+
+  bool remove(Object value) => _listBase.remove(value);
+
+  E removeAt(int index) => _listBase.removeAt(index) as E;
+
+  E removeLast() => _listBase.removeLast() as E;
+
+  void removeRange(int start, int end) {
+    _listBase.removeRange(start, end);
+  }
+
+  void removeWhere(bool test(E element)) {
+    _listBase.removeWhere(_validate(test));
+  }
+
+  void replaceRange(int start, int end, Iterable<E> iterable) {
+    _listBase.replaceRange(start, end, iterable);
+  }
+
+  void retainWhere(bool test(E element)) {
+    _listBase.retainWhere(_validate(test));
+  }
+
+  Iterable<E> get reversed => new TypeSafeIterable<E>(_listBase.reversed);
+
+  void setAll(int index, Iterable<E> iterable) {
+    _listBase.setAll(index, iterable);
+  }
+
+  void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
+    _listBase.setRange(start, end, iterable, skipCount);
+  }
+
+  void shuffle([math.Random random]) {
+    _listBase.shuffle(random);
+  }
+
+  void sort([int compare(E a, E b)]) {
+    if (compare == null) {
+      _listBase.sort();
+    } else {
+      _listBase.sort((a, b) => compare(a as E, b as E));
+    }
+  }
+
+  List<E> sublist(int start, [int end]) =>
+      new TypeSafeList<E>(_listBase.sublist(start, end));
+}
+
+/// A [Set] that asserts the types of values in a base set.
+///
+/// This is instantiated using [DelegatingSet.typed].
+class TypeSafeSet<E> extends TypeSafeIterable<E> implements DelegatingSet<E> {
+  TypeSafeSet(Set base) : super(base);
+
+  /// A [Set]-typed getter for [_base].
+  Set get _setBase => _base;
+
+  bool add(E value) => _setBase.add(value);
+
+  void addAll(Iterable<E> elements) {
+    _setBase.addAll(elements);
+  }
+
+  void clear() {
+    _setBase.clear();
+  }
+
+  bool containsAll(Iterable<Object> other) => _setBase.containsAll(other);
+
+  Set<E> difference(Set<E> other) =>
+      new TypeSafeSet<E>(_setBase.difference(other));
+
+  Set<E> intersection(Set<Object> other) =>
+      new TypeSafeSet<E>(_setBase.intersection(other));
+
+  E lookup(Object element) => _setBase.lookup(element) as E;
+
+  bool remove(Object value) => _setBase.remove(value);
+
+  void removeAll(Iterable<Object> elements) {
+    _setBase.removeAll(elements);
+  }
+
+  void removeWhere(bool test(E element)) {
+    _setBase.removeWhere(_validate(test));
+  }
+
+  void retainAll(Iterable<Object> elements) {
+    _setBase.retainAll(elements);
+  }
+
+  void retainWhere(bool test(E element)) {
+    _setBase.retainWhere(_validate(test));
+  }
+
+  Set<E> union(Set<E> other) => new TypeSafeSet<E>(_setBase.union(other));
+}
+
+/// A [Queue] that asserts the types of values in a base queue.
+///
+/// This is instantiated using [DelegatingQueue.typed].
+class TypeSafeQueue<E> extends TypeSafeIterable<E>
+    implements DelegatingQueue<E> {
+  TypeSafeQueue(Queue queue) : super(queue);
+
+  /// A [Queue]-typed getter for [_base].
+  Queue get _baseQueue => _base;
+
+  void add(E value) {
+    _baseQueue.add(value);
+  }
+
+  void addAll(Iterable<E> iterable) {
+    _baseQueue.addAll(iterable);
+  }
+
+  void addFirst(E value) {
+    _baseQueue.addFirst(value);
+  }
+
+  void addLast(E value) {
+    _baseQueue.addLast(value);
+  }
+
+  void clear() {
+    _baseQueue.clear();
+  }
+
+  bool remove(Object object) => _baseQueue.remove(object);
+
+  void removeWhere(bool test(E element)) {
+    _baseQueue.removeWhere(_validate(test));
+  }
+
+  void retainWhere(bool test(E element)) {
+    _baseQueue.retainWhere(_validate(test));
+  }
+
+  E removeFirst() => _baseQueue.removeFirst() as E;
+
+  E removeLast() => _baseQueue.removeLast() as E;
+}
+
+/// A [Map] that asserts the types of keys and values in a base map.
+///
+/// This is instantiated using [DelegatingMap.typed].
+class TypeSafeMap<K, V> implements DelegatingMap<K, V> {
+  /// The base map to which operations are delegated.
+  final Map _base;
+
+  TypeSafeMap(Map base) : _base = base;
+
+  V operator [](Object key) => _base[key] as V;
+
+  void operator []=(K key, V value) {
+    _base[key] = value;
+  }
+
+  void addAll(Map<K, V> other) {
+    _base.addAll(other);
+  }
+
+  void clear() {
+    _base.clear();
+  }
+
+  bool containsKey(Object key) => _base.containsKey(key);
+
+  bool containsValue(Object value) => _base.containsValue(value);
+
+  void forEach(void f(K key, V value)) {
+    _base.forEach((key, value) => f(key as K, value as V));
+  }
+
+  bool get isEmpty => _base.isEmpty;
+
+  bool get isNotEmpty => _base.isNotEmpty;
+
+  Iterable<K> get keys => new TypeSafeIterable<K>(_base.keys);
+
+  int get length => _base.length;
+
+  V putIfAbsent(K key, V ifAbsent()) => _base.putIfAbsent(key, ifAbsent) as V;
+
+  V remove(Object key) => _base.remove(key) as V;
+
+  Iterable<V> get values => new TypeSafeIterable<V>(_base.values);
+
+  String toString() => _base.toString();
+}
diff --git a/lib/src/wrappers.dart b/lib/src/wrappers.dart
index fcd3315..c447427 100644
--- a/lib/src/wrappers.dart
+++ b/lib/src/wrappers.dart
@@ -5,6 +5,7 @@
 import "dart:collection";
 import "dart:math" as math;
 
+import "typed_wrappers.dart";
 import "unmodifiable_wrappers.dart";
 
 typedef K _KeyForValue<K, V>(V value);
@@ -81,27 +82,53 @@
   String toString() => _base.toString();
 }
 
-/// Creates an [Iterable] that delegates all operations to a base iterable.
+/// An [Iterable] that delegates all operations to a base iterable.
 ///
-/// This class can be used hide non-`Iterable` methods of an iterable object,
+/// This class can be used to hide non-`Iterable` methods of an iterable object,
 /// or it can be extended to add extra functionality on top of an existing
 /// iterable object.
 class DelegatingIterable<E> extends _DelegatingIterableBase<E> {
   final Iterable<E> _base;
 
-  /// Create a wrapper that forwards operations to [base].
+  /// Creates a wrapper that forwards operations to [base].
   const DelegatingIterable(Iterable<E> base) : _base = base;
+
+  /// Creates a wrapper that asserts the types of values in [base].
+  ///
+  /// This soundly converts an [Iterable] without a generic type to an
+  /// `Iterable<E>` by asserting that its elements are instances of `E` whenever
+  /// they're accessed. If they're not, it throws a [CastError].
+  ///
+  /// This forwards all operations to [base], so any changes in [base] will be
+  /// reflected in [this]. If [base] is already an `Iterable<E>`, it's returned
+  /// unmodified.
+  static Iterable/*<E>*/ typed/*<E>*/(Iterable base) =>
+      base is Iterable/*<E>*/ ? base : new TypedIterable/*<E>*/(base);
 }
 
 
-/// Creates a [List] that delegates all operations to a base list.
+/// A [List] that delegates all operations to a base list.
 ///
-/// This class can be used hide non-`List` methods of a list object,
-/// or it can be extended to add extra functionality on top of an existing
-/// list object.
+/// This class can be used to hide non-`List` methods of a list object, or it
+/// can be extended to add extra functionality on top of an existing list
+/// object.
 class DelegatingList<E> extends DelegatingIterable<E> implements List<E> {
   const DelegatingList(List<E> base) : super(base);
 
+  /// Creates a wrapper that asserts the types of values in [base].
+  ///
+  /// This soundly converts a [List] without a generic type to a `List<E>` by
+  /// asserting that its elements are instances of `E` whenever they're
+  /// accessed. If they're not, it throws a [CastError]. Note that even if an
+  /// operation throws a [CastError], it may still mutate the underlying
+  /// collection.
+  ///
+  /// This forwards all operations to [base], so any changes in [base] will be
+  /// reflected in [this]. If [base] is already a `List<E>`, it's returned
+  /// unmodified.
+  static List/*<E>*/ typed/*<E>*/(List base) =>
+      base is List/*<E>*/ ? base : new TypedList/*<E>*/(base);
+
   List<E> get _listBase => _base;
 
   E operator [](int index) => _listBase[index];
@@ -191,14 +218,27 @@
 }
 
 
-/// Creates a [Set] that delegates all operations to a base set.
+/// A [Set] that delegates all operations to a base set.
 ///
-/// This class can be used hide non-`Set` methods of a set object,
-/// or it can be extended to add extra functionality on top of an existing
-/// set object.
+/// This class can be used to hide non-`Set` methods of a set object, or it can
+/// be extended to add extra functionality on top of an existing set object.
 class DelegatingSet<E> extends DelegatingIterable<E> implements Set<E> {
   const DelegatingSet(Set<E> base) : super(base);
 
+  /// Creates a wrapper that asserts the types of values in [base].
+  ///
+  /// This soundly converts a [Set] without a generic type to a `Set<E>` by
+  /// asserting that its elements are instances of `E` whenever they're
+  /// accessed. If they're not, it throws a [CastError]. Note that even if an
+  /// operation throws a [CastError], it may still mutate the underlying
+  /// collection.
+  ///
+  /// This forwards all operations to [base], so any changes in [base] will be
+  /// reflected in [this]. If [base] is already a `Set<E>`, it's returned
+  /// unmodified.
+  static Set/*<E>*/ typed/*<E>*/(Set base) =>
+      base is Set/*<E>*/ ? base : new TypedSet/*<E>*/(base);
+
   Set<E> get _setBase => _base;
 
   bool add(E value) => _setBase.add(value);
@@ -242,14 +282,28 @@
   Set<E> toSet() => new DelegatingSet<E>(_setBase.toSet());
 }
 
-/// Creates a [Queue] that delegates all operations to a base queue.
+/// A [Queue] that delegates all operations to a base queue.
 ///
-/// This class can be used hide non-`Queue` methods of a queue object,
-/// or it can be extended to add extra functionality on top of an existing
-/// queue object.
+/// This class can be used to hide non-`Queue` methods of a queue object, or it
+/// can be extended to add extra functionality on top of an existing queue
+/// object.
 class DelegatingQueue<E> extends DelegatingIterable<E> implements Queue<E> {
   const DelegatingQueue(Queue<E> queue) : super(queue);
 
+  /// Creates a wrapper that asserts the types of values in [base].
+  ///
+  /// This soundly converts a [Queue] without a generic type to a `Queue<E>` by
+  /// asserting that its elements are instances of `E` whenever they're
+  /// accessed. If they're not, it throws a [CastError]. Note that even if an
+  /// operation throws a [CastError], it may still mutate the underlying
+  /// collection.
+  ///
+  /// This forwards all operations to [base], so any changes in [base] will be
+  /// reflected in [this]. If [base] is already a `Queue<E>`, it's returned
+  /// unmodified.
+  static Queue/*<E>*/ typed/*<E>*/(Queue base) =>
+      base is Queue/*<E>*/ ? base : new TypedQueue/*<E>*/(base);
+
   Queue<E> get _baseQueue => _base;
 
   void add(E value) {
@@ -283,9 +337,9 @@
   E removeLast() => _baseQueue.removeLast();
 }
 
-/// Creates a [Map] that delegates all operations to a base map.
+/// A [Map] that delegates all operations to a base map.
 ///
-/// This class can be used hide non-`Map` methods of an object that extends
+/// This class can be used to hide non-`Map` methods of an object that extends
 /// `Map`, or it can be extended to add extra functionality on top of an
 /// existing map object.
 class DelegatingMap<K, V> implements Map<K, V> {
@@ -293,6 +347,20 @@
 
   const DelegatingMap(Map<K, V> base) : _base = base;
 
+  /// Creates a wrapper that asserts the types of keys and values in [base].
+  ///
+  /// This soundly converts a [Map] without generic types to a `Map<K, V>` by
+  /// asserting that its keys are instances of `E` and its values are instances
+  /// of `V` whenever they're accessed. If they're not, it throws a [CastError].
+  /// Note that even if an operation throws a [CastError], it may still mutate
+  /// the underlying collection.
+  ///
+  /// This forwards all operations to [base], so any changes in [base] will be
+  /// reflected in [this]. If [base] is already a `Map<K, V>`, it's returned
+  /// unmodified.
+  static Map/*<K, V>*/ typed/*<K, V>*/(Map base) =>
+      base is Map<K, V> ? base : new TypedMap<K, V>(base);
+
   V operator [](Object key) => _base[key];
 
   void operator []=(K key, V value) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 6f1d641..5023dbe 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: collection
-version: 1.4.2
+version: 1.5.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/typed_wrapper/iterable_test.dart b/test/typed_wrapper/iterable_test.dart
new file mode 100644
index 0000000..3614ba4
--- /dev/null
+++ b/test/typed_wrapper/iterable_test.dart
@@ -0,0 +1,355 @@
+// Copyright (c) 2016, 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:collection/collection.dart";
+import "package:test/test.dart";
+
+import '../utils.dart';
+
+void main() {
+  group("with valid types, forwards", () {
+    var wrapper;
+    var emptyWrapper;
+    var singleWrapper;
+    setUp(() {
+      wrapper = DelegatingIterable.typed/*<int>*/(
+          <Object>[1, 2, 3, 4, 5].map((i) => i));
+      emptyWrapper = DelegatingIterable.typed/*<int>*/(
+          <Object>[].map((i) => i));
+      singleWrapper = DelegatingIterable.typed/*<int>*/(
+          <Object>[1].map((i) => i));
+    });
+
+    test("any()", () {
+      expect(wrapper.any((i) => i > 3), isTrue);
+      expect(wrapper.any((i) => i > 5), isFalse);
+    });
+
+    test("contains()", () {
+      expect(wrapper.contains(2), isTrue);
+      expect(wrapper.contains(6), isFalse);
+      expect(wrapper.contains("foo"), isFalse);
+    });
+
+    test("elementAt()", () {
+      expect(wrapper.elementAt(1), equals(2));
+      expect(wrapper.elementAt(4), equals(5));
+      expect(() => wrapper.elementAt(5), throwsRangeError);
+      expect(() => wrapper.elementAt(-1), throwsRangeError);
+    });
+
+    test("every()", () {
+      expect(wrapper.every((i) => i < 6), isTrue);
+      expect(wrapper.every((i) => i > 3), isFalse);
+    });
+
+    test("expand()", () {
+      expect(wrapper.expand((i) => [i]), equals([1, 2, 3, 4, 5]));
+      expect(wrapper.expand((i) => [i, i]),
+          equals([1, 1, 2, 2, 3, 3, 4, 4, 5, 5]));
+    });
+
+    test("first", () {
+      expect(wrapper.first, equals(1));
+      expect(() => emptyWrapper.first, throwsStateError);
+    });
+
+    test("firstWhere()", () {
+      expect(wrapper.firstWhere((i) => i > 3), equals(4));
+      expect(() => wrapper.firstWhere((i) => i > 5), throwsStateError);
+      expect(wrapper.firstWhere((i) => i > 5, orElse: () => -1), equals(-1));
+    });
+
+    test("fold()", () {
+      expect(wrapper.fold("", (previous, i) => previous + i.toString()),
+          equals("12345"));
+      expect(emptyWrapper.fold(null, (previous, i) => previous + i), isNull);
+    });
+
+    test("forEach()", () {
+      var results = [];
+      wrapper.forEach(results.add);
+      expect(results, equals([1, 2, 3, 4, 5]));
+
+      emptyWrapper.forEach(expectAsync((_) {}, count: 0));
+    });
+
+    test("isEmpty", () {
+      expect(wrapper.isEmpty, isFalse);
+      expect(emptyWrapper.isEmpty, isTrue);
+    });
+
+    test("isNotEmpty", () {
+      expect(wrapper.isNotEmpty, isTrue);
+      expect(emptyWrapper.isNotEmpty, isFalse);
+    });
+
+    test("iterator", () {
+      var iterator = wrapper.iterator;
+      expect(iterator.current, isNull);
+      expect(iterator.moveNext(), isTrue);
+      expect(iterator.current, equals(1));
+      expect(iterator.moveNext(), isTrue);
+      expect(iterator.current, equals(2));
+      expect(iterator.moveNext(), isTrue);
+      expect(iterator.current, equals(3));
+      expect(iterator.moveNext(), isTrue);
+      expect(iterator.current, equals(4));
+      expect(iterator.moveNext(), isTrue);
+      expect(iterator.current, equals(5));
+      expect(iterator.moveNext(), isFalse);
+      expect(iterator.current, isNull);
+    });
+
+    test("join()", () {
+      expect(wrapper.join(), "12345");
+      expect(wrapper.join("-"), "1-2-3-4-5");
+    });
+
+    test("last", () {
+      expect(wrapper.last, equals(5));
+      expect(() => emptyWrapper.last, throwsStateError);
+    });
+
+    test("lastWhere()", () {
+      expect(wrapper.lastWhere((i) => i > 3), equals(5));
+      expect(() => wrapper.lastWhere((i) => i > 5), throwsStateError);
+      expect(wrapper.lastWhere((i) => i > 5, orElse: () => -1), equals(-1));
+    });
+
+    test("length", () {
+      expect(wrapper.length, equals(5));
+      expect(emptyWrapper.length, equals(0));
+    });
+
+    test("map()", () {
+      expect(wrapper.map((i) => i + 1), equals([2, 3, 4, 5, 6]));
+      expect(wrapper.map((i) => i / 2), equals([0.5, 1.0, 1.5, 2.0, 2.5]));
+    });
+
+    test("reduce()", () {
+      expect(wrapper.reduce((value, i) => value + i), equals(15));
+      expect(() => emptyWrapper.reduce((value, i) => value + i),
+          throwsStateError);
+    });
+
+    test("single", () {
+      expect(() => wrapper.single, throwsStateError);
+      expect(singleWrapper.single, equals(1));
+    });
+
+    test("singleWhere()", () {
+      expect(() => wrapper.singleWhere((i) => i.isOdd), throwsStateError);
+      expect(singleWrapper.singleWhere((i) => i.isOdd), equals(1));
+      expect(() => singleWrapper.singleWhere((i) => i.isEven),
+          throwsStateError);
+    });
+
+    test("skip()", () {
+      expect(wrapper.skip(3), equals([4, 5]));
+      expect(wrapper.skip(10), isEmpty);
+      expect(() => wrapper.skip(-1), throwsRangeError);
+    });
+
+    test("skipWhile()", () {
+      expect(wrapper.skipWhile((i) => i < 3), equals([3, 4, 5]));
+      expect(wrapper.skipWhile((i) => i < 10), isEmpty);
+    });
+
+    test("take()", () {
+      expect(wrapper.take(3), equals([1, 2, 3]));
+      expect(wrapper.take(10), equals([1, 2, 3, 4, 5]));
+      expect(() => wrapper.take(-1), throwsRangeError);
+    });
+
+    test("takeWhile()", () {
+      expect(wrapper.takeWhile((i) => i < 3), equals([1, 2]));
+      expect(wrapper.takeWhile((i) => i < 10), equals([1, 2, 3, 4, 5]));
+    });
+
+    test("toList()", () {
+      expect(wrapper.toList(), equals([1, 2, 3, 4, 5]));
+      expect(wrapper.toList(growable: false), equals([1, 2, 3, 4, 5]));
+      expect(() => wrapper.toList(growable: false).add(6),
+          throwsUnsupportedError);
+    });
+
+    test("toSet()", () {
+      expect(wrapper.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
+      expect(DelegatingIterable.typed/*<int>*/(<Object>[1, 1, 2, 2]).toSet(),
+          unorderedEquals([1, 2]));
+    });
+
+    test("where()", () {
+      expect(wrapper.where((i) => i.isOdd), equals([1, 3, 5]));
+      expect(wrapper.where((i) => i.isEven), equals([2, 4]));
+    });
+
+    test("toString()", () {
+      expect(wrapper.toString(), equals("(1, 2, 3, 4, 5)"));
+      expect(emptyWrapper.toString(), equals("()"));
+    });
+  });
+
+  group("with invalid types", () {
+    var wrapper;
+    var singleWrapper;
+    setUp(() {
+      wrapper = DelegatingIterable.typed/*<int>*/(
+          <Object>["foo", "bar", "baz"].map((element) => element));
+      singleWrapper = DelegatingIterable.typed/*<int>*/(
+          <Object>["foo"].map((element) => element));
+    });
+
+    group("throws a CastError for", () {
+      test("any()", () {
+        expect(() => wrapper.any(expectAsync((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("elementAt()", () {
+        expect(() => wrapper.elementAt(1), throwsCastError);
+      });
+
+      test("every()", () {
+        expect(() => wrapper.every(expectAsync((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("expand()", () {
+        var lazy = wrapper.expand(expectAsync((_) => [], count: 0));
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("first", () {
+        expect(() => wrapper.first, throwsCastError);
+      });
+
+      test("firstWhere()", () {
+        expect(() => wrapper.firstWhere(expectAsync((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("fold()", () {
+        expect(() => wrapper.fold(null, expectAsync((_, __) => null, count: 0)),
+            throwsCastError);
+      });
+
+      test("forEach()", () {
+        expect(() => wrapper.forEach(expectAsync((_) {}, count: 0)),
+            throwsCastError);
+      });
+
+      test("iterator", () {
+        var iterator = wrapper.iterator;
+        expect(iterator.current, isNull);
+        expect(() => iterator.moveNext(), throwsCastError);
+      });
+
+      test("last", () {
+        expect(() => wrapper.last, throwsCastError);
+      });
+
+      test("lastWhere()", () {
+        expect(() => wrapper.lastWhere(expectAsync((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("map()", () {
+        var lazy = wrapper.map(expectAsync((_) => null, count: 0));
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("reduce()", () {
+        expect(() => wrapper.reduce(expectAsync((_, __) => null, count: 0)),
+            throwsCastError);
+      });
+
+      test("single", () {
+        expect(() => singleWrapper.single, throwsCastError);
+      });
+
+      test("singleWhere()", () {
+        expect(() {
+          singleWrapper.singleWhere(expectAsync((_) => false, count: 0));
+        }, throwsCastError);
+      });
+
+      test("skip()", () {
+        var lazy = wrapper.skip(1);
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("skipWhile()", () {
+        var lazy = wrapper.skipWhile(expectAsync((_) => false, count: 0));
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("take()", () {
+        var lazy = wrapper.take(1);
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("takeWhile()", () {
+        var lazy = wrapper.takeWhile(expectAsync((_) => false, count: 0));
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("toList()", () {
+        var list = wrapper.toList();
+        expect(() => list.first, throwsCastError);
+      });
+
+      test("toSet()", () {
+        var asSet = wrapper.toSet();
+        expect(() => asSet.first, throwsCastError);
+      });
+
+      test("where()", () {
+        var lazy = wrapper.where(expectAsync((_) => false, count: 0));
+        expect(() => lazy.first, throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("contains()", () {
+        expect(wrapper.contains(1), isFalse);
+        expect(wrapper.contains("foo"), isTrue);
+      });
+
+      test("elementAt()", () {
+        expect(() => wrapper.elementAt(-1), throwsRangeError);
+        expect(() => wrapper.elementAt(10), throwsRangeError);
+      });
+
+      test("isEmpty", () {
+        expect(wrapper.isEmpty, isFalse);
+      });
+
+      test("isNotEmpty", () {
+        expect(wrapper.isNotEmpty, isTrue);
+      });
+
+      test("join()", () {
+        expect(wrapper.join(), "foobarbaz");
+      });
+
+      test("length", () {
+        expect(wrapper.length, equals(3));
+      });
+
+      test("single", () {
+        expect(() => wrapper.single, throwsStateError);
+      });
+
+      test("skip()", () {
+        expect(() => wrapper.skip(-1), throwsRangeError);
+      });
+
+      test("toString()", () {
+        expect(wrapper.toString(), equals("(foo, bar, baz)"));
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+}
diff --git a/test/typed_wrapper/list_test.dart b/test/typed_wrapper/list_test.dart
new file mode 100644
index 0000000..5dd81ca
--- /dev/null
+++ b/test/typed_wrapper/list_test.dart
@@ -0,0 +1,421 @@
+// Copyright (c) 2016, 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 'dart:math' as math;
+
+import "package:collection/collection.dart";
+import "package:test/test.dart";
+
+import '../utils.dart';
+
+void main() {
+  group("with valid types, forwards", () {
+    var wrapper;
+    var emptyWrapper;
+    setUp(() {
+      wrapper = DelegatingList.typed/*<int>*/(<Object>[1, 2, 3, 4, 5]);
+      emptyWrapper = DelegatingList.typed/*<int>*/(<Object>[]);
+    });
+
+    test("[]", () {
+      expect(wrapper[0], equals(1));
+      expect(wrapper[1], equals(2));
+      expect(() => wrapper[-1], throwsRangeError);
+      expect(() => wrapper[10], throwsRangeError);
+    });
+
+    test("[]=", () {
+      wrapper[1] = 10;
+      wrapper[3] = 15;
+      expect(wrapper, equals([1, 10, 3, 15, 5]));
+      expect(() => wrapper[-1] = 10, throwsRangeError);
+      expect(() => wrapper[10] = 10, throwsRangeError);
+    });
+
+    test("add()", () {
+      wrapper.add(6);
+      wrapper.add(7);
+      expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7]));
+    });
+
+    test("addAll()", () {
+      wrapper.addAll([6, 7, 8]);
+      expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7, 8]));
+    });
+
+    test("asMap()", () {
+      expect(wrapper.asMap(), equals({0: 1, 1: 2, 2: 3, 3: 4, 4: 5}));
+    });
+
+    test("clear()", () {
+      wrapper.clear();
+      expect(wrapper, isEmpty);
+    });
+
+    test("fillRange()", () {
+      wrapper.fillRange(2, 4);
+      expect(wrapper, equals([1, 2, null, null, 5]));
+
+      wrapper.fillRange(1, 2, 7);
+      expect(wrapper, equals([1, 7, null, null, 5]));
+
+      expect(() => wrapper.fillRange(-1, 2), throwsRangeError);
+      expect(() => wrapper.fillRange(1, 10), throwsRangeError);
+      expect(() => wrapper.fillRange(4, 2), throwsRangeError);
+      expect(() => wrapper.fillRange(10, 12), throwsRangeError);
+    });
+
+    test("getRange()", () {
+      expect(wrapper.getRange(2, 4), equals([3, 4]));
+      expect(wrapper.getRange(1, 2), equals([2]));
+
+      expect(() => wrapper.getRange(-1, 2), throwsRangeError);
+      expect(() => wrapper.getRange(1, 10), throwsRangeError);
+      expect(() => wrapper.getRange(4, 2), throwsRangeError);
+      expect(() => wrapper.getRange(10, 12), throwsRangeError);
+    });
+
+    test("indexOf()", () {
+      expect(wrapper.indexOf(4), equals(3));
+      expect(wrapper.indexOf(10), equals(-1));
+      expect(wrapper.indexOf(4, 2), equals(3));
+      expect(wrapper.indexOf(4, 4), equals(-1));
+    });
+
+    test("insert()", () {
+      wrapper.insert(3, 10);
+      expect(wrapper, equals([1, 2, 3, 10, 4, 5]));
+
+      wrapper.insert(0, 15);
+      expect(wrapper, equals([15, 1, 2, 3, 10, 4, 5]));
+
+      expect(() => wrapper.insert(-1, 0), throwsRangeError);
+      expect(() => wrapper.insert(10, 0), throwsRangeError);
+    });
+
+    test("insertAll()", () {
+      wrapper.insertAll(3, [10, 11, 12]);
+      expect(wrapper, equals([1, 2, 3, 10, 11, 12, 4, 5]));
+
+      wrapper.insertAll(0, [15, 16, 17]);
+      expect(wrapper, equals([15, 16, 17, 1, 2, 3, 10, 11, 12, 4, 5]));
+
+      expect(() => wrapper.insertAll(-1, []), throwsRangeError);
+      expect(() => wrapper.insertAll(100, []), throwsRangeError);
+    });
+
+    test("lastIndexOf()", () {
+      expect(wrapper.lastIndexOf(4), equals(3));
+      expect(wrapper.lastIndexOf(10), equals(-1));
+      expect(wrapper.lastIndexOf(4, 4), equals(3));
+      expect(wrapper.lastIndexOf(4, 2), equals(-1));
+    });
+
+    test("length=", () {
+      wrapper.length = 10;
+      expect(wrapper, equals([1, 2, 3, 4, 5, null, null, null, null, null]));
+
+      wrapper.length = 3;
+      expect(wrapper, equals([1, 2, 3]));
+    });
+
+    test("remove()", () {
+      expect(wrapper.remove(3), isTrue);
+      expect(wrapper, equals([1, 2, 4, 5]));
+
+      expect(wrapper.remove(3), isFalse);
+      expect(wrapper.remove("foo"), isFalse);
+    });
+
+    test("removeAt()", () {
+      expect(wrapper.removeAt(3), equals(4));
+      expect(wrapper, equals([1, 2, 3, 5]));
+
+      expect(() => wrapper.removeAt(-1), throwsRangeError);
+      expect(() => wrapper.removeAt(10), throwsRangeError);
+    });
+
+    test("removeLast()", () {
+      expect(wrapper.removeLast(), equals(5));
+      expect(wrapper, equals([1, 2, 3, 4]));
+
+      // See sdk#26087. We want this to pass with the current implementation and
+      // with the fix.
+      expect(() => emptyWrapper.removeLast(),
+          anyOf(throwsStateError, throwsRangeError));
+    });
+
+    test("removeRange()", () {
+      wrapper.removeRange(2, 4);
+      expect(wrapper, equals([1, 2, 5]));
+
+      expect(() => wrapper.removeRange(-1, 2), throwsRangeError);
+      expect(() => wrapper.removeRange(1, 10), throwsRangeError);
+      expect(() => wrapper.removeRange(4, 2), throwsRangeError);
+      expect(() => wrapper.removeRange(10, 12), throwsRangeError);
+    });
+
+    test("removeWhere()", () {
+      wrapper.removeWhere((i) => i.isOdd);
+      expect(wrapper, equals([2, 4]));
+    });
+
+    test("replaceRange()", () {
+      wrapper.replaceRange(2, 4, [10, 11, 12]);
+      expect(wrapper, equals([1, 2, 10, 11, 12, 5]));
+
+      expect(() => wrapper.replaceRange(-1, 2, []), throwsRangeError);
+      expect(() => wrapper.replaceRange(1, 10, []), throwsRangeError);
+      expect(() => wrapper.replaceRange(4, 2, []), throwsRangeError);
+      expect(() => wrapper.replaceRange(10, 12, []), throwsRangeError);
+    });
+
+    test("retainWhere()", () {
+      wrapper.retainWhere((i) => i.isOdd);
+      expect(wrapper, equals([1, 3, 5]));
+    });
+
+    test("reversed", () {
+      expect(wrapper.reversed, equals([5, 4, 3, 2, 1]));
+    });
+
+    test("setAll()", () {
+      wrapper.setAll(2, [10, 11]);
+      expect(wrapper, equals([1, 2, 10, 11, 5]));
+
+      expect(() => wrapper.setAll(-1, []), throwsRangeError);
+      expect(() => wrapper.setAll(10, []), throwsRangeError);
+    });
+
+    test("setRange()", () {
+      wrapper.setRange(2, 4, [10, 11, 12]);
+      expect(wrapper, equals([1, 2, 10, 11, 5]));
+
+      wrapper.setRange(2, 4, [10, 11, 12], 1);
+      expect(wrapper, equals([1, 2, 11, 12, 5]));
+
+      expect(() => wrapper.setRange(-1, 2, []), throwsRangeError);
+      expect(() => wrapper.setRange(1, 10, []), throwsRangeError);
+      expect(() => wrapper.setRange(4, 2, []), throwsRangeError);
+      expect(() => wrapper.setRange(10, 12, []), throwsRangeError);
+      expect(() => wrapper.setRange(2, 4, []), throwsStateError);
+    });
+
+    test("shuffle()", () {
+      wrapper.shuffle(new math.Random(1234));
+      expect(wrapper, equals([1, 2, 3, 4, 5]..shuffle(new math.Random(1234))));
+    });
+
+    test("sort()", () {
+      wrapper.sort((a, b) => b.compareTo(a));
+      expect(wrapper, equals([5, 4, 3, 2, 1]));
+
+      wrapper.sort();
+      expect(wrapper, equals([1, 2, 3, 4, 5]));
+    });
+
+    test("sublist()", () {
+      expect(wrapper.sublist(2), equals([3, 4, 5]));
+      expect(wrapper.sublist(2, 4), equals([3, 4]));
+    });
+  });
+
+  group("with invalid types", () {
+    var inner;
+    var wrapper;
+    setUp(() {
+      inner = <Object>["foo", "bar", "baz"];
+      wrapper = DelegatingList.typed/*<int>*/(inner);
+    });
+
+    group("throws a CastError for", () {
+      test("[]", () {
+        expect(() => wrapper[0], throwsCastError);
+      });
+
+      test("asMap()", () {
+        var map = wrapper.asMap();
+        expect(() => map[1], throwsCastError);
+      });
+
+      test("getRange()", () {
+        var lazy = wrapper.getRange(1, 2);
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("removeAt()", () {
+        expect(() => wrapper.removeAt(2), throwsCastError);
+      });
+
+      test("removeLast()", () {
+        expect(() => wrapper.removeLast(), throwsCastError);
+      });
+
+      test("removeWhere()", () {
+        expect(() => wrapper.removeWhere(expectAsync((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("retainWhere()", () {
+        expect(() => wrapper.retainWhere(expectAsync((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("reversed", () {
+        var lazy = wrapper.reversed;
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("sort()", () {
+        expect(() => wrapper.sort(expectAsync((_, __) => 0, count: 0)),
+            throwsCastError);
+      });
+
+      test("sublist()", () {
+        var list = wrapper.sublist(1);
+        expect(() => list[0], throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("[]", () {
+        expect(() => wrapper[-1], throwsRangeError);
+        expect(() => wrapper[10], throwsRangeError);
+      });
+
+      test("[]=", () {
+        wrapper[1] = 10;
+        expect(inner, equals(["foo", 10, "baz"]));
+        expect(() => wrapper[-1] = 10, throwsRangeError);
+        expect(() => wrapper[10] = 10, throwsRangeError);
+      });
+
+      test("add()", () {
+        wrapper.add(6);
+        wrapper.add(7);
+        expect(inner, equals(["foo", "bar", "baz", 6, 7]));
+      });
+
+      test("addAll()", () {
+        wrapper.addAll([6, 7, 8]);
+        expect(inner, equals(["foo", "bar", "baz", 6, 7, 8]));
+      });
+
+      test("clear()", () {
+        wrapper.clear();
+        expect(wrapper, isEmpty);
+      });
+
+      test("fillRange()", () {
+        wrapper.fillRange(1, 3, 7);
+        expect(inner, equals(["foo", 7, 7]));
+
+        expect(() => wrapper.fillRange(-1, 2), throwsRangeError);
+        expect(() => wrapper.fillRange(1, 10), throwsRangeError);
+        expect(() => wrapper.fillRange(4, 2), throwsRangeError);
+        expect(() => wrapper.fillRange(10, 12), throwsRangeError);
+      });
+
+      test("getRange()", () {
+        expect(() => wrapper.getRange(-1, 2), throwsRangeError);
+        expect(() => wrapper.getRange(1, 10), throwsRangeError);
+        expect(() => wrapper.getRange(4, 2), throwsRangeError);
+        expect(() => wrapper.getRange(10, 12), throwsRangeError);
+      });
+
+      test("indexOf()", () {
+        expect(wrapper.indexOf(4), equals(-1));
+      });
+
+      test("insert()", () {
+        wrapper.insert(0, 15);
+        expect(inner, equals([15, "foo", "bar", "baz"]));
+
+        expect(() => wrapper.insert(-1, 0), throwsRangeError);
+        expect(() => wrapper.insert(10, 0), throwsRangeError);
+      });
+
+      test("insertAll()", () {
+        wrapper.insertAll(0, [15, 16, 17]);
+        expect(inner, equals([15, 16, 17, "foo", "bar", "baz"]));
+
+        expect(() => wrapper.insertAll(-1, []), throwsRangeError);
+        expect(() => wrapper.insertAll(100, []), throwsRangeError);
+      });
+
+      test("lastIndexOf()", () {
+        expect(wrapper.lastIndexOf(4), equals(-1));
+      });
+
+      test("length=", () {
+        wrapper.length = 5;
+        expect(inner, equals(["foo", "bar", "baz", null, null]));
+
+        wrapper.length = 1;
+        expect(inner, equals(["foo"]));
+      });
+
+      test("remove()", () {
+        expect(wrapper.remove(3), isFalse);
+        expect(wrapper.remove("foo"), isTrue);
+        expect(inner, equals(["bar", "baz"]));
+      });
+
+      test("removeAt()", () {
+        expect(() => wrapper.removeAt(-1), throwsRangeError);
+        expect(() => wrapper.removeAt(10), throwsRangeError);
+      });
+
+      test("removeRange()", () {
+        wrapper.removeRange(1, 3);
+        expect(inner, equals(["foo"]));
+
+        expect(() => wrapper.removeRange(-1, 2), throwsRangeError);
+        expect(() => wrapper.removeRange(1, 10), throwsRangeError);
+        expect(() => wrapper.removeRange(4, 2), throwsRangeError);
+        expect(() => wrapper.removeRange(10, 12), throwsRangeError);
+      });
+
+      test("replaceRange()", () {
+        wrapper.replaceRange(1, 2, [10, 11, 12]);
+        expect(inner, equals(["foo", 10, 11, 12, "baz"]));
+
+        expect(() => wrapper.replaceRange(-1, 2, []), throwsRangeError);
+        expect(() => wrapper.replaceRange(1, 10, []), throwsRangeError);
+        expect(() => wrapper.replaceRange(4, 2, []), throwsRangeError);
+        expect(() => wrapper.replaceRange(10, 12, []), throwsRangeError);
+      });
+
+      test("setAll()", () {
+        wrapper.setAll(1, [10, 11]);
+        expect(inner, equals(["foo", 10, 11]));
+
+        expect(() => wrapper.setAll(-1, []), throwsRangeError);
+        expect(() => wrapper.setAll(10, []), throwsRangeError);
+      });
+
+      test("setRange()", () {
+        wrapper.setRange(1, 2, [10, 11, 12]);
+        expect(inner, equals(["foo", 10, "baz"]));
+
+        expect(() => wrapper.setRange(-1, 2, []), throwsRangeError);
+        expect(() => wrapper.setRange(1, 10, []), throwsRangeError);
+        expect(() => wrapper.setRange(4, 2, []), throwsRangeError);
+        expect(() => wrapper.setRange(10, 12, []), throwsRangeError);
+        expect(() => wrapper.setRange(1, 2, []), throwsStateError);
+      });
+
+      test("shuffle()", () {
+        wrapper.shuffle(new math.Random(1234));
+        expect(inner,
+            equals(["foo", "bar", "baz"]..shuffle(new math.Random(1234))));
+      });
+
+      test("sort()", () {
+        wrapper.sort();
+        expect(inner, equals(["bar", "baz", "foo"]));
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+}
diff --git a/test/typed_wrapper/map_test.dart b/test/typed_wrapper/map_test.dart
new file mode 100644
index 0000000..3546ae8
--- /dev/null
+++ b/test/typed_wrapper/map_test.dart
@@ -0,0 +1,315 @@
+// Copyright (c) 2016, 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:collection/collection.dart";
+import "package:test/test.dart";
+
+import '../utils.dart';
+
+void main() {
+  group("with valid types, forwards", () {
+    var wrapper;
+    var emptyWrapper;
+    setUp(() {
+      wrapper = DelegatingMap.typed/*<String, int>*/(
+          <Object, Object>{"foo": 1, "bar": 2, "baz": 3, "bang": 4});
+      emptyWrapper = DelegatingMap.typed/*<String, int>*/(<Object, Object>{});
+    });
+
+    test("[]", () {
+      expect(wrapper["foo"], equals(1));
+      expect(wrapper["bar"], equals(2));
+      expect(wrapper["qux"], isNull);
+      expect(wrapper[1], isNull);
+    });
+
+    test("[]=", () {
+      wrapper["foo"] = 5;
+      expect(wrapper, equals({"foo": 5, "bar": 2, "baz": 3, "bang": 4}));
+
+      wrapper["qux"] = 6;
+      expect(wrapper,
+          equals({"foo": 5, "bar": 2, "baz": 3, "bang": 4, "qux": 6}));
+    });
+
+    test("addAll()", () {
+      wrapper.addAll({"bar": 5, "qux": 6});
+      expect(wrapper,
+          equals({"foo": 1, "bar": 5, "baz": 3, "bang": 4, "qux": 6}));
+    });
+
+    test("clear()", () {
+      wrapper.clear();
+      expect(wrapper, isEmpty);
+    });
+
+    test("containsKey()", () {
+      expect(wrapper.containsKey("foo"), isTrue);
+      expect(wrapper.containsKey("qux"), isFalse);
+      expect(wrapper.containsKey(1), isFalse);
+    });
+
+    test("containsValue()", () {
+      expect(wrapper.containsValue(1), isTrue);
+      expect(wrapper.containsValue(7), isFalse);
+      expect(wrapper.containsValue("foo"), isFalse);
+    });
+
+    test("forEach()", () {
+      var results = [];
+      wrapper.forEach((key, value) => results.add([key, value]));
+      expect(results,
+          unorderedEquals([["foo", 1], ["bar", 2], ["baz", 3], ["bang", 4]]));
+
+      emptyWrapper.forEach(expectAsync((_, __) {}, count: 0));
+    });
+
+    test("isEmpty", () {
+      expect(wrapper.isEmpty, isFalse);
+      expect(emptyWrapper.isEmpty, isTrue);
+    });
+
+    test("isNotEmpty", () {
+      expect(wrapper.isNotEmpty, isTrue);
+      expect(emptyWrapper.isNotEmpty, isFalse);
+    });
+
+    test("keys", () {
+      expect(wrapper.keys, unorderedEquals(["foo", "bar", "baz", "bang"]));
+      expect(emptyWrapper.keys, isEmpty);
+    });
+
+    test("length", () {
+      expect(wrapper.length, equals(4));
+      expect(emptyWrapper.length, equals(0));
+    });
+
+    test("putIfAbsent()", () {
+      expect(wrapper.putIfAbsent("foo", expectAsync(() => null, count: 0)),
+          equals(1));
+
+      expect(wrapper.putIfAbsent("qux", () => 6), equals(6));
+      expect(wrapper,
+          equals({"foo": 1, "bar": 2, "baz": 3, "bang": 4, "qux": 6}));
+    });
+
+    test("remove()", () {
+      expect(wrapper.remove("foo"), equals(1));
+      expect(wrapper, equals({"bar": 2, "baz": 3, "bang": 4}));
+
+      expect(wrapper.remove("foo"), isNull);
+      expect(wrapper.remove(3), isNull);
+    });
+
+    test("values", () {
+      expect(wrapper.values, unorderedEquals([1, 2, 3, 4]));
+      expect(emptyWrapper.values, isEmpty);
+    });
+
+    test("toString()", () {
+      expect(wrapper.toString(), allOf([
+        startsWith("{"),
+        contains("foo: 1"),
+        contains("bar: 2"),
+        contains("baz: 3"),
+        contains("bang: 4"),
+        endsWith("}")
+      ]));
+    });
+  });
+
+  group("with invalid key types", () {
+    var inner;
+    var wrapper;
+    setUp(() {
+      inner = <Object, Object>{1: 1, 2: 2, 3: 3, 4: 4};
+      wrapper = DelegatingMap.typed/*<String, int>*/(inner);
+    });
+
+    group("throws a CastError for", () {
+      test("forEach()", () {
+        expect(() => wrapper.forEach(expectAsync((_, __) {}, count: 0)),
+            throwsCastError);
+      });
+
+      test("keys", () {
+        var lazy = wrapper.keys;
+        expect(() => lazy.first, throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("[]", () {
+        expect(wrapper["foo"], isNull);
+        expect(wrapper[1], equals(1));
+        expect(wrapper[7], isNull);
+      });
+
+      test("[]=", () {
+        wrapper["foo"] = 5;
+        expect(inner, equals({"foo": 5, 1: 1, 2: 2, 3: 3, 4: 4}));
+      });
+
+      test("addAll()", () {
+        wrapper.addAll({"foo": 1, "bar": 2});
+        expect(inner, equals({"foo": 1, "bar": 2, 1: 1, 2: 2, 3: 3, 4: 4}));
+      });
+
+      test("clear()", () {
+        wrapper.clear();
+        expect(wrapper, isEmpty);
+      });
+
+      test("containsKey()", () {
+        expect(wrapper.containsKey(1), isTrue);
+        expect(wrapper.containsKey(7), isFalse);
+        expect(wrapper.containsKey("foo"), isFalse);
+      });
+
+      test("containsValue()", () {
+        expect(wrapper.containsValue(1), isTrue);
+        expect(wrapper.containsValue(7), isFalse);
+        expect(wrapper.containsValue("foo"), isFalse);
+      });
+
+      test("isEmpty", () {
+        expect(wrapper.isEmpty, isFalse);
+      });
+
+      test("isNotEmpty", () {
+        expect(wrapper.isNotEmpty, isTrue);
+      });
+
+      test("length", () {
+        expect(wrapper.length, equals(4));
+      });
+
+      test("putIfAbsent()", () {
+        expect(wrapper.putIfAbsent("foo", () => 1), equals(1));
+        expect(inner, equals({"foo": 1, 1: 1, 2: 2, 3: 3, 4: 4}));
+      });
+
+      test("remove()", () {
+        expect(wrapper.remove(1), equals(1));
+        expect(inner, equals({2: 2, 3: 3, 4: 4}));
+
+        expect(wrapper.remove("foo"), isNull);
+        expect(wrapper.remove(7), isNull);
+      });
+
+      test("values", () {
+        expect(wrapper.values, unorderedEquals([1, 2, 3, 4]));
+      });
+
+      test("toString()", () {
+        expect(wrapper.toString(), allOf([
+          startsWith("{"),
+          contains("1: 1"),
+          contains("2: 2"),
+          contains("3: 3"),
+          contains("4: 4"),
+          endsWith("}")
+        ]));
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+
+  group("with invalid value types", () {
+    var inner;
+    var wrapper;
+    setUp(() {
+      inner = <Object, Object>{"foo": "bar", "baz": "bang"};
+      wrapper = DelegatingMap.typed/*<String, int>*/(inner);
+    });
+
+    group("throws a CastError for", () {
+      test("forEach()", () {
+        expect(() => wrapper.forEach(expectAsync((_, __) {}, count: 0)),
+            throwsCastError);
+      });
+
+      test("[]", () {
+        expect(() => wrapper["foo"], throwsCastError);
+        expect(wrapper["qux"], isNull);
+      });
+
+      test("putIfAbsent()", () {
+        expect(() => wrapper.putIfAbsent("foo", () => 1), throwsCastError);
+      });
+
+      test("remove()", () {
+        expect(() => wrapper.remove("foo"), throwsCastError);
+      });
+
+      test("values", () {
+        var lazy = wrapper.values;
+        expect(() => lazy.first, throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("[]=", () {
+        wrapper["foo"] = 5;
+        expect(inner, equals({"foo": 5, "baz": "bang"}));
+      });
+
+      test("addAll()", () {
+        wrapper.addAll({"foo": 1, "qux": 2});
+        expect(inner, equals({"foo": 1, "baz": "bang", "qux": 2}));
+      });
+
+      test("clear()", () {
+        wrapper.clear();
+        expect(wrapper, isEmpty);
+      });
+
+      test("containsKey()", () {
+        expect(wrapper.containsKey("foo"), isTrue);
+        expect(wrapper.containsKey(1), isFalse);
+        expect(wrapper.containsKey("qux"), isFalse);
+      });
+
+      test("containsValue()", () {
+        expect(wrapper.containsValue("bar"), isTrue);
+        expect(wrapper.containsValue(1), isFalse);
+        expect(wrapper.containsValue("foo"), isFalse);
+      });
+
+      test("isEmpty", () {
+        expect(wrapper.isEmpty, isFalse);
+      });
+
+      test("isNotEmpty", () {
+        expect(wrapper.isNotEmpty, isTrue);
+      });
+
+      test("keys", () {
+        expect(wrapper.keys, unorderedEquals(["foo", "baz"]));
+      });
+
+      test("length", () {
+        expect(wrapper.length, equals(2));
+      });
+
+      test("putIfAbsent()", () {
+        expect(wrapper.putIfAbsent("qux", () => 1), equals(1));
+        expect(inner, equals({"foo": "bar", "baz": "bang", "qux": 1}));
+      });
+
+      test("remove()", () {
+        expect(wrapper.remove("qux"), isNull);
+        expect(wrapper.remove(7), isNull);
+      });
+
+      test("toString()", () {
+        expect(wrapper.toString(), allOf([
+          startsWith("{"),
+          contains("foo: bar"),
+          contains("baz: bang"),
+          endsWith("}")
+        ]));
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+}
diff --git a/test/typed_wrapper/queue_test.dart b/test/typed_wrapper/queue_test.dart
new file mode 100644
index 0000000..dc93321
--- /dev/null
+++ b/test/typed_wrapper/queue_test.dart
@@ -0,0 +1,141 @@
+// Copyright (c) 2016, 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 "dart:collection";
+
+import "package:collection/collection.dart";
+import "package:test/test.dart";
+
+import '../utils.dart';
+
+void main() {
+  group("with valid types, forwards", () {
+    var wrapper;
+    var emptyWrapper;
+    setUp(() {
+      wrapper = DelegatingQueue.typed/*<int>*/(
+          new Queue<Object>.from([1, 2, 3, 4, 5]));
+      emptyWrapper = DelegatingQueue.typed/*<int>*/(new Queue<Object>());
+    });
+
+    test("add()", () {
+      wrapper.add(6);
+      wrapper.add(7);
+      expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7]));
+    });
+
+    test("addAll()", () {
+      wrapper.addAll([6, 7, 8]);
+      expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7, 8]));
+    });
+
+    test("addFirst()", () {
+      wrapper.addFirst(6);
+      wrapper.addFirst(7);
+      expect(wrapper, equals([7, 6, 1, 2, 3, 4, 5]));
+    });
+
+    test("addLast()", () {
+      wrapper.addLast(6);
+      wrapper.addLast(7);
+      expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7]));
+    });
+
+    test("clear()", () {
+      wrapper.clear();
+      expect(wrapper, isEmpty);
+    });
+
+    test("remove()", () {
+      expect(wrapper.remove(3), isTrue);
+      expect(wrapper, equals([1, 2, 4, 5]));
+
+      expect(wrapper.remove(3), isFalse);
+      expect(wrapper.remove("foo"), isFalse);
+    });
+
+    test("removeWhere()", () {
+      wrapper.removeWhere((i) => i.isOdd);
+      expect(wrapper, equals([2, 4]));
+    });
+
+    test("retainWhere()", () {
+      wrapper.retainWhere((i) => i.isOdd);
+      expect(wrapper, equals([1, 3, 5]));
+    });
+
+    test("removeLast()", () {
+      expect(wrapper.removeLast(), equals(5));
+      expect(wrapper, equals([1, 2, 3, 4]));
+
+      expect(() => emptyWrapper.removeLast(), throwsStateError);
+    });
+
+    test("removeFirst()", () {
+      expect(wrapper.removeFirst(), equals(1));
+      expect(wrapper, equals([2, 3, 4, 5]));
+
+      expect(() => emptyWrapper.removeFirst(), throwsStateError);
+    });
+  });
+
+  group("with invalid types", () {
+    var inner;
+    var wrapper;
+    setUp(() {
+      inner = new Queue<Object>.from(["foo", "bar", "baz"]);
+      wrapper = DelegatingQueue.typed/*<int>*/(inner);
+    });
+
+    group("throws a CastError for", () {
+      test("removeLast()", () {
+        expect(() => wrapper.removeLast(), throwsCastError);
+      });
+
+      test("removeFirst()", () {
+        expect(() => wrapper.removeFirst(), throwsCastError);
+      });
+
+      test("removeWhere()", () {
+        expect(() => wrapper.removeWhere(expectAsync((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("retainWhere()", () {
+        expect(() => wrapper.retainWhere(expectAsync((_) => false, count: 0)),
+            throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("add()", () {
+        wrapper.add(6);
+        wrapper.add(7);
+        expect(inner, equals(["foo", "bar", "baz", 6, 7]));
+      });
+
+      test("addAll()", () {
+        wrapper.addAll([6, 7, 8]);
+        expect(inner, equals(["foo", "bar", "baz", 6, 7, 8]));
+      });
+
+      test("addFirst()", () {
+        wrapper.addFirst(6);
+        wrapper.addFirst(7);
+        expect(inner, equals([7, 6, "foo", "bar", "baz"]));
+      });
+
+      test("addLast()", () {
+        wrapper.addLast(6);
+        wrapper.addLast(7);
+        expect(inner, equals(["foo", "bar", "baz", 6, 7]));
+      });
+
+      test("clear()", () {
+        wrapper.clear();
+        expect(wrapper, isEmpty);
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+}
diff --git a/test/typed_wrapper/set_test.dart b/test/typed_wrapper/set_test.dart
new file mode 100644
index 0000000..c3f84bc
--- /dev/null
+++ b/test/typed_wrapper/set_test.dart
@@ -0,0 +1,176 @@
+// Copyright (c) 2016, 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:collection/collection.dart";
+import "package:test/test.dart";
+
+import '../utils.dart';
+
+void main() {
+  group("with valid types, forwards", () {
+    var wrapper;
+    var emptyWrapper;
+    setUp(() {
+      wrapper = DelegatingSet.typed/*<int>*/(
+          new Set<Object>.from([1, 2, 3, 4, 5]));
+      emptyWrapper = DelegatingSet.typed/*<int>*/(new Set<Object>());
+    });
+
+    test("add()", () {
+      wrapper.add(1);
+      wrapper.add(6);
+      expect(wrapper, unorderedEquals([1, 2, 3, 4, 5, 6]));
+    });
+
+    test("addAll()", () {
+      wrapper.addAll([1, 6, 7]);
+      expect(wrapper, unorderedEquals([1, 2, 3, 4, 5, 6, 7]));
+    });
+
+    test("clear()", () {
+      wrapper.clear();
+      expect(wrapper, isEmpty);
+    });
+
+    test("containsAll()", () {
+      expect(wrapper.containsAll([1, 3, 5]), isTrue);
+      expect(wrapper.containsAll([1, 3, 6]), isFalse);
+      expect(wrapper.containsAll([1, 3, "foo"]), isFalse);
+    });
+
+    test("difference()", () {
+      expect(wrapper.difference(new Set.from([1, 3, 6])),
+          unorderedEquals([2, 4, 5]));
+    });
+
+    test("intersection()", () {
+      expect(wrapper.intersection(new Set.from([1, 3, 6, "foo"])),
+          unorderedEquals([1, 3]));
+    });
+
+    test("lookup()", () {
+      expect(wrapper.lookup(1), equals(1));
+      expect(wrapper.lookup(7), isNull);
+      expect(wrapper.lookup("foo"), isNull);
+    });
+
+    test("remove()", () {
+      expect(wrapper.remove(3), isTrue);
+      expect(wrapper, unorderedEquals([1, 2, 4, 5]));
+
+      expect(wrapper.remove(3), isFalse);
+      expect(wrapper.remove("foo"), isFalse);
+    });
+
+    test("removeAll()", () {
+      wrapper.removeAll([1, 3, 6, "foo"]);
+      expect(wrapper, unorderedEquals([2, 4, 5]));
+    });
+
+    test("removeWhere()", () {
+      wrapper.removeWhere((i) => i.isOdd);
+      expect(wrapper, unorderedEquals([2, 4]));
+    });
+
+    test("retainAll()", () {
+      wrapper.retainAll([1, 3, 6, "foo"]);
+      expect(wrapper, unorderedEquals([1, 3]));
+    });
+
+    test("retainWhere()", () {
+      wrapper.retainWhere((i) => i.isOdd);
+      expect(wrapper, unorderedEquals([1, 3, 5]));
+    });
+
+    test("union()", () {
+      expect(wrapper.union(new Set.from([5, 6, 7])),
+          unorderedEquals([1, 2, 3, 4, 5, 6, 7]));
+    });
+  });
+
+  group("with invalid types", () {
+    var inner;
+    var wrapper;
+    setUp(() {
+      inner = new Set<Object>.from(["foo", "bar", "baz"]);
+      wrapper = DelegatingSet.typed/*<int>*/(inner);
+    });
+
+    group("throws a CastError for", () {
+      test("difference()", () {
+        var result = wrapper.difference(new Set.from([1, 3, 6]));
+        expect(() => result.first, throwsCastError);
+      });
+
+      test("intersection()", () {
+        var result = wrapper.intersection(new Set.from([1, 3, 6, "foo"]));
+        expect(() => result.first, throwsCastError);
+      });
+
+      test("lookup()", () {
+        expect(() => wrapper.lookup("foo"), throwsCastError);
+      });
+
+      test("removeWhere()", () {
+        expect(() => wrapper.removeWhere(expectAsync((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("retainWhere()", () {
+        expect(() => wrapper.retainWhere(expectAsync((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("union()", () {
+        var result = wrapper.union(new Set.from([5, 6, 7]));
+        expect(() => result.first, throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("add()", () {
+        wrapper.add(6);
+        expect(inner, unorderedEquals(["foo", "bar", "baz", 6]));
+      });
+
+      test("addAll()", () {
+        wrapper.addAll([6, 7]);
+        expect(inner, unorderedEquals(["foo", "bar", "baz", 6, 7]));
+      });
+
+      test("clear()", () {
+        wrapper.clear();
+        expect(wrapper, isEmpty);
+      });
+
+      test("containsAll()", () {
+        expect(wrapper.containsAll(["foo", "bar"]), isTrue);
+        expect(wrapper.containsAll(["foo", "bar", 1]), isFalse);
+      });
+
+      test("lookup()", () {
+        expect(wrapper.lookup(1), isNull);
+        expect(wrapper.lookup("zap"), isNull);
+      });
+
+      test("remove()", () {
+        expect(wrapper.remove("foo"), isTrue);
+        expect(inner, unorderedEquals(["bar", "baz"]));
+
+        expect(wrapper.remove(3), isFalse);
+        expect(wrapper.remove("foo"), isFalse);
+      });
+
+      test("removeAll()", () {
+        wrapper.removeAll([1, "foo", "baz"]);
+        expect(inner, unorderedEquals(["bar"]));
+      });
+
+      test("retainAll()", () {
+        wrapper.retainAll([1, "foo", "baz"]);
+        expect(inner, unorderedEquals(["foo", "baz"]));
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+}
diff --git a/test/utils.dart b/test/utils.dart
new file mode 100644
index 0000000..d8ab082
--- /dev/null
+++ b/test/utils.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2016, 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:test/test.dart";
+
+final Matcher throwsCastError = throwsA(new isInstanceOf<CastError>());