Update to a Dart 2 compatible version of package:charted. (#7)

diff --git a/packages/charted/README.md b/packages/charted/README.md
index c48b83b..4a3144b 100644
--- a/packages/charted/README.md
+++ b/packages/charted/README.md
@@ -21,5 +21,6 @@
 ```bash
 git clone git@github.com:google/charted.git
 cd charted
+pub get
 pub serve examples
 ```
diff --git a/packages/charted/changelog.md b/packages/charted/changelog.md
new file mode 100644
index 0000000..9707402
--- /dev/null
+++ b/packages/charted/changelog.md
@@ -0,0 +1,88 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+This log starts from version 0.5.0. Older versions are omitted, but this should
+be kept up-to-date when a version update happens in the future.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
+
+## 0.6.0 - 2018-07-09
+
+
+### Changed
+- Internal changes to make charted Dart 2.0 compatible.
+
+- Several typedef signatures have changed: `CompareFunc`,
+  `InterpolatorGenerator`:
+
+  |         | Breaking typedef signature changes |
+  |---------|------------------------------------|
+  | **OLD** | `typedef int CompareFunc(dynamic a, dynamic b);`
+  | **NEW** | `typedef int CompareFunc(Comparable a, Comparable b);`
+  |         |                                    |
+  | **OLD** | `typedef Interpolator InterpolatorGenerator(a, b);`
+  | **NEW** | `typedef Interpolator InterpolatorGenerator<T>(T a, T b);`
+  |         |                                    |
+
+- Several member signatures have changed: `Extent.items`, `LinearScale.interpolator`,
+  `OrdinalScale.rangePoints`, `OrdinalScale.rangeBands`,
+  `OrdinalScale.rangeRoundBands`, `ScaleUtils.extent`, `TimeFormat.multi`:
+
+  |         | Breaking member signature changes |
+  |---------|-----------------------------------|
+  |         | **Class `Extent`**                |
+  | **OLD** | `factory Extent.items(Iterable<T> items, [Comparator compare = Comparable.compare]);`
+  | **NEW** | `factory Extent.items(Iterable<T> items, [Comparator<T> compare = Comparable.compare]);`
+  |         |                                   |
+  |         | **Class `LinearScale`**           |
+  | **OLD** | `InterpolatorGenerator interpolator;`
+  | **NEW** | `InterpolatorGenerator<num> interpolator;`
+  |         |                                   |
+  |         | **Class `OrdinalScale`**          |
+  | **OLD** | `void rangePoints(Iterable range, [double padding]);`
+  | **NEW** | `void rangePoints(Iterable<num> range, [double padding]);`
+  |         |                                   |
+  | **OLD** | `void rangeBands(Iterable range, [double padding, double outerPadding]);`
+  | **NEW** | `void rangeBands(Iterable<num> range, [double padding, double outerPadding]);`
+  |         |                                   |
+  | **OLD** | `void rangeRoundBands(Iterable range, [double padding, double outerPadding]);`
+  | **NEW** | `void rangeRoundBands(Iterable<num> range, [double padding, double outerPadding]);`
+  |         |                                   |
+  |         | **Class `ScaleUtils`**            |
+  | **OLD** | `static Extent extent(Iterable<num> values)`
+  | **NEW** | `static Extent<num> extent(Iterable<num> values)`
+  |         |                                   |
+  |         | **Class `TimeFormat`**            |
+  | **OLD** | `TimeFormatFunction multi(List<List> formats)`
+  | **NEW** | `FormatFunction multi(List<List> formats)`
+
+- The `charted.core.utils` library's `sum` function's signature changed:
+
+  |         | Breaking library member signature changes |
+  |---------|-------------------------------------------|
+  |         | **Library `charted.core.utils`**          |
+  | **OLD** | `num sum(List values)`
+  | **NEW** | `num sum(List<num> values)`
+
+- The signature of the `Extent` class changed:
+
+  |         | Breaking class signature changes |
+  |---------|----------------------------------|
+  |         | **Class `Extent`**               |
+  | **OLD** | `class Extent<T> extends Pair<T, T>`
+  | **NEW** | `class Extent<T extends Comparable> extends Pair<T, T>`
+
+## [0.5.0] - 2017-11-16
+
+### Added
+- Stacked line renderer for rendering stacked line.
+
+### Changed
+- Add type and explicit casting to enable strong mode.
+
+## 0.0.10 - 2015-03-28
+
+### Added
+- First release of charted.
+
+[0.5.0]: https://github.com/google/charted/compare/0.0.10...0.5.0
diff --git a/packages/charted/examples/selection/transitions_demo.dart b/packages/charted/examples/selection/transitions_demo.dart
index f1761af..585fd1e 100644
--- a/packages/charted/examples/selection/transitions_demo.dart
+++ b/packages/charted/examples/selection/transitions_demo.dart
@@ -74,7 +74,7 @@
 
   Transition t2 = new Transition(circle2);
   t2
-      ..ease = clampEasingFn(identityFunction(easeCubic()))
+      ..ease = clampEasingFn(easeCubic())
       ..attr('cx', 400)
       ..duration(4000);
 
diff --git a/packages/charted/lib/charts/behaviors/chart_tooltip.dart b/packages/charted/lib/charts/behaviors/chart_tooltip.dart
index f64ebe8..551bebc 100644
--- a/packages/charted/lib/charts/behaviors/chart_tooltip.dart
+++ b/packages/charted/lib/charts/behaviors/chart_tooltip.dart
@@ -121,15 +121,17 @@
     var tooltipItems = _tooltipRoot.selectAll('.tooltip-item');
     tooltipItems.append('div')
       ..classed('tooltip-item-label')
-      ..textWithCallback((int d, i, c) => _area.data.columns
-          .elementAt((showSelectedMeasure) ? d : e.series.measures.elementAt(i))
+      ..textWithCallback((d, i, c) => _area.data.columns
+          .elementAt(
+              (showSelectedMeasure) ? d as int : e.series.measures.elementAt(i))
           .label);
 
     // Display the value of the currently active series
     tooltipItems.append('div')
       ..classed('tooltip-item-value')
       ..styleWithCallback('color', (d, i, c) => _area.theme.getColorForKey(d))
-      ..textWithCallback((int d, i, c) {
+      ..textWithCallback((_d, i, c) {
+        int d = _d;
         var formatter = _getFormatterForColumn(d),
             value = _area.data.rows.elementAt(e.row).elementAt(d);
         return (formatter != null) ? formatter(value) : value.toString();
diff --git a/packages/charted/lib/charts/behaviors/hovercard.dart b/packages/charted/lib/charts/behaviors/hovercard.dart
index c6a73d6..a12951c 100644
--- a/packages/charted/lib/charts/behaviors/hovercard.dart
+++ b/packages/charted/lib/charts/behaviors/hovercard.dart
@@ -180,8 +180,8 @@
     }
   }
 
-  void _positionAtMousePointer(ChartEvent e) =>
-      _positionAtPoint(e.chartX, e.chartY, _HOVERCARD_OFFSET, _HOVERCARD_OFFSET, false, false);
+  void _positionAtMousePointer(ChartEvent e) => _positionAtPoint(
+      e.chartX, e.chartY, _HOVERCARD_OFFSET, _HOVERCARD_OFFSET, false, false);
 
   void _positionOnLayout(column, row) {
     // Currently for layouts, when hovercard is triggered due to change
@@ -209,12 +209,11 @@
       dimensionCenterOffset = dimensionOffset;
     }
 
-    var rowData = area.data.rows.elementAt(row),
-        isNegative = false;
+    var rowData = area.data.rows.elementAt(row), isNegative = false;
     num measurePosition = 0,
-        dimensionPosition = dimensionScale
-                .scale(rowData.elementAt(dimensionCol)) +
-            dimensionCenterOffset;
+        dimensionPosition =
+            dimensionScale.scale(rowData.elementAt(dimensionCol)) +
+                dimensionCenterOffset;
 
     if (_isMultiValue) {
       num max = SMALL_INT_MIN, min = SMALL_INT_MAX;
@@ -248,14 +247,12 @@
     var rect = _hovercardRoot.getBoundingClientRect(),
         width = rect.width,
         height = rect.height,
-        scaleToHostY = (_area.theme.padding != null
-                ? _area.theme.padding.top
-                : 0) +
-            (_area.layout.renderArea.y),
-        scaleToHostX = (_area.theme.padding != null
-                ? _area.theme.padding.start
-                : 0) +
-            (_area.layout.renderArea.x),
+        scaleToHostY =
+            (_area.theme.padding != null ? _area.theme.padding.top : 0) +
+                (_area.layout.renderArea.y),
+        scaleToHostX =
+            (_area.theme.padding != null ? _area.theme.padding.start : 0) +
+                (_area.layout.renderArea.x),
         renderAreaHeight = _area.layout.renderArea.height,
         renderAreaWidth = _area.layout.renderArea.width;
 
@@ -342,7 +339,7 @@
         measureVals.add(_createHovercardItem(column, row));
       });
     } else if (_columnsToShow.length > 1 || _isMultiValue) {
-      var displayedCols = [];
+      var displayedCols = <int>[];
       _area.config.series.forEach((ChartSeries series) {
         series.measures.forEach((int column) {
           if (!displayedCols.contains(column)) displayedCols.add(column);
diff --git a/packages/charted/lib/charts/behaviors/line_marker.dart b/packages/charted/lib/charts/behaviors/line_marker.dart
index f3cffb5..78779d1 100644
--- a/packages/charted/lib/charts/behaviors/line_marker.dart
+++ b/packages/charted/lib/charts/behaviors/line_marker.dart
@@ -58,8 +58,8 @@
 
     assert(index == 0 || index == 1 && _area.useTwoDimensionAxes);
 
-    var dimensionAtBottom =
-        index == 1 && _isLeftAxisPrimary || index == 0 && !_isLeftAxisPrimary,
+    var dimensionAtBottom = index == 1 && _isLeftAxisPrimary ||
+            index == 0 && !_isLeftAxisPrimary,
         scale = _area.dimensionScales.elementAt(index),
         scaled = scale.scale(positions[column]) as num,
         theme = _area.theme.getDimensionAxisTheme(),
@@ -95,7 +95,8 @@
     if (!_area.isReady) return;
     _markers = _parent.selectAll('.line-marker').data(positions.keys);
 
-    _markers.enter.append('path').each((int d, i, e) {
+    _markers.enter.append('path').each((_d, i, e) {
+      int d = _d;
       e.classes.add('line-marker');
       e.attributes['d'] = _getMarkerPath(d, animate);
     });
@@ -103,7 +104,7 @@
     if (animate) {
       _markers
           .transition()
-          .attrWithCallback('d', (int d, i, e) => _getMarkerPath(d, false));
+          .attrWithCallback('d', (d, i, e) => _getMarkerPath(d as int, false));
     }
 
     _markers.exit.remove();
diff --git a/packages/charted/lib/charts/cartesian_renderers/bar_chart_renderer.dart b/packages/charted/lib/charts/cartesian_renderers/bar_chart_renderer.dart
index 658a605..f411ae2 100644
--- a/packages/charted/lib/charts/cartesian_renderers/bar_chart_renderer.dart
+++ b/packages/charted/lib/charts/cartesian_renderers/bar_chart_renderer.dart
@@ -85,15 +85,16 @@
     // Create and update the bars
     // Avoids animation on first render unless alwaysAnimate is set to true.
 
-    var bar =
-        groups.selectAll('.bar-rdr-bar').dataWithCallback((d, i, c) => rows[i]),
+    var bar = groups
+            .selectAll('.bar-rdr-bar')
+            .dataWithCallback((d, i, c) => rows[i]),
         scaled0 = measureScale.scale(0).round();
 
     var getBarLength = (num d) {
       var scaledVal = measureScale.scale(d).round();
       num ht = verticalBars
-              ? (d >= 0 ? scaled0 - scaledVal : scaledVal - scaled0)
-              : (d >= 0 ? scaledVal - scaled0 : scaled0 - scaledVal);
+          ? (d >= 0 ? scaled0 - scaledVal : scaledVal - scaled0)
+          : (d >= 0 ? scaledVal - scaled0 : scaled0 - scaledVal);
       ht = ht - strokeWidth;
 
       // If bar would be scaled to 0 height but data is not 0, render bar
@@ -131,13 +132,17 @@
             RADIUS);
       } else {
         var fn = d > 0 ? rightRoundedRect : leftRoundedRect;
-        return fn(getBarPos(d) as int, (bars.scale(i) as num).toInt() +
-            strokeWidthOffset,
-            animate ? 0 : getBarLength(d) as int, barWidth as int, RADIUS);
+        return fn(
+            getBarPos(d) as int,
+            (bars.scale(i) as num).toInt() + strokeWidthOffset,
+            animate ? 0 : getBarLength(d) as int,
+            barWidth as int,
+            RADIUS);
       }
     };
 
-    bar.enter.appendWithCallback((num d, i, e) {
+    bar.enter.appendWithCallback((_d, i, e) {
+      num d = _d;
       var rect = Namespace.createChildElement('path', e),
           measure = series.measures.elementAt(i),
           row = int.parse(e.dataset['row']),
@@ -164,9 +169,10 @@
       }
       return rect;
     })
-      ..on('click', (num d, i, e) => _event(mouseClickController, d, i, e))
-      ..on('mouseover', (num d, i, e) => _event(mouseOverController, d, i, e))
-      ..on('mouseout', (num d, i, e) => _event(mouseOutController, d, i, e));
+      ..on('click', (d, i, e) => _event(mouseClickController, d as num, i, e))
+      ..on(
+          'mouseover', (d, i, e) => _event(mouseOverController, d as num, i, e))
+      ..on('mouseout', (d, i, e) => _event(mouseOutController, d as num, i, e));
 
     if (animateBarGroups) {
       bar.each((d, i, e) {
@@ -190,7 +196,7 @@
       });
 
       bar.transition()
-        ..attrWithCallback('d', (num d, i, e) => buildPath(d, i, false));
+        ..attrWithCallback('d', (d, i, e) => buildPath(d as num, i, false));
     }
 
     bar.exit.remove();
diff --git a/packages/charted/lib/charts/cartesian_renderers/bubble_chart_renderer.dart b/packages/charted/lib/charts/cartesian_renderers/bubble_chart_renderer.dart
index 60ee6e6..76884f6 100644
--- a/packages/charted/lib/charts/cartesian_renderers/bubble_chart_renderer.dart
+++ b/packages/charted/lib/charts/cartesian_renderers/bubble_chart_renderer.dart
@@ -46,7 +46,7 @@
         yDimensionScale = area.dimensionScales.last,
         theme = area.theme,
         bubbleRadiusFactor =
-        maxBubbleRadius / min([geometry.width, geometry.height]);
+            maxBubbleRadius / min([geometry.width, geometry.height]);
 
     String color(int i) => theme.getColorForKey(series.measures.elementAt(i));
 
@@ -108,8 +108,7 @@
   Extent get extent {
     assert(series != null && area != null);
     List<List<num>> rows = area.data.rows;
-    num max = rows.first[series.measures.first],
-        min = max;
+    num max = rows.first[series.measures.first], min = max;
 
     rows.forEach((row) {
       series.measures.forEach((int idx) {
diff --git a/packages/charted/lib/charts/cartesian_renderers/line_chart_renderer.dart b/packages/charted/lib/charts/cartesian_renderers/line_chart_renderer.dart
index c3dc7a3..fc0a12a 100644
--- a/packages/charted/lib/charts/cartesian_renderers/line_chart_renderer.dart
+++ b/packages/charted/lib/charts/cartesian_renderers/line_chart_renderer.dart
@@ -75,8 +75,8 @@
     }
 
     var line = new SvgLine(
-        xValueAccessor: (d, i) => (dimensionScale.scale(x[i]) as num) +
-            rangeBandOffset,
+        xValueAccessor: (d, i) =>
+            (dimensionScale.scale(x[i]) as num) + rangeBandOffset,
         yValueAccessor: (d, i) => measureScale.scale(d) as num);
 
     // Add lines and hook up hover and selection events.
@@ -151,7 +151,8 @@
     });
 
     linePoints
-      ..each((int d, i, e) {
+      ..each((_d, i, e) {
+        int d = _d;
         var color = colorForColumn(d);
         e.attributes
           ..['r'] = '4'
@@ -174,7 +175,8 @@
     }
 
     var yScale = area.measureScales(series).first;
-    root.selectAll('.line-rdr-point').each((int d, i, e) {
+    root.selectAll('.line-rdr-point').each((_d, i, e) {
+      int d = _d;
       num x = _xPositions[row],
           measureVal = area.data.rows.elementAt(row).elementAt(d);
       if (measureVal != null && measureVal.isFinite) {
@@ -262,9 +264,8 @@
     if (mouseClickController != null && e.tagName == 'circle') {
       var row = int.parse(e.dataset['row']),
           column = int.parse(e.dataset['column']);
-      mouseClickController.add(
-          new DefaultChartEventImpl(scope.event, area, series, row, column,
-              d as num));
+      mouseClickController.add(new DefaultChartEventImpl(
+          scope.event, area, series, row, column, d as num));
     }
   }
 
@@ -275,9 +276,8 @@
     if (mouseOverController != null && e.tagName == 'circle') {
       _savedOverRow = int.parse(e.dataset['row']);
       _savedOverColumn = int.parse(e.dataset['column']);
-      mouseOverController.add(new DefaultChartEventImpl(
-          scope.event, area, series, _savedOverRow, _savedOverColumn,
-          d as num));
+      mouseOverController.add(new DefaultChartEventImpl(scope.event, area,
+          series, _savedOverRow, _savedOverColumn, d as num));
     }
   }
 
@@ -287,9 +287,8 @@
       area.state.preview = null;
     }
     if (mouseOutController != null && e.tagName == 'circle') {
-      mouseOutController.add(new DefaultChartEventImpl(
-          scope.event, area, series, _savedOverRow, _savedOverColumn,
-          d as num));
+      mouseOutController.add(new DefaultChartEventImpl(scope.event, area,
+          series, _savedOverRow, _savedOverColumn, d as num));
     }
   }
 }
diff --git a/packages/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart b/packages/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart
index 3109199..a995bec 100644
--- a/packages/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart
+++ b/packages/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart
@@ -74,9 +74,9 @@
         ..duration(theme.transitionDurationMilliseconds);
     }
 
-    var bar =
-        groups.selectAll('.stack-rdr-bar').dataWithCallback((d, i, c) =>
-            d as Iterable);
+    var bar = groups
+        .selectAll('.stack-rdr-bar')
+        .dataWithCallback((d, i, c) => d as Iterable);
 
     var prevOffsetVal = new List();
 
@@ -175,10 +175,10 @@
               ? RADIUS
               : 0;
       var path = (length != 0)
-              ? verticalBars
-                  ? topRoundedRect(0, position, barWidth, length, radius)
-                  : rightRoundedRect(position, 0, length, barWidth, radius)
-              : '';
+          ? verticalBars
+              ? topRoundedRect(0, position, barWidth, length, radius)
+              : rightRoundedRect(position, 0, length, barWidth, radius)
+          : '';
       e.attributes['data-offset'] =
           verticalBars ? position.toString() : (position + length).toString();
       return path;
diff --git a/packages/charted/lib/charts/cartesian_renderers/stackedline_chart_renderer.dart b/packages/charted/lib/charts/cartesian_renderers/stackedline_chart_renderer.dart
index 55306f1..37dc579 100644
--- a/packages/charted/lib/charts/cartesian_renderers/stackedline_chart_renderer.dart
+++ b/packages/charted/lib/charts/cartesian_renderers/stackedline_chart_renderer.dart
@@ -67,8 +67,9 @@
     // Second Half: current accumulated values (need for drawing)
     var lines = reversedMeasures.map((column) {
       var row = area.data.rows.map((values) => values[column]).toList();
-      return accumulated.reversed.toList()..addAll(
-          new List.generate(x.length, (i) => accumulated[i] += row[i] as num));
+      return accumulated.reversed.toList()
+        ..addAll(new List.generate(
+            x.length, (i) => accumulated[i] += row[i] as num));
     }).toList();
 
     var rangeBandOffset =
@@ -77,9 +78,9 @@
     // If tracking data points is enabled, cache location of points that
     // represent data.
     if (trackDataPoints) {
-      _xPositions =
-          x.map((val) =>
-              (dimensionScale.scale(val) + rangeBandOffset) as num).toList();
+      _xPositions = x
+          .map((val) => (dimensionScale.scale(val) + rangeBandOffset) as num)
+          .toList();
     }
 
     var fillLine = new SvgLine(
@@ -92,12 +93,13 @@
         },
         yValueAccessor: (d, i) => measureScale.scale(d) as num);
     var strokeLine = new SvgLine(
-        xValueAccessor: (d, i) => (dimensionScale.scale(x[i]) as num) +
-            rangeBandOffset,
+        xValueAccessor: (d, i) =>
+            (dimensionScale.scale(x[i]) as num) + rangeBandOffset,
         yValueAccessor: (d, i) => measureScale.scale(d) as num);
 
     // Add lines and hook up hover and selection events.
-    var svgLines = root.selectAll('.stacked-line-rdr-line').data(lines.reversed);
+    var svgLines =
+        root.selectAll('.stacked-line-rdr-line').data(lines.reversed);
     svgLines.enter.append('g');
 
     svgLines.each((d, i, e) {
@@ -114,8 +116,8 @@
         ..['stroke'] = color
         ..['fill'] = color
         ..['class'] = styles.isEmpty
-          ? 'stacked-line-rdr-line'
-          : 'stacked-line-rdr-line ${styles.join(' ')}'
+            ? 'stacked-line-rdr-line'
+            : 'stacked-line-rdr-line ${styles.join(' ')}'
         ..['data-column'] = '$column';
       fill.attributes
         ..['d'] = fillLine.path(fillData, i, e)
@@ -153,9 +155,7 @@
   Extent get extent {
     assert(area != null && series != null);
     var rows = area.data.rows;
-    num max = SMALL_INT_MIN,
-        min = SMALL_INT_MAX,
-        rowIndex = 0;
+    num max = SMALL_INT_MIN, min = SMALL_INT_MAX;
 
     rows.forEach((row) {
       num line = null;
@@ -168,7 +168,6 @@
       });
       if (line > max) max = line;
       if (line < min) min = line;
-      rowIndex++;
     });
 
     return new Extent(min, max);
@@ -197,7 +196,8 @@
   }
 
   void _createTrackingCircles() {
-    var linePoints = root.selectAll('.stacked-line-rdr-point')
+    var linePoints = root
+        .selectAll('.stacked-line-rdr-point')
         .data(series.measures.toList().reversed);
     linePoints.enter.append('circle').each((d, i, e) {
       e.classes.add('stacked-line-rdr-point');
@@ -205,7 +205,8 @@
     });
 
     linePoints
-      ..each((int d, i, e) {
+      ..each((_d, i, e) {
+        int d = _d;
         var color = colorForColumn(d);
         e.attributes
           ..['r'] = '4'
@@ -227,12 +228,13 @@
       _createTrackingCircles();
     }
 
-    double cumulated =  0.0;
+    double cumulated = 0.0;
     var yScale = area.measureScales(series).first;
-    root.selectAll('.stacked-line-rdr-point').each((int d, i, e) {
+    root.selectAll('.stacked-line-rdr-point').each((_d, i, e) {
+      int d = _d;
       var x = _xPositions[row],
           measureVal =
-             cumulated += area.data.rows.elementAt(row).elementAt(d) as num;
+              cumulated += area.data.rows.elementAt(row).elementAt(d) as num;
       if (measureVal != null && measureVal.isFinite) {
         var color = colorForColumn(d), filter = filterForColumn(d);
         e.attributes
@@ -318,9 +320,8 @@
     if (mouseClickController != null && e.tagName == 'circle') {
       var row = int.parse(e.dataset['row']),
           column = int.parse(e.dataset['column']);
-      mouseClickController.add(
-          new DefaultChartEventImpl(scope.event, area, series, row, column,
-              d as int));
+      mouseClickController.add(new DefaultChartEventImpl(
+          scope.event, area, series, row, column, d as int));
     }
   }
 
@@ -331,9 +332,8 @@
     if (mouseOverController != null && e.tagName == 'circle') {
       _savedOverRow = int.parse(e.dataset['row']);
       _savedOverColumn = int.parse(e.dataset['column']);
-      mouseOverController.add(new DefaultChartEventImpl(
-          scope.event, area, series, _savedOverRow, _savedOverColumn,
-          d as int));
+      mouseOverController.add(new DefaultChartEventImpl(scope.event, area,
+          series, _savedOverRow, _savedOverColumn, d as int));
     }
   }
 
@@ -343,9 +343,8 @@
       area.state.preview = null;
     }
     if (mouseOutController != null && e.tagName == 'circle') {
-      mouseOutController.add(new DefaultChartEventImpl(
-          scope.event, area, series, _savedOverRow, _savedOverColumn,
-          d as int));
+      mouseOutController.add(new DefaultChartEventImpl(scope.event, area,
+          series, _savedOverRow, _savedOverColumn, d as int));
     }
   }
 }
diff --git a/packages/charted/lib/charts/chart_config.dart b/packages/charted/lib/charts/chart_config.dart
index 0baae21..5a3be52 100644
--- a/packages/charted/lib/charts/chart_config.dart
+++ b/packages/charted/lib/charts/chart_config.dart
@@ -66,8 +66,8 @@
   bool switchAxesForRTL;
 
   /// Factory method to create an instance of the default implementation
-  factory ChartConfig(Iterable<ChartSeries> series,
-      Iterable<int> dimensions) = DefaultChartConfigImpl;
+  factory ChartConfig(Iterable<ChartSeries> series, Iterable<int> dimensions) =
+      DefaultChartConfigImpl;
 }
 
 ///
diff --git a/packages/charted/lib/charts/chart_data.dart b/packages/charted/lib/charts/chart_data.dart
index 1367cc1..9e6b553 100644
--- a/packages/charted/lib/charts/chart_data.dart
+++ b/packages/charted/lib/charts/chart_data.dart
@@ -20,8 +20,9 @@
   Iterable<List> get rows;
 
   /// Create a new instance of [ChartData]'s internal implementation
-  factory ChartData(Iterable<ChartColumnSpec> columns,
-      Iterable<Iterable> rows) = DefaultChartDataImpl;
+  factory ChartData(
+          Iterable<ChartColumnSpec> columns, Iterable<Iterable> rows) =
+      DefaultChartDataImpl;
 }
 
 ///
diff --git a/packages/charted/lib/charts/charts.dart b/packages/charted/lib/charts/charts.dart
index ad24994..8fac9b8 100644
--- a/packages/charted/lib/charts/charts.dart
+++ b/packages/charted/lib/charts/charts.dart
@@ -25,7 +25,7 @@
 import 'package:charted/svg/shapes.dart';
 import 'package:charted/selection/transition.dart';
 
-import 'package:collection/equality.dart';
+import 'package:collection/collection.dart';
 import 'package:logging/logging.dart';
 import 'package:observable/observable.dart';
 import 'package:quiver/core.dart';
diff --git a/packages/charted/lib/charts/data_transformers/aggregation.dart b/packages/charted/lib/charts/data_transformers/aggregation.dart
index 475a657..9633777 100644
--- a/packages/charted/lib/charts/data_transformers/aggregation.dart
+++ b/packages/charted/lib/charts/data_transformers/aggregation.dart
@@ -11,7 +11,7 @@
 ///Function callback to filter items in the input
 typedef bool AggregationFilterFunc(var item);
 
-typedef int CompareFunc(dynamic a, dynamic b);
+typedef int CompareFunc(Comparable a, Comparable b);
 
 typedef dynamic FieldAccessor(dynamic item, dynamic key);
 
@@ -60,7 +60,7 @@
   List<int> _sorted;
 
   // Enumeration map for dimension values
-  List<Map<dynamic, int>> _dimToIntMap;
+  List<Map<Comparable, int>> _dimToIntMap;
 
   // Sort orders for dimension values
   List<List<int>> _dimSortOrders;
@@ -245,8 +245,8 @@
     // Create both when object is created and groupBy is called
     // Reuse dimension enumerations if possible.
     var oldDimToInt = _dimToIntMap;
-    _dimToIntMap = new List<Map<dynamic, int>>.generate(_dimFields.length,
-        (i) => i < _dimPrefixLength ? oldDimToInt[i] : new Map<dynamic, int>());
+    _dimToIntMap = new List<Map<Comparable, int>>.generate(_dimFields.length,
+        (i) => i < _dimPrefixLength ? oldDimToInt[i] : <Comparable, int>{});
   }
 
   /// Check cache entries
@@ -255,7 +255,7 @@
   /// if they aren't valid anymore.
   /// Update the entities that are valid after the groupBy.
   void _updateCachedEntities() {
-    List keys = new List.from(_entityCache.keys, growable: false);
+    var keys = new List<String>.from(_entityCache.keys, growable: false);
     keys.forEach((String key) {
       _AggregationItemImpl entity = _entityCache[key];
       if (entity == null) {
@@ -274,7 +274,8 @@
   final Map<String, List> _parsedKeys = {};
 
   /// Get value from a map-like object
-  dynamic _fetch(var item, String key) {
+  dynamic _fetch(var item, var _key) {
+    var key = _key as String;
     if (walkThroughMap && key.contains('.')) {
       return walk(item, key, _parsedKeys);
     } else {
@@ -317,7 +318,7 @@
 
       // Enumerate the dimension values and cache enumerated rows
       for (int di = 0; di < dimensionsCount; di++) {
-        var dimensionVal = dimensionAccessor(item, _dimFields[di]);
+        Comparable dimensionVal = dimensionAccessor(item, _dimFields[di]);
         int dimensionValEnum = _dimToIntMap[di][dimensionVal];
         if (dimensionValEnum == null) {
           _dimToIntMap[di][dimensionVal] = dimensionValCount[di];
@@ -336,8 +337,8 @@
         return oldSortOrders[i];
       }
 
-      List dimensionVals = new List.from(_dimToIntMap[i].keys);
-      List<int> retval = new List<int>(_dimToIntMap[i].length);
+      var dimensionVals = new List<Comparable>.from(_dimToIntMap[i].keys);
+      var retval = new List<int>(_dimToIntMap[i].length);
 
       // When a comparator is not specified, our implementation of the
       // comparator tries to gracefully handle null values.
@@ -576,7 +577,7 @@
     if (di < 0) {
       return null;
     }
-    List values = new List.from(_dimToIntMap[di].keys);
+    var values = new List<Comparable>.from(_dimToIntMap[di].keys);
     if (comparators.containsKey(dimensionFieldName)) {
       values.sort(comparators[dimensionFieldName]);
     } else {
@@ -594,7 +595,8 @@
 /// and outputs:
 ///     ["list", {"key": "val", "val": "m"},
 ///      "another", {"state": "good"}, "state"]
-List _parseKey(String key, Map parsedKeysCache) {
+List /* <String|Map<String, String>> */ _parseKey(
+    String key, Map parsedKeysCache) {
   List parts = parsedKeysCache == null ? null : parsedKeysCache[key];
   if (parts == null && key != null) {
     parts = new List();
@@ -674,7 +676,7 @@
   return parts.fold(initial, (current, part) {
     if (current == null) {
       return null;
-    } else if (current is List && part is Map) {
+    } else if (current is List && part is Map<String, dynamic>) {
       for (int i = 0; i < current.length; i++) {
         bool match = true;
         part.forEach((String key, val) {
diff --git a/packages/charted/lib/charts/data_transformers/aggregation_item.dart b/packages/charted/lib/charts/data_transformers/aggregation_item.dart
index fd739c6..05289ad 100644
--- a/packages/charted/lib/charts/data_transformers/aggregation_item.dart
+++ b/packages/charted/lib/charts/data_transformers/aggregation_item.dart
@@ -145,7 +145,7 @@
     }
 
     var lowerDimensionField = model._dimFields[dimensions.length];
-    List lowerVals = model.valuesForDimension(lowerDimensionField);
+    List<String> lowerVals = model.valuesForDimension(lowerDimensionField);
 
     lowerVals.forEach((String name) {
       List<String> lowerDims = new List.from(dimensions)..add(name);
diff --git a/packages/charted/lib/charts/data_transformers/aggregation_transformer.dart b/packages/charted/lib/charts/data_transformers/aggregation_transformer.dart
index cca0924..6528777 100644
--- a/packages/charted/lib/charts/data_transformers/aggregation_transformer.dart
+++ b/packages/charted/lib/charts/data_transformers/aggregation_transformer.dart
@@ -34,7 +34,8 @@
   AggregationModel _model;
   bool _expandAllDimension = false;
   List<int> _selectedColumns = [];
-  FieldAccessor _indexFieldAccessor = (List row, int index) => row[index];
+  FieldAccessor _indexFieldAccessor =
+      (row, index) => (row as List)[index as int];
   ChartData _data;
 
   AggregationTransformer(this._dimensionColumnIndices, this._factsColumnIndices,
@@ -92,7 +93,8 @@
     // Process rows.
     rows.clear();
     var transformedRows = <List>[];
-    for (String value in _model.valuesForDimension(_dimensionColumnIndices[0])) {
+    for (String value
+        in _model.valuesForDimension(_dimensionColumnIndices[0])) {
       _generateAggregatedRow(transformedRows, [value]);
     }
     rows.addAll(transformedRows);
@@ -179,7 +181,8 @@
   /// Expands all dimensions.
   void expandAll() {
     if (_model != null) {
-      for (String value in _model.valuesForDimension(_dimensionColumnIndices[0])) {
+      for (String value
+          in _model.valuesForDimension(_dimensionColumnIndices[0])) {
         _expandAll([value]);
       }
       _expandAllDimension = false;
diff --git a/packages/charted/lib/charts/layout_renderers/pie_chart_renderer.dart b/packages/charted/lib/charts/layout_renderers/pie_chart_renderer.dart
index de00cf7..44727cb 100644
--- a/packages/charted/lib/charts/layout_renderers/pie_chart_renderer.dart
+++ b/packages/charted/lib/charts/layout_renderers/pie_chart_renderer.dart
@@ -98,12 +98,14 @@
       indices = indices.reversed.toList();
     }
 
-    num accessor(int d, int i) {
+    num accessor(dynamic _d, int i) {
+      var d = _d as int;
       var row = d == SMALL_INT_MAX ? otherRow : area.data.rows.elementAt(d);
       return row == null || row.elementAt(measure) == null
           ? 0
           : row.elementAt(measure) as num;
     }
+
     var data = (new PieLayout()..accessor = accessor).layout(indices);
     var arc = new SvgArc(
         innerRadiusCallback: (d, i, e) => innerRadiusRatio * radius,
@@ -149,9 +151,9 @@
     _legend.clear();
     var items = new List<ChartLegendItem>.generate(data.length, (i) {
       SvgArcData d = data.elementAt(i);
-      Iterable row =
-          d.data == SMALL_INT_MAX ? otherRow : area.data.rows.elementAt(
-              d.data as int);
+      Iterable row = d.data == SMALL_INT_MAX
+          ? otherRow
+          : area.data.rows.elementAt(d.data as int);
 
       return new ChartLegendItem(
           index: d.data as int,
diff --git a/packages/charted/lib/charts/src/cartesian_area_impl.dart b/packages/charted/lib/charts/src/cartesian_area_impl.dart
index cc36443..a73ace3 100644
--- a/packages/charted/lib/charts/src/cartesian_area_impl.dart
+++ b/packages/charted/lib/charts/src/cartesian_area_impl.dart
@@ -85,8 +85,8 @@
 
   bool _pendingLegendUpdate = false;
   bool _pendingAxisConfigUpdate = false;
-  List<ChartBehavior> _behaviors = new List<ChartBehavior>();
-  Map<ChartSeries, _ChartSeriesInfo> _seriesInfoCache = new Map();
+  List<ChartBehavior> _behaviors = [];
+  Map<ChartSeries, _ChartSeriesInfo> _seriesInfoCache = {};
 
   StreamController<ChartEvent> _valueMouseOverController;
   StreamController<ChartEvent> _valueMouseOutController;
@@ -214,8 +214,8 @@
   /// if one was not already created for the given dimension [column].
   DefaultChartAxisImpl _getDimensionAxis(int column) {
     _dimensionAxes.putIfAbsent(column, () {
-      var axisConf = config.getDimensionAxis(column),
-          axis = axisConf != null
+      var axisConf = config.getDimensionAxis(column);
+      var axis = axisConf != null
               ? new DefaultChartAxisImpl.withAxisConfig(this, axisConf)
               : new DefaultChartAxisImpl(this);
       return axis;
@@ -316,7 +316,8 @@
       String transform =
           'translate(${layout.renderArea.x},${layout.renderArea.y})';
 
-      selection.each((ChartSeries s, _, Element group) {
+      selection.each((_s, _, Element group) {
+        ChartSeries s = _s;
         _ChartSeriesInfo info = _seriesInfoCache[s];
         if (info == null) {
           info = _seriesInfoCache[s] = new _ChartSeriesInfo(this, s);
@@ -329,19 +330,16 @@
 
       // A series that was rendered earlier isn't there anymore, remove it
       selection.exit
-        ..each((ChartSeries s, _, __) {
+        ..each((_s, _, __) {
+          ChartSeries s = _s;
           var info = _seriesInfoCache.remove(s);
-          if (info != null) {
-            info.dispose();
-          }
+          info?.dispose();
         })
         ..remove();
 
       // Notify on the stream that the chart has been updated.
       isReady = true;
-      if (_chartAxesUpdatedController != null) {
-        _chartAxesUpdatedController.add(this);
-      }
+      _chartAxesUpdatedController?.add(this);
     });
 
     // Save the list of valid series and initialize axes.
@@ -360,7 +358,7 @@
 
   /// Initialize the axes - required even if the axes are not being displayed.
   _initAxes({bool preRender: false}) {
-    Map measureAxisUsers = <String, Iterable<ChartSeries>>{};
+    var measureAxisUsers = <String, List<ChartSeries>>{};
     var keysToRemove = _measureAxes.keys.toList();
 
     // Create necessary measures axes.
@@ -392,7 +390,7 @@
       var sampleCol = listOfSeries.first.measures.first,
           sampleColSpec = data.columns.elementAt(sampleCol),
           axis = _getMeasureAxis(id);
-      List domain;
+      List<num> domain;
 
       if (sampleColSpec.useOrdinalScale) {
         throw new UnsupportedError(
@@ -400,10 +398,11 @@
       } else {
         // Extent is available because [ChartRenderer.prepare] was already
         // called (when checking for valid series in [draw].
-        Iterable extents = listOfSeries.map((s) =>
-           (s.renderer as CartesianRenderer).extent).toList();
-        var lowest = min(extents.map((e) => e.min as num)),
-            highest = max(extents.map((e) => e.max as num));
+        var extents = listOfSeries
+            .map((s) => (s.renderer as CartesianRenderer).extent)
+            .toList();
+        var lowest = min(extents.map((e) => e.min as num));
+        var highest = max(extents.map((e) => e.max as num));
 
         // Use default domain if lowest and highest are the same, right now
         // lowest is always 0 unless it is less than 0 - change to lowest when
@@ -420,18 +419,20 @@
     // Configure dimension axes.
     int dimensionAxesCount = useTwoDimensionAxes ? 2 : 1;
     config.dimensions.take(dimensionAxesCount).forEach((int column) {
-      var axis = _getDimensionAxis(column),
-          sampleColumnSpec = data.columns.elementAt(column),
-          values = data.rows.map((row) => row.elementAt(column));
-      List domain;
+      var axis = _getDimensionAxis(column);
+      var sampleColumnSpec = data.columns.elementAt(column);
+
+      Iterable values = data.rows
+          .map((row) => row.elementAt(column));
 
       if (sampleColumnSpec.useOrdinalScale) {
-        domain = values.map((e) => e.toString()).toList();
+        List<String> domain = values.map((v) => v.toString()).toList();
+        axis.initAxisDomain(column, true, domain);
       } else {
-        var extent = new Extent.items(values);
-        domain = [extent.min, extent.max];
+        var extent = new Extent.items(values.cast<num>());
+        List<num> domain = [extent.min, extent.max];
+        axis.initAxisDomain(column, true, domain);
       }
-      axis.initAxisDomain(column, true, domain);
     });
 
     // See if any dimensions need "band" on the axis.
@@ -448,13 +449,13 @@
     // List of measure and dimension axes that are displayed
     assert(isNullOrEmpty(config.displayedMeasureAxes) ||
         config.displayedMeasureAxes.length < 2);
-    var measureAxesCount = dimensionAxesCount == 1 ? 2 : 0,
-        displayedMeasureAxes = (isNullOrEmpty(config.displayedMeasureAxes)
+    var measureAxesCount = dimensionAxesCount == 1 ? 2 : 0;
+    var displayedMeasureAxes = (isNullOrEmpty(config.displayedMeasureAxes)
                 ? _measureAxes.keys.take(measureAxesCount)
                 : config.displayedMeasureAxes.take(measureAxesCount))
-            .toList(growable: false),
-        displayedDimensionAxes =
-        config.dimensions.take(dimensionAxesCount).toList(growable: false);
+            .toList(growable: false);
+    var displayedDimensionAxes =
+            config.dimensions.take(dimensionAxesCount).toList(growable: false);
 
     // Compute size of the dimension axes
     if (config.renderDimensionAxes != false) {
@@ -475,8 +476,8 @@
           ? MEASURE_AXIS_ORIENTATIONS.last
           : MEASURE_AXIS_ORIENTATIONS.first;
       displayedMeasureAxes.asMap().forEach((int index, String key) {
-        var axis = _measureAxes[key],
-            orientation = _orientRTL(measureAxisOrientations[index]);
+        var axis = _measureAxes[key];
+        var orientation = _orientRTL(measureAxisOrientations[index]);
         axis.prepareToDraw(orientation);
         layout._axes[orientation] = axis.size;
       });
@@ -503,7 +504,8 @@
           .data(displayedMeasureAxes);
       // Update measure axis (add/remove/update)
       axisGroups.enter.append('svg:g');
-      axisGroups.each((String axisId, index, group) {
+      axisGroups.each((_axisId, index, group) {
+        String axisId = _axisId;
         _getMeasureAxis(axisId).draw(group, _scope, preRender: preRender);
         group.attributes['class'] = 'measure-axis-group measure-${index}';
       });
@@ -517,7 +519,8 @@
           .data(displayedDimensionAxes);
       // Update dimension axes (add/remove/update)
       dimAxisGroups.enter.append('svg:g');
-      dimAxisGroups.each((int column, index, group) {
+      dimAxisGroups.each((_column, index, group) {
+        int column = _column;
         _getDimensionAxis(column).draw(group, _scope, preRender: preRender);
         group.attributes['class'] = 'dimension-axis-group dim-${index}';
       });
@@ -535,7 +538,6 @@
             ? [layout.renderArea.height, 0]
             : [0, layout.renderArea.width]);
       }
-      ;
     }
   }
 
@@ -598,7 +600,7 @@
 
   // Updates the AxisConfig, if configuration chagned since the last time the
   // AxisConfig was updated.
-  _updateAxisConfig() {
+  void _updateAxisConfig() {
     if (!_pendingAxisConfigUpdate) return;
     _series.forEach((ChartSeries s) {
       var measureAxisIds =
diff --git a/packages/charted/lib/charts/src/chart_axis_impl.dart b/packages/charted/lib/charts/src/chart_axis_impl.dart
index 890a18c..0221f22 100644
--- a/packages/charted/lib/charts/src/chart_axis_impl.dart
+++ b/packages/charted/lib/charts/src/chart_axis_impl.dart
@@ -11,7 +11,7 @@
 class DefaultChartAxisImpl {
   static const int _AXIS_TITLE_HEIGHT = 20;
 
-  CartesianArea _area;
+  final CartesianArea _area;
   ChartAxisConfig config;
   ChartAxisTheme _theme;
   SvgAxisTicks _axisTicksPlacement;
@@ -37,9 +37,7 @@
     _isDimension = isDimension;
 
     // If we don't have a scale yet, create one.
-    if (scale == null) {
-      _scale = _columnSpec.createDefaultScale();
-    }
+    _scale ??= _columnSpec.createDefaultScale();
 
     // We have the scale, get theme.
     _theme = isDimension
@@ -56,24 +54,26 @@
     _title = config?.title;
   }
 
-  void initAxisScale(Iterable range) {
+  void initAxisScale(Iterable<num> range) {
     assert(scale != null);
     if (scale is OrdinalScale) {
+      Iterable<num> numericRange = range;
       var usingBands = _area.dimensionsUsingBands.contains(_column),
           innerPadding = usingBands ? _theme.axisBandInnerPadding : 1.0,
-          outerPadding =
-          usingBands ? _theme.axisBandOuterPadding : _theme.axisOuterPadding;
+          outerPadding = usingBands
+              ? _theme.axisBandOuterPadding
+              : _theme.axisOuterPadding;
 
       // This is because when left axis is primary the first data row should
       // appear on top of the y-axis instead of on bottom.
       if (_area.config.isLeftAxisPrimary) {
-        range = range.toList().reversed;
+        numericRange = numericRange.toList().reversed;
       }
       if (usingBands) {
         (scale as OrdinalScale)
-            .rangeRoundBands(range, innerPadding, outerPadding);
+            .rangeRoundBands(numericRange, innerPadding, outerPadding);
       } else {
-        (scale as OrdinalScale).rangePoints(range, outerPadding);
+        (scale as OrdinalScale).rangePoints(numericRange, outerPadding);
       }
     } else {
       if (_title != null) {
@@ -88,7 +88,8 @@
   }
 
   void prepareToDraw(String orientation) {
-    if (orientation == null) orientation = ORIENTATION_BOTTOM;
+    assert(_theme != null);
+    orientation ??= ORIENTATION_BOTTOM;
     _orientation = orientation;
     _isVertical =
         _orientation == ORIENTATION_LEFT || _orientation == ORIENTATION_RIGHT;
@@ -104,15 +105,14 @@
 
     // Handle auto re-sizing of horizontal axis.
     var ticks = (config != null && !isNullOrEmpty(config.tickValues))
-        ? config.tickValues
-        : scale.ticks,
-
-    formatter = _columnSpec.formatter == null
-        ? scale.createTickFormatter()
-        : _columnSpec.formatter,
-    textMetrics = new TextMetrics(fontStyle: _theme.ticksFont),
-    formattedTicks = ticks.map((x) => formatter(x)).toList(),
-    shortenedTicks = formattedTicks;
+            ? config.tickValues
+            : scale.ticks,
+        formatter = _columnSpec.formatter == null
+            ? scale.createTickFormatter()
+            : _columnSpec.formatter,
+        textMetrics = new TextMetrics(fontStyle: _theme.ticksFont),
+        formattedTicks = ticks.map((x) => formatter(x)).toList(),
+        shortenedTicks = formattedTicks;
     if (_isVertical) {
       var width = textMetrics.getLongestTextWidth(formattedTicks).ceil();
       if (width > _theme.verticalAxisWidth) {
@@ -199,7 +199,6 @@
   set scale(Scale value) {
     _scale = value;
   }
-
 }
 
 class PrecomputedAxisTicks implements SvgAxisTicks {
@@ -235,8 +234,8 @@
     formattedTicks = ticks.map((x) => axis.tickFormat(x)).toList();
     shortenedTicks = formattedTicks;
 
-    var range = axis.scale.rangeExtent,
-        textMetrics = new TextMetrics(fontStyle: ticksFont);
+    Extent<num> range = axis.scale.rangeExtent;
+    var textMetrics = new TextMetrics(fontStyle: ticksFont);
     num allowedWidth = (range.max - range.min) ~/ ticks.length,
         maxLabelWidth = textMetrics.getLongestTextWidth(formattedTicks);
 
diff --git a/packages/charted/lib/charts/src/chart_config_impl.dart b/packages/charted/lib/charts/src/chart_config_impl.dart
index a4f2cd7..42d4b06 100644
--- a/packages/charted/lib/charts/src/chart_config_impl.dart
+++ b/packages/charted/lib/charts/src/chart_config_impl.dart
@@ -24,7 +24,6 @@
   @override
   bool isLeftAxisPrimary = false;
 
-  @override
   bool autoResizeAxis = true;
 
   @override
diff --git a/packages/charted/lib/charts/src/chart_data_impl.dart b/packages/charted/lib/charts/src/chart_data_impl.dart
index 79e3a1a..0ba0d11 100644
--- a/packages/charted/lib/charts/src/chart_data_impl.dart
+++ b/packages/charted/lib/charts/src/chart_data_impl.dart
@@ -80,7 +80,8 @@
               'Changes on this row will not be monitored');
         } else {
           _disposer.add(
-              (row as ObservableList).listChanges
+              (row as ObservableList)
+                  .listChanges
                   .listen((changes) => _valuesChanged(index, changes)),
               row);
         }
diff --git a/packages/charted/lib/charts/src/chart_events_impl.dart b/packages/charted/lib/charts/src/chart_events_impl.dart
index 859f21d..b2793e8 100644
--- a/packages/charted/lib/charts/src/chart_events_impl.dart
+++ b/packages/charted/lib/charts/src/chart_events_impl.dart
@@ -35,8 +35,9 @@
   DefaultChartEventImpl(this.source, this.area,
       [this.series, this.row, this.column, this.value]) {
     var hostRect = area.host.getBoundingClientRect(),
-        left =
-        area.config.isRTL ? area.theme.padding.end : area.theme.padding.start;
+        left = area.config.isRTL
+            ? area.theme.padding.end
+            : area.theme.padding.start;
     if (source is MouseEvent) {
       MouseEvent mouseSource = source;
       chartX = mouseSource.client.x - hostRect.left - left;
diff --git a/packages/charted/lib/charts/src/chart_legend_impl.dart b/packages/charted/lib/charts/src/chart_legend_impl.dart
index d2be775..0af8f35 100644
--- a/packages/charted/lib/charts/src/chart_legend_impl.dart
+++ b/packages/charted/lib/charts/src/chart_legend_impl.dart
@@ -110,11 +110,13 @@
   /// Creates legend items starting at the given index.
   void _createLegendItems() {
     var state = _area.state,
-        rows =
-        _root.selectAll('.chart-legend-row').data(_items, (x) => x.hashCode),
+        rows = _root
+            .selectAll('.chart-legend-row')
+            .data(_items, (x) => x.hashCode),
         isFirstRender = rows.length == 0;
 
-    var enter = rows.enter.appendWithCallback((ChartLegendItem d, i, e) {
+    var enter = rows.enter.appendWithCallback((_d, i, e) {
+      ChartLegendItem d = _d;
       var row = Namespace.createChildElement('div', e),
           color = Namespace.createChildElement('div', e)
             ..className = 'chart-legend-color',
@@ -158,7 +160,8 @@
 
     // We have elements in the DOM that need updating.
     if (!isFirstRender) {
-      rows.each((ChartLegendItem d, i, Element e) {
+      rows.each((_d, i, Element e) {
+        ChartLegendItem d = _d;
         var classes = e.classes;
         if (state != null) {
           if (d.index == state.preview) {
@@ -186,13 +189,15 @@
 
     if (state != null) {
       enter
-        ..on('mouseover', (ChartLegendItem d, i, e) => state.preview = d.index)
+        ..on('mouseover',
+            (d, i, e) => state.preview = (d as ChartLegendItem).index)
         ..on('mouseout', (d, i, e) {
           if (state.preview == d.index) {
             state.preview = null;
           }
         })
-        ..on('click', (ChartLegendItem d, i, e) {
+        ..on('click', (_d, i, e) {
+          ChartLegendItem d = _d;
           if (state.isSelected(d.index)) {
             state.unselect(d.index);
           } else {
diff --git a/packages/charted/lib/charts/src/chart_state_impl.dart b/packages/charted/lib/charts/src/chart_state_impl.dart
index 6b86628..0ddacba 100644
--- a/packages/charted/lib/charts/src/chart_state_impl.dart
+++ b/packages/charted/lib/charts/src/chart_state_impl.dart
@@ -26,7 +26,8 @@
   LinkedHashSet<int> hidden = new LinkedHashSet<int>();
 
   LinkedHashSet<int> selection = new LinkedHashSet<int>();
-  LinkedHashSet<Pair<int, int>> highlights = new LinkedHashSet<Pair<int, int>>();
+  LinkedHashSet<Pair<int, int>> highlights =
+      new LinkedHashSet<Pair<int, int>>();
 
   Pair<int, int> _hovered;
   int _preview;
diff --git a/packages/charted/lib/charts/themes/quantum_theme.dart b/packages/charted/lib/charts/themes/quantum_theme.dart
index de2c034..9185978 100644
--- a/packages/charted/lib/charts/themes/quantum_theme.dart
+++ b/packages/charted/lib/charts/themes/quantum_theme.dart
@@ -39,10 +39,8 @@
 
   static final _MEASURE_AXIS_THEME =
       new QuantumChartAxisTheme(ChartAxisTheme.FILL_RENDER_AREA, 5);
-  static final _ORDINAL_DIMENSION_AXIS_THEME =
-  new QuantumChartAxisTheme(0, 10);
-  static final _DEFAULT_DIMENSION_AXIS_THEME =
-  new QuantumChartAxisTheme(4, 10);
+  static final _ORDINAL_DIMENSION_AXIS_THEME = new QuantumChartAxisTheme(0, 10);
+  static final _DEFAULT_DIMENSION_AXIS_THEME = new QuantumChartAxisTheme(4, 10);
 
   final OrdinalScale _scale = new OrdinalScale()..range = COLORS;
 
@@ -71,9 +69,11 @@
 
   @override
   String getFilterForState(int state) => state & ChartState.COL_PREVIEW != 0 ||
-      state & ChartState.VAL_HOVERED != 0 ||
-      state & ChartState.COL_SELECTED != 0 ||
-      state & ChartState.VAL_HIGHLIGHTED != 0 ? 'url(#drop-shadow)' : '';
+          state & ChartState.VAL_HOVERED != 0 ||
+          state & ChartState.COL_SELECTED != 0 ||
+          state & ChartState.VAL_HIGHLIGHTED != 0
+      ? 'url(#drop-shadow)'
+      : '';
 
   @override
   String getOtherColor([int state = 0]) => OTHER_COLORS is Iterable
diff --git a/packages/charted/lib/core/interpolators/interpolators.dart b/packages/charted/lib/core/interpolators/interpolators.dart
index d13eed1..89b8c29 100644
--- a/packages/charted/lib/core/interpolators/interpolators.dart
+++ b/packages/charted/lib/core/interpolators/interpolators.dart
@@ -10,11 +10,11 @@
 
 /// [Interpolator] accepts [t], such that 0.0 < t < 1.0 and returns
 /// a value in a pre-defined range.
-typedef Interpolator(num t);
+typedef Interpolator<S> = S Function(num t);
 
 /// [InterpolatorGenerator] accepts two parameters [a], [b] and returns an
 /// [Interpolator] for transitioning from [a] to [b]
-typedef Interpolator InterpolatorGenerator(a, b);
+typedef InterpolatorGenerator<T, S> = Interpolator<S> Function(T a, T b);
 
 /// List of registered interpolators - [createInterpolatorFromRegistry]
 /// iterates through this list from backwards and the first non-null
@@ -59,13 +59,13 @@
 //
 
 /// Generate a numeric interpolator between numbers [a] and [b]
-Interpolator createNumberInterpolator(num a, num b) {
+Interpolator<num> createNumberInterpolator(num a, num b) {
   b -= a;
   return (t) => a + b * t;
 }
 
 /// Generate a rounded number interpolator between numbers [a] and [b]
-Interpolator createRoundedNumberInterpolator(num a, num b) {
+Interpolator<num> createRoundedNumberInterpolator(num a, num b) {
   b -= a;
   return (t) => (a + b * t).round();
 }
@@ -78,8 +78,8 @@
 /// merging the non numeric part of the strings.
 ///
 /// Eg: Interpolate between $100.0 and $150.0
-Interpolator createStringInterpolator(String a, String b) {
-  if (a == null || b == null) return (t) => b;
+Interpolator<String> createStringInterpolator(String a, String b) {
+  if (a == null || b == null) return (num t) => b;
 
   // See if both A and B represent colors as RGB or HEX strings.
   // If yes, use color interpolators
@@ -95,14 +95,14 @@
         new Color.fromHslString(a), new Color.fromHslString(b));
   }
 
-  var numberRegEx = new RegExp(r'[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?'),
-      numMatchesInA = numberRegEx.allMatches(a),
-      numMatchesInB = numberRegEx.allMatches(b),
-      stringParts = [],
-      numberPartsInA = [],
-      numberPartsInB = [],
-      interpolators = [],
-      s0 = 0;
+  var numberRegEx = new RegExp(r'[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?');
+  var numMatchesInA = numberRegEx.allMatches(a);
+  var numMatchesInB = numberRegEx.allMatches(b);
+  List<String> stringParts = [];
+  List<String> numberPartsInA = [];
+  List<String> numberPartsInB = [];
+  List<Interpolator<num>> interpolators = [];
+  int s0 = 0;
 
   numberPartsInA.addAll(numMatchesInA.map((m) => m.group(0)));
 
@@ -118,18 +118,16 @@
   int maxLength = math.max(numberPartsInA.length, numberPartsInB.length);
   for (var i = 0; i < numberLength; i++) {
     interpolators.add(createNumberInterpolator(
-        num.parse(numberPartsInA[i] as String),
-        num.parse(numberPartsInB[i] as String)));
+        num.parse(numberPartsInA[i]), num.parse(numberPartsInB[i])));
   }
   if (numberPartsInA.length < numberPartsInB.length) {
     for (var i = numberLength; i < maxLength; i++) {
       interpolators.add(createNumberInterpolator(
-          num.parse(numberPartsInB[i] as String),
-          num.parse(numberPartsInB[i] as String)));
+          num.parse(numberPartsInB[i]), num.parse(numberPartsInB[i])));
     }
   }
 
-  return (t) {
+  return (num t) {
     StringBuffer sb = new StringBuffer();
     for (var i = 0; i < stringParts.length; i++) {
       sb.write(stringParts[i]);
@@ -142,32 +140,34 @@
 }
 
 /// Generate an interpolator for RGB values.
-Interpolator createRgbColorInterpolator(Color a, Color b) {
-  if (a == null || b == null) return (t) => b;
+Interpolator<String> createRgbColorInterpolator(Color a, Color b) {
+  if (a == null || b == null) return (num t) => b.toRgbaString();
   var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
 
-  return (t) => new Color.fromRgba((ar + br * t).round(), (ag + bg * t).round(),
-      (ab + bb * t).round(), 1.0).toRgbaString();
+  return (num t) => new Color.fromRgba((ar + br * t).round(),
+          (ag + bg * t).round(), (ab + bb * t).round(), 1.0)
+      .toRgbaString();
 }
 
 /// Generate an interpolator using HSL color system converted to Hex string.
-Interpolator createHslColorInterpolator(Color a, Color b) {
-  if (a == null || b == null) return (t) => b;
+Interpolator<String> createHslColorInterpolator(Color a, Color b) {
+  if (a == null || b == null) return (num t) => b.toHslaString();
   var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al;
 
-  return (t) => new Color.fromHsla((ah + bh * t).round(), (as + bs * t).round(),
-      (al + bl * t).round(), 1.0).toHslaString();
+  return (num t) => new Color.fromHsla((ah + bh * t).round(),
+          (as + bs * t).round(), (al + bl * t).round(), 1.0)
+      .toHslaString();
 }
 
 /// Generates an interpolator to interpolate each element between lists
 /// [a] and [b] using registered interpolators.
-Interpolator createListInterpolator(List a, List b) {
+Interpolator<List> createListInterpolator(List a, List b) {
   if (a == null || b == null) return (t) => b;
-  var x = [],
+  var x = <Interpolator>[],
       aLength = a.length,
       numInterpolated = b.length,
-      output = new List<dynamic>.filled(
-          math.max(aLength, numInterpolated), null);
+      output =
+          new List<dynamic>.filled(math.max(aLength, numInterpolated), null);
   num n0 = math.min(aLength, numInterpolated);
   int i;
 
@@ -175,7 +175,7 @@
   for (; i < aLength; ++i) output[i] = a[i];
   for (; i < numInterpolated; ++i) output[i] = b[i];
 
-  return (t) {
+  return (num t) {
     for (i = 0; i < n0; ++i) output[i] = x[i](t);
     return output;
   };
@@ -183,7 +183,7 @@
 
 /// Generates an interpolator to interpolate each value on [a] to [b] using
 /// registered interpolators.
-Interpolator createMapInterpolator(Map a, Map b) {
+Interpolator<Map> createMapInterpolator(Map a, Map b) {
   if (a == null || b == null) return (t) => b;
   var interpolatorsMap = new Map(),
       output = new Map(),
@@ -212,14 +212,14 @@
 
 /// Returns the interpolator that interpolators two transform strings
 /// [a] and [b] by their translate, rotate, scale and skewX parts.
-Interpolator createTransformInterpolator(String a, String b) {
+Interpolator<String> createTransformInterpolator(String a, String b) {
   if (a == null || b == null) return (t) => b;
   var numRegExStr = r'[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?',
       numberRegEx = new RegExp(numRegExStr),
       translateRegEx =
-      new RegExp(r'translate\(' + '$numRegExStr,$numRegExStr' + r'\)'),
+          new RegExp(r'translate\(' + '$numRegExStr,$numRegExStr' + r'\)'),
       scaleRegEx =
-      new RegExp(r'scale\(' + numRegExStr + r',' + numRegExStr + r'\)'),
+          new RegExp(r'scale\(' + numRegExStr + r',' + numRegExStr + r'\)'),
       rotateRegEx = new RegExp(r'rotate\(' + numRegExStr + r'(deg)?\)'),
       skewRegEx = new RegExp(r'skewX\(' + numRegExStr + r'(deg)?\)'),
       translateA = translateRegEx.firstMatch(a),
@@ -229,8 +229,7 @@
       translateB = translateRegEx.firstMatch(b),
       scaleB = scaleRegEx.firstMatch(b),
       rotateB = rotateRegEx.firstMatch(b),
-      skewB = skewRegEx.firstMatch(b),
-      match;
+      skewB = skewRegEx.firstMatch(b);
 
   List<num> numSetA = [], numSetB = [];
   String tempStr;
@@ -238,7 +237,7 @@
   // translate
   if (translateA != null) {
     tempStr = a.substring(translateA.start, translateA.end);
-    match = numberRegEx.allMatches(tempStr);
+    var match = numberRegEx.allMatches(tempStr);
     for (Match m in match) {
       numSetA.add(num.parse(m.group(0)));
     }
@@ -248,7 +247,7 @@
 
   if (translateB != null) {
     tempStr = b.substring(translateB.start, translateB.end);
-    match = numberRegEx.allMatches(tempStr);
+    var match = numberRegEx.allMatches(tempStr);
     for (Match m in match) {
       numSetB.add(num.parse(m.group(0)));
     }
@@ -259,7 +258,7 @@
   // scale
   if (scaleA != null) {
     tempStr = a.substring(scaleA.start, scaleA.end);
-    match = numberRegEx.allMatches(tempStr);
+    var match = numberRegEx.allMatches(tempStr);
     for (Match m in match) {
       numSetA.add(num.parse(m.group(0)));
     }
@@ -269,7 +268,7 @@
 
   if (scaleB != null) {
     tempStr = b.substring(scaleB.start, scaleB.end);
-    match = numberRegEx.allMatches(tempStr);
+    var match = numberRegEx.allMatches(tempStr);
     for (Match m in match) {
       numSetB.add(num.parse(m.group(0)));
     }
@@ -280,16 +279,16 @@
   // rotate
   if (rotateA != null) {
     tempStr = a.substring(rotateA.start, rotateA.end);
-    match = numberRegEx.firstMatch(tempStr);
-    numSetA.add(num.parse((match as Match).group(0)));
+    var match = numberRegEx.firstMatch(tempStr);
+    numSetA.add(num.parse(match.group(0)));
   } else {
     numSetA.add(0);
   }
 
   if (rotateB != null) {
     tempStr = b.substring(rotateB.start, rotateB.end);
-    match = numberRegEx.firstMatch(tempStr);
-    numSetB.add(num.parse((match as Match).group(0)));
+    var match = numberRegEx.firstMatch(tempStr);
+    numSetB.add(num.parse(match.group(0)));
   } else {
     numSetB.add(0);
   }
@@ -306,21 +305,21 @@
   // skew
   if (skewA != null) {
     tempStr = a.substring(skewA.start, skewA.end);
-    match = numberRegEx.firstMatch(tempStr);
-    numSetA.add(num.parse((match as Match).group(0)));
+    var match = numberRegEx.firstMatch(tempStr);
+    numSetA.add(num.parse(match.group(0)));
   } else {
     numSetA.add(0);
   }
 
   if (skewB != null) {
     tempStr = b.substring(skewB.start, skewB.end);
-    match = numberRegEx.firstMatch(tempStr);
-    numSetB.add(num.parse((match as Match).group(0)));
+    var match = numberRegEx.firstMatch(tempStr);
+    numSetB.add(num.parse(match.group(0)));
   } else {
     numSetB.add(0);
   }
 
-  return (t) {
+  return (num t) {
     return 'translate(${createNumberInterpolator(numSetA[0], numSetB[0])(t)},'
         '${createNumberInterpolator(numSetA[1], numSetB[1])(t)})'
         'scale(${createNumberInterpolator(numSetA[2], numSetB[2])(t)},'
@@ -332,7 +331,7 @@
 
 /// Returns the interpolator that interpolators zoom list [a] to [b]. Zoom
 /// lists are described by triple elements [ux0, uy0, w0] and [ux1, uy1, w1].
-Interpolator createZoomInterpolator(List a, List b) {
+Interpolator<List<num>> createZoomInterpolator(List a, List b) {
   if (a == null || b == null) return (t) => b;
   assert(a.length == b.length && a.length == 3);
 
@@ -351,7 +350,7 @@
       dr = r1 - r0,
       S = ((!dr.isNaN) ? dr : math.log(w1 / w0)) / sqrt2;
 
-  return (t) {
+  return (num t) {
     var s = t * S;
     if (!dr.isNaN) {
       // General case.
@@ -360,18 +359,18 @@
       return [ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / cosh(sqrt2 * s + r0)];
     }
     // Special case for u0 ~= u1.
-    return [ux0 + t * dx, uy0 + t * dy, w0 * math.exp(sqrt2 * s)];
+    return <num>[ux0 + t * dx, uy0 + t * dy, w0 * math.exp(sqrt2 * s)];
   };
 }
 
 /// Reverse interpolator for a number.
-Interpolator uninterpolateNumber(num a, num b) {
+Interpolator<num> uninterpolateNumber(num a, num b) {
   b = 1 / (b - a);
   return (x) => (x - a) * b;
 }
 
 /// Reverse interpolator for a clamped number.
-Interpolator uninterpolateClamp(num a, num b) {
+Interpolator<num> uninterpolateClamp(num a, num b) {
   b = 1 / (b - a);
   return (x) => math.max(0, math.min(1, (x - a) * b));
 }
diff --git a/packages/charted/lib/core/scales.dart b/packages/charted/lib/core/scales.dart
index 689d42d..7125ed9 100644
--- a/packages/charted/lib/core/scales.dart
+++ b/packages/charted/lib/core/scales.dart
@@ -28,25 +28,28 @@
 part 'scales/log_scale.dart';
 part 'scales/time_scale.dart';
 
-typedef num RoundFunction(num value);
+typedef RoundFunction = num Function(num);
 
 /// Minimum common interface supported by all scales. [QuantitativeScale] and
 /// [OrdinalScale] contain the interface for their respective types.
-abstract class Scale {
+abstract class Scale<TDomain extends Comparable, TRange> {
+  static final NumberFormat numberFormatter =
+      new NumberFormat(new EnUsLocale());
+
   /// Given a [value] in the input domain, map it to the range.
   /// On [QuantitativeScale]s both parameter and return values are numbers.
-  dynamic scale(value);
+  TRange scale(TDomain value);
 
   /// Given a [value] in the output range, return value in the input domain
   /// that maps to the value.
   /// On [QuantitativeScale]s both parameter and return values are numbers.
-  dynamic invert(value);
+  TDomain invert(TRange value);
 
   /// Input domain used by this scale.
-  Iterable domain;
+  Iterable<TDomain> domain;
 
   /// Output range used by this scale.
-  Iterable range;
+  Iterable<TRange> range;
 
   /// Maximum and minimum values of the scale's output range.
   Extent get rangeExtent;
@@ -89,8 +92,9 @@
 
 /// Minimum common interface supported by scales whose input domain
 /// contains discreet values (Ordinal scales).
-abstract class OrdinalScale extends Scale {
-  factory OrdinalScale() = _OrdinalScale;
+abstract class OrdinalScale<TDomain extends Comparable, TRange>
+    extends Scale<TDomain, TRange> {
+  factory OrdinalScale() = _OrdinalScale<TDomain, TRange>;
 
   /// Amount of space that each value in the domain gets from the range. A band
   /// is available only after [rangeBands] or [rangeRoundBands] is called by
@@ -100,16 +104,17 @@
   /// Maps each value on the domain to a single point on output range.  When a
   /// non-zero value is specified, [padding] space is left unused on both ends
   /// of the range.
-  void rangePoints(Iterable range, [double padding]);
+  void rangePoints(Iterable<num> range, [double padding]);
 
   /// Maps each value on the domain to a band in the output range.  When a
   /// non-zero value is specified, [padding] space is left between each bands
   /// and [outerPadding] space is left unused at both ends of the range.
-  void rangeBands(Iterable range, [double padding, double outerPadding]);
+  void rangeBands(Iterable<num> range, [double padding, double outerPadding]);
 
   /// Similar to [rangeBands] but ensures that each band starts and ends on a
   /// pixel boundary - helps avoid anti-aliasing artifacts.
-  void rangeRoundBands(Iterable range, [double padding, double outerPadding]);
+  void rangeRoundBands(Iterable<num> range,
+      [double padding, double outerPadding]);
 }
 
 class RoundingFunctions extends Pair<RoundFunction, RoundFunction> {
@@ -119,9 +124,8 @@
   factory RoundingFunctions.defaults() =>
       new RoundingFunctions((x) => x.floor(), (x) => x.ceil());
 
-  factory RoundingFunctions.identity() => new RoundingFunctions(
-      (num n) => identityFunction/*<num>*/(n),
-      (num n) => identityFunction/*<num>*/(n));
+  factory RoundingFunctions.identity() =>
+      new RoundingFunctions((num n) => n, (num n) => n);
 
   RoundFunction get floor => super.first;
   RoundFunction get ceil => super.last;
@@ -130,15 +134,15 @@
 /// Namespacing container for utilities used by scales.
 abstract class ScaleUtils {
   /// Utility to return extent of sorted [values].
-  static Extent extent(Iterable<num> values) => values.first < values.last
-      ? new Extent(values.first, values.last)
-      : new Extent(values.last, values.first);
+  static Extent<num> extent(Iterable<num> values) => values.first < values.last
+      ? new Extent<num>(values.first, values.last)
+      : new Extent<num>(values.last, values.first);
 
   /// Extends [values] to round numbers based on the given pair of
   /// floor and ceil functions.  [functions] is a pair of rounding function
   /// among which the first is used to compute floor of a number and the
   /// second for ceil of the number.
-  static List nice(List<num> values, RoundingFunctions functions) {
+  static List<num> nice(List<num> values, RoundingFunctions functions) {
     if (values.last >= values.first) {
       values[0] = functions.floor(values.first);
       values[values.length - 1] = functions.ceil(values.last);
@@ -162,11 +166,14 @@
   /// @param range          The range of the scale.
   /// @param uninterpolator The uninterpolator for domain values.
   /// @param interpolator   The interpolator for range values.
-  static Function bilinearScale(
-      List domain, List range, Function uninterpolator, Function interpolator) {
-    var u = uninterpolator(domain[0], domain[1]),
-        i = interpolator(range[0], range[1]);
-    return (x) => i(u(x));
+  static num Function(num) bilinearScale(
+      List<num> domain,
+      List<num> range,
+      InterpolatorGenerator<num, num> uninterpolator,
+      InterpolatorGenerator<num, num> interpolator) {
+    Interpolator<num> u = uninterpolator(domain[0], domain[1]);
+    Interpolator<num> i = interpolator(range[0], range[1]);
+    return (num x) => i(u(x));
   }
 
   /// Returns a Function that given a value x on the domain, returns the
@@ -176,9 +183,15 @@
   /// @param range          The range of the scale.
   /// @param uninterpolator The uninterpolator for domain values.
   /// @param interpolator   The interpolator for range values.
-  static Function polylinearScale(
-      List<num> domain, List range, Function uninterpolator, Function interpolator) {
-    var u = [], i = [], j = 0, k = math.min(domain.length, range.length) - 1;
+  static num Function(num) polylinearScale(
+      List<num> domain,
+      List<num> range,
+      InterpolatorGenerator<num, num> uninterpolator,
+      InterpolatorGenerator<num, num> interpolator) {
+    List<Interpolator<num>> u = [];
+    List<Interpolator<num>> i = [];
+    int j = 0;
+    int k = math.min(domain.length, range.length) - 1;
 
     // Handle descending domains.
     if (domain[k] < domain[0]) {
@@ -191,7 +204,7 @@
       i.add(interpolator(range[j - 1], range[j]));
     }
 
-    return (x) {
+    return (num x) {
       int index = bisect(domain, x, 1, k) - 1;
       return i[index](u[index](x));
     };
@@ -233,5 +246,5 @@
     return lo;
   }
 
-  static Function bisect = bisectRight;
+  static int Function(List<num>, num, [int, int]) bisect = bisectRight;
 }
diff --git a/packages/charted/lib/core/scales/linear_scale.dart b/packages/charted/lib/core/scales/linear_scale.dart
index f9b4c13..2a5a387 100644
--- a/packages/charted/lib/core/scales/linear_scale.dart
+++ b/packages/charted/lib/core/scales/linear_scale.dart
@@ -7,27 +7,40 @@
 //
 part of charted.core.scales;
 
-class LinearScale implements Scale {
-  static const defaultDomain = const [0, 1];
-  static const defaultRange = const [0, 1];
+class LinearScale extends BaseLinearScale {
+  LinearScale();
+  LinearScale._clone(LinearScale source) : super._clone(source);
+
+  @override
+  Iterable<num> get ticks => _linearTickRange();
+
+  @override
+  LinearScale clone() => new LinearScale._clone(this);
+}
+
+abstract class BaseLinearScale implements Scale<num, num> {
+  static const List<int> defaultDomain = const [0, 1];
+  static const List<int> defaultRange = const [0, 1];
 
   bool _rounded = false;
-  Iterable _domain = defaultDomain;
-  Iterable _range = defaultRange;
+  Iterable<num> _domain;
+  Iterable<num> _range;
 
   int _ticksCount = 5;
   int _forcedTicksCount = -1;
 
   bool _clamp = false;
   bool _nice = false;
-  Function _invert;
-  Function _scale;
+  num Function(num) _invert;
+  num Function(num) _scale;
 
-  LinearScale();
+  BaseLinearScale()
+      : _domain = defaultDomain,
+        _range = defaultRange;
 
-  LinearScale._clone(LinearScale source)
-      : _domain = source._domain.toList(),
-        _range = source._range.toList(),
+  BaseLinearScale._clone(BaseLinearScale source)
+      : _domain = new List<num>.from(source._domain),
+        _range = new List<num>.from(source._range),
         _ticksCount = source._ticksCount,
         _clamp = source._clamp,
         _nice = source._nice,
@@ -38,7 +51,7 @@
   void _reset({bool nice: false}) {
     if (nice) {
       _domain = ScaleUtils.nice(
-          _domain as List<num>, ScaleUtils.niceStep(_linearTickRange().step));
+          _domain, ScaleUtils.niceStep(_linearTickRange().step));
     } else {
       if (_forcedTicksCount > 0) {
         var tickRange = _linearTickRange();
@@ -46,41 +59,39 @@
       }
     }
 
-    Function linear = math.min(_domain.length, _range.length) > 2
-        ? ScaleUtils.polylinearScale
-        : ScaleUtils.bilinearScale;
+    num Function(num) Function(List<num>, List<num>,
+            InterpolatorGenerator<num, num>, InterpolatorGenerator<num, num>)
+        linear = math.min(_domain.length, _range.length) > 2
+            ? ScaleUtils.polylinearScale
+            : ScaleUtils.bilinearScale;
 
-    Function uninterpolator = clamp ? uninterpolateClamp : uninterpolateNumber;
-    InterpolatorGenerator interpolator;
-    if (rounded) {
-      interpolator = createRoundedNumberInterpolator;
-    } else {
-      interpolator = createNumberInterpolator;
-    }
+    InterpolatorGenerator<num, num> uninterpolator =
+        clamp ? uninterpolateClamp : uninterpolateNumber;
+    InterpolatorGenerator<num, num> interpolator =
+        rounded ? createRoundedNumberInterpolator : createNumberInterpolator;
 
-    _invert = linear(_range, _domain, uninterpolator, createNumberInterpolator)
-        as Function;
-    _scale = linear(_domain, _range, uninterpolator, interpolator) as Function;
+    _invert = linear(_range, _domain, uninterpolator, createNumberInterpolator);
+    _scale = linear(_domain, _range, uninterpolator, interpolator);
   }
 
   @override
-  set range(Iterable value) {
+  set range(Iterable<num> value) {
     assert(value != null);
     _range = value;
     _reset();
   }
 
   @override
-  Iterable get range => _range;
+  Iterable<num> get range => _range;
 
   @override
-  set domain(Iterable value) {
+  set domain(Iterable<num> value) {
     _domain = value;
     _reset(nice: _nice);
   }
 
   @override
-  Iterable get domain => _domain;
+  Iterable<num> get domain => _domain;
 
   @override
   set rounded(bool value) {
@@ -106,15 +117,14 @@
   @override
   int get ticksCount => _ticksCount;
 
+  @override
   set forcedTicksCount(int value) {
     _forcedTicksCount = value;
     _reset(nice: false);
   }
 
-  get forcedTicksCount => _forcedTicksCount;
-
   @override
-  Iterable get ticks => _linearTickRange();
+  int get forcedTicksCount => _forcedTicksCount;
 
   @override
   set clamp(bool value) {
@@ -141,18 +151,16 @@
   bool get nice => _nice;
 
   @override
-  Extent get rangeExtent => ScaleUtils.extent(_range as Iterable<num>);
+  Extent<num> get rangeExtent => ScaleUtils.extent(_range);
 
   @override
-  scale(value) => _scale(value);
+  num scale(num value) => _scale(value);
 
   @override
-  invert(value) => _invert(value);
+  num invert(num value) => _invert(value);
 
-  Range _linearTickRange([Extent extent]) {
-    if (extent == null) {
-      extent = ScaleUtils.extent(_domain as Iterable<num>);
-    }
+  Range _linearTickRange([Extent<num> extent]) {
+    extent ??= ScaleUtils.extent(_domain);
     num span = extent.max - extent.min;
     if (span == 0) {
       span = 1.0; // [span / _ticksCount] should never be equal zero.
@@ -166,18 +174,23 @@
       // when forcing the ticks count at least the two ends of the scale would
       // look nice and has a high chance of having the intermediate tick values
       // to be nice.
-      var maxFactor = extent.max == 0 ? 1
-          : math.pow(10, (math.log((extent.max as num).abs() / forcedTicksCount)
-              / math.LN10).floor());
+      var maxFactor = extent.max == 0
+          ? 1
+          : math.pow(
+              10,
+              (math.log(extent.max.abs() / forcedTicksCount) / math.LN10)
+                  .floor());
       num max = (extent.max / maxFactor).ceil() * maxFactor;
-      num minFactor = extent.min == 0 ? 1
-          : math.pow(10, (math.log((extent.min as num).abs() / forcedTicksCount)
-              / math.LN10).floor());
+      num minFactor = extent.min == 0
+          ? 1
+          : math.pow(
+              10,
+              (math.log(extent.min.abs() / forcedTicksCount) / math.LN10)
+                  .floor());
       num min = (extent.min / minFactor).floor() * minFactor;
       step = (max - min) / forcedTicksCount;
       return new Range(min, max + step * 0.5, step);
     } else {
-
       step = math.pow(10, (math.log(span / _ticksCount) / math.LN10).floor());
       var err = _ticksCount / span * step;
 
@@ -191,23 +204,20 @@
       }
     }
 
-    return new Range(((extent.min as num) / step).ceil() * step,
-        ((extent.max as num) / step).floor() * step + step * 0.5, step);
+    return new Range((extent.min / step).ceil() * step,
+        (extent.max / step).floor() * step + step * 0.5, step);
   }
 
   @override
   FormatFunction createTickFormatter([String formatStr]) {
-    int precision(num value) {
-      return -(math.log(value) / math.LN10 + .01).floor();
-    }
-    Range tickRange = _linearTickRange();
     if (formatStr == null) {
+      int precision(num value) {
+        return -(math.log(value) / math.LN10 + .01).floor();
+      }
+
+      Range tickRange = _linearTickRange();
       formatStr = ".${precision(tickRange.step)}f";
     }
-    NumberFormat formatter = new NumberFormat(new EnUsLocale());
-    return formatter.format(formatStr);
+    return Scale.numberFormatter.format(formatStr);
   }
-
-  @override
-  LinearScale clone() => new LinearScale._clone(this);
 }
diff --git a/packages/charted/lib/core/scales/log_scale.dart b/packages/charted/lib/core/scales/log_scale.dart
index c9f9f91..71d2a93 100644
--- a/packages/charted/lib/core/scales/log_scale.dart
+++ b/packages/charted/lib/core/scales/log_scale.dart
@@ -17,7 +17,7 @@
 /// As log(0) is negative infinity, a log scale must have either an
 /// exclusively-positive or exclusively-negative domain; the domain must not
 /// include or cross zero.
-class LogScale implements Scale {
+class LogScale implements Scale<num, num> {
   static const defaultBase = 10;
   static const defaultDomain = const [1, 10];
   static final negativeNumbersRoundFunctionsPair =
@@ -43,7 +43,7 @@
 
   num _log(num x) =>
       (_positive ? math.log(x < 0 ? 0 : x) : -math.log(x > 0 ? 0 : -x)) /
-          math.log(base);
+      math.log(base);
 
   num _pow(num x) => _positive ? math.pow(base, x) : -math.pow(base, -x);
 
@@ -57,28 +57,28 @@
   int get base => _base;
 
   @override
-  num scale(x) => _linear.scale(_log(x as num)) as num;
+  num scale(num x) => _linear.scale(_log(x));
 
   @override
-  num invert(x) => _pow(_linear.invert(x as num) as num);
+  num invert(num x) => _pow(_linear.invert(x));
 
   @override
-  set domain(Iterable values) {
-    _positive = (values.first as num) >= 0;
-    _domain = values as List<num>;
+  set domain(covariant List<num> values) {
+    _positive = values.first >= 0;
+    _domain = values;
     _reset();
   }
 
   @override
-  Iterable get domain => _domain;
+  Iterable<num> get domain => _domain;
 
   @override
-  set range(Iterable newRange) {
+  set range(Iterable<num> newRange) {
     _linear.range = newRange;
   }
 
   @override
-  Iterable get range => _linear.range;
+  Iterable<num> get range => _linear.range;
 
   @override
   set rounded(bool value) {
@@ -121,7 +121,7 @@
   @override
   Extent get rangeExtent => _linear.rangeExtent;
 
-  _reset() {
+  void _reset() {
     if (_nice) {
       var niced = _domain.map((num e) => _log(e)).toList();
       var roundFunctions = _positive
@@ -135,11 +135,11 @@
     }
   }
 
-  Iterable get ticks {
-    var extent = ScaleUtils.extent(_domain),
-        ticks = <num>[];
-    num u = extent.min,
-        v = extent.max;
+  @override
+  Iterable<num> get ticks {
+    var extent = ScaleUtils.extent(_domain);
+    var ticks = <num>[];
+    num u = extent.min, v = extent.max;
     int i = (_log(u)).floor(),
         j = (_log(v)).ceil(),
         n = (_base % 1 > 0) ? 2 : _base;
@@ -159,13 +159,14 @@
     return ticks;
   }
 
+  @override
   FormatFunction createTickFormatter([String formatStr]) {
-    NumberFormat formatter = new NumberFormat(new EnUsLocale());
     FormatFunction logFormatFunction =
-        formatter.format(formatStr != null ? formatStr : ".0E");
+        Scale.numberFormatter.format(formatStr != null ? formatStr : ".0E");
     var k = math.max(.1, ticksCount / this.ticks.length),
         e = _positive ? 1e-12 : -1e-12;
-    return (num d) {
+    return (dynamic _d) {
+      var d = _d as num;
       if (_positive) {
         return d / _pow((_log(d) + e).ceil()) <= k ? logFormatFunction(d) : '';
       } else {
diff --git a/packages/charted/lib/core/scales/ordinal_scale.dart b/packages/charted/lib/core/scales/ordinal_scale.dart
index af1a11b..376856a 100644
--- a/packages/charted/lib/core/scales/ordinal_scale.dart
+++ b/packages/charted/lib/core/scales/ordinal_scale.dart
@@ -7,18 +7,20 @@
 //
 part of charted.core.scales;
 
-class _OrdinalScale implements OrdinalScale {
-  final _index = new Map<dynamic, int>();
-
-  List _domain = [];
-  List _range = [];
+class _OrdinalScale<TDomain extends Comparable, TRange>
+    implements OrdinalScale<TDomain, TRange> {
+  final Map<TDomain, int> _index = {};
+  final List<TDomain> _domain;
+  List<TRange> _range;
   num _rangeBand = 0;
-  Extent _rangeExtent;
-  Function _reset;
+  Extent<TDomain> _rangeExtent;
+  void Function(_OrdinalScale<TDomain, TRange>) _reset;
 
-  _OrdinalScale();
+  _OrdinalScale()
+      : _domain = [],
+        _range = [];
 
-  _OrdinalScale._clone(_OrdinalScale source)
+  _OrdinalScale._clone(_OrdinalScale<TDomain, TRange> source)
       : _domain = new List.from(source._domain),
         _range = new List.from(source._range),
         _reset = source._reset,
@@ -28,18 +30,18 @@
   }
 
   @override
-  scale(dynamic value) {
+  TRange scale(TDomain value) {
     if (!_index.containsKey(value)) {
       _index[value] = domain.length;
       _domain.add(value);
     }
     return _range.isNotEmpty
         ? _range.elementAt(_index[value] % _range.length)
-        : 0;
+        : null;
   }
 
   @override
-  dynamic invert(value) {
+  TDomain invert(TRange value) {
     int position = _range.indexOf(value);
     return position > -1 && position < _domain.length
         ? _domain[position]
@@ -47,12 +49,11 @@
   }
 
   @override
-  set domain(Iterable values) {
-    _domain = [];
+  set domain(Iterable<TDomain> values) {
+    _domain.clear();
     _index.clear();
 
-    for (var i = 0; i < values.length; i++) {
-      var value = values.elementAt(i);
+    for (var value in values) {
       if (_index[value] == null) {
         _index[value] = _domain.length;
         _domain.add(value);
@@ -63,39 +64,44 @@
   }
 
   @override
-  Iterable get domain => _domain;
+  Iterable<TDomain> get domain => _domain;
 
   @override
-  set range(Iterable values) => _setRange(this, values);
+  set range(Iterable<TRange> values) {
+    _reset = (_OrdinalScale<TDomain, TRange> s) {
+      s._range = new List<TRange>.from(values);
+      s._rangeBand = 0;
+      s._rangeExtent = null;
+    };
+    _reset(this);
+  }
 
   @override
-  Iterable get range => _range;
+  Iterable<TRange> get range => _range;
 
   @override
-  Extent get rangeExtent => _rangeExtent;
+  Extent<TDomain> get rangeExtent => _rangeExtent;
 
   @override
-  void rangePoints(Iterable range, [double padding = 0.0]) =>
+  void rangePoints(Iterable<num> range, [double padding = 0.0]) =>
       _setRangePoints(this, range, padding);
 
   @override
-  void rangeBands(Iterable range,
+  void rangeBands(Iterable<num> range,
           [double padding = 0.0, double outerPadding]) =>
-      _setRangeBands(
-          this, range, padding, outerPadding == null ? padding : outerPadding);
+      _setRangeBands(this, range, padding, outerPadding ?? padding);
 
   @override
-  void rangeRoundBands(Iterable range,
+  void rangeRoundBands(Iterable<num> range,
           [double padding = 0.0, double outerPadding]) =>
-      _setRangeRoundBands(
-          this, range, padding, outerPadding == null ? padding : outerPadding);
+      _setRangeRoundBands(this, range, padding, outerPadding ?? padding);
 
   @override
   num get rangeBand => _rangeBand;
 
   @override
   FormatFunction createTickFormatter([String format]) =>
-      (String s) => identityFunction/*<String>*/(s);
+      (dynamic s) => s.toString();
 
   @override
   Iterable get ticks => _domain;
@@ -103,31 +109,22 @@
   @override
   OrdinalScale clone() => new _OrdinalScale._clone(this);
 
-  List _steps(start, step) =>
+  List _steps(num start, num step) =>
       new Range(domain.length).map((num i) => start + step * i).toList();
 
-  static void _setRange(_OrdinalScale scale, Iterable values) {
-    scale._reset = (_OrdinalScale s) {
-      s._range = new List.from(values);
-      s._rangeBand = 0;
-      s._rangeExtent = null;
-    };
-    scale._reset(scale);
-  }
-
   static void _setRangePoints(
-      _OrdinalScale scale, Iterable range, double padding) {
+      _OrdinalScale scale, Iterable<num> range, double padding) {
     scale._reset = (_OrdinalScale s) {
-      var start = range.first,
-          stop = range.last,
-          step = s.domain.length > 1
-              ? (stop - start - 2 * padding) / (s.domain.length - 1)
-              : 0;
+      var start = range.first;
+      var stop = range.last;
+      var step = s.domain.length > 1
+          ? (stop - start - 2 * padding) / (s.domain.length - 1)
+          : 0;
 
       s._range = s._steps(
           s.domain.length < 2 ? (start + stop) / 2 : start + padding, step);
       s._rangeBand = 0;
-      s._rangeExtent = new Extent(start, stop);
+      s._rangeExtent = new Extent<num>(start, stop);
     };
     if (scale.domain.isNotEmpty) {
       scale._reset(scale);
@@ -140,33 +137,29 @@
       num start = range.first,
           stop = range.last,
           step = (stop - start - 2 * outerPadding) /
-              (s.domain.length > 1
-                  ? (s.domain.length - padding)
-                  : 1);
+              (s.domain.length > 1 ? (s.domain.length - padding) : 1);
 
       s._range = s._steps(start + step * outerPadding, step);
       s._rangeBand = step * (1 - padding);
-      s._rangeExtent = new Extent(start, stop);
+      s._rangeExtent = new Extent<num>(start, stop);
     };
     if (scale.domain.isNotEmpty) {
       scale._reset(scale);
     }
   }
 
-  static void _setRangeRoundBands(_OrdinalScale scale, Iterable range,
+  static void _setRangeRoundBands(_OrdinalScale scale, Iterable<num> range,
       double padding, double outerPadding) {
     scale._reset = (_OrdinalScale s) {
       num start = range.first,
           stop = range.last,
-          step =
-          ((stop - start - 2 * outerPadding) /
-              (s.domain.length > 1
-                  ? (s.domain.length - padding)
-                  : 1)).floor();
+          step = ((stop - start - 2 * outerPadding) /
+                  (s.domain.length > 1 ? (s.domain.length - padding) : 1))
+              .floor();
 
       s._range = s._steps(start + outerPadding, step);
       s._rangeBand = (step * (1 - padding)).round();
-      s._rangeExtent = new Extent(start, stop);
+      s._rangeExtent = new Extent<num>(start, stop);
     };
     if (scale.domain.isNotEmpty) {
       scale._reset(scale);
diff --git a/packages/charted/lib/core/scales/time_scale.dart b/packages/charted/lib/core/scales/time_scale.dart
index df07383..cc7785e 100644
--- a/packages/charted/lib/core/scales/time_scale.dart
+++ b/packages/charted/lib/core/scales/time_scale.dart
@@ -9,7 +9,7 @@
 part of charted.core.scales;
 
 /// TimeScale is a linear scale that operates on time.
-class TimeScale extends LinearScale {
+class TimeScale extends BaseLinearScale {
   static const _scaleSteps = const [
     1e3, // 1-second
     5e3, // 5-second
@@ -52,7 +52,7 @@
     [TimeInterval.year, 1]
   ];
 
-  static TimeFormatFunction _scaleLocalFormat = new TimeFormat().multi([
+  static final FormatFunction _scaleLocalFormat = new TimeFormat().multi([
     [".%L", (DateTime d) => d.millisecond > 0],
     [":%S", (DateTime d) => d.second > 0],
     ["%I:%M", (DateTime d) => d.minute > 0],
@@ -67,13 +67,14 @@
   TimeScale._clone(TimeScale source) : super._clone(source);
 
   @override
-  scale(Object val) =>
+  num scale(Object val) =>
       super.scale(val is DateTime ? val.millisecondsSinceEpoch : val);
 
   @override
-  set domain(Iterable value) {
-    super.domain =
-        value.map((d) => d is DateTime ? d.millisecondsSinceEpoch : d).toList();
+  set domain(Iterable<dynamic> value) {
+    super.domain = value
+        .map<num>((d) => d is DateTime ? d.millisecondsSinceEpoch : d as num)
+        .toList();
   }
 
   @override
@@ -82,7 +83,7 @@
   @override
   TimeScale clone() => new TimeScale._clone(this);
 
-  List _getTickMethod(Extent extent, int count) {
+  List<dynamic> _getTickMethod(Extent<num> extent, int count) {
     num target = (extent.max - extent.min) / count;
     int i = ScaleUtils.bisect(_scaleSteps, target);
 
@@ -90,42 +91,43 @@
         ? [
             TimeInterval.year,
             _linearTickRange(
-                new Extent(extent.min / 31536e6, extent.max / 31536e6)).step
+                    new Extent<num>(extent.min / 31536e6, extent.max / 31536e6))
+                .step
           ]
         : i == 0
             ? [new ScaleMilliSeconds(), _linearTickRange(extent).step]
-            : _scaleLocalMethods[target / _scaleSteps[i - 1] <
-                _scaleSteps[i] / target ? i - 1 : i];
+            : _scaleLocalMethods[
+                target / _scaleSteps[i - 1] < _scaleSteps[i] / target
+                    ? i - 1
+                    : i];
   }
 
-  List niceInterval(int ticksCount, [int skip = 1]) {
-    var extent = ScaleUtils.extent(domain as Iterable<num>),
-        method = _getTickMethod(extent, ticksCount),
-        interval;
+  List<num> niceInterval(int ticksCount) {
+    var extent = ScaleUtils.extent(domain);
+    var method = _getTickMethod(extent, ticksCount);
+    TimeInterval interval = method[0];
+    int skip = method[1];
 
-    if (method != null) {
-      interval = method[0];
-      skip = method[1] as int;
-    }
-
-    bool skipped(var date) {
-      if (date is DateTime) date = date.millisecondsSinceEpoch;
-      return (interval as TimeInterval).range(date, date + 1, skip).length == 0;
+    bool skipped(DateTime date) {
+      var seconds = date.millisecondsSinceEpoch;
+      return interval.range(seconds, seconds + 1, skip).length == 0;
     }
 
     if (skip > 1) {
       domain = ScaleUtils.nice(
           domain as List<num>,
           new RoundingFunctions((dateMillis) {
-            var date = new DateTime.fromMillisecondsSinceEpoch(dateMillis.round());
-            while (skipped(date = (interval as TimeInterval).floor(date))) {
+            var date =
+                new DateTime.fromMillisecondsSinceEpoch(dateMillis.round());
+            while (skipped(date = interval.floor(date))) {
               date = new DateTime.fromMillisecondsSinceEpoch(
                   date.millisecondsSinceEpoch - 1);
             }
             return date.millisecondsSinceEpoch;
           }, (dateMillis) {
-            var date = new DateTime.fromMillisecondsSinceEpoch(dateMillis.round());
-            while (skipped(date = (interval as TimeInterval).ceil(date))) {
+            var date =
+                new DateTime.fromMillisecondsSinceEpoch(dateMillis.round());
+            while (skipped(date = interval.ceil(date))) {
               date = new DateTime.fromMillisecondsSinceEpoch(
                   date.millisecondsSinceEpoch + 1);
             }
@@ -135,12 +137,10 @@
       domain = ScaleUtils.nice(
           domain as List<num>,
           new RoundingFunctions(
-              (date) => (interval as TimeInterval).
-                  floor(date).millisecondsSinceEpoch,
-              (date) => (interval as TimeInterval).
-                  ceil(date).millisecondsSinceEpoch));
+              (date) => interval.floor(date).millisecondsSinceEpoch,
+              (date) => interval.ceil(date).millisecondsSinceEpoch));
     }
-    return domain as List;
+    return domain;
   }
 
   @override
@@ -152,44 +152,44 @@
     }
   }
 
-  List ticksInterval(int ticksCount, [int skip]) {
-    var extent = ScaleUtils.extent(domain as Iterable<num>),
-        method = _getTickMethod(extent, ticksCount),
-        interval;
-    if (method != null) {
-      interval = method[0];
-      skip = method[1] as int;
-    }
-    return (interval as TimeInterval).range(extent.min, extent.max + 1,
-        skip < 1 ? 1 : skip).toList();
+  List<DateTime> ticksInterval(int ticksCount) {
+    var extent = ScaleUtils.extent(domain);
+    var method = _getTickMethod(extent, ticksCount);
+    TimeInterval interval = method[0];
+    int skip = method[1];
+    return interval
+        .range(extent.min, extent.max + 1, skip < 1 ? 1 : skip)
+        .toList();
   }
 
   @override
-  List get ticks => ticksInterval(ticksCount);
+  List<DateTime> get ticks => ticksInterval(ticksCount);
 }
 
 class ScaleMilliSeconds implements TimeInterval {
-  DateTime _toDateTime(x) {
-    assert(x is int || x is DateTime);
-    return x is int ?
-        new DateTime.fromMillisecondsSinceEpoch(x as int) : x as DateTime;
-  }
-
   DateTime floor(dynamic val) => _toDateTime(val);
   DateTime ceil(dynamic val) => _toDateTime(val);
   DateTime round(dynamic val) => _toDateTime(val);
 
   DateTime offset(Object val, int dt) {
-    assert(val is int || val is DateTime);
-    return new DateTime.fromMillisecondsSinceEpoch(
-        val is int ? val + dt : (val as DateTime).millisecondsSinceEpoch + dt);
+    return new DateTime.fromMillisecondsSinceEpoch(_toMilliseconds(val) + dt);
   }
 
-  List<DateTime> range(var t0, var t1, int step) {
-    int start = t0 is DateTime ? t0.millisecondsSinceEpoch : t0,
-        stop = t1 is DateTime ? t1.millisecondsSinceEpoch : t1;
+  List<DateTime> range(dynamic t0, dynamic t1, int step) {
+    int start = _toMilliseconds(t0);
+    int stop = _toMilliseconds(t1);
     return new Range((start / step).ceil() * step, stop, step)
         .map((d) => new DateTime.fromMillisecondsSinceEpoch(d as int))
         .toList();
   }
+
+  static DateTime _toDateTime(/* int | DateTime */ dynamic x) {
+    return x is int
+        ? new DateTime.fromMillisecondsSinceEpoch(x)
+        : x as DateTime;
+  }
+
+  static int _toMilliseconds(/* int | DateTime */ dynamic val) {
+    return val is int ? val : (val as DateTime).millisecondsSinceEpoch;
+  }
 }
diff --git a/packages/charted/lib/core/time_interval.dart b/packages/charted/lib/core/time_interval.dart
index 962b0b2..fd3a858 100644
--- a/packages/charted/lib/core/time_interval.dart
+++ b/packages/charted/lib/core/time_interval.dart
@@ -8,14 +8,10 @@
 
 library charted.core.time_intervals;
 
-typedef DateTime TimeFloorFunction(DateTime val);
-typedef DateTime TimeStepFunction(DateTime val, int offset);
-typedef int TimeToNumberFunction(DateTime val);
-
 class TimeInterval {
-  TimeFloorFunction _floor;
-  TimeStepFunction _step;
-  TimeToNumberFunction _number;
+  final DateTime Function(DateTime) _floor;
+  final DateTime Function(DateTime, int) _step;
+  final int Function(DateTime) _number;
 
   TimeInterval(this._floor, this._step, this._number);
 
@@ -44,13 +40,13 @@
     assert(t1 is int || t1 is DateTime);
 
     List<DateTime> values = [];
-    if (t1 is int) {
-      t1 = new DateTime.fromMillisecondsSinceEpoch(t1 as int);
-    }
+    DateTime t1Date = t1 is DateTime
+        ? t1
+        : new DateTime.fromMillisecondsSinceEpoch(t1 as int);
 
     DateTime time = ceil(t0);
     if (dt > 1) {
-      while (time.isBefore(t1 as DateTime)) {
+      while (time.isBefore(t1Date)) {
         if ((_number(time) % dt) == 0) {
           values.add(new DateTime.fromMillisecondsSinceEpoch(
               time.millisecondsSinceEpoch));
@@ -58,7 +54,7 @@
         time = _step(time, 1);
       }
     } else {
-      while (time.isBefore(t1 as DateTime)) {
+      while (time.isBefore(t1Date)) {
         values.add(new DateTime.fromMillisecondsSinceEpoch(
             time.millisecondsSinceEpoch));
         time = _step(time, 1);
@@ -67,31 +63,31 @@
     return values;
   }
 
-  static TimeInterval second = new TimeInterval(
+  static final TimeInterval second = new TimeInterval(
       (DateTime date) => new DateTime.fromMillisecondsSinceEpoch(
           (date.millisecondsSinceEpoch ~/ 1000) * 1000),
-      (DateTime date, int offset) =>
-          date = new DateTime.fromMillisecondsSinceEpoch(
+      (DateTime date, int offset) => date =
+          new DateTime.fromMillisecondsSinceEpoch(
               date.millisecondsSinceEpoch + offset * 1000),
       (DateTime date) => date.second);
 
-  static TimeInterval minute = new TimeInterval(
+  static final TimeInterval minute = new TimeInterval(
       (DateTime date) => new DateTime.fromMillisecondsSinceEpoch(
           (date.millisecondsSinceEpoch ~/ 60000) * 60000),
-      (DateTime date, int offset) =>
-          date = new DateTime.fromMillisecondsSinceEpoch(
+      (DateTime date, int offset) => date =
+          new DateTime.fromMillisecondsSinceEpoch(
               date.millisecondsSinceEpoch + offset * 60000),
       (DateTime date) => date.minute);
 
-  static TimeInterval hour = new TimeInterval(
+  static final TimeInterval hour = new TimeInterval(
       (DateTime date) => new DateTime.fromMillisecondsSinceEpoch(
           (date.millisecondsSinceEpoch ~/ 3600000) * 3600000),
-      (DateTime date, int offset) =>
-          date = new DateTime.fromMillisecondsSinceEpoch(
+      (DateTime date, int offset) => date =
+          new DateTime.fromMillisecondsSinceEpoch(
               date.millisecondsSinceEpoch + offset * 3600000),
       (DateTime date) => date.hour);
 
-  static TimeInterval day = new TimeInterval(
+  static final TimeInterval day = new TimeInterval(
       (DateTime date) => new DateTime(date.year, date.month, date.day),
       (DateTime date, int offset) => new DateTime(
           date.year,
@@ -103,7 +99,7 @@
           date.millisecond),
       (DateTime date) => date.day - 1);
 
-  static TimeInterval week = new TimeInterval(
+  static final TimeInterval week = new TimeInterval(
       (DateTime date) =>
           new DateTime(date.year, date.month, date.day - (date.weekday % 7)),
       (DateTime date, int offset) => new DateTime(
@@ -118,7 +114,7 @@
     return (dayOfYear(date) + day % 7) ~/ 7;
   });
 
-  static TimeInterval month = new TimeInterval(
+  static final TimeInterval month = new TimeInterval(
       (DateTime date) => new DateTime(date.year, date.month, 1),
       (DateTime date, int offset) => new DateTime(
           date.year,
@@ -130,7 +126,7 @@
           date.millisecond),
       (DateTime date) => date.month - 1);
 
-  static TimeInterval year = new TimeInterval(
+  static final TimeInterval year = new TimeInterval(
       (DateTime date) => new DateTime(date.year),
       (DateTime date, int offset) => new DateTime(
           date.year + offset,
diff --git a/packages/charted/lib/core/utils.dart b/packages/charted/lib/core/utils.dart
index d153a34..16729cf 100644
--- a/packages/charted/lib/core/utils.dart
+++ b/packages/charted/lib/core/utils.dart
@@ -31,11 +31,8 @@
 const String ORIENTATION_TOP = 'top';
 const String ORIENTATION_BOTTOM = 'bottom';
 
-/// Identity function that returns the value passed as it's parameter.
-/*=T*/ identityFunction/*<T>*/(/*=T*/x) => x;
-
 /// Function that formats a value to String.
-typedef String FormatFunction(value);
+typedef FormatFunction = String Function(dynamic);
 
 /// Test if the given String or Iterable, [val] is null or empty
 bool isNullOrEmpty(val) {
diff --git a/packages/charted/lib/core/utils/color.dart b/packages/charted/lib/core/utils/color.dart
index 5f80759..c2ae5a5 100644
--- a/packages/charted/lib/core/utils/color.dart
+++ b/packages/charted/lib/core/utils/color.dart
@@ -223,6 +223,7 @@
           ? "0" + math.max(0, v).toInt().toRadixString(16)
           : math.min(255, v).toInt().toRadixString(16);
     }
+
     return '#${_hexify(r)}${_hexify(g)}${_hexify(b)}';
   }
 
diff --git a/packages/charted/lib/core/utils/lists.dart b/packages/charted/lib/core/utils/lists.dart
index b272ccb..16fcffc 100644
--- a/packages/charted/lib/core/utils/lists.dart
+++ b/packages/charted/lib/core/utils/lists.dart
@@ -9,7 +9,7 @@
 part of charted.core.utils;
 
 /// Returns a sum of all values in the given list of values
-num sum(List values) => values == null || values.isEmpty
+num sum(List<num> values) => values == null || values.isEmpty
     ? 0
     : values.fold(0.0, (num old, num next) => old + next);
 
@@ -37,12 +37,12 @@
 }
 
 /// Represents a pair of mininum and maximum values in a List.
-class Extent<T> extends Pair<T, T> {
-  final T min;
-  final T max;
+class Extent<T extends Comparable> extends Pair<T, T> {
+  T get min => first;
+  T get max => last;
 
   factory Extent.items(Iterable<T> items,
-      [Comparator compare = Comparable.compare]) {
+      [Comparator<T> compare = Comparable.compare]) {
     if (items.length == 0) return new Extent(null, null);
     var max = items.first, min = items.first;
     for (var value in items) {
@@ -53,9 +53,7 @@
   }
 
   const Extent(T min, T max)
-      : min = min,
-        max = max,
-        super(min, max);
+      : super(min, max);
 }
 
 /// Iterable representing a range of values containing the start, stop
diff --git a/packages/charted/lib/core/utils/rect.dart b/packages/charted/lib/core/utils/rect.dart
index beaa2cc..2fb8bae 100644
--- a/packages/charted/lib/core/utils/rect.dart
+++ b/packages/charted/lib/core/utils/rect.dart
@@ -68,7 +68,8 @@
   const AbsoluteRect(this.top, this.end, this.bottom, this.start);
 
   @override
-  bool operator ==(other) => other is AbsoluteRect &&
+  bool operator ==(other) =>
+      other is AbsoluteRect &&
       start == other.start &&
       end == other.end &&
       top == other.top &&
diff --git a/packages/charted/lib/layout/src/hierarchy_layout.dart b/packages/charted/lib/layout/src/hierarchy_layout.dart
index d3a4c59..0bb8933 100644
--- a/packages/charted/lib/layout/src/hierarchy_layout.dart
+++ b/packages/charted/lib/layout/src/hierarchy_layout.dart
@@ -30,8 +30,8 @@
       List rows, int parentColumn, int labelColumn, int valueColumn) {
     List<T> nodeList = [];
     for (var row in rows) {
-      nodeList.add(createNode(row[labelColumn] as String,
-          row[valueColumn] as num, 0));
+      nodeList.add(
+          createNode(row[labelColumn] as String, row[valueColumn] as num, 0));
     }
 
     for (var i = 0; i < rows.length; i++) {
diff --git a/packages/charted/lib/layout/src/pie_layout.dart b/packages/charted/lib/layout/src/pie_layout.dart
index d66d992..5d56e04 100644
--- a/packages/charted/lib/layout/src/pie_layout.dart
+++ b/packages/charted/lib/layout/src/pie_layout.dart
@@ -44,13 +44,14 @@
    */
   List<SvgArcData> layout(List<num> data, [int ei, Element e]) {
     var values =
-        new List.generate(data.length, (int i) => accessor(data[i], i)),
+            new List.generate(data.length, (int i) => accessor(data[i], i)),
         startAngle = startAngleCallback(data, ei, e),
         endAngle = endAngleCallback(data, ei, e),
         total = sum(values),
         scaleFactor = (endAngle - startAngle) / (total > 0 ? total : 1),
         arcs = new List<SvgArcData>(data.length);
-    List<int> index = new Range.integers(values.length).toList();
+    List<int> index =
+        new Range.integers(values.length).map<int>((v) => v as int).toList();
 
     if (compare != null) {
       index.sort((left, right) => compare(data[left], data[right]));
@@ -77,7 +78,7 @@
   }
 
   /** Default value accessor */
-  static num defaultValueAccessor(num d, i) => d;
+  static num defaultValueAccessor(d, int i) => d as num;
 
   /** Default start angle callback - returns 0 */
   static num defaultStartAngleCallback(d, i, _) => 0;
diff --git a/packages/charted/lib/layout/src/treemap_layout.dart b/packages/charted/lib/layout/src/treemap_layout.dart
index 56c3d48..4a01020 100644
--- a/packages/charted/lib/layout/src/treemap_layout.dart
+++ b/packages/charted/lib/layout/src/treemap_layout.dart
@@ -111,7 +111,7 @@
   }
 
   /// Applies padding between each nodes.
-  MutableRect _treeMapPad(TreeMapNode node, List<num>padding) {
+  MutableRect _treeMapPad(TreeMapNode node, List<num> padding) {
     var x = node.x + padding[3];
     var y = node.y + padding.first;
     var dx = node.dx - padding[1] - padding[3];
@@ -138,9 +138,7 @@
 
   /// Computes the most amount of area needed to layout the list of nodes.
   num _worst(List<TreeMapNode> nodes, num length, num pArea) {
-    num area,
-        rmax = 0,
-        rmin = double.INFINITY;
+    num area, rmax = 0, rmin = double.INFINITY;
     for (var node in nodes) {
       area = node.area;
       if (area <= 0) continue;
diff --git a/packages/charted/lib/locale/format.dart b/packages/charted/lib/locale/format.dart
index a41afd9..0ecbee6 100644
--- a/packages/charted/lib/locale/format.dart
+++ b/packages/charted/lib/locale/format.dart
@@ -28,10 +28,8 @@
  *
  * @see <a href="http://docs.python.org/release/3.1.3/library/string.html#formatspec">Python format specification mini-language</a>
  */
-FormatFunction format(String specifier, [Locale locale = null]) {
-  if (locale == null) {
-    locale = new EnUsLocale();
-  }
+FormatFunction format(String specifier, [Locale locale]) {
+  locale ??= new EnUsLocale();
   return locale.numberFormat.format(specifier);
 }
 
@@ -40,7 +38,7 @@
  */
 class FormatPrefix {
   // SI scale units in increments of 1000.
-  static const List<String> unitPrefixes = const [
+  static const List<String> _unitPrefixes = const [
     "y",
     "z",
     "a",
@@ -60,7 +58,7 @@
     "Y"
   ];
 
-  Function _scale;
+  num Function(num) _scale;
   String _symbol;
 
   FormatPrefix(num value, [int precision = 0]) {
@@ -80,7 +78,7 @@
     // Sets the scale and symbol of the value.
     var k = math.pow(10, (8 - i).abs() * 3);
     _scale = i > 8 ? (d) => d / k : (d) => d * k;
-    _symbol = unitPrefixes[i];
+    _symbol = _unitPrefixes[i];
   }
 
   num _formatPrecision(num x, num p) {
@@ -93,8 +91,8 @@
   }
 
   /** Returns the SI prefix for the value. */
-  get symbol => _symbol;
+  String get symbol => _symbol;
 
   /** Returns the scale for the value corresponding to the SI prefix. */
-  get scale => _scale;
+  num Function(num) get scale => _scale;
 }
diff --git a/packages/charted/lib/locale/format/number_format.dart b/packages/charted/lib/locale/format/number_format.dart
index f53e933..4008e53 100644
--- a/packages/charted/lib/locale/format/number_format.dart
+++ b/packages/charted/lib/locale/format/number_format.dart
@@ -15,41 +15,21 @@
  */
 class NumberFormat {
   // [[fill]align][sign][symbol][0][width][,][.precision][type]
-  static RegExp FORMAT_REGEX = new RegExp(
+  static final RegExp FORMAT_REGEX = new RegExp(
       r'(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?'
       r'(\.-?\d+)?([a-z%])?',
       caseSensitive: false);
 
-  String localeDecimal;
-  String localeThousands;
-  List<int> localeGrouping;
-  List<String> localeCurrency;
-  Function formatGroup;
+  final String _localeDecimal;
+  final List<int> _localeGrouping;
+  final String _localeThousands;
+  final List<String> _localeCurrency;
 
-  NumberFormat(Locale locale) {
-    localeDecimal = locale.decimal;
-    localeThousands = locale.thousands;
-    localeGrouping = locale.grouping;
-    localeCurrency = locale.currency;
-    formatGroup = (localeGrouping != null)
-        ? (String value) {
-            int i = value.length, j = 0, g = localeGrouping[0];
-            var t = [];
-            while (i > 0 && g > 0) {
-              if (i - g >= 0) {
-                i = i - g;
-              } else {
-                g = i;
-                i = 0;
-              }
-              var length = (i + g) < value.length ? (i + g) : value.length;
-              t.add(value.substring(i, length));
-              g = localeGrouping[j = (j + 1) % localeGrouping.length];
-            }
-            return t.reversed.join(localeThousands);
-          }
-        : (x) => x;
-  }
+  NumberFormat(Locale locale)
+      : _localeDecimal = locale.decimal,
+        _localeGrouping = locale.grouping,
+        _localeThousands = locale.thousands,
+        _localeCurrency = locale.currency;
 
   /**
    * Returns a new format function with the given string specifier. A format
@@ -70,8 +50,9 @@
         zfill = match.group(5),
         width = match.group(6) != null ? int.parse(match.group(6)) : 0,
         comma = match.group(7) != null,
-        precision =
-        match.group(8) != null ? int.parse(match.group(8).substring(1)) : null,
+        precision = match.group(8) != null
+            ? int.parse(match.group(8).substring(1))
+            : null,
         type = match.group(9),
         scale = 1,
         prefix = '',
@@ -119,8 +100,8 @@
     }
 
     if (symbol == '\$') {
-      prefix = localeCurrency[0];
-      suffix = localeCurrency[1];
+      prefix = _localeCurrency[0];
+      suffix = _localeCurrency[1];
     }
 
     // If no precision is specified for r, fallback to general notation.
@@ -141,7 +122,8 @@
 
     var zcomma = (zfill != null) && comma;
 
-    return (num value) {
+    return (dynamic _value) {
+      var value = _value as num;
       if (value == null) return '-';
       var fullSuffix = suffix;
 
@@ -163,7 +145,7 @@
       if (scale < 0) {
         FormatPrefix unit =
             new FormatPrefix(value, (precision != null) ? precision : 0);
-        value = unit.scale(value) as num;
+        value = unit.scale(value);
         fullSuffix = unit.symbol + suffix;
       } else {
         value *= scale;
@@ -181,13 +163,12 @@
       // (after).
       int i = stringValue.lastIndexOf('.');
       String before = i < 0 ? stringValue : stringValue.substring(0, i),
-          after = i < 0 ? '' : localeDecimal + stringValue.substring(i + 1);
-
+          after = i < 0 ? '' : _localeDecimal + stringValue.substring(i + 1);
 
       // If the fill character is not '0', grouping is applied before
       //padding.
       if (zfill == null && comma) {
-        before = formatGroup(before) as String;
+        before = _formatGroup(before);
       }
 
       int length = prefix.length +
@@ -200,7 +181,7 @@
 
       // If the fill character is '0', grouping is applied after padding.
       if (zcomma) {
-        before = formatGroup(padding + before) as String;
+        before = _formatGroup(padding + before);
       }
 
       // Apply prefix.
@@ -219,9 +200,9 @@
                           negative +
                           stringValue +
                           padding.substring(length)
-                      : negative + (zcomma
-                          ? stringValue
-                          : padding + stringValue)) + fullSuffix;
+                      : negative +
+                          (zcomma ? stringValue : padding + stringValue)) +
+          fullSuffix;
     };
   }
 
@@ -250,4 +231,24 @@
         return (num x, [int p = 0]) => x.toString();
     }
   }
+
+  String _formatGroup(String value) {
+    if (_localeGrouping == null) {
+      return value;
+    }
+    int i = value.length, j = 0, g = _localeGrouping[0];
+    var t = <String>[];
+    while (i > 0 && g > 0) {
+      if (i - g >= 0) {
+        i = i - g;
+      } else {
+        g = i;
+        i = 0;
+      }
+      var length = (i + g) < value.length ? (i + g) : value.length;
+      t.add(value.substring(i, length));
+      g = _localeGrouping[j = (j + 1) % _localeGrouping.length];
+    }
+    return t.reversed.join(_localeThousands);
+  }
 }
diff --git a/packages/charted/lib/locale/format/time_format.dart b/packages/charted/lib/locale/format/time_format.dart
index 89ab88c..5b15f80 100644
--- a/packages/charted/lib/locale/format/time_format.dart
+++ b/packages/charted/lib/locale/format/time_format.dart
@@ -7,25 +7,19 @@
  */
 part of charted.locale.format;
 
-typedef String TimeFormatFunction(DateTime date);
-
 //TODO(songrenchu): Document time format; Add test for time format.
 
 class TimeFormat {
-  String _template;
-  String _locale;
-  DateFormat _dateFormat;
+  final String _template;
+  final String _locale;
+  final DateFormat _dateFormat;
 
-  TimeFormat([String template = null, String identifier = 'en_US']) {
-    _template = template;
-    _locale = identifier;
-    if (_template != null) _dateFormat =
-        new DateFormat(_wrapStrptime2ICU(_template), _locale);
-  }
-
-  TimeFormat _getInstance(String template) {
-    return new TimeFormat(template, _locale);
-  }
+  TimeFormat([String template, String identifier = 'en_US'])
+      : _template = template,
+        _locale = identifier,
+        _dateFormat = template == null
+            ? null
+            : new DateFormat(_wrapStrptime2ICU(template), identifier);
 
   String apply(DateTime date) {
     assert(_dateFormat != null);
@@ -39,35 +33,32 @@
     return _dateFormat.parse(string);
   }
 
-  TimeFormatFunction multi(List<List> formats) {
+  FormatFunction multi(List<List> formats) {
     var n = formats.length, i = -1;
-    while (++i < n) formats[i][0] = _getInstance(formats[i][0] as String);
-    return (var date) {
-      if (date is num) {
-        date = new DateTime.fromMillisecondsSinceEpoch((date as num).toInt());
-      }
+    while (++i < n) {
+      formats[i][0] = new TimeFormat(formats[i][0] as String, _locale);
+    }
+    return (dynamic _date) {
+      DateTime date = _date is DateTime
+          ? _date
+          : new DateTime.fromMillisecondsSinceEpoch((_date as num).toInt());
       var i = 0, f = formats[i];
-      while (f.length < 2 || f[1](date) == false) {
+      while (f.length < 2 || !(f[1] as bool Function(DateTime))(date)) {
         i++;
         if (i < n) f = formats[i];
       }
       if (i == n) return null;
-      return f[0].apply(date);
+      return (f[0] as TimeFormat).apply(date);
     };
   }
 
-  UTCTimeFormat utc([String specifier = null]) {
-    return new UTCTimeFormat(
-        specifier == null ? _template : specifier, _locale);
-  }
-
-  static UTCTimeFormat iso() {
-    return new UTCTimeFormat("%Y-%m-%dT%H:%M:%S.%LZ");
-  }
-
-  static Map timeFormatPads = {"-": "", "_": " ", "0": "0"};
+  static const Map<String, String> _timeFormatPads = const {
+    "-": "",
+    "_": " ",
+    "0": "0"
+  };
   // TODO(songrenchu): Cannot fully be equivalent now.
-  static Map timeFormatsTransform = {
+  static const Map<String, String> _timeFormatsTransform = const {
     'a': 'EEE',
     'A': 'EEEE',
     'b': 'MMM',
@@ -96,33 +87,20 @@
     '%': '%'
   };
 
-  String _wrapStrptime2ICU(String template) {
-    var string = [], i = -1, j = 0, n = template.length, tempChar;
+  static String _wrapStrptime2ICU(String template) {
+    List<String> string = [];
+    int i = -1, j = 0, n = template.length;
     while (++i < n) {
       if (template[i] == '%') {
         string.add(template.substring(j, i));
-        if ((timeFormatPads[tempChar = template[++i]]) != null) tempChar =
-            template[++i];
-        if (timeFormatsTransform[tempChar] != null) string
-            .add(timeFormatsTransform[tempChar]);
+        String ch = template[++i];
+        if ((_timeFormatPads[ch]) != null) ch = template[++i];
+        if (_timeFormatsTransform[ch] != null)
+          string.add(_timeFormatsTransform[ch]);
         j = i + 1;
       }
     }
-    if (j < i) string.add("'" + template.substring(j, i) + "'");
-    return string.join("");
-  }
-}
-
-class UTCTimeFormat extends TimeFormat {
-  UTCTimeFormat(String template, [String identifier = 'en_US'])
-      : super(template, identifier);
-
-  UTCTimeFormat _getInstance(String template) {
-    return new UTCTimeFormat(template, _locale);
-  }
-
-  DateTime parse(String string) {
-    assert(_dateFormat != null);
-    return _dateFormat.parseUTC(string);
+    if (j < i) string.add("'${template.substring(j, i)}'");
+    return string.join();
   }
 }
diff --git a/packages/charted/lib/locale/languages/en_us.dart b/packages/charted/lib/locale/languages/en_us.dart
index 7d6b354..6f5deb8 100644
--- a/packages/charted/lib/locale/languages/en_us.dart
+++ b/packages/charted/lib/locale/languages/en_us.dart
@@ -12,25 +12,32 @@
   static EnUsLocale instance;
 
   factory EnUsLocale() {
-    if (EnUsLocale.instance == null) {
-      EnUsLocale.instance = new EnUsLocale._create();
-    }
-    return EnUsLocale.instance;
+    return instance ??= new EnUsLocale._();
   }
 
-  EnUsLocale._create();
+  EnUsLocale._();
 
-  final identifier = 'en_US';
-  final decimal = '.';
-  final thousands = ',';
-  final grouping = const [3];
-  final currency = const ['\$', ''];
-  final dateTime = '%a %b %e %X %Y';
-  final date = '%m/%d/%Y';
-  final time = '%H =>%M =>%S';
-  final periods = const ['AM', 'PM'];
+  @override
+  final String identifier = 'en_US';
+  @override
+  final String decimal = '.';
+  @override
+  final String thousands = ',';
+  @override
+  final List<int> grouping = const [3];
+  @override
+  final List<String> currency = const ['\$', ''];
+  @override
+  final String dateTime = '%a %b %e %X %Y';
+  @override
+  final String date = '%m/%d/%Y';
+  @override
+  final String time = '%H =>%M =>%S';
+  @override
+  final List<String> periods = const ['AM', 'PM'];
 
-  final days = const [
+  @override
+  final List<String> days = const [
     'Sunday',
     'Monday',
     'Tuesday',
@@ -39,9 +46,19 @@
     'Friday',
     'Saturday'
   ];
-  final shortDays = const ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+  @override
+  final List<String> shortDays = const [
+    'Sun',
+    'Mon',
+    'Tue',
+    'Wed',
+    'Thu',
+    'Fri',
+    'Sat'
+  ];
 
-  final months = const [
+  @override
+  final List<String> months = const [
     'January',
     'February',
     'March',
@@ -55,7 +72,8 @@
     'November',
     'December'
   ];
-  final shortMonths = const [
+  @override
+  final List<String> shortMonths = const [
     'Jan',
     'Feb',
     'Mar',
diff --git a/packages/charted/lib/locale/locale.dart b/packages/charted/lib/locale/locale.dart
index 2b22efd..9884d7a 100644
--- a/packages/charted/lib/locale/locale.dart
+++ b/packages/charted/lib/locale/locale.dart
@@ -22,17 +22,18 @@
   String get dateTime;
   String get date;
   String get time;
-  List get periods;
+  List<String> get periods;
 
-  List get days;
-  List get shortDays;
+  List<String> get days;
+  List<String> get shortDays;
 
-  List get months;
-  List get shortMonths;
+  List<String> get months;
+  List<String> get shortMonths;
 
   Locale();
 
   NumberFormat get numberFormat => new NumberFormat(this);
-  TimeFormat timeFormat([String specifier = null]) =>
+
+  TimeFormat timeFormat([String specifier]) =>
       new TimeFormat(specifier, this.identifier);
 }
diff --git a/packages/charted/lib/selection/selection.dart b/packages/charted/lib/selection/selection.dart
index 92823bd..0d94977 100644
--- a/packages/charted/lib/selection/selection.dart
+++ b/packages/charted/lib/selection/selection.dart
@@ -37,7 +37,7 @@
 typedef E SelectionValueAccessor<E>(datum, int index);
 
 /** Create a ChartedCallback that always returns [val] */
-SelectionCallback/*<T>*/ toCallback/*<T>*/(/*=T*/ val) => (datum, index, element) => val;
+SelectionCallback<T> toCallback<T>(T val) => (datum, index, element) => val;
 
 /** Create a ChartedValueAccessor that always returns [val] */
 SelectionValueAccessor toValueAccessor(val) => (datum, index) => val;
diff --git a/packages/charted/lib/selection/src/selection_impl.dart b/packages/charted/lib/selection/src/selection_impl.dart
index eef281b..5e1e8d7 100644
--- a/packages/charted/lib/selection/src/selection_impl.dart
+++ b/packages/charted/lib/selection/src/selection_impl.dart
@@ -308,8 +308,9 @@
         fn: (datum, ei, e) {
           Element child = fn(datum, ei, e);
           Element before = beforeFn(datum, ei, e);
-          return child == null ? null : e.insertBefore(child, before)
-              as Element;
+          return child == null
+              ? null
+              : e.insertBefore(child, before) as Element;
         },
         source: this);
   }
@@ -343,6 +344,7 @@
       scope.associate(element, val);
       return element;
     }
+
     ;
 
     // Joins data to all elements in the group.
@@ -431,6 +433,7 @@
       updateGroups.add(new _SelectionGroupImpl(update, parent: g.parent));
       exitGroups.add(new _SelectionGroupImpl(exit, parent: g.parent));
     }
+
     ;
 
     for (int gi = 0; gi < groups.length; ++gi) {
diff --git a/packages/charted/lib/selection/src/transition_impl.dart b/packages/charted/lib/selection/src/transition_impl.dart
index a560e1e..e5b6d17 100644
--- a/packages/charted/lib/selection/src/transition_impl.dart
+++ b/packages/charted/lib/selection/src/transition_impl.dart
@@ -16,10 +16,10 @@
   SelectionCallback _duration =
       (d, i, c) => Transition.defaultDurationMilliseconds;
   Selection _selection;
-  Map _attrs = {};
-  Map _styles = {};
-  Map _attrTweens = {};
-  Map _styleTweens = {};
+  Map<String, dynamic> _attrs = {};
+  Map<String, dynamic> _styles = {};
+  Map<String, dynamic> _attrTweens = {};
+  Map<String, dynamic> _styleTweens = {};
   Map<AnimationTimer, Element> _timerMap = {};
   Map<Element, List<Interpolator>> _attrMap = {};
   Map<Element, int> _durationMap = {};
@@ -87,9 +87,8 @@
           tweenList.add(_getAttrInterpolator(c, key, value(d, i, c)));
         });
         _attrTweens.forEach((String key, value) {
-          tweenList.add(
-              (t) => c.setAttribute(key,
-                  value(d, i, c.getAttribute(key))(t) as String));
+          tweenList.add((t) => c.setAttribute(
+              key, value(d, i, c.getAttribute(key))(t) as String));
         });
         _styles.forEach((String key, value) {
           tweenList.add(_getStyleInterpolator(
diff --git a/packages/charted/lib/svg/axis.dart b/packages/charted/lib/svg/axis.dart
index 6adc9d4..2f9e879 100644
--- a/packages/charted/lib/svg/axis.dart
+++ b/packages/charted/lib/svg/axis.dart
@@ -54,9 +54,8 @@
       : scale = scale == null ? new LinearScale() : scale {
     _tickFormat =
         tickFormat == null ? this.scale.createTickFormatter() : tickFormat;
-    _tickValues = isNullOrEmpty(tickValues)
-        ? this.scale.ticks.toList()
-        : tickValues;
+    _tickValues =
+        isNullOrEmpty(tickValues) ? this.scale.ticks.toList() : tickValues;
   }
 
   Iterable get tickValues => _tickValues;
@@ -64,12 +63,12 @@
   FormatFunction get tickFormat => _tickFormat;
 
   /// Draw an axis on each non-null element in selection
-  draw(Selection g, {SvgAxisTicks axisTicksBuilder, bool isRTL: false}) =>
+  void draw(Selection g, {SvgAxisTicks axisTicksBuilder, bool isRTL: false}) =>
       g.each((d, i, e) =>
           create(e, g.scope, axisTicksBuilder: axisTicksBuilder, isRTL: isRTL));
 
   /// Create an axis on [element].
-  create(Element element, SelectionScope scope,
+  void create(Element element, SelectionScope scope,
       {SvgAxisTicks axisTicksBuilder, bool isRTL: false}) {
     var group = scope.selectElements([element]),
         older = _scales[element],
@@ -83,21 +82,22 @@
         isTop = !(isVertical || isBottom) && orientation == ORIENTATION_TOP,
         isHorizontal = !isVertical;
 
-    if (older == null) older = current;
-    if (axisTicksBuilder == null) {
-      axisTicksBuilder = new SvgAxisTicks();
-    }
+    older ??= current;
+    axisTicksBuilder ??= new SvgAxisTicks();
     axisTicksBuilder.init(this);
 
     var values = axisTicksBuilder.ticks,
         formatted = axisTicksBuilder.formattedTicks,
         ellipsized = axisTicksBuilder.shortenedTicks;
 
-    var ticks = group.selectAll('.tick').data(values, current.scale),
-        exit = ticks.exit,
-        transform = isVertical ? _yAxisTransform : _xAxisTransform,
-        sign = isTop || isLeft ? -1 : 1,
-        isEllipsized = ellipsized != formatted;
+    // Need to wrap the function `current.scale` to avoid fuzzy arrow.
+    dynamic Function(dynamic) fn = (value) => current.scale(value);
+
+    var ticks = group.selectAll('.tick').data(values, fn);
+    var exit = ticks.exit;
+    var transform = isVertical ? _yAxisTransform : _xAxisTransform;
+    var sign = isTop || isLeft ? -1 : 1;
+    var isEllipsized = ellipsized != formatted;
 
     var enter = ticks.enter.appendWithCallback((d, i, e) {
       var group = Namespace.createChildElement('g', e)
diff --git a/packages/charted/lib/svg/shapes/arc.dart b/packages/charted/lib/svg/shapes/arc.dart
index dfbcb70..a842449 100644
--- a/packages/charted/lib/svg/shapes/arc.dart
+++ b/packages/charted/lib/svg/shapes/arc.dart
@@ -91,12 +91,14 @@
   /// Default [innerRadiusCallback] returns data.innerRadius
   static num defaultInnerRadiusCallback(d, i, e) =>
       d is! SvgArcData || d.innerRadius == null
-          ? 0 : (d as SvgArcData).innerRadius;
+          ? 0
+          : (d as SvgArcData).innerRadius;
 
   /// Default [outerRadiusCallback] returns data.outerRadius
   static num defaultOuterRadiusCallback(d, i, e) =>
       d is! SvgArcData || d.outerRadius == null
-         ? 0 : (d as SvgArcData).outerRadius;
+          ? 0
+          : (d as SvgArcData).outerRadius;
 
   /// Default [startAngleCallback] returns data.startAngle
   static num defaultStartAngleCallback(d, i, e) =>
@@ -106,8 +108,7 @@
 
   /// Default [endAngleCallback] that returns data.endAngle
   static num defaultEndAngleCallback(d, i, e) =>
-      d is! SvgArcData || d.endAngle == null
-          ? 0 : (d as SvgArcData).endAngle;
+      d is! SvgArcData || d.endAngle == null ? 0 : (d as SvgArcData).endAngle;
 }
 
 /// Value type for SvgArc as used by default property accessors in SvgArc
diff --git a/packages/charted/lib/svg/shapes/line.dart b/packages/charted/lib/svg/shapes/line.dart
index f53bf0a..15fd1fe 100644
--- a/packages/charted/lib/svg/shapes/line.dart
+++ b/packages/charted/lib/svg/shapes/line.dart
@@ -75,8 +75,8 @@
 
   /// Default implementation of [yValueAccessor].
   /// Returns the second element if [d] is an iterable, otherwise returns [d].
-  static num defaultDataToY(d, i) => d is Iterable
-      ? d.elementAt(1) as num : d as num;
+  static num defaultDataToY(d, i) =>
+      d is Iterable ? d.elementAt(1) as num : d as num;
 
   /// Default implementation of [isDefined].
   /// Returns true for all non-null values of [d].
diff --git a/packages/charted/pubspec.yaml b/packages/charted/pubspec.yaml
index e92537a..0004e9e 100644
--- a/packages/charted/pubspec.yaml
+++ b/packages/charted/pubspec.yaml
@@ -1,5 +1,5 @@
 name: charted
-version: 0.5.0
+version: 0.6.1
 authors:
 - Prasad Sunkari <prsd@google.com>
 - Michael Cheng <midoringo@google.com>
@@ -7,12 +7,13 @@
 description: Visualization toolkit for Dart - Provides D3 (http://d3js.org) like Selection API, utilities to achieve data driven DOM and an easy-to-use Charting library based on the Selection API.
 homepage: https://github.com/google/charted
 environment:
-  sdk: ">=1.23.0 <2.0.0"
+  sdk: '>=2.0.0-dev.55.0 <3.0.0'
 dependencies:
   browser: any
+  collection: ^1.14.10
   intl: any
   logging: any
-  observable: '>=0.14.0 <0.15.0'
+  observable: '^0.22.1'
   quiver: any
 dev_dependencies:
   hop: '>=0.27.0'
diff --git a/packages/collection/.gitignore b/packages/collection/.gitignore
index 25a1df3..98d6d21 100644
--- a/packages/collection/.gitignore
+++ b/packages/collection/.gitignore
@@ -2,6 +2,7 @@
 .DS_Store
 .idea
 .pub/
+.dart_tool/
 .settings/
 build/
 packages
diff --git a/packages/collection/.travis.yml b/packages/collection/.travis.yml
index 264316d..b4d1316 100644
--- a/packages/collection/.travis.yml
+++ b/packages/collection/.travis.yml
@@ -1,24 +1,54 @@
+
 language: dart
 
-dart:
-  - dev
-  - stable
+# Gives more resources on Travis (8GB Ram, 2 CPUs).
+# Do not remove without verifying w/ Travis.
+sudo: required
+addons:
+  chrome: stable
 
-dart_task:
-  - test: --platform vm
-  - test: --platform firefox -j 1
-  - dartanalyzer
-  - dartfmt
+# Build stages: https://docs.travis-ci.com/user/build-stages/.
+stages:
+  - presubmit
+  - build
+  - testing
 
+# 1. Run dartfmt, dartanalyzer, pub run test (VM).
+# 2. Then run a build.
+# 3. Then run tests compiled via dartdevc and dart2js.
+jobs:
+  include:
+    - stage: presubmit
+      script: ./tool/travis.sh dartfmt
+      dart: dev
+    - stage: presubmit
+      script: ./tool/travis.sh dartanalyzer
+      dart: dev
+    - stage: build
+      script: ./tool/travis.sh dartdevc_build
+      dart: dev
+    - stage: testing
+      script: ./tool/travis.sh vm_test
+      dart: dev
+    - stage: testing
+      script: ./tool/travis.sh dartdevc_test
+      dart: dev
+    - stage: testing
+      script: ./tool/travis.sh dart2js_test
+      dart: dev
+
+# Only building master means that we don't run two builds for each pull request.
 branches:
   only: [master]
 
-matrix:
-  # Only run dartfmt checks with stable.
-  exclude:
-    - dart: dev
-      dart_task: dartfmt
-
+# Incremental pub cache and builds.
 cache:
   directories:
     - $HOME/.pub-cache
+    - .dart_tool
+
+# Necessary for Chrome and Firefox to run
+before_install:
+ - export DISPLAY=:99.0
+ - sh -e /etc/init.d/xvfb start
+ - "t=0; until (xdpyinfo -display :99 &> /dev/null || test $t -gt 10); do sleep 1; let t=$t+1; done"
diff --git a/packages/collection/CHANGELOG.md b/packages/collection/CHANGELOG.md
index b676ee1..3c9d4dd 100644
--- a/packages/collection/CHANGELOG.md
+++ b/packages/collection/CHANGELOG.md
@@ -1,3 +1,27 @@
+## 1.14.10
+
+* Fix the parameter names in overridden methods to match the source.
+* Make tests Dart 2 type-safe.
+* Stop depending on SDK `retype` and deprecate methods.
+
+## 1.14.9
+
+* 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
+  `cast` methods. For example, `DelegatingList.typed<String>(list)` can now be
+  written as `list.cast<String>()`.
+
+## 1.14.7
+
+* Only the Dart 2 dev SDK (`>=2.0.0-dev.22.0`) is now supported.
+* Added support for all Dart 2 SDK methods that threw `UnimplementedError`.
+
 ## 1.14.6
 
 * Make `DefaultEquality`'s `equals()` and `hash()` methods take any `Object`
@@ -15,7 +39,7 @@
 ## 1.14.4
 
 * Add implementation stubs of upcoming Dart 2.0 core library methods, namely
-  new methods for classes that implement `Iterable`, `List`, `Map`, `Queue`, 
+  new methods for classes that implement `Iterable`, `List`, `Map`, `Queue`,
   and `Set`.
 
 ## 1.14.3
diff --git a/packages/collection/CONTRIBUTING.md b/packages/collection/CONTRIBUTING.md
index 6f5e0ea..e88abc6 100644
--- a/packages/collection/CONTRIBUTING.md
+++ b/packages/collection/CONTRIBUTING.md
@@ -20,10 +20,19 @@
 ### Code reviews
 All submissions, including submissions by project members, require review.
 
+### Presubmit testing
+* All code must pass analysis by the `dartanalyzer` (`dartanalyzer --fatal-warnings .`)
+* All code must be formatted by `dartfmt` (`dartfmt -w .`)
+  * _NOTE_: We currently require formatting by the `dev` channel SDK.
+* All code must pass unit tests for the VM, Dart2JS, and DartDevC (`pub run build_runner test`).
+  * _NOTE_: We currently use `build_runner` for compilation with DartDevC. It's
+    possible to run only Dart2JS and the VM without it using `pub run test`
+    directly.
+
 ### File headers
 All files in the project must start with the following header.
 
-    // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+    // Copyright (c) 2018, 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.
 
diff --git a/packages/collection/analysis_options.yaml b/packages/collection/analysis_options.yaml
index 5b5eb96..705910c 100644
--- a/packages/collection/analysis_options.yaml
+++ b/packages/collection/analysis_options.yaml
@@ -20,6 +20,7 @@
 
     # Style
     - avoid_init_to_null
+    - avoid_renaming_method_parameters
     - avoid_return_types_on_setters
     - await_only_futures
     - camel_case_types
@@ -32,6 +33,5 @@
     - only_throw_errors
     - prefer_final_fields
     - prefer_is_not_empty
-    - prefer_single_quotes
     - slash_for_doc_comments
     - type_init_formals
diff --git a/packages/collection/dart_test.yaml b/packages/collection/dart_test.yaml
new file mode 100644
index 0000000..d2b9d1d
--- /dev/null
+++ b/packages/collection/dart_test.yaml
@@ -0,0 +1,22 @@
+presets:
+  # When run with -P travis, we have different settings/options.
+  #
+  # 1: We don't use Chrome --headless:
+  # 2: We use --reporter expanded
+  # 3: We skip anything tagged "fails-on-travis".
+  travis:
+    # TODO(https://github.com/dart-lang/test/issues/772)
+    override_platforms:
+      chrome:
+        settings:
+          headless: false
+
+    # Don't run any tests that are tagged ["fails-on-travis"].
+    exclude_tags: "fails-on-travis"
+
+    # https://github.com/dart-lang/test/blob/master/doc/configuration.md#reporter
+    reporter: expanded
+
+platforms:
+  - chrome
+  - vm
diff --git a/packages/collection/lib/src/algorithms.dart b/packages/collection/lib/src/algorithms.dart
index 567230c..0d37d64 100644
--- a/packages/collection/lib/src/algorithms.dart
+++ b/packages/collection/lib/src/algorithms.dart
@@ -64,7 +64,7 @@
 /// Shuffles a list randomly.
 ///
 /// A sub-range of a list can be shuffled by providing [start] and [end].
-void shuffle(List list, [int start = 0, int end = null]) {
+void shuffle(List list, [int start = 0, int end]) {
   var random = new math.Random();
   if (end == null) end = list.length;
   int length = end - start;
@@ -78,7 +78,7 @@
 }
 
 /// Reverses a list, or a part of a list, in-place.
-void reverse(List list, [int start = 0, int end = null]) {
+void reverse(List list, [int start = 0, int end]) {
   if (end == null) end = list.length;
   _reverse(list, start, end);
 }
diff --git a/packages/collection/lib/src/canonicalized_map.dart b/packages/collection/lib/src/canonicalized_map.dart
index 117cd63..9e1d5cf 100644
--- a/packages/collection/lib/src/canonicalized_map.dart
+++ b/packages/collection/lib/src/canonicalized_map.dart
@@ -69,17 +69,11 @@
     other.forEach((key, value) => this[key] = value);
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  void addEntries(Iterable<Object> entries) {
-    // Change Iterable<Object> to Iterable<MapEntry<K, V>> when
-    // the MapEntry class has been added.
-    throw new UnimplementedError('addEntries');
-  }
+  void addEntries(Iterable<MapEntry<K, V>> entries) =>
+      _base.addEntries(entries.map(
+          (e) => new MapEntry(_canonicalize(e.key), new Pair(e.key, e.value))));
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Map<K2, V2> cast<K2, V2>() {
-    throw new UnimplementedError('cast');
-  }
+  Map<K2, V2> cast<K2, V2>() => _base.cast<K2, V2>();
 
   void clear() {
     _base.clear();
@@ -93,12 +87,8 @@
   bool containsValue(Object value) =>
       _base.values.any((pair) => pair.last == value);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<Null> get entries {
-    // Change Iterable<Null> to Iterable<MapEntry<K, V>> when
-    // the MapEntry class has been added.
-    throw new UnimplementedError('entries');
-  }
+  Iterable<MapEntry<K, V>> get entries =>
+      _base.entries.map((e) => new MapEntry(e.value.first, e.value.last));
 
   void forEach(void f(K key, V value)) {
     _base.forEach((key, pair) => f(pair.first, pair.last));
@@ -112,12 +102,8 @@
 
   int get length => _base.length;
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Map<K2, V2> map<K2, V2>(Object transform(K key, V value)) {
-    // Change Object to MapEntry<K2, V2> when
-    // the MapEntry class has been added.
-    throw new UnimplementedError('map');
-  }
+  Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(K key, V value)) =>
+      _base.map((_, pair) => transform(pair.first, pair.last));
 
   V putIfAbsent(K key, V ifAbsent()) {
     return _base
@@ -131,25 +117,19 @@
     return pair == null ? null : pair.last;
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  void removeWhere(bool test(K key, V value)) {
-    throw new UnimplementedError('removeWhere');
-  }
+  void removeWhere(bool test(K key, V value)) =>
+      _base.removeWhere((_, pair) => test(pair.first, pair.last));
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Map<K2, V2> retype<K2, V2>() {
-    throw new UnimplementedError('retype');
-  }
+  @deprecated
+  Map<K2, V2> retype<K2, V2>() => cast<K2, V2>();
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  V update(K key, V update(V value), {V ifAbsent()}) {
-    throw new UnimplementedError('update');
-  }
+  V update(K key, V update(V value), {V ifAbsent()}) => _base
+      .update(_canonicalize(key), (pair) => new Pair(key, update(pair.last)),
+          ifAbsent: ifAbsent == null ? null : () => new Pair(key, ifAbsent()))
+      .last;
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  void updateAll(V update(K key, V value)) {
-    throw new UnimplementedError('updateAll');
-  }
+  void updateAll(V update(K key, V value)) => _base.updateAll(
+      (_, pair) => new Pair(pair.first, update(pair.first, pair.last)));
 
   Iterable<V> get values => _base.values.map((pair) => pair.last);
 
diff --git a/packages/collection/lib/src/combined_wrappers/combined_list.dart b/packages/collection/lib/src/combined_wrappers/combined_list.dart
index d2e0349..21a68f6 100644
--- a/packages/collection/lib/src/combined_wrappers/combined_list.dart
+++ b/packages/collection/lib/src/combined_wrappers/combined_list.dart
@@ -56,11 +56,11 @@
     return null;
   }
 
-  void removeWhere(bool filter(T element)) {
+  void removeWhere(bool test(T element)) {
     _throw();
   }
 
-  void retainWhere(bool filter(T element)) {
+  void retainWhere(bool test(T element)) {
     _throw();
   }
 }
diff --git a/packages/collection/lib/src/empty_unmodifiable_set.dart b/packages/collection/lib/src/empty_unmodifiable_set.dart
index 436e88d..997619d 100644
--- a/packages/collection/lib/src/empty_unmodifiable_set.dart
+++ b/packages/collection/lib/src/empty_unmodifiable_set.dart
@@ -25,6 +25,7 @@
   bool containsAll(Iterable<Object> other) => other.isEmpty;
   Iterable<E> followedBy(Iterable<E> other) => new Set.from(other);
   E lookup(Object element) => null;
+  @deprecated
   EmptyUnmodifiableSet<T> retype<T>() => new EmptyUnmodifiableSet<T>();
   E singleWhere(bool test(E element), {E orElse()}) => super.singleWhere(test);
   Iterable<T> whereType<T>() => new EmptyUnmodifiableSet<T>();
diff --git a/packages/collection/lib/src/queue_list.dart b/packages/collection/lib/src/queue_list.dart
index 224ac86..8b706bf 100644
--- a/packages/collection/lib/src/queue_list.dart
+++ b/packages/collection/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) {
@@ -52,9 +70,9 @@
     _add(element);
   }
 
-  void addAll(Iterable<E> elements) {
-    if (elements is List) {
-      var list = elements;
+  void addAll(Iterable<E> iterable) {
+    if (iterable is List) {
+      var list = iterable;
       int addCount = list.length;
       int length = this.length;
       if (length + addCount >= _table.length) {
@@ -76,19 +94,14 @@
         }
       }
     } else {
-      for (E element in elements) _add(element);
+      for (E element in iterable) _add(element);
     }
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  QueueList<T> cast<T>() {
-    throw new UnimplementedError('cast');
-  }
+  QueueList<T> cast<T>() => QueueList._castFrom<E, T>(this);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  QueueList<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
+  @deprecated
+  QueueList<T> retype<T>() => cast<T>();
 
   String toString() => IterableBase.iterableToFullString(this, "{", "}");
 
@@ -231,3 +244,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/packages/collection/lib/src/typed_wrappers.dart b/packages/collection/lib/src/typed_wrappers.dart
deleted file mode 100644
index cef2a58..0000000
--- a/packages/collection/lib/src/typed_wrappers.dart
+++ /dev/null
@@ -1,471 +0,0 @@
-// 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));
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<T> cast<T>() {
-    throw new UnimplementedError('cast');
-  }
-
-  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));
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<E> followedBy(Iterable<E> other) {
-    throw new UnimplementedError('followedBy');
-  }
-
-  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;
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
-
-  E get single => _base.single as E;
-
-  E singleWhere(bool test(E element), {E orElse()}) {
-    if (orElse != null) throw new UnimplementedError('singleWhere:orElse');
-    return _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)));
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<T> whereType<T>() {
-    throw new UnimplementedError('whereType');
-  }
-
-  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;
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  List<E> operator +(List<E> other) {
-    throw new UnimplementedError('+');
-  }
-
-  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());
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  List<T> cast<T>() {
-    throw new UnimplementedError('cast');
-  }
-
-  void clear() {
-    _listBase.clear();
-  }
-
-  void fillRange(int start, int end, [E fillValue]) {
-    _listBase.fillRange(start, end, fillValue);
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  set first(E value) {
-    if (this.isEmpty) throw new RangeError.index(0, this);
-    this[0] = value;
-  }
-
-  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);
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  int indexWhere(bool test(E element), [int start = 0]) {
-    throw new UnimplementedError('indexWhere');
-  }
-
-  void insert(int index, E element) {
-    _listBase.insert(index, element);
-  }
-
-  void insertAll(int index, Iterable<E> iterable) {
-    _listBase.insertAll(index, iterable);
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  set last(E value) {
-    if (this.isEmpty) throw new RangeError.index(0, this);
-    this[this.length - 1] = value;
-  }
-
-  int lastIndexOf(E element, [int start]) =>
-      _listBase.lastIndexOf(element, start);
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  int lastIndexWhere(bool test(E element), [int start]) {
-    throw new UnimplementedError('lastIndexWhere');
-  }
-
-  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));
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  List<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
-
-  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);
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Set<T> cast<T>() {
-    throw new UnimplementedError('cast');
-  }
-
-  void clear() {
-    _setBase.clear();
-  }
-
-  bool containsAll(Iterable<Object> other) => _setBase.containsAll(other);
-
-  Set<E> difference(Set<Object> 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));
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Set<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
-
-  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);
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Queue<T> cast<T>() {
-    throw new UnimplementedError('cast');
-  }
-
-  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));
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Queue<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
-
-  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);
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  void addEntries(Iterable<Object> entries) {
-    // Change Iterable<Object> to Iterable<MapEntry<K, V>> when
-    // the MapEntry class has been added.
-    throw new UnimplementedError('addEntries');
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Map<K2, V2> cast<K2, V2>() {
-    throw new UnimplementedError('cast');
-  }
-
-  void clear() {
-    _base.clear();
-  }
-
-  bool containsKey(Object key) => _base.containsKey(key);
-
-  bool containsValue(Object value) => _base.containsValue(value);
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<Null> get entries {
-    // Change Iterable<Null> to Iterable<MapEntry<K, V>> when
-    // the MapEntry class has been added.
-    throw new UnimplementedError('entries');
-  }
-
-  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;
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Map<K2, V2> map<K2, V2>(Object transform(K key, V value)) {
-    // Change Object to MapEntry<K2, V2> when
-    // the MapEntry class has been added.
-    throw new UnimplementedError('map');
-  }
-
-  V putIfAbsent(K key, V ifAbsent()) => _base.putIfAbsent(key, ifAbsent) as V;
-
-  V remove(Object key) => _base.remove(key) as V;
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  void removeWhere(bool test(K key, V value)) {
-    throw new UnimplementedError('removeWhere');
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Map<K2, V2> retype<K2, V2>() {
-    throw new UnimplementedError('retype');
-  }
-
-  Iterable<V> get values => new TypeSafeIterable<V>(_base.values);
-
-  String toString() => _base.toString();
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  V update(K key, V update(V value), {V ifAbsent()}) {
-    throw new UnimplementedError('update');
-  }
-
-  // TODO: Dart 2.0 requires this method to be implemented.
-  void updateAll(V update(K key, V value)) {
-    throw new UnimplementedError('updateAll');
-  }
-}
diff --git a/packages/collection/lib/src/wrappers.dart b/packages/collection/lib/src/wrappers.dart
index 1abfd45..f7fe578 100644
--- a/packages/collection/lib/src/wrappers.dart
+++ b/packages/collection/lib/src/wrappers.dart
@@ -5,7 +5,6 @@
 import "dart:collection";
 import "dart:math" as math;
 
-import "typed_wrappers.dart";
 import "unmodifiable_wrappers.dart";
 
 typedef K _KeyForValue<K, V>(V value);
@@ -21,10 +20,7 @@
 
   bool any(bool test(E element)) => _base.any(test);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<T> cast<T>() {
-    throw new UnimplementedError('cast');
-  }
+  Iterable<T> cast<T>() => _base.cast<T>();
 
   bool contains(Object element) => _base.contains(element);
 
@@ -42,10 +38,7 @@
   T fold<T>(T initialValue, T combine(T previousValue, E element)) =>
       _base.fold(initialValue, combine);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<E> followedBy(Iterable<E> other) {
-    throw new UnimplementedError('followedBy');
-  }
+  Iterable<E> followedBy(Iterable<E> other) => _base.followedBy(other);
 
   void forEach(void f(E element)) => _base.forEach(f);
 
@@ -68,16 +61,13 @@
 
   E reduce(E combine(E value, E element)) => _base.reduce(combine);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
+  @deprecated
+  Iterable<T> retype<T>() => cast<T>();
 
   E get single => _base.single;
 
   E singleWhere(bool test(E element), {E orElse()}) {
-    if (orElse != null) throw new UnimplementedError('singleWhere:orElse');
-    return _base.singleWhere(test);
+    return _base.singleWhere(test, orElse: orElse);
   }
 
   Iterable<E> skip(int n) => _base.skip(n);
@@ -94,10 +84,7 @@
 
   Iterable<E> where(bool test(E element)) => _base.where(test);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<T> whereType<T>() {
-    throw new UnimplementedError("whereType");
-  }
+  Iterable<T> whereType<T>() => _base.whereType<T>();
 
   String toString() => _base.toString();
 }
@@ -122,8 +109,8 @@
   /// 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 TypeSafeIterable<E>(base);
+  @Deprecated('Use iterable.cast<E> instead.')
+  static Iterable<E> typed<E>(Iterable base) => base.cast<E>();
 }
 
 /// A [List] that delegates all operations to a base list.
@@ -145,8 +132,8 @@
   /// 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 TypeSafeList<E>(base);
+  @Deprecated('Use list.cast<E> instead.')
+  static List<E> typed<E>(List base) => base.cast<E>();
 
   List<E> get _listBase => _base;
 
@@ -156,10 +143,7 @@
     _listBase[index] = value;
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  List<E> operator +(List<E> other) {
-    throw new UnimplementedError('+');
-  }
+  List<E> operator +(List<E> other) => _listBase + other;
 
   void add(E value) {
     _listBase.add(value);
@@ -171,10 +155,7 @@
 
   Map<int, E> asMap() => _listBase.asMap();
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  List<T> cast<T>() {
-    throw new UnimplementedError('cast');
-  }
+  List<T> cast<T>() => _listBase.cast<T>();
 
   void clear() {
     _listBase.clear();
@@ -184,7 +165,6 @@
     _listBase.fillRange(start, end, fillValue);
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
   set first(E value) {
     if (this.isEmpty) throw new RangeError.index(0, this);
     this[0] = value;
@@ -194,10 +174,8 @@
 
   int indexOf(E element, [int start = 0]) => _listBase.indexOf(element, start);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  int indexWhere(bool test(E element), [int start = 0]) {
-    throw new UnimplementedError('indexWhere');
-  }
+  int indexWhere(bool test(E element), [int start = 0]) =>
+      _listBase.indexWhere(test, start);
 
   void insert(int index, E element) {
     _listBase.insert(index, element);
@@ -207,7 +185,6 @@
     _listBase.insertAll(index, iterable);
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
   set last(E value) {
     if (this.isEmpty) throw new RangeError.index(0, this);
     this[this.length - 1] = value;
@@ -216,10 +193,8 @@
   int lastIndexOf(E element, [int start]) =>
       _listBase.lastIndexOf(element, start);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  int lastIndexWhere(bool test(E element), [int start]) {
-    throw new UnimplementedError('lastIndexWhere');
-  }
+  int lastIndexWhere(bool test(E element), [int start]) =>
+      _listBase.lastIndexWhere(test, start);
 
   set length(int newLength) {
     _listBase.length = newLength;
@@ -247,10 +222,8 @@
     _listBase.retainWhere(test);
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  List<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
+  @deprecated
+  List<T> retype<T>() => cast<T>();
 
   Iterable<E> get reversed => _listBase.reversed;
 
@@ -291,8 +264,8 @@
   /// 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 TypeSafeSet<E>(base);
+  @Deprecated('Use set.cast<E> instead.')
+  static Set<E> typed<E>(Set base) => base.cast<E>();
 
   Set<E> get _setBase => _base;
 
@@ -302,10 +275,7 @@
     _setBase.addAll(elements);
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Set<T> cast<T>() {
-    throw new UnimplementedError('cast');
-  }
+  Set<T> cast<T>() => _setBase.cast<T>();
 
   void clear() {
     _setBase.clear();
@@ -333,10 +303,8 @@
     _setBase.retainAll(elements);
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Set<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
+  @deprecated
+  Set<T> retype<T>() => cast<T>();
 
   void retainWhere(bool test(E element)) {
     _setBase.retainWhere(test);
@@ -366,8 +334,8 @@
   /// 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 TypeSafeQueue<E>(base);
+  @Deprecated('Use queue.cast<E> instead.')
+  static Queue<E> typed<E>(Queue base) => base.cast<E>();
 
   Queue<E> get _baseQueue => _base;
 
@@ -387,10 +355,7 @@
     _baseQueue.addLast(value);
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Queue<T> cast<T>() {
-    throw new UnimplementedError('cast');
-  }
+  Queue<T> cast<T>() => _baseQueue.cast<T>();
 
   void clear() {
     _baseQueue.clear();
@@ -406,10 +371,8 @@
     _baseQueue.retainWhere(test);
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Queue<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
+  @deprecated
+  Queue<T> retype<T>() => cast<T>();
 
   E removeFirst() => _baseQueue.removeFirst();
 
@@ -437,8 +400,8 @@
   /// 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 TypeSafeMap<K, V>(base);
+  @Deprecated('Use map.cast<K, V> instead.')
+  static Map<K, V> typed<K, V>(Map base) => base.cast<K, V>();
 
   V operator [](Object key) => _base[key];
 
@@ -450,32 +413,21 @@
     _base.addAll(other);
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  void addEntries(Iterable<Object> entries) {
-    // Change Iterable<Object> to Iterable<MapEntry<K, V>> when
-    // the MapEntry class has been added.
-    throw new UnimplementedError('addEntries');
+  void addEntries(Iterable<MapEntry<K, V>> entries) {
+    _base.addEntries(entries);
   }
 
   void clear() {
     _base.clear();
   }
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Map<K2, V2> cast<K2, V2>() {
-    throw new UnimplementedError('cast');
-  }
+  Map<K2, V2> cast<K2, V2>() => _base.cast<K2, V2>();
 
   bool containsKey(Object key) => _base.containsKey(key);
 
   bool containsValue(Object value) => _base.containsValue(value);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Iterable<Null> get entries {
-    // Change Iterable<Null> to Iterable<MapEntry<K, V>> when
-    // the MapEntry class has been added.
-    throw new UnimplementedError('entries');
-  }
+  Iterable<MapEntry<K, V>> get entries => _base.entries;
 
   void forEach(void f(K key, V value)) {
     _base.forEach(f);
@@ -489,40 +441,26 @@
 
   int get length => _base.length;
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Map<K2, V2> map<K2, V2>(Object transform(K key, V value)) {
-    // Change Object to MapEntry<K2, V2> when
-    // the MapEntry class has been added.
-    throw new UnimplementedError('map');
-  }
+  Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(K key, V value)) =>
+      _base.map(transform);
 
   V putIfAbsent(K key, V ifAbsent()) => _base.putIfAbsent(key, ifAbsent);
 
   V remove(Object key) => _base.remove(key);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  void removeWhere(bool test(K key, V value)) {
-    throw new UnimplementedError('removeWhere');
-  }
+  void removeWhere(bool test(K key, V value)) => _base.removeWhere(test);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Map<K2, V2> retype<K2, V2>() {
-    throw new UnimplementedError('retype');
-  }
+  @deprecated
+  Map<K2, V2> retype<K2, V2>() => cast<K2, V2>();
 
   Iterable<V> get values => _base.values;
 
   String toString() => _base.toString();
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  V update(K key, V update(V value), {V ifAbsent()}) {
-    throw new UnimplementedError('update');
-  }
+  V update(K key, V update(V value), {V ifAbsent()}) =>
+      _base.update(key, update, ifAbsent: ifAbsent);
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  void updateAll(V update(K key, V value)) {
-    throw new UnimplementedError('updateAll');
-  }
+  void updateAll(V update(K key, V value)) => _base.updateAll(update);
 }
 
 /// An unmodifiable [Set] view of the keys of a [Map].
@@ -542,9 +480,11 @@
 
   Iterable<E> get _base => _baseMap.keys;
 
-  // TODO: Dart 2.0 requires this method to be implemented.
   Set<T> cast<T>() {
-    throw new UnimplementedError('cast');
+    if (this is MapKeySet<T>) {
+      return this as MapKeySet<T>;
+    }
+    return Set.castFrom<E, T>(this);
   }
 
   bool contains(Object element) => _baseMap.containsKey(element);
@@ -583,10 +523,8 @@
   E lookup(Object element) =>
       throw new UnsupportedError("MapKeySet doesn't support lookup().");
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Set<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
+  @deprecated
+  Set<T> retype<T>() => Set.castFrom<E, T>(this);
 
   /// Returns a new set which contains all the elements of [this] and [other].
   ///
@@ -634,9 +572,11 @@
 
   Iterable<V> get _base => _baseMap.values;
 
-  // TODO: Dart 2.0 requires this method to be implemented.
   Set<T> cast<T>() {
-    throw new UnimplementedError('cast');
+    if (this is Set<T>) {
+      return this as Set<T>;
+    }
+    return Set.castFrom<V, T>(this);
   }
 
   bool contains(Object element) {
@@ -735,10 +675,8 @@
   void retainWhere(bool test(V element)) =>
       removeWhere((element) => !test(element));
 
-  // TODO: Dart 2.0 requires this method to be implemented.
-  Set<T> retype<T>() {
-    throw new UnimplementedError('retype');
-  }
+  @deprecated
+  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/packages/collection/pubspec.yaml b/packages/collection/pubspec.yaml
index e97a358..323f039 100644
--- a/packages/collection/pubspec.yaml
+++ b/packages/collection/pubspec.yaml
@@ -1,9 +1,15 @@
 name: collection
-version: 1.14.6
+version: 1.14.10
 author: Dart Team <misc@dartlang.org>
 description: Collections and utilities functions and classes related to collections.
 homepage: https://www.github.com/dart-lang/collection
+
 environment:
-  sdk: '>=1.21.0 <2.0.0'
+  # Required for Dart 2.0 collection changes.
+  sdk: '>=2.0.0-dev.55.0 <2.0.0'
+
 dev_dependencies:
-  test: '^0.12.0'
+  build_runner: ^0.8.0
+  build_test: ^0.10.0
+  build_web_compilers: ^0.3.1
+  test: ^0.12.35
diff --git a/packages/collection/test/canonicalized_map_test.dart b/packages/collection/test/canonicalized_map_test.dart
index f48778d..078e57c 100644
--- a/packages/collection/test/canonicalized_map_test.dart
+++ b/packages/collection/test/canonicalized_map_test.dart
@@ -7,9 +7,10 @@
 
 void main() {
   group("with an empty canonicalized map", () {
-    var map;
+    CanonicalizedMap<int, String, String> map;
+
     setUp(() {
-      map = new CanonicalizedMap<int, String, String>(int.parse,
+      map = new CanonicalizedMap(int.parse,
           isValidKey: (s) => new RegExp(r"^\d+$").hasMatch(s as String));
     });
 
@@ -130,6 +131,33 @@
 
       expect(map.values, equals(["value 01", "value 2", "value 03"]));
     });
+
+    test("entries returns all key-value pairs in the map", () {
+      map.addAll({
+        "1": "value 1",
+        "01": "value 01",
+        "2": "value 2",
+      });
+
+      var entries = map.entries.toList();
+      expect(entries[0].key, "01");
+      expect(entries[0].value, "value 01");
+      expect(entries[1].key, "2");
+      expect(entries[1].value, "value 2");
+    });
+
+    test("addEntries adds key-value pairs to the map", () {
+      map.addEntries([
+        new MapEntry("1", "value 1"),
+        new MapEntry("01", "value 01"),
+        new MapEntry("2", "value 2"),
+      ]);
+      expect(map, {"01": "value 01", "2": "value 2"});
+    });
+
+    test("cast returns a new map instance", () {
+      expect(map.cast<Pattern, Pattern>(), isNot(same(map)));
+    });
   });
 
   group("CanonicalizedMap builds an informative string representation", () {
diff --git a/packages/collection/test/queue_list_test.dart b/packages/collection/test/queue_list_test.dart
index 9e6d199..639eb68 100644
--- a/packages/collection/test/queue_list_test.dart
+++ b/packages/collection/test/queue_list_test.dart
@@ -5,6 +5,8 @@
 import "package:collection/collection.dart";
 import "package:test/test.dart";
 
+import "utils.dart";
+
 void main() {
   group("new QueueList()", () {
     test("creates an empty QueueList", () {
@@ -250,6 +252,47 @@
           throwsConcurrentModificationError);
     });
   });
+
+  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("cast returns a new QueueList", () {
+    var queue = new QueueList<String>();
+    expect(queue.cast<Pattern>(), isNot(same(queue)));
+  });
 }
 
 /// Returns a queue whose internal ring buffer is full enough that adding a new
@@ -262,7 +305,7 @@
 
 /// Returns a queue whose internal tail has a lower index than its head.
 QueueList withInternalGap() {
-  var queue = new QueueList.from([null, null, null, null, 1, 2, 3, 4]);
+  var queue = new QueueList.from(<dynamic>[null, null, null, null, 1, 2, 3, 4]);
   for (var i = 0; i < 4; i++) {
     queue.removeFirst();
   }
diff --git a/packages/collection/test/typed_wrapper/iterable_test.dart b/packages/collection/test/typed_wrapper/iterable_test.dart
deleted file mode 100644
index 5de9651..0000000
--- a/packages/collection/test/typed_wrapper/iterable_test.dart
+++ /dev/null
@@ -1,354 +0,0 @@
-// 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(expectAsync1((_) {}, 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(expectAsync1((_) => false, count: 0)),
-            throwsCastError);
-      });
-
-      test("elementAt()", () {
-        expect(() => wrapper.elementAt(1), throwsCastError);
-      });
-
-      test("every()", () {
-        expect(() => wrapper.every(expectAsync1((_) => false, count: 0)),
-            throwsCastError);
-      });
-
-      test("expand()", () {
-        var lazy = wrapper.expand(expectAsync1((_) => [], count: 0));
-        expect(() => lazy.first, throwsCastError);
-      });
-
-      test("first", () {
-        expect(() => wrapper.first, throwsCastError);
-      });
-
-      test("firstWhere()", () {
-        expect(() => wrapper.firstWhere(expectAsync1((_) => false, count: 0)),
-            throwsCastError);
-      });
-
-      test("fold()", () {
-        expect(
-            () => wrapper.fold(null, expectAsync2((_, __) => null, count: 0)),
-            throwsCastError);
-      });
-
-      test("forEach()", () {
-        expect(() => wrapper.forEach(expectAsync1((_) {}, 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(expectAsync1((_) => false, count: 0)),
-            throwsCastError);
-      });
-
-      test("map()", () {
-        var lazy = wrapper.map(expectAsync1((_) => null, count: 0));
-        expect(() => lazy.first, throwsCastError);
-      });
-
-      test("reduce()", () {
-        expect(() => wrapper.reduce(expectAsync2((_, __) => null, count: 0)),
-            throwsCastError);
-      });
-
-      test("single", () {
-        expect(() => singleWrapper.single, throwsCastError);
-      });
-
-      test("singleWhere()", () {
-        expect(() {
-          singleWrapper.singleWhere(expectAsync1((_) => false, count: 0));
-        }, throwsCastError);
-      });
-
-      test("skip()", () {
-        var lazy = wrapper.skip(1);
-        expect(() => lazy.first, throwsCastError);
-      });
-
-      test("skipWhile()", () {
-        var lazy = wrapper.skipWhile(expectAsync1((_) => false, count: 0));
-        expect(() => lazy.first, throwsCastError);
-      });
-
-      test("take()", () {
-        var lazy = wrapper.take(1);
-        expect(() => lazy.first, throwsCastError);
-      });
-
-      test("takeWhile()", () {
-        var lazy = wrapper.takeWhile(expectAsync1((_) => 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(expectAsync1((_) => 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/packages/collection/test/typed_wrapper/list_test.dart b/packages/collection/test/typed_wrapper/list_test.dart
deleted file mode 100644
index 7876dbe..0000000
--- a/packages/collection/test/typed_wrapper/list_test.dart
+++ /dev/null
@@ -1,421 +0,0 @@
-// 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", () {
-    List 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(expectAsync1((_) => false, count: 0)),
-            throwsCastError);
-      });
-
-      test("retainWhere()", () {
-        expect(() => wrapper.retainWhere(expectAsync1((_) => false, count: 0)),
-            throwsCastError);
-      });
-
-      test("reversed", () {
-        var lazy = wrapper.reversed;
-        expect(() => lazy.first, throwsCastError);
-      });
-
-      test("sort()", () {
-        expect(() => wrapper.sort(expectAsync2((_, __) => 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/packages/collection/test/typed_wrapper/map_test.dart b/packages/collection/test/typed_wrapper/map_test.dart
deleted file mode 100644
index 75078bd..0000000
--- a/packages/collection/test/typed_wrapper/map_test.dart
+++ /dev/null
@@ -1,327 +0,0 @@
-// 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(expectAsync2((_, __) {}, 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", expectAsync1((_) => 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", () {
-    Map 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(expectAsync2((_, __) {}, 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", () {
-    Map 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(expectAsync2((_, __) {}, 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/packages/collection/test/typed_wrapper/queue_test.dart b/packages/collection/test/typed_wrapper/queue_test.dart
deleted file mode 100644
index e9ffb3a..0000000
--- a/packages/collection/test/typed_wrapper/queue_test.dart
+++ /dev/null
@@ -1,141 +0,0 @@
-// 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", () {
-    Queue 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(expectAsync1((_) => false, count: 0)),
-            throwsCastError);
-      });
-
-      test("retainWhere()", () {
-        expect(() => wrapper.retainWhere(expectAsync1((_) => 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/packages/collection/test/typed_wrapper/set_test.dart b/packages/collection/test/typed_wrapper/set_test.dart
deleted file mode 100644
index d7eed5f..0000000
--- a/packages/collection/test/typed_wrapper/set_test.dart
+++ /dev/null
@@ -1,173 +0,0 @@
-// 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;
-    setUp(() {
-      wrapper = DelegatingSet.typed<int>(new Set<Object>.from([1, 2, 3, 4, 5]));
-    });
-
-    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", () {
-    Set 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(expectAsync1((_) => false, count: 0)),
-            throwsCastError);
-      });
-
-      test("retainWhere()", () {
-        expect(() => wrapper.retainWhere(expectAsync1((_) => 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/packages/collection/test/union_set_controller_test.dart b/packages/collection/test/union_set_controller_test.dart
index 366b781..8c561d3 100644
--- a/packages/collection/test/union_set_controller_test.dart
+++ b/packages/collection/test/union_set_controller_test.dart
@@ -7,11 +7,11 @@
 import "package:collection/collection.dart";
 
 void main() {
-  var controller;
+  UnionSetController<int> controller;
   Set<int> innerSet;
   setUp(() {
     innerSet = new Set.from([1, 2, 3]);
-    controller = new UnionSetController<int>()..add(innerSet);
+    controller = new UnionSetController()..add(innerSet);
   });
 
   test("exposes a union set", () {
diff --git a/packages/collection/test/union_set_test.dart b/packages/collection/test/union_set_test.dart
index f2a792a..d0437ff 100644
--- a/packages/collection/test/union_set_test.dart
+++ b/packages/collection/test/union_set_test.dart
@@ -42,10 +42,10 @@
   group("with multiple disjoint sets", () {
     var set;
     setUp(() {
-      set = new UnionSet<int>.from([
-        new Set.from([1, 2]),
-        new Set.from([3, 4]),
-        new Set.from([5]),
+      set = new UnionSet.from([
+        new Set.of([1, 2]),
+        new Set.of([3, 4]),
+        new Set.of([5]),
         new Set()
       ], disjoint: true);
     });
@@ -81,10 +81,10 @@
   group("with multiple overlapping sets", () {
     var set;
     setUp(() {
-      set = new UnionSet<int>.from([
-        new Set.from([1, 2, 3]),
-        new Set.from([3, 4]),
-        new Set.from([5, 1]),
+      set = new UnionSet.from([
+        new Set.of([1, 2, 3]),
+        new Set.of([3, 4]),
+        new Set.of([5, 1]),
         new Set()
       ]);
     });
@@ -114,8 +114,8 @@
       expect(duration1, isNot(same(duration2)));
 
       var set = new UnionSet.from([
-        new Set.from([duration1]),
-        new Set.from([duration2])
+        new Set.of([duration1]),
+        new Set.of([duration2])
       ]);
 
       expect(set.lookup(new Duration(seconds: 0)), same(duration1));
@@ -134,10 +134,10 @@
   group("after an inner set was modified", () {
     var set;
     setUp(() {
-      var innerSet = new Set<int>.from([3, 7]);
-      set = new UnionSet<int>.from([
-        new Set.from([1, 2]),
-        new Set.from([5]),
+      var innerSet = new Set.of([3, 7]);
+      set = new UnionSet.from([
+        new Set.of([1, 2]),
+        new Set.of([5]),
         innerSet
       ]);
 
@@ -178,16 +178,16 @@
   group("after the outer set was modified", () {
     var set;
     setUp(() {
-      var innerSet = new Set.from([6]);
-      var outerSet = new Set<Set<int>>.from([
-        new Set.from([1, 2]),
-        new Set.from([5]),
+      var innerSet = new Set.of([6]);
+      var outerSet = new Set.of([
+        new Set.of([1, 2]),
+        new Set.of([5]),
         innerSet
       ]);
 
       set = new UnionSet<int>(outerSet);
       outerSet.remove(innerSet);
-      outerSet.add(new Set.from([3, 4]));
+      outerSet.add(new Set.of([3, 4]));
     });
 
     test("length returns the total length", () {
diff --git a/packages/collection/test/unmodifiable_collection_test.dart b/packages/collection/test/unmodifiable_collection_test.dart
index a6705f6..c7b539f 100644
--- a/packages/collection/test/unmodifiable_collection_test.dart
+++ b/packages/collection/test/unmodifiable_collection_test.dart
@@ -37,13 +37,13 @@
   testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "empty");
   aSet = new Set();
   testUnmodifiableSet(aSet, const UnmodifiableSetView.empty(), "const empty");
-  aSet = new Set.from([42]);
+  aSet = new Set.of([42]);
   testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "single-42");
-  aSet = new Set.from([7]);
+  aSet = new Set.of([7]);
   testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "single!42");
-  aSet = new Set.from([1, 42, 10]);
+  aSet = new Set.of([1, 42, 10]);
   testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "three-42");
-  aSet = new Set.from([1, 7, 10]);
+  aSet = new Set.of([1, 7, 10]);
   testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "three!42");
 }
 
@@ -311,8 +311,8 @@
   });
 }
 
-void testNoWriteList(List original, List wrapped, String name) {
-  List copy = new List.from(original);
+void testNoWriteList(List<int> original, List<int> wrapped, String name) {
+  var copy = new List.of(original);
 
   testThrows(name, thunk) {
     test(name, () {
@@ -345,7 +345,7 @@
 }
 
 void testWriteList(List<int> original, List wrapped, String name) {
-  var copy = new List<int>.from(original);
+  var copy = new List.of(original);
 
   test("$name - []=", () {
     if (original.isNotEmpty) {
@@ -361,7 +361,7 @@
   });
 
   test("$name - sort", () {
-    List sortCopy = new List.from(original);
+    List sortCopy = new List.of(original);
     sortCopy.sort();
     wrapped.sort();
     expect(original, orderedEquals(sortCopy));
@@ -391,8 +391,9 @@
   });
 }
 
-void testNoChangeLengthList(List original, List wrapped, String name) {
-  List copy = new List.from(original);
+void testNoChangeLengthList(
+    List<int> original, List<int> wrapped, String name) {
+  var copy = new List.of(original);
 
   void testThrows(String name, thunk) {
     test(name, () {
@@ -455,8 +456,8 @@
   });
 }
 
-void testReadSet(Set original, Set wrapped, String name) {
-  Set copy = new Set.from(original);
+void testReadSet(Set<int> original, Set<int> wrapped, String name) {
+  var copy = new Set.of(original);
 
   test("$name - containsAll", () {
     expect(wrapped.containsAll(copy), isTrue);
@@ -468,27 +469,27 @@
   test("$name - intersection", () {
     expect(wrapped.intersection(new Set()), isEmpty);
     expect(wrapped.intersection(copy), unorderedEquals(original));
-    expect(wrapped.intersection(new Set.from([42])),
-        new Set.from(original.contains(42) ? [42] : []));
+    expect(wrapped.intersection(new Set.of([42])),
+        new Set.of(original.contains(42) ? [42] : []));
   });
 
   test("$name - union", () {
     expect(wrapped.union(new Set()), unorderedEquals(original));
     expect(wrapped.union(copy), unorderedEquals(original));
-    expect(wrapped.union(new Set.from([42])),
-        equals(original.union(new Set.from([42]))));
+    expect(wrapped.union(new Set.of([42])),
+        equals(original.union(new Set.of([42]))));
   });
 
   test("$name - difference", () {
     expect(wrapped.difference(new Set()), unorderedEquals(original));
     expect(wrapped.difference(copy), isEmpty);
-    expect(wrapped.difference(new Set.from([42])),
-        equals(original.difference(new Set.from([42]))));
+    expect(wrapped.difference(new Set.of([42])),
+        equals(original.difference(new Set.of([42]))));
   });
 }
 
-void testNoChangeSet(Set original, Set wrapped, String name) {
-  List originalElements = original.toList();
+void testNoChangeSet(Set<int> original, Set<int> wrapped, String name) {
+  var originalElements = original.toList();
 
   testThrows(name, thunk) {
     test(name, () {
@@ -588,8 +589,8 @@
   });
 }
 
-testNoChangeMap(Map original, Map wrapped, String name) {
-  Map copy = new Map.from(original);
+testNoChangeMap(Map<int, int> original, Map<int, int> wrapped, String name) {
+  var copy = new Map.of(original);
 
   testThrows(name, thunk) {
     test(name, () {
diff --git a/packages/collection/test/utils.dart b/packages/collection/test/utils.dart
index d8ab082..73926a3 100644
--- a/packages/collection/test/utils.dart
+++ b/packages/collection/test/utils.dart
@@ -5,3 +5,13 @@
 import "package:test/test.dart";
 
 final Matcher throwsCastError = throwsA(new isInstanceOf<CastError>());
+
+/// A hack to determine whether we are running in a Dart 2 runtime.
+final bool isDart2 = _isTypeArgString('');
+bool _isTypeArgString<T>(T arg) {
+  try {
+    return T == String;
+  } catch (_) {
+    return false;
+  }
+}
diff --git a/packages/collection/test/wrapper_test.dart b/packages/collection/test/wrapper_test.dart
index 95e6f36..bb8f90f 100644
--- a/packages/collection/test/wrapper_test.dart
+++ b/packages/collection/test/wrapper_test.dart
@@ -25,250 +25,237 @@
 
 /// Utility class to record a member access and a member access on a wrapped
 /// object, and compare them for equality.
+///
+/// Use as `(expector..someAccess()).equals.someAccess();`.
+/// Alle the intercepted member accesses returns `null`.
 abstract class Expector {
-  getWrappedObject(action(Invocation i));
-  // Hack to test assignment ([]=) because it doesn't return the result
-  // of the member call. Instead use (expect..[4]=5).equal[4]=5  where
-  // you would normally use expect[4].equals[4] for non-assignments.
+  wrappedChecker(Invocation i);
+  // After calling any member on the Expector, equals is an object that expects
+  // the *same* invocation on the wrapped object.
   var equals;
 
-  noSuchMethod(Invocation m) => new _Equals(equals = getWrappedObject((m2) {
-        testInvocations(m, m2);
-      }));
+  noSuchMethod(Invocation i) {
+    equals = wrappedChecker(i);
+    return null;
+  }
 
-  // dartanalyzer complains if this method is named `toString()`, since, if it
-  // truly overrides Object's `toString()`, it should return a String.
-  asString() => new _Equals(equals = getWrappedObject((m2) {
-        testInvocations(TO_STRING_INVOCATION, m2);
-      }));
+  toString() {
+    // Cannot return an _Equals object since toString must return a String.
+    // Just set equals and return a string.
+    equals = wrappedChecker(toStringInvocation);
+    return "";
+  }
 }
 
-// An object with a field called "equals", only introduced into the
-// flow to allow writing expect.xxx.equals.xxx.
-class _Equals {
-  final equals;
-  _Equals(this.equals);
+// Parameterization of noSuchMethod. Calls [_action] on every
+// member invocation.
+class InvocationChecker {
+  Invocation _expected;
+  InvocationChecker(this._expected);
+  noSuchMethod(Invocation actual) {
+    testInvocations(_expected, actual);
+    return null;
+  }
+
+  toString() {
+    testInvocations(_expected, toStringInvocation);
+    return "";
+  }
+  // Could also handle runtimeType, hashCode and == the same way as
+  // toString, but we are not testing them since collections generally
+  // don't override those and so the wrappers don't forward those.
 }
 
-class SyntheticInvocation implements Invocation {
-  static const int METHOD = 0x00;
-  static const int GETTER = 0x01;
-  static const int SETTER = 0x02;
-  final Symbol memberName;
-  final List positionalArguments;
-  final Map<Symbol, dynamic> namedArguments;
-  final int _type;
-  const SyntheticInvocation(this.memberName, this.positionalArguments,
-      this.namedArguments, this._type);
+final toStringInvocation = new Invocation.method(#toString, const []);
 
-  List<Type> get typeArguments => const <Type>[];
-
-  bool get isMethod => _type == METHOD;
-
-  bool get isGetter => _type == GETTER;
-
-  bool get isSetter => _type == SETTER;
-
-  bool get isAccessor => isGetter || isSetter;
+// InvocationCheckers with types Queue, Set, List or Iterable to allow them as
+// argument to DelegatingIterable/Set/List/Queue.
+class IterableInvocationChecker<T> extends InvocationChecker
+    implements Iterable<T> {
+  IterableInvocationChecker(Invocation expected) : super(expected);
 }
 
-// Parameterization of noSuchMethod.
-class NSM {
-  Function _action;
-  NSM(this._action);
-  noSuchMethod(Invocation i) => _action(i);
+class ListInvocationChecker<T> extends InvocationChecker implements List<T> {
+  ListInvocationChecker(Invocation expected) : super(expected);
 }
 
-const TO_STRING_INVOCATION = const SyntheticInvocation(
-    #toString, const [], const {}, SyntheticInvocation.METHOD);
+class SetInvocationChecker<T> extends InvocationChecker implements Set<T> {
+  SetInvocationChecker(Invocation expected) : super(expected);
+}
 
-// LikeNSM, but has types Iterable, Set and List to allow it as
-// argument to DelegatingIterable/Set/List.
-class IterableNSM extends NSM implements Iterable, Set, List, Queue {
-  IterableNSM(action(Invocation i)) : super(action);
-  toString() => super.noSuchMethod(TO_STRING_INVOCATION) as String;
+class QueueInvocationChecker<T> extends InvocationChecker implements Queue<T> {
+  QueueInvocationChecker(Invocation expected) : super(expected);
+}
 
-  Null cast<T>();
-  Null retype<T>();
+class MapInvocationChecker<K, V> extends InvocationChecker
+    implements Map<K, V> {
+  MapInvocationChecker(Invocation expected) : super(expected);
 }
 
 // Expector that wraps in DelegatingIterable.
-class IterableExpector extends Expector {
-  getWrappedObject(void action(Invocation i)) {
-    return new DelegatingIterable(new IterableNSM(action));
-  }
+class IterableExpector<T> extends Expector implements Iterable<T> {
+  wrappedChecker(Invocation i) =>
+      new DelegatingIterable<T>(new IterableInvocationChecker<T>(i));
 }
 
 // Expector that wraps in DelegatingList.
-class ListExpector extends Expector {
-  getWrappedObject(void action(Invocation i)) {
-    return new DelegatingList(new IterableNSM(action));
-  }
+class ListExpector<T> extends Expector implements List<T> {
+  wrappedChecker(Invocation i) =>
+      new DelegatingList<T>(new ListInvocationChecker<T>(i));
 }
 
 // Expector that wraps in DelegatingSet.
-class SetExpector extends Expector {
-  getWrappedObject(void action(Invocation i)) {
-    return new DelegatingSet(new IterableNSM(action));
-  }
+class SetExpector<T> extends Expector implements Set<T> {
+  wrappedChecker(Invocation i) =>
+      new DelegatingSet<T>(new SetInvocationChecker<T>(i));
 }
 
 // Expector that wraps in DelegatingSet.
-class QueueExpector extends Expector {
-  getWrappedObject(void action(Invocation i)) {
-    return new DelegatingQueue(new IterableNSM(action));
-  }
-}
-
-// Like NSM but implements Map to allow as argument for DelegatingMap.
-class MapNSM extends NSM implements Map {
-  MapNSM(action(Invocation i)) : super(action);
-  toString() => super.noSuchMethod(TO_STRING_INVOCATION) as String;
+class QueueExpector<T> extends Expector implements Queue<T> {
+  wrappedChecker(Invocation i) =>
+      new DelegatingQueue<T>(new QueueInvocationChecker<T>(i));
 }
 
 // Expector that wraps in DelegatingMap.
-class MapExpector extends Expector {
-  getWrappedObject(void action(Invocation i)) {
-    return new DelegatingMap(new MapNSM(action));
-  }
+class MapExpector<K, V> extends Expector implements Map<K, V> {
+  wrappedChecker(Invocation i) =>
+      new DelegatingMap<K, V>(new MapInvocationChecker<K, V>(i));
 }
 
 // Utility values to use as arguments in calls.
-func0() {}
-func1(x) {}
-func2(x, y) {}
+Null func0() => null;
+Null func1(Object x) => null;
+Null func2(Object x, Object y) => null;
 var val = new Object();
 
 void main() {
   testIterable(var expect) {
-    expect.any(func1).equals.any(func1);
-    expect.contains(val).equals.contains(val);
-    expect.elementAt(0).equals.elementAt(0);
-    expect.every(func1).equals.every(func1);
-    expect.expand(func1).equals.expand(func1);
-    expect.first.equals.first;
+    (expect..any(func1)).equals.any(func1);
+    (expect..contains(val)).equals.contains(val);
+    (expect..elementAt(0)).equals.elementAt(0);
+    (expect..every(func1)).equals.every(func1);
+    (expect..expand(func1)).equals.expand(func1);
+    (expect..first).equals.first;
     // Default values of the Iterable interface will be added in the
     // second call to firstWhere, so we must record them in our
     // expectation (which doesn't have the interface implemented or
     // its default values).
-    expect.firstWhere(func1, orElse: null).equals.firstWhere(func1);
-    expect
-        .firstWhere(func1, orElse: func0)
+    (expect..firstWhere(func1, orElse: null)).equals.firstWhere(func1);
+    (expect..firstWhere(func1, orElse: func0))
         .equals
         .firstWhere(func1, orElse: func0);
-    expect.fold(null, func2).equals.fold(null, func2);
-    expect.forEach(func1).equals.forEach(func1);
-    expect.isEmpty.equals.isEmpty;
-    expect.isNotEmpty.equals.isNotEmpty;
-    expect.iterator.equals.iterator;
-    expect.join('').equals.join();
-    expect.join("X").equals.join("X");
-    expect.last.equals.last;
-    expect.lastWhere(func1, orElse: null).equals.lastWhere(func1);
-    expect
-        .lastWhere(func1, orElse: func0)
+    (expect..fold(null, func2)).equals.fold(null, func2);
+    (expect..forEach(func1)).equals.forEach(func1);
+    (expect..isEmpty).equals.isEmpty;
+    (expect..isNotEmpty).equals.isNotEmpty;
+    (expect..iterator).equals.iterator;
+    (expect..join('')).equals.join();
+    (expect..join("X")).equals.join("X");
+    (expect..last).equals.last;
+    (expect..lastWhere(func1, orElse: null)).equals.lastWhere(func1);
+    (expect..lastWhere(func1, orElse: func0))
         .equals
         .lastWhere(func1, orElse: func0);
-    expect.length.equals.length;
-    expect.map(func1).equals.map(func1);
-    expect.reduce(func2).equals.reduce(func2);
-    expect.single.equals.single;
-    expect.singleWhere(func1).equals.singleWhere(func1);
-    expect.skip(5).equals.skip(5);
-    expect.skipWhile(func1).equals.skipWhile(func1);
-    expect.take(5).equals.take(5);
-    expect.takeWhile(func1).equals.takeWhile(func1);
-    expect.toList(growable: true).equals.toList();
-    expect.toList(growable: true).equals.toList(growable: true);
-    expect.toList(growable: false).equals.toList(growable: false);
-    expect.toSet().equals.toSet();
-    expect.asString().equals.toString();
-    expect.where(func1).equals.where(func1);
+    (expect..length).equals.length;
+    (expect..map(func1)).equals.map(func1);
+    (expect..reduce(func2)).equals.reduce(func2);
+    (expect..single).equals.single;
+    (expect..singleWhere(func1, orElse: null)).equals.singleWhere(func1);
+    (expect..skip(5)).equals.skip(5);
+    (expect..skipWhile(func1)).equals.skipWhile(func1);
+    (expect..take(5)).equals.take(5);
+    (expect..takeWhile(func1)).equals.takeWhile(func1);
+    (expect..toList(growable: true)).equals.toList();
+    (expect..toList(growable: true)).equals.toList(growable: true);
+    (expect..toList(growable: false)).equals.toList(growable: false);
+    (expect..toSet()).equals.toSet();
+    (expect..toString()).equals.toString();
+    (expect..where(func1)).equals.where(func1);
   }
 
   void testList(var expect) {
     testIterable(expect);
 
-    expect[4].equals[4];
+    (expect..[4]).equals[4];
     (expect..[4] = 5).equals[4] = 5;
 
-    expect.add(val).equals.add(val);
-    expect.addAll([val]).equals.addAll([val]);
-    expect.asMap().equals.asMap();
-    expect.clear().equals.clear();
-    expect.fillRange(4, 5, null).equals.fillRange(4, 5);
-    expect.fillRange(4, 5, val).equals.fillRange(4, 5, val);
-    expect.getRange(4, 5).equals.getRange(4, 5);
-    expect.indexOf(val, 0).equals.indexOf(val);
-    expect.indexOf(val, 4).equals.indexOf(val, 4);
-    expect.insert(4, val).equals.insert(4, val);
-    expect.insertAll(4, [val]).equals.insertAll(4, [val]);
-    expect.lastIndexOf(val, null).equals.lastIndexOf(val);
-    expect.lastIndexOf(val, 4).equals.lastIndexOf(val, 4);
+    (expect..add(val)).equals.add(val);
+    (expect..addAll([val])).equals.addAll([val]);
+    (expect..asMap()).equals.asMap();
+    (expect..clear()).equals.clear();
+    (expect..fillRange(4, 5, null)).equals.fillRange(4, 5);
+    (expect..fillRange(4, 5, val)).equals.fillRange(4, 5, val);
+    (expect..getRange(4, 5)).equals.getRange(4, 5);
+    (expect..indexOf(val, 0)).equals.indexOf(val);
+    (expect..indexOf(val, 4)).equals.indexOf(val, 4);
+    (expect..insert(4, val)).equals.insert(4, val);
+    (expect..insertAll(4, [val])).equals.insertAll(4, [val]);
+    (expect..lastIndexOf(val, null)).equals.lastIndexOf(val);
+    (expect..lastIndexOf(val, 4)).equals.lastIndexOf(val, 4);
     (expect..length = 4).equals.length = 4;
-    expect.remove(val).equals.remove(val);
-    expect.removeAt(4).equals.removeAt(4);
-    expect.removeLast().equals.removeLast();
-    expect.removeRange(4, 5).equals.removeRange(4, 5);
-    expect.removeWhere(func1).equals.removeWhere(func1);
-    expect.replaceRange(4, 5, [val]).equals.replaceRange(4, 5, [val]);
-    expect.retainWhere(func1).equals.retainWhere(func1);
-    expect.reversed.equals.reversed;
-    expect.setAll(4, [val]).equals.setAll(4, [val]);
-    expect.setRange(4, 5, [val], 0).equals.setRange(4, 5, [val]);
-    expect.setRange(4, 5, [val], 3).equals.setRange(4, 5, [val], 3);
-    expect.sort(null).equals.sort();
-    expect.sort(func2).equals.sort(func2);
-    expect.sublist(4, null).equals.sublist(4);
-    expect.sublist(4, 5).equals.sublist(4, 5);
+    (expect..remove(val)).equals.remove(val);
+    (expect..removeAt(4)).equals.removeAt(4);
+    (expect..removeLast()).equals.removeLast();
+    (expect..removeRange(4, 5)).equals.removeRange(4, 5);
+    (expect..removeWhere(func1)).equals.removeWhere(func1);
+    (expect..replaceRange(4, 5, [val])).equals.replaceRange(4, 5, [val]);
+    (expect..retainWhere(func1)).equals.retainWhere(func1);
+    (expect..reversed).equals.reversed;
+    (expect..setAll(4, [val])).equals.setAll(4, [val]);
+    (expect..setRange(4, 5, [val], 0)).equals.setRange(4, 5, [val]);
+    (expect..setRange(4, 5, [val], 3)).equals.setRange(4, 5, [val], 3);
+    (expect..sort(null)).equals.sort();
+    (expect..sort(func2)).equals.sort(func2);
+    (expect..sublist(4, null)).equals.sublist(4);
+    (expect..sublist(4, 5)).equals.sublist(4, 5);
   }
 
   void testSet(var expect) {
     testIterable(expect);
     Set set = new Set();
-    expect.add(val).equals.add(val);
-    expect.addAll([val]).equals.addAll([val]);
-    expect.clear().equals.clear();
-    expect.containsAll([val]).equals.containsAll([val]);
-    expect.difference(set).equals.difference(set);
-    expect.intersection(set).equals.intersection(set);
-    expect.remove(val).equals.remove(val);
-    expect.removeAll([val]).equals.removeAll([val]);
-    expect.removeWhere(func1).equals.removeWhere(func1);
-    expect.retainAll([val]).equals.retainAll([val]);
-    expect.retainWhere(func1).equals.retainWhere(func1);
-    expect.union(set).equals.union(set);
+    (expect..add(val)).equals.add(val);
+    (expect..addAll([val])).equals.addAll([val]);
+    (expect..clear()).equals.clear();
+    (expect..containsAll([val])).equals.containsAll([val]);
+    (expect..difference(set)).equals.difference(set);
+    (expect..intersection(set)).equals.intersection(set);
+    (expect..remove(val)).equals.remove(val);
+    (expect..removeAll([val])).equals.removeAll([val]);
+    (expect..removeWhere(func1)).equals.removeWhere(func1);
+    (expect..retainAll([val])).equals.retainAll([val]);
+    (expect..retainWhere(func1)).equals.retainWhere(func1);
+    (expect..union(set)).equals.union(set);
   }
 
   void testQueue(var expect) {
     testIterable(expect);
-    expect.add(val).equals.add(val);
-    expect.addAll([val]).equals.addAll([val]);
-    expect.addFirst(val).equals.addFirst(val);
-    expect.addLast(val).equals.addLast(val);
-    expect.clear().equals.clear();
-    expect.remove(val).equals.remove(val);
-    expect.removeFirst().equals.removeFirst();
-    expect.removeLast().equals.removeLast();
+    (expect..add(val)).equals.add(val);
+    (expect..addAll([val])).equals.addAll([val]);
+    (expect..addFirst(val)).equals.addFirst(val);
+    (expect..addLast(val)).equals.addLast(val);
+    (expect..clear()).equals.clear();
+    (expect..remove(val)).equals.remove(val);
+    (expect..removeFirst()).equals.removeFirst();
+    (expect..removeLast()).equals.removeLast();
   }
 
   void testMap(var expect) {
     Map map = new Map();
-    expect[val].equals[val];
+    (expect..[val]).equals[val];
     (expect..[val] = val).equals[val] = val;
-    expect.addAll(map).equals.addAll(map);
-    expect.clear().equals.clear();
-    expect.containsKey(val).equals.containsKey(val);
-    expect.containsValue(val).equals.containsValue(val);
-    expect.forEach(func2).equals.forEach(func2);
-    expect.isEmpty.equals.isEmpty;
-    expect.isNotEmpty.equals.isNotEmpty;
-    expect.keys.equals.keys;
-    expect.length.equals.length;
-    expect.putIfAbsent(val, func0).equals.putIfAbsent(val, func0);
-    expect.remove(val).equals.remove(val);
-    expect.values.equals.values;
-    expect.asString().equals.toString();
+    (expect..addAll(map)).equals.addAll(map);
+    (expect..clear()).equals.clear();
+    (expect..containsKey(val)).equals.containsKey(val);
+    (expect..containsValue(val)).equals.containsValue(val);
+    (expect..forEach(func2)).equals.forEach(func2);
+    (expect..isEmpty).equals.isEmpty;
+    (expect..isNotEmpty).equals.isNotEmpty;
+    (expect..keys).equals.keys;
+    (expect..length).equals.length;
+    (expect..putIfAbsent(val, func0)).equals.putIfAbsent(val, func0);
+    (expect..remove(val)).equals.remove(val);
+    (expect..values).equals.values;
+    (expect..toString()).equals.toString();
   }
 
   // Runs tests of Set behavior.
@@ -276,7 +263,7 @@
   // [setUpSet] should return a set with two elements: "foo" and "bar".
   void testTwoElementSet(Set<String> setUpSet()) {
     group("with two elements", () {
-      var set;
+      Set<String> set;
       setUp(() => set = setUpSet());
 
       test(".any", () {
diff --git a/packages/collection/tool/travis.sh b/packages/collection/tool/travis.sh
new file mode 100755
index 0000000..0e584b9
--- /dev/null
+++ b/packages/collection/tool/travis.sh
@@ -0,0 +1,65 @@
+# Copyright 2018 the Dart project authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#!/bin/bash
+
+if [ "$#" == "0" ]; then
+  echo -e '\033[31mAt least one task argument must be provided!\033[0m'
+  exit 1
+fi
+
+EXIT_CODE=0
+
+while (( "$#" )); do
+  TASK=$1
+  case $TASK in
+  dartfmt) echo
+    echo -e '\033[1mTASK: dartfmt\033[22m'
+    echo -e 'dartfmt -n --set-exit-if-changed .'
+    dartfmt -n --set-exit-if-changed . || EXIT_CODE=$?
+    ;;
+  dartanalyzer) echo
+    echo -e '\033[1mTASK: dartanalyzer\033[22m'
+    echo -e 'dartanalyzer --fatal-warnings .'
+    dartanalyzer --fatal-warnings . || EXIT_CODE=$?
+    ;;
+  vm_test) echo
+    echo -e '\033[1mTASK: vm_test\033[22m'
+    echo -e 'pub run test -P travis -p vm -x requires-dart2'
+    pub run test -p vm || EXIT_CODE=$?
+    ;;
+  dartdevc_build) echo
+    echo -e '\033[1mTASK: build\033[22m'
+    echo -e 'pub run build_runner build --fail-on-severe'
+    pub run build_runner build --fail-on-severe || EXIT_CODE=$?
+    ;;
+  dartdevc_test) echo
+    echo -e '\033[1mTASK: dartdevc_test\033[22m'
+    echo -e 'pub run build_runner test -- -P travis -p chrome'
+    pub run build_runner test -- -p chrome || EXIT_CODE=$?
+    ;;
+  dart2js_test) echo
+    echo -e '\033[1mTASK: dart2js_test\033[22m'
+    echo -e 'pub run test -P travis -p chrome -x requires-dart2'
+    pub run test -p chrome || EXIT_CODE=$?
+    ;;
+  *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m"
+    EXIT_CODE=1
+    ;;
+  esac
+
+  shift
+done
+
+exit $EXIT_CODE
diff --git a/packages/dart_internal/LICENSE b/packages/dart_internal/LICENSE
new file mode 100644
index 0000000..389ce98
--- /dev/null
+++ b/packages/dart_internal/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/dart_internal/README.md b/packages/dart_internal/README.md
new file mode 100644
index 0000000..3ce15a1
--- /dev/null
+++ b/packages/dart_internal/README.md
@@ -0,0 +1,15 @@
+**Scary warning: This package is experimental and may deprecated in a future
+version of Dart.**
+
+This package is not intended for wide use. It provides a temporary API to
+solve the problem: "Given an object some generic type A, how do I construct an
+instance of generic type B with the same type argument(s)?"
+
+This is necessary in a few rare places in order to migrate existing code to
+Dart 2's stronger type system. Eventually, the hope is to have direct
+language support for solving this problem but we don't have time to get that
+into 2.0, so this package is provided as a temporary workaround.
+
+We will very likely remove support for this in a later version of Dart. Please
+avoid using this if you can. If you feel you *do* need to use it, please reach
+out to @munificent or @leafpetersen and let us know.
diff --git a/packages/dart_internal/analysis_options.yaml b/packages/dart_internal/analysis_options.yaml
new file mode 100644
index 0000000..1a46de2
--- /dev/null
+++ b/packages/dart_internal/analysis_options.yaml
@@ -0,0 +1,3 @@
+analyzer:
+  strong-mode:
+    implicit-casts: false
diff --git a/packages/dart_internal/lib/extract_type_arguments.dart b/packages/dart_internal/lib/extract_type_arguments.dart
new file mode 100644
index 0000000..218d075
--- /dev/null
+++ b/packages/dart_internal/lib/extract_type_arguments.dart
@@ -0,0 +1,44 @@
+// 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.
+
+// The actual functionality exposed by this package is implemented in
+// "dart:_internal" since it is specific to each platform's runtime
+// implementation. This package exists as a shell to expose that internal API
+// to outside code.
+//
+// Only this exact special file is allowed to import "dart:_internal" without
+// causing a compile error.
+// ignore: import_internal_library
+import 'dart:_internal' as internal;
+
+/// Given an [Iterable], invokes [extract], passing the [iterable]'s type
+/// argument as the type argument to the generic function.
+///
+/// Example:
+///
+/// ```dart
+/// Object iterable = <int>[];
+/// print(extractIterableTypeArgument(iterable, <T>() => new Set<T>());
+/// // Prints "Instance of 'Set<int>'".
+/// ```
+Object extractIterableTypeArgument(
+        Iterable iterable, Object Function<T>() extract) =>
+    internal.extractTypeArguments<Iterable>(iterable, extract);
+
+/// Given a [Map], invokes [extract], passing the [map]'s key and value type
+/// arguments as the type arguments to the generic function.
+///
+/// Example:
+///
+/// ```dart
+/// class Two<A, B> {}
+///
+/// main() {
+///   Object map = <String, int>{};
+///   print(extractMapTypeArguments(map, <K, V>() => new Two<K, V>());
+///   // Prints "Instance of 'Two<String, int>'".
+/// }
+/// ```
+Object extractMapTypeArguments(Map map, Object Function<K, V>() extract) =>
+    internal.extractTypeArguments<Map>(map, extract);
diff --git a/packages/dart_internal/pubspec.yaml b/packages/dart_internal/pubspec.yaml
new file mode 100644
index 0000000..945aaa1
--- /dev/null
+++ b/packages/dart_internal/pubspec.yaml
@@ -0,0 +1,19 @@
+name: dart_internal
+version: 0.1.1
+author: "Dart Team <misc@dartlang.org>"
+homepage: http://www.dartlang.org
+description: >
+  This package is not intended for wide use. It provides a temporary API to
+  solve the problem: "Given an object some generic type A, how do I construct an
+  instance of generic type B with the same type argument(s)?"
+
+  This is necessary in a few rare places in order to migrate existing code to
+  Dart 2's stronger type system. Eventually, the hope is to have direct
+  language support for solving this problem but we don't have time to get that
+  into 2.0, so this package is provided as a temporary workaround.
+
+  We will very likely remove support for this in a later version of Dart.
+environment:
+  # Restrict the upper bound so that we can remove support for this in a later
+  # version of the SDK without it being a breaking change.
+  sdk: ">=2.0.0-dev.12.0 <2.1.0"
diff --git a/packages/matcher/.gitignore b/packages/matcher/.gitignore
index 7dbf035..ab3cb76 100644
--- a/packages/matcher/.gitignore
+++ b/packages/matcher/.gitignore
@@ -1,5 +1,6 @@
 # Don’t commit the following directories created by pub.
 .buildlog
+.dart_tool/
 .pub/
 build/
 packages
@@ -12,4 +13,4 @@
 *.js.map
 
 # Include when developing application packages.
-pubspec.lock
\ No newline at end of file
+pubspec.lock
diff --git a/packages/matcher/.travis.yml b/packages/matcher/.travis.yml
index aa48444..3d7f554 100644
--- a/packages/matcher/.travis.yml
+++ b/packages/matcher/.travis.yml
@@ -1,7 +1,6 @@
 language: dart
 
 dart:
-  - stable
   - dev
 
 dart_task:
@@ -9,11 +8,7 @@
     xvfb: false
   - test: -p firefox
   - dartanalyzer
-
-matrix:
-  include:
-  - dart: stable
-    dart_task: dartfmt
+  - dartfmt
 
 # Only building master means that we don't run two builds for each pull request.
 branches:
diff --git a/packages/matcher/CHANGELOG.md b/packages/matcher/CHANGELOG.md
index a5ebcfd..04e3ba0 100644
--- a/packages/matcher/CHANGELOG.md
+++ b/packages/matcher/CHANGELOG.md
@@ -1,3 +1,35 @@
+## 0.12.3+1
+
+- Set max SDK version to <3.0.0, and adjusted other dependencies.
+
+## 0.12.3
+
+- Many improvements to `TypeMatcher`
+  - Can now be used directly as `const TypeMatcher<MyType>()`.
+  - Added a type parameter to specify the target `Type`. 
+    - Made the `name` constructor parameter optional and marked it deprecated.
+      It's redundant to the type parameter.
+  - Migrated all `isType` matchers to `TypeMatcher`.
+  - Added a `having` function that allows chained validations of specific
+    features of the target type.
+
+    ```dart
+    /// Validates that the object is a [RangeError] with a message containing
+    /// the string 'details' and `start` and `end` properties that are `null`.
+    final _rangeMatcher = isRangeError
+       .having((e) => e.message, 'message', contains('details'))
+       .having((e) => e.start, 'start', isNull)
+       .having((e) => e.end, 'end', isNull);
+    ```
+
+- Deprecated the `isInstanceOf` class. Use `TypeMatcher` instead.
+
+- Improved the output of `Matcher` instances that fail due to type errors.
+
+## 0.12.2+1
+
+- Updated SDK version to 2.0.0-dev.17.0
+
 ## 0.12.2
 
 * Fixed `unorderedMatches` in cases where the matchers may match more than one
diff --git a/packages/matcher/analysis_options.yaml b/packages/matcher/analysis_options.yaml
index a10d4c5..bcff04b 100644
--- a/packages/matcher/analysis_options.yaml
+++ b/packages/matcher/analysis_options.yaml
@@ -1,2 +1,77 @@
 analyzer:
-  strong-mode: true
+  errors:
+    dead_code: error
+    override_on_non_overriding_method: error
+    unused_element: error
+    unused_import: error
+    unused_local_variable: error
+linter:
+  rules:
+    - always_declare_return_types
+    #- annotate_overrides
+    - avoid_empty_else
+    - avoid_function_literals_in_foreach_calls
+    - avoid_init_to_null
+    - avoid_null_checks_in_equality_operators
+    - avoid_renaming_method_parameters
+    - avoid_return_types_on_setters
+    - avoid_returning_null
+    - avoid_types_as_parameter_names
+    - avoid_unused_constructor_parameters
+    - await_only_futures
+    - camel_case_types
+    - cancel_subscriptions
+    #- cascade_invocations
+    - comment_references
+    - constant_identifier_names
+    - control_flow_in_finally
+    - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
+    - empty_statements
+    - hash_and_equals
+    - implementation_imports
+    - invariant_booleans
+    - iterable_contains_unrelated_type
+    - library_names
+    - library_prefixes
+    - list_remove_unrelated_type
+    - literal_only_boolean_expressions
+    - no_adjacent_strings_in_list
+    - no_duplicate_case_values
+    - non_constant_identifier_names
+    - omit_local_variable_types
+    - only_throw_errors
+    - overridden_fields
+    #- package_api_docs
+    - package_names
+    - package_prefixed_library_names
+    - prefer_adjacent_string_concatenation
+    - prefer_collection_literals
+    - prefer_conditional_assignment
+    - prefer_const_constructors
+    - prefer_contains
+    - prefer_equal_for_default_values
+    - prefer_final_fields
+    - prefer_initializing_formals
+    #- prefer_interpolation_to_compose_strings
+    - prefer_is_empty
+    - prefer_is_not_empty
+    #- prefer_single_quotes
+    - prefer_typing_uninitialized_variables
+    - recursive_getters
+    - slash_for_doc_comments
+    - super_goes_last
+    - test_types_in_equals
+    - throw_in_finally
+    - type_init_formals
+    - unawaited_futures
+    - unnecessary_brace_in_string_interps
+    - unnecessary_getters_setters
+    - unnecessary_lambdas
+    - unnecessary_null_aware_assignments
+    - unnecessary_statements
+    - unnecessary_this
+    - unrelated_type_equality_checks
+    - use_rethrow_when_possible
+    - valid_regexps
diff --git a/packages/matcher/lib/matcher.dart b/packages/matcher/lib/matcher.dart
index b4b9d22..72918aa 100644
--- a/packages/matcher/lib/matcher.dart
+++ b/packages/matcher/lib/matcher.dart
@@ -4,7 +4,9 @@
 
 /// Support for specifying test expectations, such as for unit tests.
 export 'src/core_matchers.dart';
+export 'src/custom_matcher.dart';
 export 'src/description.dart';
+export 'src/equals_matcher.dart';
 export 'src/error_matchers.dart';
 export 'src/interfaces.dart';
 export 'src/iterable_matchers.dart';
@@ -13,4 +15,5 @@
 export 'src/operator_matchers.dart';
 export 'src/order_matchers.dart';
 export 'src/string_matchers.dart';
+export 'src/type_matcher.dart';
 export 'src/util.dart';
diff --git a/packages/matcher/lib/mirror_matchers.dart b/packages/matcher/lib/mirror_matchers.dart
index 0ce90da..eb2225a 100644
--- a/packages/matcher/lib/mirror_matchers.dart
+++ b/packages/matcher/lib/mirror_matchers.dart
@@ -29,8 +29,8 @@
       addStateInfo(matchState, {'reason': 'has no property named "$_name"'});
       return false;
     }
-    bool isInstanceField = candidate is VariableMirror && !candidate.isStatic;
-    bool isInstanceGetter =
+    var isInstanceField = candidate is VariableMirror && !candidate.isStatic;
+    var isInstanceGetter =
         candidate is MethodMirror && candidate.isGetter && !candidate.isStatic;
     if (!(isInstanceField || isInstanceGetter)) {
       addStateInfo(matchState, {
@@ -60,14 +60,14 @@
       item, Description mismatchDescription, Map matchState, bool verbose) {
     var reason = matchState == null ? null : matchState['reason'];
     if (reason != null) {
-      mismatchDescription.add(reason);
+      mismatchDescription.add(reason as String);
     } else {
       mismatchDescription
           .add('has property "$_name" with value ')
           .addDescriptionOf(matchState['value']);
       var innerDescription = new StringDescription();
-      _matcher.describeMismatch(
-          matchState['value'], innerDescription, matchState['state'], verbose);
+      _matcher.describeMismatch(matchState['value'], innerDescription,
+          matchState['state'] as Map, verbose);
       if (innerDescription.length > 0) {
         mismatchDescription.add(' which ').add(innerDescription.toString());
       }
diff --git a/packages/matcher/lib/src/core_matchers.dart b/packages/matcher/lib/src/core_matchers.dart
index e8fdb11..73570e3 100644
--- a/packages/matcher/lib/src/core_matchers.dart
+++ b/packages/matcher/lib/src/core_matchers.dart
@@ -2,10 +2,9 @@
 // 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:stack_trace/stack_trace.dart';
-
-import 'description.dart';
+import 'feature_matcher.dart';
 import 'interfaces.dart';
+import 'type_matcher.dart';
 import 'util.dart';
 
 /// Returns a matcher that matches the isEmpty property.
@@ -72,15 +71,17 @@
 /// A matcher that matches any non-NaN value.
 const Matcher isNotNaN = const _IsNotNaN();
 
-class _IsNaN extends Matcher {
+class _IsNaN extends FeatureMatcher<num> {
   const _IsNaN();
-  bool matches(item, Map matchState) => double.NAN.compareTo(item) == 0;
+  bool typedMatches(num item, Map matchState) =>
+      double.nan.compareTo(item) == 0;
   Description describe(Description description) => description.add('NaN');
 }
 
-class _IsNotNaN extends Matcher {
+class _IsNotNaN extends FeatureMatcher<num> {
   const _IsNotNaN();
-  bool matches(item, Map matchState) => double.NAN.compareTo(item) != 0;
+  bool typedMatches(num item, Map matchState) =>
+      double.nan.compareTo(item) != 0;
   Description describe(Description description) => description.add('not NaN');
 }
 
@@ -89,7 +90,7 @@
 Matcher same(expected) => new _IsSameAs(expected);
 
 class _IsSameAs extends Matcher {
-  final _expected;
+  final Object _expected;
   const _IsSameAs(this._expected);
   bool matches(item, Map matchState) => identical(item, _expected);
   // If all types were hashable we could show a hash here.
@@ -97,271 +98,6 @@
       description.add('same instance as ').addDescriptionOf(_expected);
 }
 
-/// Returns a matcher that matches if the value is structurally equal to
-/// [expected].
-///
-/// If [expected] is a [Matcher], then it matches using that. Otherwise it tests
-/// for equality using `==` on the expected value.
-///
-/// For [Iterable]s and [Map]s, this will recursively match the elements. To
-/// handle cyclic structures a recursion depth [limit] can be provided. The
-/// default limit is 100. [Set]s will be compared order-independently.
-Matcher equals(expected, [int limit = 100]) => expected is String
-    ? new _StringEqualsMatcher(expected)
-    : new _DeepMatcher(expected, limit);
-
-typedef _RecursiveMatcher = List<String> Function(
-    dynamic, dynamic, String, int);
-
-class _DeepMatcher extends Matcher {
-  final _expected;
-  final int _limit;
-
-  _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit;
-
-  // Returns a pair (reason, location)
-  List<String> _compareIterables(Iterable expected, Object actual,
-      _RecursiveMatcher matcher, int depth, String location) {
-    if (actual is Iterable) {
-      var expectedIterator = expected.iterator;
-      var actualIterator = actual.iterator;
-      for (var index = 0;; index++) {
-        // Advance in lockstep.
-        var expectedNext = expectedIterator.moveNext();
-        var actualNext = actualIterator.moveNext();
-
-        // If we reached the end of both, we succeeded.
-        if (!expectedNext && !actualNext) return null;
-
-        // Fail if their lengths are different.
-        var newLocation = '$location[$index]';
-        if (!expectedNext) return ['longer than expected', newLocation];
-        if (!actualNext) return ['shorter than expected', newLocation];
-
-        // Match the elements.
-        var rp = matcher(expectedIterator.current, actualIterator.current,
-            newLocation, depth);
-        if (rp != null) return rp;
-      }
-    } else {
-      return ['is not Iterable', location];
-    }
-  }
-
-  List<String> _compareSets(Set expected, Object actual,
-      _RecursiveMatcher matcher, int depth, String location) {
-    if (actual is Iterable) {
-      Set other = actual.toSet();
-
-      for (var expectedElement in expected) {
-        if (other.every((actualElement) =>
-            matcher(expectedElement, actualElement, location, depth) != null)) {
-          return ['does not contain $expectedElement', location];
-        }
-      }
-
-      if (other.length > expected.length) {
-        return ['larger than expected', location];
-      } else if (other.length < expected.length) {
-        return ['smaller than expected', location];
-      } else {
-        return null;
-      }
-    } else {
-      return ['is not Iterable', location];
-    }
-  }
-
-  List<String> _recursiveMatch(
-      Object expected, Object actual, String location, int depth) {
-    // If the expected value is a matcher, try to match it.
-    if (expected is Matcher) {
-      var matchState = {};
-      if (expected.matches(actual, matchState)) return null;
-
-      var description = new StringDescription();
-      expected.describe(description);
-      return ['does not match $description', location];
-    } else {
-      // Otherwise, test for equality.
-      try {
-        if (expected == actual) return null;
-      } catch (e) {
-        // TODO(gram): Add a test for this case.
-        return ['== threw "$e"', location];
-      }
-    }
-
-    if (depth > _limit) return ['recursion depth limit exceeded', location];
-
-    // If _limit is 1 we can only recurse one level into object.
-    if (depth == 0 || _limit > 1) {
-      if (expected is Set) {
-        return _compareSets(
-            expected, actual, _recursiveMatch, depth + 1, location);
-      } else if (expected is Iterable) {
-        return _compareIterables(
-            expected, actual, _recursiveMatch, depth + 1, location);
-      } else if (expected is Map) {
-        if (actual is! Map) return ['expected a map', location];
-        var map = (actual as Map);
-        var err =
-            (expected.length == map.length) ? '' : 'has different length and ';
-        for (var key in expected.keys) {
-          if (!map.containsKey(key)) {
-            return ["${err}is missing map key '$key'", location];
-          }
-        }
-
-        for (var key in map.keys) {
-          if (!expected.containsKey(key)) {
-            return ["${err}has extra map key '$key'", location];
-          }
-        }
-
-        for (var key in expected.keys) {
-          var rp = _recursiveMatch(
-              expected[key], map[key], "$location['$key']", depth + 1);
-          if (rp != null) return rp;
-        }
-
-        return null;
-      }
-    }
-
-    var description = new StringDescription();
-
-    // If we have recursed, show the expected value too; if not, expect() will
-    // show it for us.
-    if (depth > 0) {
-      description
-          .add('was ')
-          .addDescriptionOf(actual)
-          .add(' instead of ')
-          .addDescriptionOf(expected);
-      return [description.toString(), location];
-    }
-
-    // We're not adding any value to the actual value.
-    return ["", location];
-  }
-
-  String _match(expected, actual, Map matchState) {
-    var rp = _recursiveMatch(expected, actual, '', 0);
-    if (rp == null) return null;
-    String reason;
-    if (rp[0].length > 0) {
-      if (rp[1].length > 0) {
-        reason = "${rp[0]} at location ${rp[1]}";
-      } else {
-        reason = rp[0];
-      }
-    } else {
-      reason = '';
-    }
-    // Cache the failure reason in the matchState.
-    addStateInfo(matchState, {'reason': reason});
-    return reason;
-  }
-
-  bool matches(item, Map matchState) =>
-      _match(_expected, item, matchState) == null;
-
-  Description describe(Description description) =>
-      description.addDescriptionOf(_expected);
-
-  Description describeMismatch(
-      item, Description mismatchDescription, Map matchState, bool verbose) {
-    var reason = matchState['reason'] ?? '';
-    // If we didn't get a good reason, that would normally be a
-    // simple 'is <value>' message. We only add that if the mismatch
-    // description is non empty (so we are supplementing the mismatch
-    // description).
-    if (reason.length == 0 && mismatchDescription.length > 0) {
-      mismatchDescription.add('is ').addDescriptionOf(item);
-    } else {
-      mismatchDescription.add(reason);
-    }
-    return mismatchDescription;
-  }
-}
-
-/// A special equality matcher for strings.
-class _StringEqualsMatcher extends Matcher {
-  final String _value;
-
-  _StringEqualsMatcher(this._value);
-
-  bool get showActualValue => true;
-
-  bool matches(item, Map matchState) => _value == item;
-
-  Description describe(Description description) =>
-      description.addDescriptionOf(_value);
-
-  Description describeMismatch(
-      item, Description mismatchDescription, Map matchState, bool verbose) {
-    if (item is! String) {
-      return mismatchDescription.addDescriptionOf(item).add('is not a string');
-    } else {
-      var buff = new StringBuffer();
-      buff.write('is different.');
-      var escapedItem = escape(item);
-      var escapedValue = escape(_value);
-      int minLength = escapedItem.length < escapedValue.length
-          ? escapedItem.length
-          : escapedValue.length;
-      var start = 0;
-      for (; start < minLength; start++) {
-        if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) {
-          break;
-        }
-      }
-      if (start == minLength) {
-        if (escapedValue.length < escapedItem.length) {
-          buff.write(' Both strings start the same, but the actual value also'
-              ' has the following trailing characters: ');
-          _writeTrailing(buff, escapedItem, escapedValue.length);
-        } else {
-          buff.write(' Both strings start the same, but the actual value is'
-              ' missing the following trailing characters: ');
-          _writeTrailing(buff, escapedValue, escapedItem.length);
-        }
-      } else {
-        buff.write('\nExpected: ');
-        _writeLeading(buff, escapedValue, start);
-        _writeTrailing(buff, escapedValue, start);
-        buff.write('\n  Actual: ');
-        _writeLeading(buff, escapedItem, start);
-        _writeTrailing(buff, escapedItem, start);
-        buff.write('\n          ');
-        for (int i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' ');
-        buff.write('^\n Differ at offset $start');
-      }
-
-      return mismatchDescription.add(buff.toString());
-    }
-  }
-
-  static void _writeLeading(StringBuffer buff, String s, int start) {
-    if (start > 10) {
-      buff.write('... ');
-      buff.write(s.substring(start - 10, start));
-    } else {
-      buff.write(s.substring(0, start));
-    }
-  }
-
-  static void _writeTrailing(StringBuffer buff, String s, int start) {
-    if (start + 10 > s.length) {
-      buff.write(s.substring(start));
-    } else {
-      buff.write(s.substring(start, start + 10));
-      buff.write(' ...');
-    }
-  }
-}
-
 /// A matcher that matches any value.
 const Matcher anything = const _IsAnything();
 
@@ -371,23 +107,14 @@
   Description describe(Description description) => description.add('anything');
 }
 
+/// **DEPRECATED** Use [TypeMatcher] instead.
+///
 /// Returns a matcher that matches if an object is an instance
 /// of [T] (or a subtype).
-///
-/// As types are not first class objects in Dart we can only
-/// approximate this test by using a generic wrapper class.
-///
-/// For example, to test whether 'bar' is an instance of type
-/// 'Foo', we would write:
-///
-///     expect(bar, new isInstanceOf<Foo>());
-class isInstanceOf<T> extends Matcher {
+@Deprecated('Use `const TypeMatcher<MyType>()` instead.')
+// ignore: camel_case_types
+class isInstanceOf<T> extends TypeMatcher<T> {
   const isInstanceOf();
-
-  bool matches(obj, Map matchState) => obj is T;
-
-  Description describe(Description description) =>
-      description.add('an instance of $T');
 }
 
 /// A matcher that matches a function call against no exception.
@@ -398,10 +125,10 @@
 /// a wrapper will have to be created.
 const Matcher returnsNormally = const _ReturnsNormally();
 
-class _ReturnsNormally extends Matcher {
+class _ReturnsNormally extends FeatureMatcher<Function> {
   const _ReturnsNormally();
 
-  bool matches(f, Map matchState) {
+  bool typedMatches(Function f, Map matchState) {
     try {
       f();
       return true;
@@ -414,8 +141,8 @@
   Description describe(Description description) =>
       description.add("return normally");
 
-  Description describeMismatch(
-      item, Description mismatchDescription, Map matchState, bool verbose) {
+  Description describeTypedMismatch(Function item,
+      Description mismatchDescription, Map matchState, bool verbose) {
     mismatchDescription.add('threw ').addDescriptionOf(matchState['exception']);
     if (verbose) {
       mismatchDescription.add(' at ').add(matchState['stack'].toString());
@@ -424,48 +151,11 @@
   }
 }
 
-/*
- * Matchers for different exception types. Ideally we should just be able to
- * use something like:
- *
- * final Matcher throwsException =
- *     const _Throws(const isInstanceOf<Exception>());
- *
- * Unfortunately instanceOf is not working with dart2js.
- *
- * Alternatively, if static functions could be used in const expressions,
- * we could use:
- *
- * bool _isException(x) => x is Exception;
- * final Matcher isException = const _Predicate(_isException, "Exception");
- * final Matcher throwsException = const _Throws(isException);
- *
- * But currently using static functions in const expressions is not supported.
- * For now the only solution for all platforms seems to be separate classes
- * for each exception type.
- */
+/// A matcher for [Map].
+const isMap = const TypeMatcher<Map>();
 
-abstract class TypeMatcher extends Matcher {
-  final String _name;
-  const TypeMatcher(this._name);
-  Description describe(Description description) => description.add(_name);
-}
-
-/// A matcher for Map types.
-const Matcher isMap = const _IsMap();
-
-class _IsMap extends TypeMatcher {
-  const _IsMap() : super("Map");
-  bool matches(item, Map matchState) => item is Map;
-}
-
-/// A matcher for List types.
-const Matcher isList = const _IsList();
-
-class _IsList extends TypeMatcher {
-  const _IsList() : super("List");
-  bool matches(item, Map matchState) => item is List;
-}
+/// A matcher for [List].
+const isList = const TypeMatcher<List>();
 
 /// Returns a matcher that matches if an object has a length property
 /// that matches [matcher].
@@ -473,7 +163,7 @@
 
 class _HasLength extends Matcher {
   final Matcher _matcher;
-  const _HasLength([Matcher matcher = null]) : this._matcher = matcher;
+  const _HasLength([Matcher matcher]) : this._matcher = matcher;
 
   bool matches(item, Map matchState) {
     try {
@@ -482,8 +172,10 @@
       if (item.length * item.length >= 0) {
         return _matcher.matches(item.length, matchState);
       }
-    } catch (e) {}
-    return false;
+    } catch (e) {
+      return false;
+    }
+    throw new UnsupportedError('Should never get here');
   }
 
   Description describe(Description description) =>
@@ -499,8 +191,10 @@
             .add('has length of ')
             .addDescriptionOf(item.length);
       }
-    } catch (e) {}
-    return mismatchDescription.add('has no length property');
+    } catch (e) {
+      return mismatchDescription.add('has no length property');
+    }
+    throw new UnsupportedError('Should never get here');
   }
 }
 
@@ -514,16 +208,17 @@
 Matcher contains(expected) => new _Contains(expected);
 
 class _Contains extends Matcher {
-  final _expected;
+  final Object _expected;
 
   const _Contains(this._expected);
 
   bool matches(item, Map matchState) {
+    var expected = _expected;
     if (item is String) {
-      return item.indexOf(_expected) >= 0;
+      return expected is Pattern && item.contains(expected);
     } else if (item is Iterable) {
-      if (_expected is Matcher) {
-        return item.any((e) => _expected.matches(e, matchState));
+      if (expected is Matcher) {
+        return item.any((e) => expected.matches(e, matchState));
       } else {
         return item.contains(_expected);
       }
@@ -549,26 +244,29 @@
 
 /// Returns a matcher that matches if the match argument is in
 /// the expected value. This is the converse of [contains].
-Matcher isIn(expected) => new _In(expected);
-
-class _In extends Matcher {
-  final _expected;
-
-  const _In(this._expected);
-
-  bool matches(item, Map matchState) {
-    if (_expected is String) {
-      return _expected.indexOf(item) >= 0;
-    } else if (_expected is Iterable) {
-      return _expected.any((e) => e == item);
-    } else if (_expected is Map) {
-      return _expected.containsKey(item);
-    }
-    return false;
+Matcher isIn(expected) {
+  if (expected is Iterable) {
+    return new _In(expected, expected.contains);
+  } else if (expected is String) {
+    return new _In<Pattern>(expected, expected.contains);
+  } else if (expected is Map) {
+    return new _In(expected, expected.containsKey);
   }
 
+  throw new ArgumentError.value(
+      expected, 'expected', 'Only Iterable, Map, and String are supported.');
+}
+
+class _In<T> extends FeatureMatcher<T> {
+  final Object _source;
+  final bool Function(T) _containsFunction;
+
+  const _In(this._source, this._containsFunction);
+
+  bool typedMatches(T item, Map matchState) => _containsFunction(item);
+
   Description describe(Description description) =>
-      description.add('is in ').addDescriptionOf(_expected);
+      description.add('is in ').addDescriptionOf(_source);
 }
 
 /// Returns a matcher that uses an arbitrary function that returns
@@ -583,99 +281,14 @@
 
 typedef bool _PredicateFunction<T>(T value);
 
-class _Predicate<T> extends Matcher {
+class _Predicate<T> extends FeatureMatcher<T> {
   final _PredicateFunction<T> _matcher;
   final String _description;
 
   _Predicate(this._matcher, this._description);
 
-  bool matches(item, Map matchState) => _matcher(item as T);
+  bool typedMatches(T item, Map matchState) => _matcher(item);
 
   Description describe(Description description) =>
       description.add(_description);
 }
-
-/// A useful utility class for implementing other matchers through inheritance.
-/// Derived classes should call the base constructor with a feature name and
-/// description, and an instance matcher, and should implement the
-/// [featureValueOf] abstract method.
-///
-/// The feature description will typically describe the item and the feature,
-/// while the feature name will just name the feature. For example, we may
-/// have a Widget class where each Widget has a price; we could make a
-/// [CustomMatcher] that can make assertions about prices with:
-///
-/// ```dart
-/// class HasPrice extends CustomMatcher {
-///   HasPrice(matcher) : super("Widget with price that is", "price", matcher);
-///   featureValueOf(actual) => actual.price;
-/// }
-/// ```
-///
-/// and then use this for example like:
-///
-/// ```dart
-/// expect(inventoryItem, new HasPrice(greaterThan(0)));
-/// ```
-class CustomMatcher extends Matcher {
-  final String _featureDescription;
-  final String _featureName;
-  final Matcher _matcher;
-
-  CustomMatcher(this._featureDescription, this._featureName, matcher)
-      : this._matcher = wrapMatcher(matcher);
-
-  /// Override this to extract the interesting feature.
-  featureValueOf(actual) => actual;
-
-  bool matches(item, Map matchState) {
-    try {
-      var f = featureValueOf(item);
-      if (_matcher.matches(f, matchState)) return true;
-      addStateInfo(matchState, {'custom.feature': f});
-    } catch (exception, stack) {
-      addStateInfo(matchState, {
-        'custom.exception': exception.toString(),
-        'custom.stack': new Chain.forTrace(stack)
-            .foldFrames(
-                (frame) =>
-                    frame.package == 'test' ||
-                    frame.package == 'stream_channel' ||
-                    frame.package == 'matcher',
-                terse: true)
-            .toString()
-      });
-    }
-    return false;
-  }
-
-  Description describe(Description description) =>
-      description.add(_featureDescription).add(' ').addDescriptionOf(_matcher);
-
-  Description describeMismatch(
-      item, Description mismatchDescription, Map matchState, bool verbose) {
-    if (matchState['custom.exception'] != null) {
-      mismatchDescription
-          .add('threw ')
-          .addDescriptionOf(matchState['custom.exception'])
-          .add('\n')
-          .add(matchState['custom.stack'].toString());
-      return mismatchDescription;
-    }
-
-    mismatchDescription
-        .add('has ')
-        .add(_featureName)
-        .add(' with value ')
-        .addDescriptionOf(matchState['custom.feature']);
-    var innerDescription = new StringDescription();
-
-    _matcher.describeMismatch(matchState['custom.feature'], innerDescription,
-        matchState['state'], verbose);
-
-    if (innerDescription.length > 0) {
-      mismatchDescription.add(' which ').add(innerDescription.toString());
-    }
-    return mismatchDescription;
-  }
-}
diff --git a/packages/matcher/lib/src/custom_matcher.dart b/packages/matcher/lib/src/custom_matcher.dart
new file mode 100644
index 0000000..862d133
--- /dev/null
+++ b/packages/matcher/lib/src/custom_matcher.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2012, 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:stack_trace/stack_trace.dart';
+
+import 'description.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// A useful utility class for implementing other matchers through inheritance.
+/// Derived classes should call the base constructor with a feature name and
+/// description, and an instance matcher, and should implement the
+/// [featureValueOf] abstract method.
+///
+/// The feature description will typically describe the item and the feature,
+/// while the feature name will just name the feature. For example, we may
+/// have a Widget class where each Widget has a price; we could make a
+/// [CustomMatcher] that can make assertions about prices with:
+///
+/// ```dart
+/// class HasPrice extends CustomMatcher {
+///   HasPrice(matcher) : super("Widget with price that is", "price", matcher);
+///   featureValueOf(actual) => actual.price;
+/// }
+/// ```
+///
+/// and then use this for example like:
+///
+/// ```dart
+/// expect(inventoryItem, new HasPrice(greaterThan(0)));
+/// ```
+class CustomMatcher extends Matcher {
+  final String _featureDescription;
+  final String _featureName;
+  final Matcher _matcher;
+
+  CustomMatcher(this._featureDescription, this._featureName, matcher)
+      : this._matcher = wrapMatcher(matcher);
+
+  /// Override this to extract the interesting feature.
+  Object featureValueOf(actual) => actual;
+
+  bool matches(item, Map matchState) {
+    try {
+      var f = featureValueOf(item);
+      if (_matcher.matches(f, matchState)) return true;
+      addStateInfo(matchState, {'custom.feature': f});
+    } catch (exception, stack) {
+      addStateInfo(matchState, {
+        'custom.exception': exception.toString(),
+        'custom.stack': new Chain.forTrace(stack)
+            .foldFrames(
+                (frame) =>
+                    frame.package == 'test' ||
+                    frame.package == 'stream_channel' ||
+                    frame.package == 'matcher',
+                terse: true)
+            .toString()
+      });
+    }
+    return false;
+  }
+
+  Description describe(Description description) =>
+      description.add(_featureDescription).add(' ').addDescriptionOf(_matcher);
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (matchState['custom.exception'] != null) {
+      mismatchDescription
+          .add('threw ')
+          .addDescriptionOf(matchState['custom.exception'])
+          .add('\n')
+          .add(matchState['custom.stack'].toString());
+      return mismatchDescription;
+    }
+
+    mismatchDescription
+        .add('has ')
+        .add(_featureName)
+        .add(' with value ')
+        .addDescriptionOf(matchState['custom.feature']);
+    var innerDescription = new StringDescription();
+
+    _matcher.describeMismatch(matchState['custom.feature'], innerDescription,
+        matchState['state'] as Map, verbose);
+
+    if (innerDescription.length > 0) {
+      mismatchDescription.add(' which ').add(innerDescription.toString());
+    }
+    return mismatchDescription;
+  }
+}
diff --git a/packages/matcher/lib/src/equals_matcher.dart b/packages/matcher/lib/src/equals_matcher.dart
new file mode 100644
index 0000000..31e369b
--- /dev/null
+++ b/packages/matcher/lib/src/equals_matcher.dart
@@ -0,0 +1,267 @@
+// Copyright (c) 2018, 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 'description.dart';
+import 'feature_matcher.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a matcher that matches if the value is structurally equal to
+/// [expected].
+///
+/// If [expected] is a [Matcher], then it matches using that. Otherwise it tests
+/// for equality using `==` on the expected value.
+///
+/// For [Iterable]s and [Map]s, this will recursively match the elements. To
+/// handle cyclic structures a recursion depth [limit] can be provided. The
+/// default limit is 100. [Set]s will be compared order-independently.
+Matcher equals(expected, [int limit = 100]) => expected is String
+    ? new _StringEqualsMatcher(expected)
+    : new _DeepMatcher(expected, limit);
+
+typedef _RecursiveMatcher = List<String> Function(
+    dynamic, dynamic, String, int);
+
+/// A special equality matcher for strings.
+class _StringEqualsMatcher extends FeatureMatcher<String> {
+  final String _value;
+
+  _StringEqualsMatcher(this._value);
+
+  bool typedMatches(String item, Map matchState) => _value == item;
+
+  Description describe(Description description) =>
+      description.addDescriptionOf(_value);
+
+  Description describeTypedMismatch(String item,
+      Description mismatchDescription, Map matchState, bool verbose) {
+    var buff = new StringBuffer();
+    buff.write('is different.');
+    var escapedItem = escape(item);
+    var escapedValue = escape(_value);
+    var minLength = escapedItem.length < escapedValue.length
+        ? escapedItem.length
+        : escapedValue.length;
+    var start = 0;
+    for (; start < minLength; start++) {
+      if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) {
+        break;
+      }
+    }
+    if (start == minLength) {
+      if (escapedValue.length < escapedItem.length) {
+        buff.write(' Both strings start the same, but the actual value also'
+            ' has the following trailing characters: ');
+        _writeTrailing(buff, escapedItem, escapedValue.length);
+      } else {
+        buff.write(' Both strings start the same, but the actual value is'
+            ' missing the following trailing characters: ');
+        _writeTrailing(buff, escapedValue, escapedItem.length);
+      }
+    } else {
+      buff.write('\nExpected: ');
+      _writeLeading(buff, escapedValue, start);
+      _writeTrailing(buff, escapedValue, start);
+      buff.write('\n  Actual: ');
+      _writeLeading(buff, escapedItem, start);
+      _writeTrailing(buff, escapedItem, start);
+      buff.write('\n          ');
+      for (var i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' ');
+      buff.write('^\n Differ at offset $start');
+    }
+
+    return mismatchDescription.add(buff.toString());
+  }
+
+  static void _writeLeading(StringBuffer buff, String s, int start) {
+    if (start > 10) {
+      buff.write('... ');
+      buff.write(s.substring(start - 10, start));
+    } else {
+      buff.write(s.substring(0, start));
+    }
+  }
+
+  static void _writeTrailing(StringBuffer buff, String s, int start) {
+    if (start + 10 > s.length) {
+      buff.write(s.substring(start));
+    } else {
+      buff.write(s.substring(start, start + 10));
+      buff.write(' ...');
+    }
+  }
+}
+
+class _DeepMatcher extends Matcher {
+  final Object _expected;
+  final int _limit;
+
+  _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit;
+
+  // Returns a pair (reason, location)
+  List<String> _compareIterables(Iterable expected, Object actual,
+      _RecursiveMatcher matcher, int depth, String location) {
+    if (actual is Iterable) {
+      var expectedIterator = expected.iterator;
+      var actualIterator = actual.iterator;
+      for (var index = 0;; index++) {
+        // Advance in lockstep.
+        var expectedNext = expectedIterator.moveNext();
+        var actualNext = actualIterator.moveNext();
+
+        // If we reached the end of both, we succeeded.
+        if (!expectedNext && !actualNext) return null;
+
+        // Fail if their lengths are different.
+        var newLocation = '$location[$index]';
+        if (!expectedNext) return ['longer than expected', newLocation];
+        if (!actualNext) return ['shorter than expected', newLocation];
+
+        // Match the elements.
+        var rp = matcher(expectedIterator.current, actualIterator.current,
+            newLocation, depth);
+        if (rp != null) return rp;
+      }
+    } else {
+      return ['is not Iterable', location];
+    }
+  }
+
+  List<String> _compareSets(Set expected, Object actual,
+      _RecursiveMatcher matcher, int depth, String location) {
+    if (actual is Iterable) {
+      var other = actual.toSet();
+
+      for (var expectedElement in expected) {
+        if (other.every((actualElement) =>
+            matcher(expectedElement, actualElement, location, depth) != null)) {
+          return ['does not contain $expectedElement', location];
+        }
+      }
+
+      if (other.length > expected.length) {
+        return ['larger than expected', location];
+      } else if (other.length < expected.length) {
+        return ['smaller than expected', location];
+      } else {
+        return null;
+      }
+    } else {
+      return ['is not Iterable', location];
+    }
+  }
+
+  List<String> _recursiveMatch(
+      Object expected, Object actual, String location, int depth) {
+    // If the expected value is a matcher, try to match it.
+    if (expected is Matcher) {
+      var matchState = {};
+      if (expected.matches(actual, matchState)) return null;
+
+      var description = new StringDescription();
+      expected.describe(description);
+      return ['does not match $description', location];
+    } else {
+      // Otherwise, test for equality.
+      try {
+        if (expected == actual) return null;
+      } catch (e) {
+        // TODO(gram): Add a test for this case.
+        return ['== threw "$e"', location];
+      }
+    }
+
+    if (depth > _limit) return ['recursion depth limit exceeded', location];
+
+    // If _limit is 1 we can only recurse one level into object.
+    if (depth == 0 || _limit > 1) {
+      if (expected is Set) {
+        return _compareSets(
+            expected, actual, _recursiveMatch, depth + 1, location);
+      } else if (expected is Iterable) {
+        return _compareIterables(
+            expected, actual, _recursiveMatch, depth + 1, location);
+      } else if (expected is Map) {
+        if (actual is! Map) return ['expected a map', location];
+        var map = (actual as Map);
+        var err =
+            (expected.length == map.length) ? '' : 'has different length and ';
+        for (var key in expected.keys) {
+          if (!map.containsKey(key)) {
+            return ["${err}is missing map key '$key'", location];
+          }
+        }
+
+        for (var key in map.keys) {
+          if (!expected.containsKey(key)) {
+            return ["${err}has extra map key '$key'", location];
+          }
+        }
+
+        for (var key in expected.keys) {
+          var rp = _recursiveMatch(
+              expected[key], map[key], "$location['$key']", depth + 1);
+          if (rp != null) return rp;
+        }
+
+        return null;
+      }
+    }
+
+    var description = new StringDescription();
+
+    // If we have recursed, show the expected value too; if not, expect() will
+    // show it for us.
+    if (depth > 0) {
+      description
+          .add('was ')
+          .addDescriptionOf(actual)
+          .add(' instead of ')
+          .addDescriptionOf(expected);
+      return [description.toString(), location];
+    }
+
+    // We're not adding any value to the actual value.
+    return ["", location];
+  }
+
+  String _match(expected, actual, Map matchState) {
+    var rp = _recursiveMatch(expected, actual, '', 0);
+    if (rp == null) return null;
+    String reason;
+    if (rp[0].isNotEmpty) {
+      if (rp[1].isNotEmpty) {
+        reason = "${rp[0]} at location ${rp[1]}";
+      } else {
+        reason = rp[0];
+      }
+    } else {
+      reason = '';
+    }
+    // Cache the failure reason in the matchState.
+    addStateInfo(matchState, {'reason': reason});
+    return reason;
+  }
+
+  bool matches(item, Map matchState) =>
+      _match(_expected, item, matchState) == null;
+
+  Description describe(Description description) =>
+      description.addDescriptionOf(_expected);
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    var reason = matchState['reason'] as String ?? '';
+    // If we didn't get a good reason, that would normally be a
+    // simple 'is <value>' message. We only add that if the mismatch
+    // description is non empty (so we are supplementing the mismatch
+    // description).
+    if (reason.isEmpty && mismatchDescription.length > 0) {
+      mismatchDescription.add('is ').addDescriptionOf(item);
+    } else {
+      mismatchDescription.add(reason);
+    }
+    return mismatchDescription;
+  }
+}
diff --git a/packages/matcher/lib/src/error_matchers.dart b/packages/matcher/lib/src/error_matchers.dart
index 1f37538..eb185f4 100644
--- a/packages/matcher/lib/src/error_matchers.dart
+++ b/packages/matcher/lib/src/error_matchers.dart
@@ -2,94 +2,39 @@
 // 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 'core_matchers.dart';
-import 'interfaces.dart';
+import 'type_matcher.dart';
 
-/// A matcher for ArgumentErrors.
-const Matcher isArgumentError = const _ArgumentError();
+/// A matcher for [ArgumentError].
+const isArgumentError = const TypeMatcher<ArgumentError>();
 
-class _ArgumentError extends TypeMatcher {
-  const _ArgumentError() : super("ArgumentError");
-  bool matches(item, Map matchState) => item is ArgumentError;
-}
+/// A matcher for [ConcurrentModificationError].
+const isConcurrentModificationError =
+    const TypeMatcher<ConcurrentModificationError>();
 
-/// A matcher for ConcurrentModificationError.
-const Matcher isConcurrentModificationError =
-    const _ConcurrentModificationError();
+/// A matcher for [CyclicInitializationError].
+const isCyclicInitializationError =
+    const TypeMatcher<CyclicInitializationError>();
 
-class _ConcurrentModificationError extends TypeMatcher {
-  const _ConcurrentModificationError() : super("ConcurrentModificationError");
-  bool matches(item, Map matchState) => item is ConcurrentModificationError;
-}
+/// A matcher for [Exception].
+const isException = const TypeMatcher<Exception>();
 
-/// A matcher for CyclicInitializationError.
-const Matcher isCyclicInitializationError = const _CyclicInitializationError();
+/// A matcher for [FormatException].
+const isFormatException = const TypeMatcher<FormatException>();
 
-class _CyclicInitializationError extends TypeMatcher {
-  const _CyclicInitializationError() : super("CyclicInitializationError");
-  bool matches(item, Map matchState) => item is CyclicInitializationError;
-}
+/// A matcher for [NoSuchMethodError].
+const isNoSuchMethodError = const TypeMatcher<NoSuchMethodError>();
 
-/// A matcher for Exceptions.
-const Matcher isException = const _Exception();
+/// A matcher for [NullThrownError].
+const isNullThrownError = const TypeMatcher<NullThrownError>();
 
-class _Exception extends TypeMatcher {
-  const _Exception() : super("Exception");
-  bool matches(item, Map matchState) => item is Exception;
-}
+/// A matcher for [RangeError].
+const isRangeError = const TypeMatcher<RangeError>();
 
-/// A matcher for FormatExceptions.
-const Matcher isFormatException = const _FormatException();
+/// A matcher for [StateError].
+const isStateError = const TypeMatcher<StateError>();
 
-class _FormatException extends TypeMatcher {
-  const _FormatException() : super("FormatException");
-  bool matches(item, Map matchState) => item is FormatException;
-}
+/// A matcher for [UnimplementedError].
+const isUnimplementedError = const TypeMatcher<UnimplementedError>();
 
-/// A matcher for NoSuchMethodErrors.
-const Matcher isNoSuchMethodError = const _NoSuchMethodError();
-
-class _NoSuchMethodError extends TypeMatcher {
-  const _NoSuchMethodError() : super("NoSuchMethodError");
-  bool matches(item, Map matchState) => item is NoSuchMethodError;
-}
-
-/// A matcher for NullThrownError.
-const Matcher isNullThrownError = const _NullThrownError();
-
-class _NullThrownError extends TypeMatcher {
-  const _NullThrownError() : super("NullThrownError");
-  bool matches(item, Map matchState) => item is NullThrownError;
-}
-
-/// A matcher for RangeErrors.
-const Matcher isRangeError = const _RangeError();
-
-class _RangeError extends TypeMatcher {
-  const _RangeError() : super("RangeError");
-  bool matches(item, Map matchState) => item is RangeError;
-}
-
-/// A matcher for StateErrors.
-const Matcher isStateError = const _StateError();
-
-class _StateError extends TypeMatcher {
-  const _StateError() : super("StateError");
-  bool matches(item, Map matchState) => item is StateError;
-}
-
-/// A matcher for UnimplementedErrors.
-const Matcher isUnimplementedError = const _UnimplementedError();
-
-class _UnimplementedError extends TypeMatcher {
-  const _UnimplementedError() : super("UnimplementedError");
-  bool matches(item, Map matchState) => item is UnimplementedError;
-}
-
-/// A matcher for UnsupportedError.
-const Matcher isUnsupportedError = const _UnsupportedError();
-
-class _UnsupportedError extends TypeMatcher {
-  const _UnsupportedError() : super("UnsupportedError");
-  bool matches(item, Map matchState) => item is UnsupportedError;
-}
+/// A matcher for [UnsupportedError].
+const isUnsupportedError = const TypeMatcher<UnsupportedError>();
diff --git a/packages/matcher/lib/src/feature_matcher.dart b/packages/matcher/lib/src/feature_matcher.dart
new file mode 100644
index 0000000..520e441
--- /dev/null
+++ b/packages/matcher/lib/src/feature_matcher.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2018, 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 'interfaces.dart';
+import 'type_matcher.dart';
+
+/// A package-private [TypeMatcher] implementation that makes it easy for
+/// subclasses to validate aspects of specific types while providing consistent
+/// type checking.
+abstract class FeatureMatcher<T> extends TypeMatcher<T> {
+  const FeatureMatcher();
+
+  bool matches(item, Map matchState) =>
+      super.matches(item, matchState) && typedMatches(item, matchState);
+
+  bool typedMatches(T item, Map matchState);
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    if (item is T) {
+      return describeTypedMismatch(
+          item, mismatchDescription, matchState, verbose);
+    }
+
+    return super.describe(mismatchDescription.add('not an '));
+  }
+
+  Description describeTypedMismatch(T item, Description mismatchDescription,
+          Map matchState, bool verbose) =>
+      mismatchDescription;
+}
diff --git a/packages/matcher/lib/src/having_matcher.dart b/packages/matcher/lib/src/having_matcher.dart
new file mode 100644
index 0000000..1684a93
--- /dev/null
+++ b/packages/matcher/lib/src/having_matcher.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2018, 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 'custom_matcher.dart';
+import 'interfaces.dart';
+import 'type_matcher.dart';
+import 'util.dart';
+
+/// A package-private [TypeMatcher] implementation that handles is returned
+/// by calls to [TypeMatcher.having].
+class HavingMatcher<T> implements TypeMatcher<T> {
+  final TypeMatcher<T> _parent;
+  final List<_FunctionMatcher> _functionMatchers;
+
+  HavingMatcher(TypeMatcher<T> parent, String description,
+      Object feature(T source), Object matcher,
+      [Iterable<_FunctionMatcher> existing])
+      : this._parent = parent,
+        this._functionMatchers = <_FunctionMatcher>[]
+          ..addAll(existing ?? [])
+          ..add(new _FunctionMatcher<T>(description, feature, matcher));
+
+  TypeMatcher<T> having(
+          Object feature(T source), String description, Object matcher) =>
+      new HavingMatcher(
+          _parent, description, feature, matcher, _functionMatchers);
+
+  bool matches(item, Map matchState) {
+    for (var matcher in <Matcher>[_parent].followedBy(_functionMatchers)) {
+      if (!matcher.matches(item, matchState)) {
+        addStateInfo(matchState, {'matcher': matcher});
+        return false;
+      }
+    }
+    return true;
+  }
+
+  Description describeMismatch(
+      item, Description mismatchDescription, Map matchState, bool verbose) {
+    var matcher = matchState['matcher'] as Matcher;
+    matcher.describeMismatch(
+        item, mismatchDescription, matchState['state'] as Map, verbose);
+    return mismatchDescription;
+  }
+
+  Description describe(Description description) => description
+      .add('')
+      .addDescriptionOf(_parent)
+      .add(' with ')
+      .addAll('', ' and ', '', _functionMatchers);
+}
+
+class _FunctionMatcher<T> extends CustomMatcher {
+  final dynamic Function(T value) _feature;
+
+  _FunctionMatcher(String name, this._feature, matcher)
+      : super('`$name`:', '`$name`', matcher);
+
+  @override
+  Object featureValueOf(covariant T actual) => _feature(actual);
+}
diff --git a/packages/matcher/lib/src/interfaces.dart b/packages/matcher/lib/src/interfaces.dart
index 6ab0f14..1f39fa4 100644
--- a/packages/matcher/lib/src/interfaces.dart
+++ b/packages/matcher/lib/src/interfaces.dart
@@ -2,14 +2,12 @@
 // 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.
 
-// To decouple the reporting of errors, and allow for extensibility of
-// matchers, we make use of some interfaces.
-
-/// Matchers build up their error messages by appending to
-/// Description objects. This interface is implemented by
-/// StringDescription. This interface is unlikely to need
-/// other implementations, but could be useful to replace in
-/// some cases - e.g. language conversion.
+/// Matchers build up their error messages by appending to Description objects.
+///
+/// This interface is implemented by StringDescription.
+///
+/// This interface is unlikely to need other implementations, but could be
+/// useful to replace in some cases - e.g. language conversion.
 abstract class Description {
   int get length;
 
@@ -27,29 +25,32 @@
   Description addAll(String start, String separator, String end, Iterable list);
 }
 
-/// [expect] Matchers must implement/extend the Matcher class.
+/// The base class for all matchers.
 ///
-/// The base Matcher class has a generic implementation of [describeMismatch]
-/// so this does not need to be provided unless a more clear description is
-/// required. The other two methods ([matches] and [describe])
-/// must always be provided as they are highly matcher-specific.
+/// [matches] and [describe] must be implemented by subclasses.
+///
+/// Subclasses can override [describeMismatch] if a more specific description is
+/// required when the matcher fails.
 abstract class Matcher {
   const Matcher();
 
-  /// This does the matching of the actual vs expected values.
+  /// Does the matching of the actual vs expected values.
+  ///
   /// [item] is the actual value. [matchState] can be supplied
   /// and may be used to add details about the mismatch that are too
   /// costly to determine in [describeMismatch].
   bool matches(item, Map matchState);
 
-  /// This builds a textual description of the matcher.
+  /// Builds a textual description of the matcher.
   Description describe(Description description);
 
-  /// This builds a textual description of a specific mismatch. [item]
-  /// is the value that was tested by [matches]; [matchState] is
+  /// Builds a textual description of a specific mismatch.
+  ///
+  /// [item] is the value that was tested by [matches]; [matchState] is
   /// the [Map] that was passed to and supplemented by [matches]
   /// with additional information about the mismatch, and [mismatchDescription]
   /// is the [Description] that is being built to describe the mismatch.
+  ///
   /// A few matchers make use of the [verbose] flag to provide detailed
   /// information that is not typically included but can be of help in
   /// diagnosing failures, such as stack traces.
diff --git a/packages/matcher/lib/src/iterable_matchers.dart b/packages/matcher/lib/src/iterable_matchers.dart
index a4a122c..02c41aa 100644
--- a/packages/matcher/lib/src/iterable_matchers.dart
+++ b/packages/matcher/lib/src/iterable_matchers.dart
@@ -2,8 +2,9 @@
 // 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 'core_matchers.dart';
 import 'description.dart';
+import 'equals_matcher.dart';
+import 'feature_matcher.dart';
 import 'interfaces.dart';
 import 'util.dart';
 
@@ -16,10 +17,7 @@
 
   _EveryElement(this._matcher);
 
-  bool matches(item, Map matchState) {
-    if (item is! Iterable) {
-      return false;
-    }
+  bool typedMatches(Iterable item, Map matchState) {
     var i = 0;
     for (var element in item) {
       if (!_matcher.matches(element, matchState)) {
@@ -34,7 +32,7 @@
   Description describe(Description description) =>
       description.add('every element(').addDescriptionOf(_matcher).add(')');
 
-  Description describeMismatch(
+  Description describeTypedMismatch(
       item, Description mismatchDescription, Map matchState, bool verbose) {
     if (matchState['index'] != null) {
       var index = matchState['index'];
@@ -45,7 +43,7 @@
           .add(' which ');
       var subDescription = new StringDescription();
       _matcher.describeMismatch(
-          element, subDescription, matchState['state'], verbose);
+          element, subDescription, matchState['state'] as Map, verbose);
       if (subDescription.length > 0) {
         mismatchDescription.add(subDescription.toString());
       } else {
@@ -69,9 +67,8 @@
 
   _AnyElement(this._matcher);
 
-  bool matches(item, Map matchState) {
-    return item.any((e) => _matcher.matches(e, matchState));
-  }
+  bool typedMatches(Iterable item, Map matchState) =>
+      item.any((e) => _matcher.matches(e, matchState));
 
   Description describe(Description description) =>
       description.add('some element ').addDescriptionOf(_matcher);
@@ -83,28 +80,22 @@
 /// This is equivalent to [equals] but does not recurse.
 Matcher orderedEquals(Iterable expected) => new _OrderedEquals(expected);
 
-class _OrderedEquals extends Matcher {
+class _OrderedEquals extends _IterableMatcher {
   final Iterable _expected;
-  Matcher _matcher;
+  final Matcher _matcher;
 
-  _OrderedEquals(this._expected) {
-    _matcher = equals(_expected, 1);
-  }
+  _OrderedEquals(this._expected) : _matcher = equals(_expected, 1);
 
-  bool matches(item, Map matchState) =>
-      (item is Iterable) && _matcher.matches(item, matchState);
+  bool typedMatches(Iterable item, Map matchState) =>
+      _matcher.matches(item, matchState);
 
   Description describe(Description description) =>
       description.add('equals ').addDescriptionOf(_expected).add(' ordered');
 
-  Description describeMismatch(
-      item, Description mismatchDescription, Map matchState, bool verbose) {
-    if (item is! Iterable) {
-      return mismatchDescription.add('is not an Iterable');
-    } else {
-      return _matcher.describeMismatch(
-          item, mismatchDescription, matchState, verbose);
-    }
+  Description describeTypedMismatch(Iterable item,
+      Description mismatchDescription, Map matchState, bool verbose) {
+    return _matcher.describeMismatch(
+        item, mismatchDescription, matchState, verbose);
   }
 }
 
@@ -130,17 +121,8 @@
 
 /// Iterable matchers match against [Iterable]s. We add this intermediate
 /// class to give better mismatch error messages than the base Matcher class.
-abstract class _IterableMatcher extends Matcher {
+abstract class _IterableMatcher extends FeatureMatcher<Iterable> {
   const _IterableMatcher();
-  Description describeMismatch(
-      item, Description mismatchDescription, Map matchState, bool verbose) {
-    if (item is! Iterable) {
-      return mismatchDescription.addDescriptionOf(item).add(' not an Iterable');
-    } else {
-      return super
-          .describeMismatch(item, mismatchDescription, matchState, verbose);
-    }
-  }
 }
 
 /// Returns a matcher which matches [Iterable]s whose elements match the
@@ -150,7 +132,7 @@
 /// only be used on small iterables.
 Matcher unorderedMatches(Iterable expected) => new _UnorderedMatches(expected);
 
-class _UnorderedMatches extends Matcher {
+class _UnorderedMatches extends _IterableMatcher {
   final List<Matcher> _expected;
   final bool _allowUnmatchedValues;
 
@@ -158,11 +140,7 @@
       : _expected = expected.map(wrapMatcher).toList(),
         _allowUnmatchedValues = allowUnmatchedValues ?? false;
 
-  String _test(item) {
-    if (item is! Iterable) return 'not iterable';
-
-    var values = item.toList();
-
+  String _test(List values) {
     // Check the lengths are the same.
     if (_expected.length > values.length) {
       return 'has too few elements (${values.length} < ${_expected.length})';
@@ -172,8 +150,8 @@
 
     var edges =
         new List.generate(values.length, (_) => <int>[], growable: false);
-    for (int v = 0; v < values.length; v++) {
-      for (int m = 0; m < _expected.length; m++) {
+    for (var v = 0; v < values.length; v++) {
+      for (var m = 0; m < _expected.length; m++) {
         if (_expected[m].matches(values[v], {})) {
           edges[v].add(m);
         }
@@ -182,10 +160,10 @@
     // The index into `values` matched with each matcher or `null` if no value
     // has been matched yet.
     var matched = new List<int>(_expected.length);
-    for (int valueIndex = 0; valueIndex < values.length; valueIndex++) {
+    for (var valueIndex = 0; valueIndex < values.length; valueIndex++) {
       _findPairing(edges, valueIndex, matched);
     }
-    for (int matcherIndex = 0;
+    for (var matcherIndex = 0;
         matcherIndex < _expected.length;
         matcherIndex++) {
       if (matched[matcherIndex] == null) {
@@ -205,21 +183,22 @@
     return null;
   }
 
-  bool matches(item, Map mismatchState) => _test(item) == null;
+  bool typedMatches(Iterable item, Map mismatchState) =>
+      _test(item.toList()) == null;
 
   Description describe(Description description) => description
       .add('matches ')
       .addAll('[', ', ', ']', _expected)
       .add(' unordered');
 
-  Description describeMismatch(item, Description mismatchDescription,
+  Description describeTypedMismatch(item, Description mismatchDescription,
           Map matchState, bool verbose) =>
-      mismatchDescription.add(_test(item));
+      mismatchDescription.add(_test(item.toList()));
 
-  /// Returns [true] if the value at [valueIndex] can be paired with some
+  /// Returns `true` if the value at [valueIndex] can be paired with some
   /// unmatched matcher and updates the state of [matched].
   ///
-  /// If there is a conflic where multiple values may match the same matcher
+  /// If there is a conflict where multiple values may match the same matcher
   /// recursively looks for a new place to match the old value. [reserved]
   /// tracks the matchers that have been used _during_ this search.
   bool _findPairing(List<List<int>> edges, int valueIndex, List<int> matched,
@@ -260,34 +239,28 @@
 
   _PairwiseCompare(this._expected, this._comparator, this._description);
 
-  bool matches(item, Map matchState) {
-    if (item is Iterable) {
-      if (item.length != _expected.length) return false;
-      var iterator = item.iterator;
-      var i = 0;
-      for (var e in _expected) {
-        iterator.moveNext();
-        if (!_comparator(e, iterator.current)) {
-          addStateInfo(matchState,
-              {'index': i, 'expected': e, 'actual': iterator.current});
-          return false;
-        }
-        i++;
+  bool typedMatches(Iterable item, Map matchState) {
+    if (item.length != _expected.length) return false;
+    var iterator = item.iterator;
+    var i = 0;
+    for (var e in _expected) {
+      iterator.moveNext();
+      if (!_comparator(e, iterator.current as T)) {
+        addStateInfo(matchState,
+            {'index': i, 'expected': e, 'actual': iterator.current});
+        return false;
       }
-      return true;
-    } else {
-      return false;
+      i++;
     }
+    return true;
   }
 
   Description describe(Description description) =>
       description.add('pairwise $_description ').addDescriptionOf(_expected);
 
-  Description describeMismatch(
-      item, Description mismatchDescription, Map matchState, bool verbose) {
-    if (item is! Iterable) {
-      return mismatchDescription.add('is not an Iterable');
-    } else if (item.length != _expected.length) {
+  Description describeTypedMismatch(Iterable item,
+      Description mismatchDescription, Map matchState, bool verbose) {
+    if (item.length != _expected.length) {
       return mismatchDescription
           .add('has length ${item.length} instead of ${_expected.length}');
     } else {
@@ -342,13 +315,12 @@
 Matcher containsAllInOrder(Iterable expected) =>
     new _ContainsAllInOrder(expected);
 
-class _ContainsAllInOrder implements Matcher {
+class _ContainsAllInOrder extends _IterableMatcher {
   final Iterable _expected;
 
   _ContainsAllInOrder(this._expected);
 
-  String _test(item, Map matchState) {
-    if (item is! Iterable) return 'not an iterable';
+  String _test(Iterable item, Map matchState) {
     var matchers = _expected.map(wrapMatcher).toList();
     var matcherIndex = 0;
     for (var value in item) {
@@ -363,7 +335,8 @@
   }
 
   @override
-  bool matches(item, Map matchState) => _test(item, matchState) == null;
+  bool typedMatches(Iterable item, Map matchState) =>
+      _test(item, matchState) == null;
 
   @override
   Description describe(Description description) => description
@@ -372,7 +345,7 @@
       .add(')');
 
   @override
-  Description describeMismatch(item, Description mismatchDescription,
-          Map matchState, bool verbose) =>
+  Description describeTypedMismatch(Iterable item,
+          Description mismatchDescription, Map matchState, bool verbose) =>
       mismatchDescription.add(_test(item, matchState));
 }
diff --git a/packages/matcher/lib/src/map_matchers.dart b/packages/matcher/lib/src/map_matchers.dart
index 47cddef..9825c9b 100644
--- a/packages/matcher/lib/src/map_matchers.dart
+++ b/packages/matcher/lib/src/map_matchers.dart
@@ -9,7 +9,7 @@
 Matcher containsValue(value) => new _ContainsValue(value);
 
 class _ContainsValue extends Matcher {
-  final _value;
+  final Object _value;
 
   const _ContainsValue(this._value);
 
@@ -24,7 +24,7 @@
     new _ContainsMapping(key, wrapMatcher(value));
 
 class _ContainsMapping extends Matcher {
-  final _key;
+  final Object _key;
   final Matcher _valueMatcher;
 
   const _ContainsMapping(this._key, this._valueMatcher);
diff --git a/packages/matcher/lib/src/numeric_matchers.dart b/packages/matcher/lib/src/numeric_matchers.dart
index c405391..4187078 100644
--- a/packages/matcher/lib/src/numeric_matchers.dart
+++ b/packages/matcher/lib/src/numeric_matchers.dart
@@ -2,6 +2,7 @@
 // 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 'feature_matcher.dart';
 import 'interfaces.dart';
 
 /// Returns a matcher which matches if the match argument is within [delta]
@@ -11,19 +12,15 @@
 /// than or equal [value]-[delta] and less than or equal to [value]+[delta].
 Matcher closeTo(num value, num delta) => new _IsCloseTo(value, delta);
 
-class _IsCloseTo extends Matcher {
+class _IsCloseTo extends FeatureMatcher<num> {
   final num _value, _delta;
 
   const _IsCloseTo(this._value, this._delta);
 
-  bool matches(item, Map matchState) {
-    if (item is num) {
-      var diff = item - _value;
-      if (diff < 0) diff = -diff;
-      return (diff <= _delta);
-    } else {
-      return false;
-    }
+  bool typedMatches(item, Map matchState) {
+    var diff = item - _value;
+    if (diff < 0) diff = -diff;
+    return (diff <= _delta);
   }
 
   Description describe(Description description) => description
@@ -32,15 +29,11 @@
       .add(' of ')
       .addDescriptionOf(_value);
 
-  Description describeMismatch(
+  Description describeTypedMismatch(
       item, Description mismatchDescription, Map matchState, bool verbose) {
-    if (item is num) {
-      var diff = item - _value;
-      if (diff < 0) diff = -diff;
-      return mismatchDescription.add(' differs by ').addDescriptionOf(diff);
-    } else {
-      return mismatchDescription.add(' not numeric');
-    }
+    var diff = item - _value;
+    if (diff < 0) diff = -diff;
+    return mismatchDescription.add(' differs by ').addDescriptionOf(diff);
   }
 }
 
@@ -64,42 +57,28 @@
 Matcher inClosedOpenRange(num low, num high) =>
     new _InRange(low, high, true, false);
 
-class _InRange extends Matcher {
+class _InRange extends FeatureMatcher<num> {
   final num _low, _high;
   final bool _lowMatchValue, _highMatchValue;
 
   const _InRange(
       this._low, this._high, this._lowMatchValue, this._highMatchValue);
 
-  bool matches(value, Map matchState) {
-    if (value is num) {
-      if (value < _low || value > _high) {
-        return false;
-      }
-      if (value == _low) {
-        return _lowMatchValue;
-      }
-      if (value == _high) {
-        return _highMatchValue;
-      }
-      return true;
-    } else {
+  bool typedMatches(value, Map matchState) {
+    if (value < _low || value > _high) {
       return false;
     }
+    if (value == _low) {
+      return _lowMatchValue;
+    }
+    if (value == _high) {
+      return _highMatchValue;
+    }
+    return true;
   }
 
   Description describe(Description description) =>
       description.add("be in range from "
           "$_low (${_lowMatchValue ? 'inclusive' : 'exclusive'}) to "
           "$_high (${_highMatchValue ? 'inclusive' : 'exclusive'})");
-
-  Description describeMismatch(
-      item, Description mismatchDescription, Map matchState, bool verbose) {
-    if (item is! num) {
-      return mismatchDescription.addDescriptionOf(item).add(' not numeric');
-    } else {
-      return super
-          .describeMismatch(item, mismatchDescription, matchState, verbose);
-    }
-  }
 }
diff --git a/packages/matcher/lib/src/operator_matchers.dart b/packages/matcher/lib/src/operator_matchers.dart
index 5d6baed..827fbf1 100644
--- a/packages/matcher/lib/src/operator_matchers.dart
+++ b/packages/matcher/lib/src/operator_matchers.dart
@@ -106,5 +106,5 @@
     args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6].where((e) => e != null);
   }
 
-  return args.map((e) => wrapMatcher(e)).toList();
+  return args.map(wrapMatcher).toList();
 }
diff --git a/packages/matcher/lib/src/order_matchers.dart b/packages/matcher/lib/src/order_matchers.dart
index b6079e9..829aadb 100644
--- a/packages/matcher/lib/src/order_matchers.dart
+++ b/packages/matcher/lib/src/order_matchers.dart
@@ -52,7 +52,7 @@
 // `==` and `<` operators to evaluate the match. Or change the matcher.
 class _OrderingMatcher extends Matcher {
   /// Expected value.
-  final _value;
+  final Object _value;
 
   /// What to return if actual == expected
   final bool _equalValue;
diff --git a/packages/matcher/lib/src/pretty_print.dart b/packages/matcher/lib/src/pretty_print.dart
index 826cad0..1cbe139 100644
--- a/packages/matcher/lib/src/pretty_print.dart
+++ b/packages/matcher/lib/src/pretty_print.dart
@@ -125,6 +125,7 @@
   // So we play safe here.
   try {
     if (x == null) return "null";
+    if (x is Type) return "Type";
     var type = x.runtimeType.toString();
     // TODO(nweiz): if the object's type is private, find a public superclass to
     // display once there's a portable API to do that.
diff --git a/packages/matcher/lib/src/string_matchers.dart b/packages/matcher/lib/src/string_matchers.dart
index 14e6fbb..47703c3 100644
--- a/packages/matcher/lib/src/string_matchers.dart
+++ b/packages/matcher/lib/src/string_matchers.dart
@@ -2,13 +2,14 @@
 // 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 'feature_matcher.dart';
 import 'interfaces.dart';
 
 /// Returns a matcher which matches if the match argument is a string and
 /// is equal to [value] when compared case-insensitively.
 Matcher equalsIgnoringCase(String value) => new _IsEqualIgnoringCase(value);
 
-class _IsEqualIgnoringCase extends _StringMatcher {
+class _IsEqualIgnoringCase extends FeatureMatcher<String> {
   final String _value;
   final String _matchValue;
 
@@ -16,8 +17,8 @@
       : _value = value,
         _matchValue = value.toLowerCase();
 
-  bool matches(item, Map matchState) =>
-      item is String && _matchValue == item.toLowerCase();
+  bool typedMatches(String item, Map matchState) =>
+      _matchValue == item.toLowerCase();
 
   Description describe(Description description) =>
       description.addDescriptionOf(_value).add(' ignoring case');
@@ -43,7 +44,7 @@
 Matcher equalsIgnoringWhitespace(String value) =>
     new _IsEqualIgnoringWhitespace(value);
 
-class _IsEqualIgnoringWhitespace extends _StringMatcher {
+class _IsEqualIgnoringWhitespace extends FeatureMatcher<String> {
   final String _value;
   final String _matchValue;
 
@@ -51,23 +52,18 @@
       : _value = value,
         _matchValue = collapseWhitespace(value);
 
-  bool matches(item, Map matchState) =>
-      item is String && _matchValue == collapseWhitespace(item);
+  bool typedMatches(String item, Map matchState) =>
+      _matchValue == collapseWhitespace(item);
 
   Description describe(Description description) =>
       description.addDescriptionOf(_matchValue).add(' ignoring whitespace');
 
-  Description describeMismatch(
+  Description describeTypedMismatch(
       item, Description mismatchDescription, Map matchState, bool verbose) {
-    if (item is String) {
-      return mismatchDescription
-          .add('is ')
-          .addDescriptionOf(collapseWhitespace(item))
-          .add(' with whitespace compressed');
-    } else {
-      return super
-          .describeMismatch(item, mismatchDescription, matchState, verbose);
-    }
+    return mismatchDescription
+        .add('is ')
+        .addDescriptionOf(collapseWhitespace(item))
+        .add(' with whitespace compressed');
   }
 }
 
@@ -75,13 +71,12 @@
 /// starts with [prefixString].
 Matcher startsWith(String prefixString) => new _StringStartsWith(prefixString);
 
-class _StringStartsWith extends _StringMatcher {
+class _StringStartsWith extends FeatureMatcher<String> {
   final String _prefix;
 
   const _StringStartsWith(this._prefix);
 
-  bool matches(item, Map matchState) =>
-      item is String && item.startsWith(_prefix);
+  bool typedMatches(item, Map matchState) => item.startsWith(_prefix);
 
   Description describe(Description description) =>
       description.add('a string starting with ').addDescriptionOf(_prefix);
@@ -91,13 +86,12 @@
 /// ends with [suffixString].
 Matcher endsWith(String suffixString) => new _StringEndsWith(suffixString);
 
-class _StringEndsWith extends _StringMatcher {
+class _StringEndsWith extends FeatureMatcher<String> {
   final String _suffix;
 
   const _StringEndsWith(this._suffix);
 
-  bool matches(item, Map matchState) =>
-      item is String && item.endsWith(_suffix);
+  bool typedMatches(item, Map matchState) => item.endsWith(_suffix);
 
   Description describe(Description description) =>
       description.add('a string ending with ').addDescriptionOf(_suffix);
@@ -112,22 +106,18 @@
 Matcher stringContainsInOrder(List<String> substrings) =>
     new _StringContainsInOrder(substrings);
 
-class _StringContainsInOrder extends _StringMatcher {
+class _StringContainsInOrder extends FeatureMatcher<String> {
   final List<String> _substrings;
 
   const _StringContainsInOrder(this._substrings);
 
-  bool matches(item, Map matchState) {
-    if (item is String) {
-      var from_index = 0;
-      for (var s in _substrings) {
-        from_index = item.indexOf(s, from_index);
-        if (from_index < 0) return false;
-      }
-      return true;
-    } else {
-      return false;
+  bool typedMatches(item, Map matchState) {
+    var fromIndex = 0;
+    for (var s in _substrings) {
+      fromIndex = item.indexOf(s, fromIndex);
+      if (fromIndex < 0) return false;
     }
+    return true;
   }
 
   Description describe(Description description) => description.addAll(
@@ -141,7 +131,7 @@
 /// used to create a RegExp instance.
 Matcher matches(re) => new _MatchesRegExp(re);
 
-class _MatchesRegExp extends _StringMatcher {
+class _MatchesRegExp extends FeatureMatcher<String> {
   RegExp _regexp;
 
   _MatchesRegExp(re) {
@@ -154,28 +144,12 @@
     }
   }
 
-  bool matches(item, Map matchState) =>
-      item is String ? _regexp.hasMatch(item) : false;
+  bool typedMatches(item, Map matchState) => _regexp.hasMatch(item);
 
   Description describe(Description description) =>
       description.add("match '${_regexp.pattern}'");
 }
 
-// String matchers match against a string. We add this intermediate
-// class to give better mismatch error messages than the base Matcher class.
-abstract class _StringMatcher extends Matcher {
-  const _StringMatcher();
-  Description describeMismatch(
-      item, Description mismatchDescription, Map matchState, bool verbose) {
-    if (!(item is String)) {
-      return mismatchDescription.addDescriptionOf(item).add(' not a string');
-    } else {
-      return super
-          .describeMismatch(item, mismatchDescription, matchState, verbose);
-    }
-  }
-}
-
 /// Utility function to collapse whitespace runs to single spaces
 /// and strip leading/trailing whitespace.
 String collapseWhitespace(String string) {
diff --git a/packages/matcher/lib/src/type_matcher.dart b/packages/matcher/lib/src/type_matcher.dart
new file mode 100644
index 0000000..91552c9
--- /dev/null
+++ b/packages/matcher/lib/src/type_matcher.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2018, 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 'having_matcher.dart';
+import 'interfaces.dart';
+
+/// A [Matcher] subclass that supports validating the [Type] of the target
+/// object.
+///
+/// ```dart
+/// expect(shouldBeDuration, new TypeMatcher<Duration>());
+/// ```
+///
+/// If you want to further validate attributes of the specified [Type], use the
+/// [having] function.
+///
+/// ```dart
+/// void shouldThrowRangeError(int value) {
+///   throw new RangeError.range(value, 10, 20);
+/// }
+///
+/// expect(
+///     () => shouldThrowRangeError(5),
+///     throwsA(const TypeMatcher<RangeError>()
+///         .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+///         .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+/// ```
+///
+/// Notice that you can chain multiple calls to [having] to verify multiple
+/// aspects of an object.
+///
+/// Note: All of the top-level `isType` matchers exposed by this package are
+/// instances of [TypeMatcher], so you can use the [having] function without
+/// creating your own instance.
+///
+/// ```dart
+/// expect(
+///     () => shouldThrowRangeError(5),
+///     throwsA(isRangeError
+///         .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+///         .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+/// ```
+class TypeMatcher<T> extends Matcher {
+  final String _name;
+  const TypeMatcher(
+      [@Deprecated('Provide a type argument to TypeMatcher and omit the name. '
+          'This argument will be removed in the next release.')
+          String name])
+      : this._name =
+            // ignore: deprecated_member_use
+            name;
+
+  /// Returns a new [TypeMatcher] that validates the existing type as well as
+  /// a specific [feature] of the object with the provided [matcher].
+  ///
+  /// Provides a human-readable [description] of the [feature] to make debugging
+  /// failures easier.
+  ///
+  /// ```dart
+  /// /// Validates that the object is a [RangeError] with a message containing
+  /// /// the string 'details' and `start` and `end` properties that are `null`.
+  /// final _rangeMatcher = isRangeError
+  ///    .having((e) => e.message, 'message', contains('details'))
+  ///    .having((e) => e.start, 'start', isNull)
+  ///    .having((e) => e.end, 'end', isNull);
+  /// ```
+  TypeMatcher<T> having(
+          Object feature(T source), String description, Object matcher) =>
+      new HavingMatcher(this, description, feature, matcher);
+
+  Description describe(Description description) {
+    var name = _name ?? _stripDynamic(T);
+    return description.add("<Instance of '$name'>");
+  }
+
+  bool matches(Object item, Map matchState) => item is T;
+}
+
+final _dart2DynamicArgs = new RegExp('<dynamic(, dynamic)*>');
+
+/// With this expression `{}.runtimeType.toString`,
+/// Dart 1: "<Instance of Map>
+/// Dart 2: "<Instance of Map<dynamic, dynamic>>"
+///
+/// This functions returns the Dart 1 output, when Dart 2 runtime semantics
+/// are enabled.
+String _stripDynamic(Type type) =>
+    type.toString().replaceAll(_dart2DynamicArgs, '');
diff --git a/packages/matcher/lib/src/util.dart b/packages/matcher/lib/src/util.dart
index 7e7ff05..1df39ef 100644
--- a/packages/matcher/lib/src/util.dart
+++ b/packages/matcher/lib/src/util.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'core_matchers.dart';
+import 'equals_matcher.dart';
 import 'interfaces.dart';
 
 typedef bool _Predicate<T>(T value);
@@ -44,6 +45,7 @@
   } else if (x is _Predicate<Null>) {
     // x is a unary predicate, but expects a specific type
     // so wrap it.
+    // ignore: unnecessary_lambdas
     return predicate((a) => (x as dynamic)(a));
   } else {
     return equals(x);
@@ -65,6 +67,6 @@
 
 /// Given single-character string, return the hex-escaped equivalent.
 String _getHexLiteral(String input) {
-  int rune = input.runes.single;
+  var rune = input.runes.single;
   return r'\x' + rune.toRadixString(16).toUpperCase().padLeft(2, '0');
 }
diff --git a/packages/matcher/pubspec.yaml b/packages/matcher/pubspec.yaml
index c898554..8a00849 100644
--- a/packages/matcher/pubspec.yaml
+++ b/packages/matcher/pubspec.yaml
@@ -1,14 +1,15 @@
 name: matcher
-version: 0.12.2
-author: Dart Team <misc@dartlang.org>
-description: Support for specifying test expectations
-homepage: https://github.com/dart-lang/matcher
-environment:
-  sdk: '>=1.23.0 <2.0.0'
-dependencies:
-  stack_trace: '^1.2.0'
-dev_dependencies:
-  test: '>=0.12.0 <0.13.0'
+version: 0.12.3+1
 
-dependency_overrides:
-  test: ^0.12.0
+description: Support for specifying test expectations
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/matcher
+
+environment:
+  sdk: '>=2.0.0-dev.17.0 <3.0.0'
+
+dependencies:
+  stack_trace: ^1.2.0
+
+dev_dependencies:
+  test: '>=0.12.0 <2.0.0'
diff --git a/packages/matcher/test/core_matchers_test.dart b/packages/matcher/test/core_matchers_test.dart
index 8261c6f..927cc7b 100644
--- a/packages/matcher/test/core_matchers_test.dart
+++ b/packages/matcher/test/core_matchers_test.dart
@@ -7,11 +7,6 @@
 
 import 'test_utils.dart';
 
-class BadCustomMatcher extends CustomMatcher {
-  BadCustomMatcher() : super("feature", "description", {1: "a"});
-  featureValueOf(actual) => throw new Exception("bang");
-}
-
 void main() {
   test('isTrue', () {
     shouldPass(true, isTrue);
@@ -35,25 +30,27 @@
   });
 
   test('isNaN', () {
-    shouldPass(double.NAN, isNaN);
+    shouldPass(double.nan, isNaN);
     shouldFail(3.1, isNaN, "Expected: NaN Actual: <3.1>");
+    shouldFail('not a num', isNaN, endsWith('not an <Instance of \'num\'>'));
   });
 
   test('isNotNaN', () {
     shouldPass(3.1, isNotNaN);
-    shouldFail(double.NAN, isNotNaN, "Expected: not NaN Actual: <NaN>");
+    shouldFail(double.nan, isNotNaN, "Expected: not NaN Actual: <NaN>");
+    shouldFail('not a num', isNotNaN, endsWith('not an <Instance of \'num\'>'));
   });
 
   test('same', () {
-    var a = new Map();
-    var b = new Map();
+    var a = {};
+    var b = {};
     shouldPass(a, same(a));
     shouldFail(b, same(a), "Expected: same instance as {} Actual: {}");
   });
 
   test('equals', () {
-    var a = new Map();
-    var b = new Map();
+    var a = {};
+    var b = {};
     shouldPass(a, equals(a));
     shouldPass(a, equals(b));
   });
@@ -81,7 +78,7 @@
   });
 
   test('anything', () {
-    var a = new Map();
+    var a = {};
     shouldPass(0, anything);
     shouldPass(null, anything);
     shouldPass(a, anything);
@@ -95,12 +92,14 @@
         returnsNormally,
         matches(r"Expected: return normally"
             r"  Actual: <Closure.*>"
-            r"   Which: threw 'X'"));
+            r"   Which: threw StateError:<Bad state: X>"));
+    shouldFail('not a function', returnsNormally,
+        contains('not an <Instance of \'Function\'>'));
   });
 
   test('hasLength', () {
-    var a = new Map();
-    var b = new List();
+    var a = {};
+    var b = [];
     shouldPass(a, hasLength(0));
     shouldPass(b, hasLength(0));
     shouldPass('a', hasLength(1));
@@ -214,42 +213,19 @@
     shouldFail(actual3, equals(expected3), reason3);
   });
 
-  test('isInstanceOf', () {
-    shouldFail(0, new isInstanceOf<String>(),
-        "Expected: an instance of String Actual: <0>");
-    shouldPass('cow', new isInstanceOf<String>());
-  });
-
   group('Predicate Matchers', () {
     test('isInstanceOf', () {
       shouldFail(0, predicate((x) => x is String, "an instance of String"),
           "Expected: an instance of String Actual: <0>");
       shouldPass('cow', predicate((x) => x is String, "an instance of String"));
+
+      if (isDart2) {
+        // With Dart2 semantics, predicate picks up a type argument of `bool`
+        // and we get nice type checking.
+        // Without Dart2 semantics a gnarly type error is thrown.
+        shouldFail(0, predicate((bool x) => x, "bool value is true"),
+            endsWith("not an <Instance of \'bool\'>"));
+      }
     });
   });
-
-  test("Feature Matcher", () {
-    var w = new Widget();
-    w.price = 10;
-    shouldPass(w, new HasPrice(10));
-    shouldPass(w, new HasPrice(greaterThan(0)));
-    shouldFail(
-        w,
-        new HasPrice(greaterThan(10)),
-        "Expected: Widget with a price that is a value greater than <10> "
-        "Actual: <Instance of 'Widget'> "
-        "Which: has price with value <10> which is not "
-        "a value greater than <10>");
-  });
-
-  test("Custom Matcher Exception", () {
-    shouldFail(
-        "a",
-        new BadCustomMatcher(),
-        allOf([
-          contains("Expected: feature {1: 'a'} "),
-          contains("Actual: 'a' "),
-          contains("Which: threw 'Exception: bang' "),
-        ]));
-  });
 }
diff --git a/packages/matcher/test/custom_matcher_test.dart b/packages/matcher/test/custom_matcher_test.dart
new file mode 100644
index 0000000..53f035b
--- /dev/null
+++ b/packages/matcher/test/custom_matcher_test.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2012, 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:matcher/matcher.dart';
+import 'package:test/test.dart' show test;
+
+import 'test_utils.dart';
+
+class _BadCustomMatcher extends CustomMatcher {
+  _BadCustomMatcher() : super("feature", "description", {1: "a"});
+  Object featureValueOf(actual) => throw new Exception("bang");
+}
+
+class _HasPrice extends CustomMatcher {
+  _HasPrice(matcher) : super("Widget with a price that is", "price", matcher);
+  Object featureValueOf(actual) => actual.price;
+}
+
+void main() {
+  test("Feature Matcher", () {
+    var w = new Widget();
+    w.price = 10;
+    shouldPass(w, new _HasPrice(10));
+    shouldPass(w, new _HasPrice(greaterThan(0)));
+    shouldFail(
+        w,
+        new _HasPrice(greaterThan(10)),
+        "Expected: Widget with a price that is a value greater than <10> "
+        "Actual: <Instance of 'Widget'> "
+        "Which: has price with value <10> which is not "
+        "a value greater than <10>");
+  });
+
+  test("Custom Matcher Exception", () {
+    shouldFail(
+        "a",
+        new _BadCustomMatcher(),
+        allOf([
+          contains("Expected: feature {1: 'a'} "),
+          contains("Actual: 'a' "),
+          contains("Which: threw 'Exception: bang' "),
+        ]));
+  });
+}
diff --git a/packages/matcher/test/having_test.dart b/packages/matcher/test/having_test.dart
new file mode 100644
index 0000000..31cf7ac
--- /dev/null
+++ b/packages/matcher/test/having_test.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2018, 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:matcher/matcher.dart';
+import 'package:test/test.dart' show test, expect, throwsA, group;
+
+import 'test_utils.dart';
+
+void main() {
+  test('success', () {
+    shouldPass(new RangeError('details'), _rangeMatcher);
+  });
+
+  test('failure', () {
+    shouldFail(
+        new RangeError.range(-1, 1, 10),
+        _rangeMatcher,
+        "Expected: <Instance of 'RangeError'> with "
+        "`message`: contains 'details' and `start`: null and `end`: null "
+        'Actual: RangeError:<RangeError: '
+        'Invalid value: Not in range 1..10, inclusive: -1> '
+        "Which: has `message` with value 'Invalid value'");
+  });
+
+  // This code is used in the [TypeMatcher] doc comments.
+  test('integaration and example', () {
+    void shouldThrowRangeError(int value) {
+      throw new RangeError.range(value, 10, 20);
+    }
+
+    expect(
+        () => shouldThrowRangeError(5),
+        throwsA(const TypeMatcher<RangeError>()
+            .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+            .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+
+    expect(
+        () => shouldThrowRangeError(5),
+        throwsA(isRangeError
+            .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+            .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+  });
+
+  group('CustomMater copy', () {
+    test("Feature Matcher", () {
+      var w = new Widget();
+      w.price = 10;
+      shouldPass(w, _hasPrice(10));
+      shouldPass(w, _hasPrice(greaterThan(0)));
+      shouldFail(
+          w,
+          _hasPrice(greaterThan(10)),
+          "Expected: <Instance of 'Widget'> with `price`: a value greater than <10> "
+          "Actual: <Instance of 'Widget'> "
+          "Which: has `price` with value <10> which is not "
+          "a value greater than <10>");
+    });
+
+    test("Custom Matcher Exception", () {
+      shouldFail(
+          'a',
+          _badCustomMatcher(),
+          allOf([
+            contains(
+                "Expected: <Instance of 'Widget'> with `feature`: {1: 'a'} "),
+            contains("Actual: 'a'"),
+          ]));
+      shouldFail(
+          new Widget(),
+          _badCustomMatcher(),
+          allOf([
+            contains(
+                "Expected: <Instance of 'Widget'> with `feature`: {1: 'a'} "),
+            contains("Actual: <Instance of 'Widget'> "),
+            contains("Which: threw 'Exception: bang' "),
+          ]));
+    });
+  });
+}
+
+final _rangeMatcher = isRangeError
+    .having((e) => e.message, 'message', contains('details'))
+    .having((e) => e.start, 'start', isNull)
+    .having((e) => e.end, 'end', isNull);
+
+Matcher _hasPrice(matcher) =>
+    const TypeMatcher<Widget>().having((e) => e.price, 'price', matcher);
+
+Matcher _badCustomMatcher() => const TypeMatcher<Widget>()
+    .having((e) => throw new Exception("bang"), 'feature', {1: "a"});
diff --git a/packages/matcher/test/iterable_matchers_test.dart b/packages/matcher/test/iterable_matchers_test.dart
index e8a8df8..7c28338 100644
--- a/packages/matcher/test/iterable_matchers_test.dart
+++ b/packages/matcher/test/iterable_matchers_test.dart
@@ -26,6 +26,9 @@
         contains(0),
         "Expected: contains <0> "
         "Actual: [1, 2]");
+
+    shouldFail(
+        'String', contains(42), "Expected: contains <42> Actual: 'String'");
   });
 
   test('equals with matcher element', () {
@@ -40,9 +43,22 @@
   });
 
   test('isIn', () {
-    var d = [1, 2];
-    shouldPass(1, isIn(d));
-    shouldFail(0, isIn(d), "Expected: is in [1, 2] Actual: <0>");
+    // Iterable
+    shouldPass(1, isIn([1, 2]));
+    shouldFail(0, isIn([1, 2]), "Expected: is in [1, 2] Actual: <0>");
+
+    // Map
+    shouldPass(1, isIn({1: null}));
+    shouldFail(0, isIn({1: null}), "Expected: is in {1: null} Actual: <0>");
+
+    // String
+    shouldPass('42', isIn('1421'));
+    shouldFail('42', isIn('41'), "Expected: is in '41' Actual: '42'");
+    shouldFail(
+        0, isIn('a string'), endsWith('not an <Instance of \'Pattern\'>'));
+
+    // Invalid arg
+    expect(() => isIn(42), throwsArgumentError);
   });
 
   test('everyElement', () {
@@ -55,6 +71,8 @@
         "Actual: [1, 2] "
         "Which: has value <2> which doesn't match <1> at index 1");
     shouldPass(e, everyElement(1));
+    shouldFail('not iterable', everyElement(1),
+        endsWith('not an <Instance of \'Iterable\'>'));
   });
 
   test('nested everyElement', () {
@@ -109,6 +127,8 @@
     shouldPass(d, anyElement(2));
     shouldFail(
         e, anyElement(2), "Expected: some element <2> Actual: [1, 1, 1]");
+    shouldFail('not an iterable', anyElement(2),
+        endsWith('not an <Instance of \'Iterable\'>'));
   });
 
   test('orderedEquals', () {
@@ -121,6 +141,8 @@
         "Expected: equals [2, 1] ordered "
         "Actual: [1, 2] "
         "Which: was <1> instead of <2> at location [0]");
+    shouldFail('not an iterable', orderedEquals([1]),
+        endsWith('not an <Instance of \'Iterable\'>'));
   });
 
   test('unorderedEquals', () {
@@ -151,6 +173,8 @@
         "Actual: [1, 2] "
         "Which: has no match for <3> at index 0"
         " along with 1 other unmatched");
+    shouldFail('not an iterable', unorderedEquals([1]),
+        endsWith('not an <Instance of \'Iterable\'>'));
   });
 
   test('unorderedMatches', () {
@@ -197,6 +221,8 @@
         "<0>] unordered "
         "Actual: [1, 2] "
         "Which: has no match for a value greater than <3> at index 0");
+    shouldFail('not an iterable', unorderedMatches([greaterThan(1)]),
+        endsWith('not an <Instance of \'Iterable\'>'));
   });
 
   test('containsAll', () {
@@ -216,7 +242,7 @@
         containsAll([1]),
         "Expected: contains all of [1] "
         "Actual: <1> "
-        "Which: not iterable");
+        "Which: not an <Instance of \'Iterable\'>");
     shouldFail(
         [-1, 2],
         containsAll([greaterThan(0), greaterThan(1)]),
@@ -224,6 +250,8 @@
         "<a value greater than <1>>] "
         "Actual: [-1, 2] "
         "Which: has no match for a value greater than <1> at index 1");
+    shouldFail('not an iterable', containsAll([1, 2, 3]),
+        endsWith('not an <Instance of \'Iterable\'>'));
   });
 
   test('containsAllInOrder', () {
@@ -257,7 +285,7 @@
         containsAllInOrder([1]),
         "Expected: contains in order([1]) "
         "Actual: <1> "
-        "Which: not an iterable");
+        "Which: not an <Instance of \'Iterable\'>");
   });
 
   test('pairwise compare', () {
@@ -266,20 +294,21 @@
     var e = [1, 4, 9];
     shouldFail(
         'x',
-        pairwiseCompare(e, (e, a) => a <= e, "less than or equal"),
+        pairwiseCompare(e, (int e, int a) => a <= e, "less than or equal"),
         "Expected: pairwise less than or equal [1, 4, 9] "
         "Actual: 'x' "
-        "Which: is not an Iterable");
+        "Which: not an <Instance of \'Iterable\'>");
     shouldFail(
         c,
-        pairwiseCompare(e, (e, a) => a <= e, "less than or equal"),
+        pairwiseCompare(e, (int e, int a) => a <= e, "less than or equal"),
         "Expected: pairwise less than or equal [1, 4, 9] "
         "Actual: [1, 2] "
         "Which: has length 2 instead of 3");
-    shouldPass(d, pairwiseCompare(e, (e, a) => a <= e, "less than or equal"));
+    shouldPass(
+        d, pairwiseCompare(e, (int e, int a) => a <= e, "less than or equal"));
     shouldFail(
         d,
-        pairwiseCompare(e, (e, a) => a < e, "less than"),
+        pairwiseCompare(e, (int e, int a) => a < e, "less than"),
         "Expected: pairwise less than [1, 4, 9] "
         "Actual: [1, 2, 3] "
         "Which: has <1> which is not less than <1> at index 0");
@@ -290,6 +319,10 @@
         "Expected: pairwise double [1, 4, 9] "
         "Actual: [1, 2, 3] "
         "Which: has <1> which is not double <1> at index 0");
+    shouldFail(
+        'not an iterable',
+        pairwiseCompare(e, (e, a) => a + a == e, "double"),
+        endsWith('not an <Instance of \'Iterable\'>'));
   });
 
   test('isEmpty', () {
diff --git a/packages/matcher/test/mirror_matchers_test.dart b/packages/matcher/test/mirror_matchers_test.dart
index bbd80a1..e9f88b4 100644
--- a/packages/matcher/test/mirror_matchers_test.dart
+++ b/packages/matcher/test/mirror_matchers_test.dart
@@ -11,9 +11,9 @@
 
 class C {
   var instanceField = 1;
-  get instanceGetter => 2;
+  int get instanceGetter => 2;
   static var staticField = 3;
-  static get staticGetter => 4;
+  static int get staticGetter => 4;
 }
 
 void main() {
diff --git a/packages/matcher/test/numeric_matchers_test.dart b/packages/matcher/test/numeric_matchers_test.dart
index 9a365d8..a85f13b 100644
--- a/packages/matcher/test/numeric_matchers_test.dart
+++ b/packages/matcher/test/numeric_matchers_test.dart
@@ -24,6 +24,8 @@
         "Expected: a numeric value within <1> of <0> "
         "Actual: <-1.001> "
         "Which: differs by <1.001>");
+    shouldFail(
+        'not a num', closeTo(0, 1), endsWith('not an <Instance of \'num\'>'));
   });
 
   test('inInclusiveRange', () {
@@ -40,6 +42,8 @@
         inInclusiveRange(0, 2),
         "Expected: be in range from 0 (inclusive) to 2 (inclusive) "
         "Actual: <3>");
+    shouldFail('not a num', inInclusiveRange(0, 1),
+        endsWith('not an <Instance of \'num\'>'));
   });
 
   test('inExclusiveRange', () {
@@ -54,6 +58,8 @@
         inExclusiveRange(0, 2),
         "Expected: be in range from 0 (exclusive) to 2 (exclusive) "
         "Actual: <2>");
+    shouldFail('not a num', inExclusiveRange(0, 1),
+        endsWith('not an <Instance of \'num\'>'));
   });
 
   test('inOpenClosedRange', () {
@@ -64,6 +70,8 @@
         "Actual: <0>");
     shouldPass(1, inOpenClosedRange(0, 2));
     shouldPass(2, inOpenClosedRange(0, 2));
+    shouldFail('not a num', inOpenClosedRange(0, 1),
+        endsWith('not an <Instance of \'num\'>'));
   });
 
   test('inClosedOpenRange', () {
@@ -74,5 +82,7 @@
         inClosedOpenRange(0, 2),
         "Expected: be in range from 0 (inclusive) to 2 (exclusive) "
         "Actual: <2>");
+    shouldFail('not a num', inClosedOpenRange(0, 1),
+        endsWith('not an <Instance of \'num\'>'));
   });
 }
diff --git a/packages/matcher/test/pretty_print_test.dart b/packages/matcher/test/pretty_print_test.dart
index c809df0..59a5950 100644
--- a/packages/matcher/test/pretty_print_test.dart
+++ b/packages/matcher/test/pretty_print_test.dart
@@ -8,6 +8,8 @@
 import 'package:matcher/src/pretty_print.dart';
 import 'package:test/test.dart' show group, test, expect;
 
+import 'test_utils.dart';
+
 class DefaultToString {}
 
 class CustomToString {
@@ -244,12 +246,19 @@
 
   group('with an iterable', () {
     test("that's not a list", () {
-      expect(prettyPrint([1, 2, 3, 4].map((n) => n * 2)),
-          equals("MappedListIterable:[2, 4, 6, 8]"));
+      expect(
+          prettyPrint([1, 2, 3, 4].map((n) => n * 2)),
+          equals(isDart2
+              ? "MappedListIterable<int, int>:[2, 4, 6, 8]"
+              : "MappedListIterable:[2, 4, 6, 8]"));
     });
 
     test("that's not a list and has a private name", () {
       expect(prettyPrint(new _PrivateNameIterable()), equals("?:[1, 2, 3]"));
     });
   });
+
+  test('Type', () {
+    expect(prettyPrint(''.runtimeType), 'Type:<String>');
+  });
 }
diff --git a/packages/matcher/test/string_matchers_test.dart b/packages/matcher/test/string_matchers_test.dart
index 28d085c..834df8c 100644
--- a/packages/matcher/test/string_matchers_test.dart
+++ b/packages/matcher/test/string_matchers_test.dart
@@ -55,6 +55,8 @@
     shouldPass('hello', equalsIgnoringCase('HELLO'));
     shouldFail('hi', equalsIgnoringCase('HELLO'),
         "Expected: 'HELLO' ignoring case Actual: 'hi'");
+    shouldFail(42, equalsIgnoringCase('HELLO'),
+        endsWith('not an <Instance of \'String\'>'));
   });
 
   test('equalsIgnoringWhitespace', () {
@@ -65,6 +67,8 @@
         "Expected: 'hello world' ignoring whitespace "
         "Actual: ' helloworld ' "
         "Which: is 'helloworld' with whitespace compressed");
+    shouldFail(42, equalsIgnoringWhitespace('HELLO'),
+        endsWith('not an <Instance of \'String\'>'));
   });
 
   test('startsWith', () {
@@ -76,6 +80,8 @@
         startsWith('hello '),
         "Expected: a string starting with 'hello ' "
         "Actual: 'hello'");
+    shouldFail(
+        42, startsWith('hello '), endsWith('not an <Instance of \'String\'>'));
   });
 
   test('endsWith', () {
@@ -87,6 +93,8 @@
         endsWith(' hello'),
         "Expected: a string ending with ' hello' "
         "Actual: 'hello'");
+    shouldFail(
+        42, startsWith('hello '), endsWith('not an <Instance of \'String\'>'));
   });
 
   test('contains', () {
@@ -124,5 +132,7 @@
     shouldPass('c0d', matches(new RegExp('[a-z][0-9][a-z]')));
     shouldFail('cOd', matches('[a-z][0-9][a-z]'),
         "Expected: match '[a-z][0-9][a-z]' Actual: 'cOd'");
+    shouldFail(42, matches('[a-z][0-9][a-z]'),
+        endsWith('not an <Instance of \'String\'>'));
   });
 }
diff --git a/packages/matcher/test/test_utils.dart b/packages/matcher/test/test_utils.dart
index a0dad48..22ad681 100644
--- a/packages/matcher/test/test_utils.dart
+++ b/packages/matcher/test/test_utils.dart
@@ -4,6 +4,11 @@
 
 import 'package:test/test.dart';
 
+final bool isDart2 = () {
+  Type checkType<T>() => T;
+  return checkType<String>() == String;
+}();
+
 void shouldFail(value, Matcher matcher, expected) {
   var failed = false;
   try {
@@ -11,12 +16,12 @@
   } on TestFailure catch (err) {
     failed = true;
 
-    var _errorString = err.message;
+    var errorString = err.message;
 
     if (expected is String) {
-      expect(_errorString, equalsIgnoringWhitespace(expected));
+      expect(errorString, equalsIgnoringWhitespace(expected));
     } else {
-      expect(_errorString.replaceAll('\n', ''), expected);
+      expect(errorString.replaceAll('\n', ''), expected);
     }
   }
 
@@ -27,20 +32,15 @@
   expect(value, matcher);
 }
 
-doesNotThrow() {}
-doesThrow() {
-  throw 'X';
+void doesNotThrow() {}
+void doesThrow() {
+  throw new StateError('X');
 }
 
 class Widget {
   int price;
 }
 
-class HasPrice extends CustomMatcher {
-  HasPrice(matcher) : super("Widget with a price that is", "price", matcher);
-  featureValueOf(actual) => actual.price;
-}
-
 class SimpleIterable extends Iterable<int> {
   final int count;
 
diff --git a/packages/matcher/test/type_matcher_test.dart b/packages/matcher/test/type_matcher_test.dart
new file mode 100644
index 0000000..e20294f
--- /dev/null
+++ b/packages/matcher/test/type_matcher_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2012, 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:matcher/matcher.dart';
+import 'package:test/test.dart' show test, group;
+
+import 'test_utils.dart';
+
+void main() {
+  _test('Map', isMap, {});
+  _test('List', isList, []);
+  _test('ArgumentError', isArgumentError, new ArgumentError());
+  _test('Exception', isException, const FormatException());
+  _test('FormatException', isFormatException, const FormatException());
+  _test('StateError', isStateError, new StateError('oops'));
+  _test('RangeError', isRangeError, new RangeError('oops'));
+  _test('UnimplementedError', isUnimplementedError,
+      new UnimplementedError('oops'));
+  _test('UnsupportedError', isUnsupportedError, new UnsupportedError('oops'));
+  _test('ConcurrentModificationError', isConcurrentModificationError,
+      new ConcurrentModificationError());
+  _test('CyclicInitializationError', isCyclicInitializationError,
+      new CyclicInitializationError());
+  _test('NoSuchMethodError', isNoSuchMethodError, null);
+  _test('NullThrownError', isNullThrownError, new NullThrownError());
+
+  group('custom `TypeMatcher`', () {
+    // ignore: deprecated_member_use
+    _test('String', const isInstanceOf<String>(), 'hello');
+    _test('String', const _StringMatcher(), 'hello');
+    _test('String', const TypeMatcher<String>(), 'hello');
+  });
+}
+
+// TODO: drop `name` and use a type argument – once Dart2 semantics are enabled
+void _test(String name, Matcher typeMatcher, Object matchingType) {
+  group('for `$name`', () {
+    if (matchingType != null) {
+      test('succeeds', () {
+        shouldPass(matchingType, typeMatcher);
+      });
+    }
+
+    test('fails', () {
+      shouldFail(const TestType(), typeMatcher,
+          "Expected: <Instance of '$name'> Actual: <Instance of 'TestType'>");
+    });
+  });
+}
+
+// Validate that existing implementations continue to work.
+class _StringMatcher extends TypeMatcher {
+  const _StringMatcher() : super(
+            // ignore: deprecated_member_use
+            'String');
+
+  bool matches(item, Map matchState) => item is String;
+}
+
+class TestType {
+  const TestType();
+}
diff --git a/packages/observable/.analysis_options b/packages/observable/.analysis_options
deleted file mode 100644
index dbb6342..0000000
--- a/packages/observable/.analysis_options
+++ /dev/null
@@ -1,49 +0,0 @@
-analyzer:
-  strong-mode: true
-linter:
-  rules:
-    #- always_declare_return_types
-    #- always_specify_types
-    #- annotate_overrides
-    #- avoid_as
-    - avoid_empty_else
-    - avoid_init_to_null
-    - avoid_return_types_on_setters
-    - await_only_futures
-    - camel_case_types
-    - cancel_subscriptions
-    #- close_sinks
-    #- comment_references
-    - constant_identifier_names
-    - control_flow_in_finally
-    - empty_catches
-    - empty_constructor_bodies
-    - empty_statements
-    - hash_and_equals
-    - implementation_imports
-    - iterable_contains_unrelated_type
-    - library_names
-    - library_prefixes
-    - list_remove_unrelated_type
-    - non_constant_identifier_names
-    - one_member_abstracts
-    - only_throw_errors
-    - overridden_fields
-    - package_api_docs
-    - package_names
-    - package_prefixed_library_names
-    - prefer_is_not_empty
-    #- public_member_api_docs
-    #- slash_for_doc_comments
-    #- sort_constructors_first
-    #- sort_unnamed_constructors_first
-    - super_goes_last
-    - test_types_in_equals
-    - throw_in_finally
-    #- type_annotate_public_apis
-    - type_init_formals
-    #- unawaited_futures
-    - unnecessary_brace_in_string_interp
-    - unnecessary_getters_setters
-    - unrelated_type_equality_checks
-    - valid_regexps
diff --git a/packages/observable/.gitignore b/packages/observable/.gitignore
index 96bc6bb..e43b40f 100644
--- a/packages/observable/.gitignore
+++ b/packages/observable/.gitignore
@@ -1,4 +1,5 @@
 # Files and directories created by pub
+.dart_tool/
 .packages
 .pub/
 packages
diff --git a/packages/observable/.travis.yml b/packages/observable/.travis.yml
new file mode 100644
index 0000000..a784107
--- /dev/null
+++ b/packages/observable/.travis.yml
@@ -0,0 +1,53 @@
+language: dart
+
+# Gives more resources on Travis (8GB Ram, 2 CPUs).
+# Do not remove without verifying w/ Travis.
+sudo: required
+addons:
+  chrome: stable
+
+# Build stages: https://docs.travis-ci.com/user/build-stages/.
+stages:
+  - presubmit
+  - build
+  - testing
+
+# 1. Run dartfmt, dartanalyzer, pub run test (VM).
+# 2. Then run a build.
+# 3. Then run tests compiled via dartdevc and dart2js.
+jobs:
+  include:
+    - stage: presubmit
+      script: ./tool/travis.sh dartfmt
+      dart: dev
+    - stage: presubmit
+      script: ./tool/travis.sh dartanalyzer
+      dart: dev
+    - stage: build
+      script: ./tool/travis.sh dartdevc_build
+      dart: dev
+    - stage: testing
+      script: ./tool/travis.sh vm_test
+      dart: dev
+    - stage: testing
+      script: ./tool/travis.sh dartdevc_test
+      dart: dev
+    - stage: testing
+      script: ./tool/travis.sh dart2js_test
+      dart: dev
+
+# Only building master means that we don't run two builds for each pull request.
+branches:
+  only: [master]
+
+# Incremental pub cache and builds.
+cache:
+  directories:
+    - $HOME/.pub-cache
+    - .dart_tool
+
+# Necessary for Chrome and Firefox to run
+before_install:
+ - export DISPLAY=:99.0
+ - sh -e /etc/init.d/xvfb start
+ - "t=0; until (xdpyinfo -display :99 &> /dev/null || test $t -gt 10); do sleep 1; let t=$t+1; done"
diff --git a/packages/observable/CHANGELOG.md b/packages/observable/CHANGELOG.md
index aca61a5..fb9e89d 100644
--- a/packages/observable/CHANGELOG.md
+++ b/packages/observable/CHANGELOG.md
@@ -1,3 +1,247 @@
+## 0.22.1+3
+
+Update implementations of the `cast()` and the deprecated `retype()` methods.
+* The `retype()` method on List and Map is deprecated and will be removed.
+* The `cast()` method should do what the `retype()` method did.
+
+## 0.22.1+2
+
+* Widen dependency on quiver to include v0.29.
+
+## 0.22.1+1
+
+* Fixes for Dart2 runtime type errors.
+
+## 0.22.1
+
+* Added `ObservableList.castFrom`, similar to `List.castFrom`.
+
+* Changed `ObservableList`'s `cast` and `retype` function to create a forwarding
+  instance of `ObservableList` instead of an instance of `List`.
+
+## 0.22.0
+
+* Added `ObservableMap.castFrom`, similar to `Map.castFrom`.
+
+* Fixed a bug where `ObservableMap`'s `cast` and `retype` function would create
+  a new empty instance instead of a forwarding instance.
+
+## 0.21.3
+
+* Support Dart 2 collection methods where previously threw `UnimplementedError`.
+
+## 0.21.2
+
+* Fix `toObservable(deep: false)` to be shallow again.
+* Remove use of `Maps`, for better compatibility with Dart 2.
+
+## 0.21.1
+
+* Updated one test to comply with Dart 2 voidness semantics.
+* Fix Dart 2 runtime cast failure in `toObservable()`.
+* Loosen `ObservableList.from()` to take `Iterable`, not `Iterable<T>`. This
+  matches `List.from()` and avoids some unnecessary cast failures.
+
+## 0.21.0
+
+### Breaking Changes
+
+Version 0.21.0 reverts to version 0.17.0+1 with fixes to support Dart 2.
+Versions 0.18, 0.19, and 0.20 were not used by the package authors and
+effectively unsupported. This resolves the fork that happened at version 0.18
+and development can now be supported by the authors.
+
+#### Reverted Changes
+
+(From 0.20.1)
+* Revert add `Observable<List|Set|Map>.unmodifiable` for immutable collections
+* Revert add `Observable<List|Set|Map>.EMPTY` for empty immutable collections
+  * This can be used as an optimization for libraries that always need to return
+    an observable collection, but don't want to allocate a new instance to
+    represent an empty immutable.
+
+(From 0.20.0)
+* Revert add `ObservableSet`, `SetChangeRecord`, and `SetDiffer`
+
+(From 0.19.0)
+* Revert refactor and deprecate `ObservableMap`-specific API
+    * `ObservableMap` no longer emits `#keys` and `#values` change records
+    * `ObservableMap.spy` is deprecated, becomes `.delegate` instead
+* Revert Potentially breaking: `ObservableMap` may no longer be extended
+
+Revert considered deprecated to be notified of `length` changes.
+
+(From 0.18.0)
+* Revert refactor and deprecate `ObservableList`-specific API
+    * `ObservableList.applyChangeRecords`
+    * `ObservableList.calculateChangeRecords`
+    * `ObservableList.withLength`
+    * `ObservableList.deliverListChanges`
+    * `ObservableList.discardListChanges`
+    * `ObservableList.hasListChanges`
+    * `ObservableList.listChanges`
+    * `ObservableList.notifyListChange`
+* Revert potentially breaking: `ObservableList` may no longer be extended
+
+Revert considered deprecated to be notified of `length`, `isEmpty` and
+`isNotEmpty` `PropertyChangeRecord`s on `ObservableList`
+
+#### Changes Applied on top of version 0.17.0+1
+(With internal change numbers)
+
+* Flip deliverChanges from `@protected` to `@visibleForTesting`. cl/147029982
+* Fix a typing bug in observable when running with DDC: `ChangeRecord.NONE`
+  creates a `List<ChangeRecord>`, while the call sites expect a
+  `List<ListChangeRecord>` or `List<MapChangeRecord>`, respectively.
+  cl/155201160
+* Fix `Observable._isNotGeneric` check. cl/162282107
+* Fix issue with type in deliverChanges. cl/162493576
+* Stop using the comment syntax for generics. cl/163224019
+* Fix ListChangeRecord's added getter. Add checks for the added and removed
+  getters in listChangeTests. cl/169261086.
+* Migrate observable to real generic method syntax. cl/170239122
+* Fix only USES_DYNAMIC_AS_BOTTOM error in observable. cl/179946618
+* Cherry pick https://github.com/dart-lang/observable/pull/46.
+* Stub out Dart 2 core lib changes in ObservableMap.
+* Removed `Observable{List|Map}.NONE` (not Dart2 compatible).
+* Fix issue with type in `ObservableList._notifyListChange`. cl/182284033
+
+## 0.20.4+3
+
+* Support the latest release of `pkg/quiver` (0.27).
+
+## 0.20.4+2
+
+* Support the latest release of `pkg/quiver` (0.26).
+* Bug fix: Some minor type fixes for strict runtimes (and Dart 2.0), namely:
+  * PropertyChangeNotifier merely `extends
+    ChangeNotifier` rather than `extends ChangeNotifier<PropertyChangeRecord>`.
+  * Introduce new `ListChangeRecord.NONE` and `MapChangeRecord.NONE`.
+
+## 0.20.4+1
+
+* Support the latest release of `pkg/quiver` (0.25).
+
+## 0.20.4
+
+* Bug fix: Additional fix around `ObservableList.listChanges`
+
+## 0.20.3
+
+* Bug fix: Avoid emitting an empty list via `ObservableList.listChanges`
+
+## 0.20.2
+
+* Bug fix: Avoid emitting a no-op `MapChangeRecord`
+* Bug fix: Restore `ObservableList.discardListChanges` functionality
+
+## 0.20.1
+
+* Add `Observable<List|Set|Map>.unmodifiable` for immutable collections
+* Add `Observable<List|Set|Map>.EMPTY` for empty immutable collections
+    * This can be used as an optimization for libraries that always
+      need to return an observable collection, but don't want to
+      allocate a new instance to represent an empty immutable.
+
+## 0.20.0
+
+* Add `ObservableSet`, `SetChangeRecord`, and `SetDiffer`
+
+## 0.19.0
+
+* Refactor and deprecate `ObservableMap`-specific API
+    * `ObservableMap` no longer emits `#keys` and `#values` change records
+    * `ObservableMap.spy` is deprecated, becomes `.delegate` instead
+* Potentially breaking: `ObservableMap` may no longer be extended
+
+It is also considered deprecated to be notified of `length` changes.
+
+## 0.18.1
+
+* Bug fix: Do not throw when `Observable<T>.notifyChange` is used
+
+## 0.18.0
+
+* Refactor and deprecate `ObservableList`-specific API
+    * `ObservableList.applyChangeRecords`
+    * `ObservableList.calculateChangeRecords`
+    * `ObservableList.withLength`
+    * `ObservableList.deliverListChanges`
+    * `ObservableList.discardListChanges`
+    * `ObservableList.hasListChanges`
+    * `ObservableList.listChanges`
+    * `ObservableList.notifyListChange`
+* Potentially breaking: `ObservableList` may no longer be extended
+
+It is also considered deprecated to be notified of `length`, `isEmpty`
+and `isNotEmpty` `PropertyChangeRecord`s on `ObservableList` - in a
+future release `ObservableList.changes` will be
+`Stream<List<ListChangeRecord>>`.
+
+## 0.17.0+1
+
+* Revert `PropertyChangeMixin`, which does not work in dart2js
+
+## 0.17.0
+
+This is a larger change with a goal of no runtime changes for current
+customers, but in the future `Observable` will [become][issue_10] a very
+lightweight interface, i.e.:
+
+```dart
+abstract class Observable<C extends ChangeRecord> {
+  Stream<List<C>> get changes;
+}
+```
+
+[issue_10]: https://github.com/dart-lang/observable/issues/10
+
+* Started deprecating the wide `Observable` interface
+    * `ChangeNotifier` should be used as a base class for these methods:
+        * `Observable.observed`
+        * `Observable.unobserved`
+        * `Observable.hasObservers`
+        * `Observable.deliverChanges`
+        * `Observable.notifyChange`
+    * `PropertyChangeNotifier` should be used for these methods:
+        * `Observable.notifyPropertyChange`
+    * Temporarily, `Observable` _uses_ `ChangeNotifier`
+        * Existing users of anything but `implements Observable` should
+          move to implementing or extending `ChangeNotifier`. In a
+          future release `Observable` will reduce API surface down to
+          an abstract `Stream<List<C>> get changes`.
+* Added the `ChangeNotifier` and `PropertyChangeNotifier` classes
+    * Can be used to implement `Observable` in a generic manner
+* Observable is now `Observable<C extends ChangeRecord>`
+    * When passing a generic type `C`, `notifyPropertyChange` is illegal
+
+## 0.16.0
+
+* Refactored `MapChangeRecord`
+    * Added equality and hashCode checks
+    * Added `MapChangeRecord.apply` to apply a change record
+* Added `MapDiffer`, which implements `Differ` for a `Map`
+
+## 0.15.0+2
+
+* Fix a bug in `ListDiffer` that caused a `RangeError`
+
+## 0.15.0+1
+
+* Fix analysis errors caused via missing `/*<E>*/` syntax in `0.15.0`
+
+## 0.15.0
+
+* Added the `Differ` interface, as well as `EqualityDiffer`
+* Refactored list diffing into a `ListDiffer`
+* Added concept of `ChangeRecord.ANY` and `ChangeRecord.NONE`
+    * Low-GC ways to expression "something/nothing" changed
+* Refactored `ListChangeRecord`
+    * Added named constructors for common use cases
+    * Added equality and hashCode checks
+    * Added `ListChangeRecord.apply` to apply a change record
+* Added missing `@override` annotations to satisfy `annotate_overrides`
+
 ## 0.14.0+1
 
 * Add a missing dependency on `pkg/meta`.
diff --git a/packages/observable/PATENTS b/packages/observable/PATENTS
deleted file mode 100644
index 6954196..0000000
--- a/packages/observable/PATENTS
+++ /dev/null
@@ -1,23 +0,0 @@
-Additional IP Rights Grant (Patents)
-
-"This implementation" means the copyrightable works distributed by
-Google as part of the Dart Project.
-
-Google hereby grants to you a perpetual, worldwide, non-exclusive,
-no-charge, royalty-free, irrevocable (except as stated in this
-section) patent license to make, have made, use, offer to sell, sell,
-import, transfer, and otherwise run, modify and propagate the contents
-of this implementation of Dart, where such license applies only to
-those patent claims, both currently owned by Google and acquired in
-the future, licensable by Google that are necessarily infringed by
-this implementation of Dart. This grant does not include claims that
-would be infringed only as a consequence of further modification of
-this implementation. If you or your agent or exclusive licensee
-institute or order or agree to the institution of patent litigation
-against any entity (including a cross-claim or counterclaim in a
-lawsuit) alleging that this implementation of Dart or any code
-incorporated within this implementation of Dart constitutes direct or
-contributory patent infringement, or inducement of patent
-infringement, then any patent rights granted to you under this License
-for this implementation of Dart shall terminate as of the date such
-litigation is filed.
diff --git a/packages/observable/README.md b/packages/observable/README.md
index 3965a6f..bd82499 100644
--- a/packages/observable/README.md
+++ b/packages/observable/README.md
@@ -1,5 +1,20 @@
-Support for marking objects as observable, and getting notifications when those
-objects are mutated.
+[![Build Status](https://travis-ci.org/dart-lang/observable.svg?branch=master)](https://travis-ci.org/dart-lang/observable)
 
-This library is used to observe changes to observable types. It also
-has helpers to make implementing and using observable objects easy.
+Support for detecting and being notified when an object is mutated.
+
+An observable is a way to be notified of a continuous stream of events over time.
+
+Some suggested uses for this library:
+
+* Observe objects for changes, and log when a change occurs
+* Optimize for observable collections in your own APIs and libraries instead of diffing
+* Implement simple data-binding by listening to streams
+
+You may want to look at the former TC39 proposal [Observe.observe](https://github.com/tc39/proposal-observable), which was deprecated.
+
+### Usage
+
+There are two general ways to detect changes:
+
+* Listen to `Observable.changes` and be notified when an object changes
+* Use `Differ.diff` to determine changes between two objects
diff --git a/packages/observable/analysis_options.yaml b/packages/observable/analysis_options.yaml
new file mode 100644
index 0000000..6f04432
--- /dev/null
+++ b/packages/observable/analysis_options.yaml
@@ -0,0 +1,28 @@
+analyzer:
+  strong-mode: true
+linter:
+  rules:
+    # Errors
+    - avoid_empty_else
+    - control_flow_in_finally
+    - empty_statements
+    - test_types_in_equals
+    - throw_in_finally
+    - valid_regexps
+
+    # Style
+    - annotate_overrides
+    - avoid_init_to_null
+    - avoid_return_types_on_setters
+    - await_only_futures
+    - camel_case_types
+    - comment_references
+    - empty_catches
+    - empty_constructor_bodies
+    - hash_and_equals
+    - library_prefixes
+    - non_constant_identifier_names
+    - prefer_is_not_empty
+    - slash_for_doc_comments
+    - type_init_formals
+    - unrelated_type_equality_checks
diff --git a/packages/observable/dart_test.yaml b/packages/observable/dart_test.yaml
new file mode 100644
index 0000000..d2b9d1d
--- /dev/null
+++ b/packages/observable/dart_test.yaml
@@ -0,0 +1,22 @@
+presets:
+  # When run with -P travis, we have different settings/options.
+  #
+  # 1: We don't use Chrome --headless:
+  # 2: We use --reporter expanded
+  # 3: We skip anything tagged "fails-on-travis".
+  travis:
+    # TODO(https://github.com/dart-lang/test/issues/772)
+    override_platforms:
+      chrome:
+        settings:
+          headless: false
+
+    # Don't run any tests that are tagged ["fails-on-travis"].
+    exclude_tags: "fails-on-travis"
+
+    # https://github.com/dart-lang/test/blob/master/doc/configuration.md#reporter
+    reporter: expanded
+
+platforms:
+  - chrome
+  - vm
diff --git a/packages/observable/lib/observable.dart b/packages/observable/lib/observable.dart
index ce82061..516ff70 100644
--- a/packages/observable/lib/observable.dart
+++ b/packages/observable/lib/observable.dart
@@ -4,10 +4,11 @@
 
 library observable;
 
-export 'src/change_record.dart';
-export 'src/list_diff.dart' show ListChangeRecord;
+export 'src/change_notifier.dart' show ChangeNotifier, PropertyChangeNotifier;
+export 'src/differs.dart' show Differ, EqualityDiffer, ListDiffer, MapDiffer;
+export 'src/records.dart'
+    show ChangeRecord, ListChangeRecord, MapChangeRecord, PropertyChangeRecord;
 export 'src/observable.dart';
 export 'src/observable_list.dart';
 export 'src/observable_map.dart';
-export 'src/property_change_record.dart';
 export 'src/to_observable.dart';
diff --git a/packages/observable/lib/src/change_notifier.dart b/packages/observable/lib/src/change_notifier.dart
new file mode 100644
index 0000000..9464740
--- /dev/null
+++ b/packages/observable/lib/src/change_notifier.dart
@@ -0,0 +1,132 @@
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+
+import 'internal.dart';
+import 'observable.dart';
+import 'records.dart';
+
+/// Supplies [changes] and various hooks to implement [Observable].
+///
+/// May use [notifyChange] to queue a change record; they are asynchronously
+/// delivered at the end of the VM turn.
+///
+/// [ChangeNotifier] may be extended, mixed in, or used as a delegate.
+class ChangeNotifier<C extends ChangeRecord> implements Observable<C> {
+  StreamController<List<C>> _changes;
+
+  bool _scheduled = false;
+  List<C> _queue;
+
+  /// Emits a list of changes when the state of the object changes.
+  ///
+  /// Changes should produced in order, if significant.
+  @override
+  Stream<List<C>> get changes {
+    return (_changes ??= new StreamController<List<C>>.broadcast(
+      sync: true,
+      onListen: observed,
+      onCancel: unobserved,
+    ))
+        .stream;
+  }
+
+  /// May override to be notified when [changes] is first observed.
+  @override
+  @protected
+  @mustCallSuper
+  void observed() {}
+
+  /// May override to be notified when [changes] is no longer observed.
+  @override
+  @protected
+  @mustCallSuper
+  void unobserved() {
+    _changes = _queue = null;
+  }
+
+  /// If [hasObservers], synchronously emits [changes] that have been queued.
+  ///
+  /// Returns `true` if changes were emitted.
+  @override
+  @mustCallSuper
+  bool deliverChanges() {
+    List<ChangeRecord> changes;
+    if (_scheduled && hasObservers) {
+      if (_queue != null) {
+        changes = freezeInDevMode(_queue);
+        _queue = null;
+      } else {
+        changes = ChangeRecord.ANY;
+      }
+      _scheduled = false;
+      _changes.add(changes);
+    }
+    return changes != null;
+  }
+
+  /// Whether [changes] has at least one active listener.
+  ///
+  /// May be used to optimize whether to produce change records.
+  @override
+  bool get hasObservers => _changes?.hasListener == true;
+
+  /// Schedules [change] to be delivered.
+  ///
+  /// If [change] is omitted then [ChangeRecord.ANY] will be sent.
+  ///
+  /// If there are no listeners to [changes], this method does nothing.
+  @override
+  void notifyChange([C change]) {
+    if (!hasObservers) {
+      return;
+    }
+    if (change != null) {
+      (_queue ??= <C>[]).add(change);
+    }
+    if (!_scheduled) {
+      scheduleMicrotask(deliverChanges);
+      _scheduled = true;
+    }
+  }
+
+  @Deprecated('Exists to make migrations off Observable easier')
+  @override
+  @protected
+  T notifyPropertyChange<T>(
+    Symbol field,
+    T oldValue,
+    T newValue,
+  ) {
+    throw new UnsupportedError('Not supported by ChangeNotifier');
+  }
+}
+
+/// Supplies property `changes` and various hooks to implement [Observable].
+///
+/// May use `notifyChange` or `notifyPropertyChange` to queue a property change
+/// record; they are asynchronously delivered at the end of the VM turn.
+///
+/// [PropertyChangeNotifier] may be extended or used as a delegate. To use as
+/// a mixin, instead use with [PropertyChangeMixin]:
+///     with ChangeNotifier<PropertyChangeRecord>, PropertyChangeMixin
+class PropertyChangeNotifier extends ChangeNotifier {
+  @override
+  T notifyPropertyChange<T>(
+    Symbol field,
+    T oldValue,
+    T newValue,
+  ) {
+    if (hasObservers && oldValue != newValue) {
+      notifyChange(
+        new PropertyChangeRecord<T>(
+          this,
+          field,
+          oldValue,
+          newValue,
+        ),
+      );
+    }
+    return newValue;
+  }
+}
diff --git a/packages/observable/lib/src/change_record.dart b/packages/observable/lib/src/change_record.dart
deleted file mode 100644
index b4c4f24..0000000
--- a/packages/observable/lib/src/change_record.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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.
-
-library observable.src.change_record;
-
-/// Records a change to an [Observable].
-// TODO(jmesserly): remove this type
-abstract class ChangeRecord {}
diff --git a/packages/observable/lib/src/differs.dart b/packages/observable/lib/src/differs.dart
new file mode 100644
index 0000000..40f005e
--- /dev/null
+++ b/packages/observable/lib/src/differs.dart
@@ -0,0 +1,40 @@
+// 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.
+
+library observable.src.differs;
+
+import 'dart:math' as math;
+
+import 'package:collection/collection.dart';
+
+import 'records.dart';
+
+import 'internal.dart';
+
+part 'differs/list_differ.dart';
+part 'differs/map_differ.dart';
+
+/// Generic comparisons between two comparable objects.
+abstract class Differ<E> {
+  /// Returns a list of change records between [oldValue] and [newValue].
+  ///
+  /// A return value of an empty [ChangeRecord.NONE] means no changes found.
+  List<ChangeRecord> diff(E oldValue, E newValue);
+}
+
+/// Uses [Equality] to determine a simple [ChangeRecord.ANY] response.
+class EqualityDiffer<E> implements Differ<E> {
+  final Equality<E> _equality;
+
+  const EqualityDiffer([this._equality = const DefaultEquality()]);
+
+  const EqualityDiffer.identity() : this._equality = const IdentityEquality();
+
+  @override
+  List<ChangeRecord> diff(E oldValue, E newValue) {
+    return _equality.equals(oldValue, newValue)
+        ? ChangeRecord.NONE
+        : ChangeRecord.ANY;
+  }
+}
diff --git a/packages/observable/lib/src/differs/list_differ.dart b/packages/observable/lib/src/differs/list_differ.dart
new file mode 100644
index 0000000..c59830e
--- /dev/null
+++ b/packages/observable/lib/src/differs/list_differ.dart
@@ -0,0 +1,469 @@
+// 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.
+
+part of observable.src.differs;
+
+/// Determines differences between two lists, returning [ListChangeRecord]s.
+///
+/// While [ListChangeRecord] has more information and can be replayed they carry
+/// a more significant cost to calculate and create and should only be used when
+/// the details in the record will actually be used.
+///
+/// See also [EqualityDiffer] for a simpler comparison.
+class ListDiffer<E> implements Differ<List<E>> {
+  final Equality<E> _equality;
+
+  const ListDiffer([this._equality = const DefaultEquality()]);
+
+  @override
+  List<ListChangeRecord<E>> diff(List<E> e1, List<E> e2) {
+    return _calcSplices<E>(
+      e2,
+      _equality,
+      0,
+      e2.length,
+      e1,
+      0,
+      e1.length,
+    );
+  }
+}
+
+enum _Edit {
+  leave,
+  update,
+  add,
+  delete,
+}
+
+// Note: This function is *based* on the computation of the Levenshtein
+// "edit" distance. The one change is that "updates" are treated as two
+// edits - not one. With List splices, an update is really a delete
+// followed by an add. By retaining this, we optimize for "keeping" the
+// maximum array items in the original array. For example:
+//
+//   'xxxx123' -> '123yyyy'
+//
+// With 1-edit updates, the shortest path would be just to update all seven
+// characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
+// leaves the substring '123' intact.
+List<List<int>> _calcEditDistance<E>(
+  List<E> current,
+  int currentStart,
+  int currentEnd,
+  List<E> old,
+  int oldStart,
+  int oldEnd,
+) {
+  // 'Deletion' columns.
+  final rowCount = oldEnd - oldStart + 1;
+  final columnCount = currentEnd - currentStart + 1;
+  final distances = new List<List<int>>(rowCount);
+
+  // 'Addition' rows. Initialize null column.
+  for (var i = 0; i < rowCount; i++) {
+    distances[i] = new List<int>(columnCount);
+    distances[i][0] = i;
+  }
+
+  // Initialize null row.
+  for (var j = 0; j < columnCount; j++) {
+    distances[0][j] = j;
+  }
+
+  for (var i = 1; i < rowCount; i++) {
+    for (var j = 1; j < columnCount; j++) {
+      if (old[oldStart + i - 1] == current[currentStart + j - 1]) {
+        distances[i][j] = distances[i - 1][j - 1];
+      } else {
+        final north = distances[i - 1][j] + 1;
+        final west = distances[i][j - 1] + 1;
+        distances[i][j] = math.min(north, west);
+      }
+    }
+  }
+
+  return distances;
+}
+
+// This starts at the final weight, and walks "backward" by finding
+// the minimum previous weight recursively until the origin of the weight
+// matrix.
+Iterable<_Edit> _spliceOperationsFromEditDistances(List<List<int>> distances) {
+  var i = distances.length - 1;
+  var j = distances[0].length - 1;
+  var current = distances[i][j];
+  final edits = <_Edit>[];
+  while (i > 0 || j > 0) {
+    if (i == 0) {
+      edits.add(_Edit.add);
+      j--;
+      continue;
+    }
+    if (j == 0) {
+      edits.add(_Edit.delete);
+      i--;
+      continue;
+    }
+    final northWest = distances[i - 1][j - 1];
+    final west = distances[i - 1][j];
+    final north = distances[i][j - 1];
+
+    final min = math.min(math.min(west, north), northWest);
+    if (min == northWest) {
+      if (northWest == current) {
+        edits.add(_Edit.leave);
+      } else {
+        edits.add(_Edit.update);
+        current = northWest;
+      }
+      i--;
+      j--;
+    } else if (min == west) {
+      edits.add(_Edit.delete);
+      i--;
+      current = west;
+    } else {
+      edits.add(_Edit.add);
+      j--;
+      current = north;
+    }
+  }
+
+  return edits.reversed;
+}
+
+int _sharedPrefix<E>(
+  Equality<E> equality,
+  List<E> e1,
+  List<E> e2,
+  int searchLength,
+) {
+  for (var i = 0; i < searchLength; i++) {
+    if (!equality.equals(e1[i], e2[i])) {
+      return i;
+    }
+  }
+  return searchLength;
+}
+
+int _sharedSuffix<E>(
+  Equality<E> equality,
+  List<E> e1,
+  List<E> e2,
+  int searchLength,
+) {
+  var index1 = e1.length;
+  var index2 = e2.length;
+  var count = 0;
+  while (count < searchLength && equality.equals(e1[--index1], e2[--index2])) {
+    count++;
+  }
+  return count;
+}
+
+// Lacking individual splice mutation information, the minimal set of
+// splices can be synthesized given the previous state and final state of an
+// array. The basic approach is to calculate the edit distance matrix and
+// choose the shortest path through it.
+//
+// Complexity: O(l * p)
+//   l: The length of the current array
+//   p: The length of the old array
+List<ListChangeRecord<E>> _calcSplices<E>(
+  List<E> current,
+  Equality<E> equality,
+  int currentStart,
+  int currentEnd,
+  List<E> old,
+  int oldStart,
+  int oldEnd,
+) {
+  var prefixCount = 0;
+  var suffixCount = 0;
+  final minLength = math.min(currentEnd - currentStart, oldEnd - oldStart);
+  if (currentStart == 0 && oldStart == 0) {
+    prefixCount = _sharedPrefix(
+      equality,
+      current,
+      old,
+      minLength,
+    );
+  }
+  if (currentEnd == current.length && oldEnd == old.length) {
+    suffixCount = _sharedSuffix(
+      equality,
+      current,
+      old,
+      minLength - prefixCount,
+    );
+  }
+
+  currentStart += prefixCount;
+  oldStart += prefixCount;
+  currentEnd -= suffixCount;
+  oldEnd -= suffixCount;
+
+  if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) {
+    return const [];
+  }
+
+  if (currentStart == currentEnd) {
+    final spliceRemoved = old.sublist(oldStart, oldEnd);
+    return [
+      new ListChangeRecord<E>.remove(
+        current,
+        currentStart,
+        spliceRemoved,
+      ),
+    ];
+  }
+  if (oldStart == oldEnd) {
+    return [
+      new ListChangeRecord<E>.add(
+        current,
+        currentStart,
+        currentEnd - currentStart,
+      ),
+    ];
+  }
+
+  final ops = _spliceOperationsFromEditDistances(
+    _calcEditDistance(
+      current,
+      currentStart,
+      currentEnd,
+      old,
+      oldStart,
+      oldEnd,
+    ),
+  );
+
+  var spliceIndex = -1;
+  var spliceRemovals = <E>[];
+  var spliceAddedCount = 0;
+
+  bool hasSplice() => spliceIndex != -1;
+  void resetSplice() {
+    spliceIndex = -1;
+    spliceRemovals = <E>[];
+    spliceAddedCount = 0;
+  }
+
+  var splices = <ListChangeRecord<E>>[];
+
+  var index = currentStart;
+  var oldIndex = oldStart;
+  for (final op in ops) {
+    switch (op) {
+      case _Edit.leave:
+        if (hasSplice()) {
+          splices.add(new ListChangeRecord<E>(
+            current,
+            spliceIndex,
+            removed: spliceRemovals,
+            addedCount: spliceAddedCount,
+          ));
+          resetSplice();
+        }
+        index++;
+        oldIndex++;
+        break;
+      case _Edit.update:
+        if (!hasSplice()) {
+          spliceIndex = index;
+        }
+        spliceAddedCount++;
+        index++;
+        spliceRemovals.add(old[oldIndex]);
+        oldIndex++;
+        break;
+      case _Edit.add:
+        if (!hasSplice()) {
+          spliceIndex = index;
+        }
+        spliceAddedCount++;
+        index++;
+        break;
+      case _Edit.delete:
+        if (!hasSplice()) {
+          spliceIndex = index;
+        }
+        spliceRemovals.add(old[oldIndex]);
+        oldIndex++;
+        break;
+    }
+  }
+  if (hasSplice()) {
+    splices.add(new ListChangeRecord<E>(
+      current,
+      spliceIndex,
+      removed: spliceRemovals,
+      addedCount: spliceAddedCount,
+    ));
+  }
+  assert(() {
+    splices = new List<ListChangeRecord<E>>.unmodifiable(splices);
+    return true;
+  }());
+  return splices;
+}
+
+int _intersect(int start1, int end1, int start2, int end2) {
+  return math.min(end1, end2) - math.max(start1, start2);
+}
+
+void _mergeSplices<E>(
+  List<ListChangeRecord<E>> splices,
+  ListChangeRecord<E> record,
+) {
+  var spliceIndex = record.index;
+  var spliceRemoved = record.removed;
+  var spliceAdded = record.addedCount;
+
+  var inserted = false;
+  var insertionOffset = 0;
+
+  // I think the way this works is:
+  // - the loop finds where the merge should happen
+  // - it applies the merge in a particular splice
+  // - then continues and updates the subsequent splices with any offset diff.
+  for (var i = 0; i < splices.length; i++) {
+    var current = splices[i];
+    current = splices[i] = new ListChangeRecord<E>(
+      current.object,
+      current.index + insertionOffset,
+      removed: current.removed,
+      addedCount: current.addedCount,
+    );
+
+    if (inserted) continue;
+
+    var intersectCount = _intersect(
+      spliceIndex,
+      spliceIndex + spliceRemoved.length,
+      current.index,
+      current.index + current.addedCount,
+    );
+    if (intersectCount >= 0) {
+      // Merge the two splices.
+      splices.removeAt(i);
+      i--;
+
+      insertionOffset -= current.addedCount - current.removed.length;
+      spliceAdded += current.addedCount - intersectCount;
+
+      final deleteCount =
+          spliceRemoved.length + current.removed.length - intersectCount;
+      if (spliceAdded == 0 && deleteCount == 0) {
+        // Merged splice is a no-op, discard.
+        inserted = true;
+      } else {
+        final removed = current.removed.toList();
+        if (spliceIndex < current.index) {
+          // Some prefix of splice.removed is prepended to current.removed.
+          removed.insertAll(
+            0,
+            spliceRemoved.getRange(0, current.index - spliceIndex),
+          );
+        }
+        if (spliceIndex + spliceRemoved.length >
+            current.index + current.addedCount) {
+          // Some suffix of splice.removed is appended to current.removed.
+          removed.addAll(spliceRemoved.getRange(
+            current.index + current.addedCount - spliceIndex,
+            spliceRemoved.length,
+          ));
+        }
+        spliceRemoved = removed;
+        if (current.index < spliceIndex) {
+          spliceIndex = current.index;
+        }
+      }
+    } else if (spliceIndex < current.index) {
+      // Insert splice here.
+      inserted = true;
+      splices.insert(
+        i,
+        new ListChangeRecord<E>(
+          record.object,
+          spliceIndex,
+          removed: spliceRemoved,
+          addedCount: spliceAdded,
+        ),
+      );
+      i++;
+      final offset = spliceAdded - spliceRemoved.length;
+      current = splices[i] = new ListChangeRecord<E>(
+        current.object,
+        current.index + offset,
+        removed: current.removed,
+        addedCount: current.addedCount,
+      );
+      insertionOffset += offset;
+    }
+  }
+  if (!inserted) {
+    splices.add(new ListChangeRecord<E>(
+      record.object,
+      spliceIndex,
+      removed: spliceRemoved,
+      addedCount: spliceAdded,
+    ));
+  }
+}
+
+List<ListChangeRecord<E>> _createInitialSplices<E>(
+  List<E> list,
+  List<ListChangeRecord<E>> records,
+) {
+  final splices = <ListChangeRecord<E>>[];
+  for (var i = 0; i < records.length; i++) {
+    _mergeSplices(splices, records[i]);
+  }
+  return splices;
+}
+
+// We need to summarize change records. Consumers of these records want to
+// apply the batch sequentially, and ensure that they can find inserted
+// items by looking at that position in the list. This property does not
+// hold in our record-as-you-go records. Consider:
+//
+//     var model = toObservable(['a', 'b']);
+//     model.removeAt(1);
+//     model.insertAll(0, ['c', 'd', 'e']);
+//     model.removeRange(1, 3);
+//     model.insert(1, 'f');
+//
+// Here, we inserted some records and then removed some of them.
+// If someone processed these records naively, they would "play back" the
+// insert incorrectly, because those items will be shifted.
+List<ListChangeRecord<E>> projectListSplices<E>(
+    List<E> list, List<ListChangeRecord<E>> records,
+    [Equality<E> equality]) {
+  equality ??= new DefaultEquality<E>();
+  if (records.length <= 1) return records;
+  final splices = <ListChangeRecord<E>>[];
+  final initialSplices = _createInitialSplices(list, records);
+  for (final splice in initialSplices) {
+    if (splice.addedCount == 1 && splice.removed.length == 1) {
+      if (splice.removed[0] != list[splice.index]) {
+        splices.add(splice);
+      }
+      continue;
+    }
+    splices.addAll(
+      _calcSplices(
+        list,
+        equality,
+        splice.index,
+        splice.index + splice.addedCount,
+        splice.removed,
+        0,
+        splice.removed.length,
+      ),
+    );
+  }
+  return splices;
+}
diff --git a/packages/observable/lib/src/differs/map_differ.dart b/packages/observable/lib/src/differs/map_differ.dart
new file mode 100644
index 0000000..6f01fb7
--- /dev/null
+++ b/packages/observable/lib/src/differs/map_differ.dart
@@ -0,0 +1,38 @@
+// 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.
+
+part of observable.src.differs;
+
+/// Determines differences between two maps, returning [MapChangeRecord]s.
+///
+/// While [MapChangeRecord] has more information and can be replayed they carry
+/// a more significant cost to calculate and create and should only be used when
+/// the details in the record will actually be used.
+///
+/// See also [EqualityDiffer] for a simpler comparison.
+class MapDiffer<K, V> implements Differ<Map<K, V>> {
+  const MapDiffer();
+
+  @override
+  List<MapChangeRecord<K, V>> diff(Map<K, V> oldValue, Map<K, V> newValue) {
+    if (identical(oldValue, newValue)) {
+      return const [];
+    }
+    final changes = <MapChangeRecord<K, V>>[];
+    oldValue.forEach((oldK, oldV) {
+      final newV = newValue[oldK];
+      if (newV == null && !newValue.containsKey(oldK)) {
+        changes.add(new MapChangeRecord<K, V>.remove(oldK, oldV));
+      } else if (newV != oldV) {
+        changes.add(new MapChangeRecord<K, V>(oldK, oldV, newV));
+      }
+    });
+    newValue.forEach((newK, newV) {
+      if (!oldValue.containsKey(newK)) {
+        changes.add(new MapChangeRecord<K, V>.insert(newK, newV));
+      }
+    });
+    return freezeInDevMode(changes);
+  }
+}
diff --git a/packages/observable/lib/src/internal.dart b/packages/observable/lib/src/internal.dart
new file mode 100644
index 0000000..d600f50
--- /dev/null
+++ b/packages/observable/lib/src/internal.dart
@@ -0,0 +1,12 @@
+// 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.
+
+List<E> freezeInDevMode<E>(List<E> list) {
+  if (list == null) return const [];
+  assert(() {
+    list = new List<E>.unmodifiable(list);
+    return true;
+  }());
+  return list;
+}
diff --git a/packages/observable/lib/src/list_diff.dart b/packages/observable/lib/src/list_diff.dart
deleted file mode 100644
index c16a613..0000000
--- a/packages/observable/lib/src/list_diff.dart
+++ /dev/null
@@ -1,398 +0,0 @@
-// 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.
-
-library observable.src.list_diff;
-
-import 'dart:collection' show UnmodifiableListView;
-import 'dart:math' as math;
-
-import 'change_record.dart' show ChangeRecord;
-
-/// A summary of an individual change to a [List].
-///
-/// Each delta represents that at the [index], [removed] sequence of items were
-/// removed, and counting forward from [index], [addedCount] items were added.
-class ListChangeRecord /* TODO(tvolkert): remove */ extends ChangeRecord {
-  /// The list that changed.
-  final List object;
-
-  /// The index of the change.
-  int get index => _index;
-
-  /// The items removed, if any. Otherwise this will be an empty list.
-  List get removed => _unmodifiableRemoved;
-  UnmodifiableListView _unmodifiableRemoved;
-
-  /// Mutable version of [removed], used during the algorithms as they are
-  /// constructing the object.
-  List _removed;
-
-  /// The number of items added.
-  int get addedCount => _addedCount;
-
-  // Note: conceptually these are final, but for convenience we increment it as
-  // we build the object. It will be "frozen" by the time it is returned the the
-  // user.
-  int _index, _addedCount;
-
-  ListChangeRecord._(this.object, this._index, List removed, this._addedCount)
-      : _removed = removed,
-        _unmodifiableRemoved = new UnmodifiableListView(removed);
-
-  factory ListChangeRecord(List object, int index,
-      {List removed, int addedCount}) {
-    if (removed == null) removed = [];
-    if (addedCount == null) addedCount = 0;
-    return new ListChangeRecord._(object, index, removed, addedCount);
-  }
-
-  /// Returns true if the provided [ref] index was changed by this operation.
-  bool indexChanged(int ref) {
-    // If ref is before the index, then it wasn't changed.
-    if (ref < index) return false;
-
-    // If this was a shift operation, anything after index is changed.
-    if (addedCount != removed.length) return true;
-
-    // Otherwise, anything in the update range was changed.
-    return ref < index + addedCount;
-  }
-
-  String toString() => '#<ListChangeRecord index: $index, '
-      'removed: $removed, addedCount: $addedCount>';
-}
-
-// Note: This function is *based* on the computation of the Levenshtein
-// "edit" distance. The one change is that "updates" are treated as two
-// edits - not one. With List splices, an update is really a delete
-// followed by an add. By retaining this, we optimize for "keeping" the
-// maximum array items in the original array. For example:
-//
-//   'xxxx123' -> '123yyyy'
-//
-// With 1-edit updates, the shortest path would be just to update all seven
-// characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
-// leaves the substring '123' intact.
-List<List<int>> _calcEditDistances(List current, int currentStart,
-    int currentEnd, List old, int oldStart, int oldEnd) {
-  // "Deletion" columns
-  int rowCount = oldEnd - oldStart + 1;
-  int columnCount = currentEnd - currentStart + 1;
-  List<List<int>> distances = new List<List<int>>(rowCount);
-
-  // "Addition" rows. Initialize null column.
-  for (int i = 0; i < rowCount; i++) {
-    distances[i] = new List(columnCount);
-    distances[i][0] = i;
-  }
-
-  // Initialize null row
-  for (int j = 0; j < columnCount; j++) {
-    distances[0][j] = j;
-  }
-
-  for (int i = 1; i < rowCount; i++) {
-    for (int j = 1; j < columnCount; j++) {
-      if (old[oldStart + i - 1] == current[currentStart + j - 1]) {
-        distances[i][j] = distances[i - 1][j - 1];
-      } else {
-        int north = distances[i - 1][j] + 1;
-        int west = distances[i][j - 1] + 1;
-        distances[i][j] = math.min(north, west);
-      }
-    }
-  }
-
-  return distances;
-}
-
-const _kEditLeave = 0;
-const _kEditUpdate = 1;
-const _kEditAdd = 2;
-const _kEditDelete = 3;
-
-// This starts at the final weight, and walks "backward" by finding
-// the minimum previous weight recursively until the origin of the weight
-// matrix.
-List<int> _spliceOperationsFromEditDistances(List<List<int>> distances) {
-  int i = distances.length - 1;
-  int j = distances[0].length - 1;
-  int current = distances[i][j];
-  List<int> edits = <int>[];
-  while (i > 0 || j > 0) {
-    if (i == 0) {
-      edits.add(_kEditAdd);
-      j--;
-      continue;
-    }
-    if (j == 0) {
-      edits.add(_kEditDelete);
-      i--;
-      continue;
-    }
-    int northWest = distances[i - 1][j - 1];
-    int west = distances[i - 1][j];
-    int north = distances[i][j - 1];
-
-    int min = math.min(math.min(west, north), northWest);
-
-    if (min == northWest) {
-      if (northWest == current) {
-        edits.add(_kEditLeave);
-      } else {
-        edits.add(_kEditUpdate);
-        current = northWest;
-      }
-      i--;
-      j--;
-    } else if (min == west) {
-      edits.add(_kEditDelete);
-      i--;
-      current = west;
-    } else {
-      edits.add(_kEditAdd);
-      j--;
-      current = north;
-    }
-  }
-
-  return edits.reversed.toList();
-}
-
-int _sharedPrefix(List arr1, List arr2, int searchLength) {
-  for (int i = 0; i < searchLength; i++) {
-    if (arr1[i] != arr2[i]) {
-      return i;
-    }
-  }
-  return searchLength;
-}
-
-int _sharedSuffix(List arr1, List arr2, int searchLength) {
-  int index1 = arr1.length;
-  int index2 = arr2.length;
-  int count = 0;
-  while (count < searchLength && arr1[--index1] == arr2[--index2]) {
-    count++;
-  }
-  return count;
-}
-
-/// Lacking individual splice mutation information, the minimal set of
-/// splices can be synthesized given the previous state and final state of an
-/// array. The basic approach is to calculate the edit distance matrix and
-/// choose the shortest path through it.
-///
-/// Complexity: O(l * p)
-///   l: The length of the current array
-///   p: The length of the old array
-List<ListChangeRecord> calcSplices(List current, int currentStart,
-    int currentEnd, List old, int oldStart, int oldEnd) {
-  int prefixCount = 0;
-  int suffixCount = 0;
-
-  int minLength = math.min(currentEnd - currentStart, oldEnd - oldStart);
-  if (currentStart == 0 && oldStart == 0) {
-    prefixCount = _sharedPrefix(current, old, minLength);
-  }
-
-  if (currentEnd == current.length && oldEnd == old.length) {
-    suffixCount = _sharedSuffix(current, old, minLength - prefixCount);
-  }
-
-  currentStart += prefixCount;
-  oldStart += prefixCount;
-  currentEnd -= suffixCount;
-  oldEnd -= suffixCount;
-
-  if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) {
-    return const [];
-  }
-
-  if (currentStart == currentEnd) {
-    ListChangeRecord splice = new ListChangeRecord(current, currentStart);
-    while (oldStart < oldEnd) {
-      splice._removed.add(old[oldStart++]);
-    }
-
-    return [splice];
-  } else if (oldStart == oldEnd) {
-    return [
-      new ListChangeRecord(current, currentStart,
-          addedCount: currentEnd - currentStart)
-    ];
-  }
-
-  List<int> ops = _spliceOperationsFromEditDistances(_calcEditDistances(
-      current, currentStart, currentEnd, old, oldStart, oldEnd));
-
-  ListChangeRecord splice;
-  List<ListChangeRecord> splices = <ListChangeRecord>[];
-  int index = currentStart;
-  int oldIndex = oldStart;
-  for (int i = 0; i < ops.length; i++) {
-    switch (ops[i]) {
-      case _kEditLeave:
-        if (splice != null) {
-          splices.add(splice);
-          splice = null;
-        }
-
-        index++;
-        oldIndex++;
-        break;
-      case _kEditUpdate:
-        if (splice == null) splice = new ListChangeRecord(current, index);
-
-        splice._addedCount++;
-        index++;
-
-        splice._removed.add(old[oldIndex]);
-        oldIndex++;
-        break;
-      case _kEditAdd:
-        if (splice == null) splice = new ListChangeRecord(current, index);
-
-        splice._addedCount++;
-        index++;
-        break;
-      case _kEditDelete:
-        if (splice == null) splice = new ListChangeRecord(current, index);
-
-        splice._removed.add(old[oldIndex]);
-        oldIndex++;
-        break;
-    }
-  }
-
-  if (splice != null) splices.add(splice);
-  return splices;
-}
-
-int _intersect(int start1, int end1, int start2, int end2) =>
-    math.min(end1, end2) - math.max(start1, start2);
-
-void _mergeSplice(List<ListChangeRecord> splices, ListChangeRecord record) {
-  ListChangeRecord splice = new ListChangeRecord(record.object, record.index,
-      removed: record._removed.toList(), addedCount: record.addedCount);
-
-  bool inserted = false;
-  int insertionOffset = 0;
-
-  // I think the way this works is:
-  // - the loop finds where the merge should happen
-  // - it applies the merge in a particular splice
-  // - then continues and updates the subsequent splices with any offset diff.
-  for (int i = 0; i < splices.length; i++) {
-    final ListChangeRecord current = splices[i];
-    current._index += insertionOffset;
-
-    if (inserted) continue;
-
-    int intersectCount = _intersect(
-        splice.index,
-        splice.index + splice.removed.length,
-        current.index,
-        current.index + current.addedCount);
-
-    if (intersectCount >= 0) {
-      // Merge the two splices
-
-      splices.removeAt(i);
-      i--;
-
-      insertionOffset -= current.addedCount - current.removed.length;
-
-      splice._addedCount += current.addedCount - intersectCount;
-      int deleteCount =
-          splice.removed.length + current.removed.length - intersectCount;
-
-      if (splice.addedCount == 0 && deleteCount == 0) {
-        // merged splice is a noop. discard.
-        inserted = true;
-      } else {
-        List removed = current._removed;
-
-        if (splice.index < current.index) {
-          // some prefix of splice.removed is prepended to current.removed.
-          removed.insertAll(
-              0, splice.removed.getRange(0, current.index - splice.index));
-        }
-
-        if (splice.index + splice.removed.length >
-            current.index + current.addedCount) {
-          // some suffix of splice.removed is appended to current.removed.
-          removed.addAll(splice.removed.getRange(
-              current.index + current.addedCount - splice.index,
-              splice.removed.length));
-        }
-
-        splice._removed = removed;
-        splice._unmodifiableRemoved = current._unmodifiableRemoved;
-        if (current.index < splice.index) {
-          splice._index = current.index;
-        }
-      }
-    } else if (splice.index < current.index) {
-      // Insert splice here.
-
-      inserted = true;
-
-      splices.insert(i, splice);
-      i++;
-
-      int offset = splice.addedCount - splice.removed.length;
-      current._index += offset;
-      insertionOffset += offset;
-    }
-  }
-
-  if (!inserted) splices.add(splice);
-}
-
-List<ListChangeRecord> _createInitialSplices(
-    List<Object> list, List<ListChangeRecord> records) {
-  List<ListChangeRecord> splices = [];
-  for (ListChangeRecord record in records) {
-    _mergeSplice(splices, record);
-  }
-  return splices;
-}
-
-/// We need to summarize change records. Consumers of these records want to
-/// apply the batch sequentially, and ensure that they can find inserted
-/// items by looking at that position in the list. This property does not
-/// hold in our record-as-you-go records. Consider:
-///
-///     var model = toObservable(['a', 'b']);
-///     model.removeAt(1);
-///     model.insertAll(0, ['c', 'd', 'e']);
-///     model.removeRange(1, 3);
-///     model.insert(1, 'f');
-///
-/// Here, we inserted some records and then removed some of them.
-/// If someone processed these records naively, they would "play back" the
-/// insert incorrectly, because those items will be shifted.
-List<ListChangeRecord> projectListSplices(
-    List<Object> list, List<ListChangeRecord> records) {
-  if (records.length <= 1) return records;
-
-  List<ListChangeRecord> splices = <ListChangeRecord>[];
-  for (ListChangeRecord splice in _createInitialSplices(list, records)) {
-    if (splice.addedCount == 1 && splice.removed.length == 1) {
-      if (splice.removed[0] != list[splice.index]) splices.add(splice);
-      continue;
-    }
-
-    splices.addAll(calcSplices(
-        list,
-        splice.index,
-        splice.index + splice.addedCount,
-        splice._removed,
-        0,
-        splice.removed.length));
-  }
-
-  return splices;
-}
diff --git a/packages/observable/lib/src/observable.dart b/packages/observable/lib/src/observable.dart
index 7d1ec6f..edc536b 100644
--- a/packages/observable/lib/src/observable.dart
+++ b/packages/observable/lib/src/observable.dart
@@ -5,74 +5,57 @@
 library observable.src.observable;
 
 import 'dart:async';
-import 'dart:collection' show UnmodifiableListView;
 
 import 'package:meta/meta.dart';
 
-import 'change_record.dart' show ChangeRecord;
-import 'property_change_record.dart' show PropertyChangeRecord;
+import 'change_notifier.dart';
+import 'records.dart';
 
-/// Represents an object with observable properties. This is used by data in
-/// model-view architectures to notify interested parties of [changes] to the
-/// object's properties (fields or getter/setter pairs).
+/// Represents an object with observable state or properties.
 ///
 /// The interface does not require any specific technique to implement
-/// observability. You can implement it in the following ways:
-///
-/// - Deriving from this class via a mixin or base class. When a field,
-///   property, or indexable item is changed, the derived class should call
-///   [notifyPropertyChange]. See that method for an example.
-/// - Implementing this interface and providing your own implementation.
-abstract class Observable {
-  StreamController<List<ChangeRecord>> _changes;
+/// observability. You may implement it in the following ways:
+/// - Extend or mixin [ChangeNotifier]
+/// - Implement the interface yourself and provide your own implementation
+abstract class Observable<C extends ChangeRecord> {
+  // To be removed when https://github.com/dart-lang/observable/issues/10
+  final ChangeNotifier<C> _delegate = new ChangeNotifier<C>();
 
-  List<ChangeRecord> _records;
+  // Whether Observable was not given a type.
+  final bool _isNotGeneric = C == dynamic || C == ChangeRecord;
 
-  /// The stream of property change records to this object, delivered
-  /// asynchronously.
+  /// Emits a list of changes when the state of the object changes.
   ///
-  /// [deliverChanges] can be called to force synchronous delivery.
-  Stream<List<ChangeRecord>> get changes {
-    if (_changes == null) {
-      _changes = new StreamController.broadcast(
-          sync: true, onListen: observed, onCancel: unobserved);
-    }
-    return _changes.stream;
-  }
+  /// Changes should produced in order, if significant.
+  Stream<List<C>> get changes => _delegate.changes;
 
-  /// Derived classes may override this method to be called when the [changes]
-  /// are first observed.
-  // TODO(tvolkert): @mustCallSuper (github.com/dart-lang/sdk/issues/27275)
+  /// May override to be notified when [changes] is first observed.
   @protected
-  void observed() {}
+  @mustCallSuper
+  @Deprecated('Use ChangeNotifier instead to have this method available')
+  // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
+  // ignore: invalid_use_of_protected_member
+  void observed() => _delegate.observed();
 
-  /// Derived classes may override this method to be called when the [changes]
-  /// are no longer being observed.
-  // TODO(tvolkert): @mustCallSuper (github.com/dart-lang/sdk/issues/27275)
+  /// May override to be notified when [changes] is no longer observed.
   @protected
-  void unobserved() {
-    // Free some memory
-    _changes = null;
-  }
+  @mustCallSuper
+  @Deprecated('Use ChangeNotifier instead to have this method available')
+  // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
+  // ignore: invalid_use_of_protected_member
+  void unobserved() => _delegate.unobserved();
 
   /// True if this object has any observers.
-  bool get hasObservers => _changes != null && _changes.hasListener;
+  @Deprecated('Use ChangeNotifier instead to have this method available')
+  bool get hasObservers => _delegate.hasObservers;
 
-  /// Synchronously deliver pending [changes].
+  /// If [hasObservers], synchronously emits [changes] that have been queued.
   ///
-  /// Returns `true` if any records were delivered, otherwise `false`.
-  /// Pending records will be cleared regardless, to keep newly added
-  /// observers from being notified of changes that occurred before
-  /// they started observing.
-  bool deliverChanges() {
-    List<ChangeRecord> records = _records;
-    _records = null;
-    if (hasObservers && records != null) {
-      _changes.add(new UnmodifiableListView<ChangeRecord>(records));
-      return true;
-    }
-    return false;
-  }
+  /// Returns `true` if changes were emitted.
+  @Deprecated('Use ChangeNotifier instead to have this method available')
+  // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
+  // ignore: invalid_use_of_protected_member
+  bool deliverChanges() => _delegate.deliverChanges();
 
   /// Notify that the [field] name of this object has been changed.
   ///
@@ -80,27 +63,45 @@
   /// equal, no change will be recorded.
   ///
   /// For convenience this returns [newValue].
-  /*=T*/ notifyPropertyChange/*<T>*/(
-      Symbol field, /*=T*/ oldValue, /*=T*/ newValue) {
+  ///
+  /// ## Deprecated
+  ///
+  /// All [Observable] objects will no longer be required to emit change records
+  /// when any property changes. For example, `ObservableList` will only emit
+  /// on `ObservableList.changes`, instead of on `ObservableList.listChanges`.
+  ///
+  /// If you are using a typed `implements/extends Observable<C>`, it is illegal
+  /// to call this method - will throw an [UnsupportedError] when called.
+  @Deprecated('Use PropertyChangeNotifier')
+  T notifyPropertyChange<T>(
+    Symbol field,
+    T oldValue,
+    T newValue,
+  ) {
     if (hasObservers && oldValue != newValue) {
-      notifyChange(new PropertyChangeRecord(this, field, oldValue, newValue));
+      if (_isNotGeneric) {
+        notifyChange(
+          new PropertyChangeRecord(
+            this,
+            field,
+            oldValue,
+            newValue,
+          ) as C,
+        );
+      } else {
+        // Internal specific patch: Just do nothing.
+        //
+        // Generic typed Observable does not support.
+      }
     }
     return newValue;
   }
 
-  /// Notify observers of a change.
+  /// Schedules [change] to be delivered.
   ///
-  /// This will automatically schedule [deliverChanges].
+  /// If [change] is omitted then [ChangeRecord.ANY] will be sent.
   ///
-  /// For most objects [Observable.notifyPropertyChange] is more convenient, but
-  /// collections sometimes deliver other types of changes such as a
-  /// [MapChangeRecord].
-  void notifyChange(ChangeRecord record) {
-    if (!hasObservers) return;
-    if (_records == null) {
-      _records = [];
-      scheduleMicrotask(deliverChanges);
-    }
-    _records.add(record);
-  }
+  /// If there are no listeners to [changes], this method does nothing.
+  @Deprecated('Use ChangeNotifier instead to have this method available')
+  void notifyChange([C change]) => _delegate.notifyChange(change);
 }
diff --git a/packages/observable/lib/src/observable_list.dart b/packages/observable/lib/src/observable_list.dart
index 16c49f5..d91bb68 100644
--- a/packages/observable/lib/src/observable_list.dart
+++ b/packages/observable/lib/src/observable_list.dart
@@ -7,16 +7,31 @@
 import 'dart:async';
 import 'dart:collection' show ListBase, UnmodifiableListView;
 
-import 'list_diff.dart' show ListChangeRecord, projectListSplices, calcSplices;
+import 'differs.dart';
+import 'records.dart';
 import 'observable.dart' show Observable;
 
 /// Represents an observable list of model values. If any items are added,
 /// removed, or replaced, then observers that are listening to [changes]
 /// will be notified.
 class ObservableList<E> extends ListBase<E> with Observable {
-  List<ListChangeRecord> _listRecords;
+  /// Adapts [source] to be a `ObservableList<T>`.
+  ///
+  /// Any time the list 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 list,
+  /// 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 into the returned list are actually instance
+  /// of [S], then the returned list can be used as a `ObservableList<T>`.
+  static ObservableList<T> castFrom<S, T>(ObservableList<S> source) =>
+      new ObservableList<T>._spy(source._list.cast<T>());
 
-  StreamController<List<ListChangeRecord>> _listChanges;
+  List<ListChangeRecord<E>> _listRecords;
+
+  StreamController<List<ListChangeRecord<E>>> _listChanges;
 
   /// The inner [List<E>] with the actual storage.
   final List<E> _list;
@@ -41,8 +56,37 @@
 
   /// Creates an observable list with the elements of [other]. The order in
   /// the list will be the order provided by the iterator of [other].
-  factory ObservableList.from(Iterable<E> other) =>
-      new ObservableList<E>()..addAll(other);
+  ObservableList.from(Iterable other) : _list = new List<E>.from(other);
+
+  ObservableList._spy(List<E> other) : _list = other;
+
+  /// Returns a view of this list as a list of [T] instances.
+  ///
+  /// If this list contains only instances of [T], all read operations
+  /// will work correctly. If any operation tries to access an element
+  /// that is not an instance of [T], the access will throw instead.
+  ///
+  /// Elements added to the list (e.g., by using [add] or [addAll])
+  /// must be instance of [T] to be valid arguments to the adding function,
+  /// and they must be instances of [E] as well to be accepted by
+  /// this list as well.
+  @override
+  ObservableList<T> cast<T>() => ObservableList.castFrom<E, T>(this);
+
+  /// Returns a view of this list as a list of [T] instances.
+  ///
+  /// If this list contains only instances of [T], all read operations
+  /// will work correctly. If any operation tries to access an element
+  /// that is not an instance of [T], the access will throw instead.
+  ///
+  /// Elements added to the list (e.g., by using [add] or [addAll])
+  /// must be instance of [T] to be valid arguments to the adding function,
+  /// and they must be instances of [E] as well to be accepted by
+  /// this list as well.
+  @deprecated
+  @override
+  // ignore: override_on_non_overriding_method
+  ObservableList<T> retype<T>() => cast<T>();
 
   /// The stream of summarized list changes, delivered asynchronously.
   ///
@@ -64,7 +108,7 @@
   ///     #<ListChangeRecord index: 3, removed: [b], addedCount: 0>
   ///
   /// [deliverChanges] can be called to force synchronous delivery.
-  Stream<List<ListChangeRecord>> get listChanges {
+  Stream<List<ListChangeRecord<E>>> get listChanges {
     if (_listChanges == null) {
       // TODO(jmesserly): split observed/unobserved notions?
       _listChanges = new StreamController.broadcast(
@@ -79,8 +123,10 @@
 
   bool get hasListObservers => _listChanges != null && _listChanges.hasListener;
 
+  @override
   int get length => _list.length;
 
+  @override
   set length(int value) {
     int len = _list.length;
     if (len == value) return;
@@ -98,8 +144,10 @@
     _list.length = value;
   }
 
+  @override
   E operator [](int index) => _list[index];
 
+  @override
   void operator []=(int index, E value) {
     E oldValue = _list[index];
     if (hasListObservers && oldValue != value) {
@@ -109,7 +157,10 @@
   }
 
   // Forwarders so we can reflect on the properties.
+  @override
   bool get isEmpty => super.isEmpty;
+
+  @override
   bool get isNotEmpty => super.isNotEmpty;
 
   // TODO(jmesserly): should we support first/last/single? They're kind of
@@ -118,7 +169,7 @@
   // existing list notifications.
 
   // The following methods are here so that we can provide nice change events.
-
+  @override
   void setAll(int index, Iterable<E> iterable) {
     if (iterable is! List && iterable is! Set) {
       iterable = iterable.toList();
@@ -131,6 +182,7 @@
     _list.setAll(index, iterable);
   }
 
+  @override
   void add(E value) {
     int len = _list.length;
     _notifyChangeLength(len, len + 1);
@@ -141,6 +193,7 @@
     _list.add(value);
   }
 
+  @override
   void addAll(Iterable<E> iterable) {
     int len = _list.length;
     _list.addAll(iterable);
@@ -153,6 +206,7 @@
     }
   }
 
+  @override
   bool remove(Object element) {
     for (int i = 0; i < this.length; i++) {
       if (this[i] == element) {
@@ -163,6 +217,7 @@
     return false;
   }
 
+  @override
   void removeRange(int start, int end) {
     _rangeCheck(start, end);
     int rangeLength = end - start;
@@ -176,6 +231,7 @@
     _list.removeRange(start, end);
   }
 
+  @override
   void insertAll(int index, Iterable<E> iterable) {
     if (index < 0 || index > length) {
       throw new RangeError.range(index, 0, length);
@@ -201,6 +257,7 @@
     }
   }
 
+  @override
   void insert(int index, E element) {
     if (index < 0 || index > length) {
       throw new RangeError.range(index, 0, length);
@@ -223,6 +280,7 @@
     _list[index] = element;
   }
 
+  @override
   E removeAt(int index) {
     E result = this[index];
     removeRange(index, index + 1);
@@ -238,14 +296,22 @@
     }
   }
 
-  void _notifyListChange(int index, {List removed, int addedCount}) {
+  void _notifyListChange(
+    int index, {
+    List<E> removed,
+    int addedCount: 0,
+  }) {
     if (!hasListObservers) return;
     if (_listRecords == null) {
       _listRecords = [];
       scheduleMicrotask(deliverListChanges);
     }
-    _listRecords.add(new ListChangeRecord(this, index,
-        removed: removed, addedCount: addedCount));
+    _listRecords.add(new ListChangeRecord<E>(
+      this,
+      index,
+      removed: removed,
+      addedCount: addedCount,
+    ));
   }
 
   void _notifyChangeLength(int oldValue, int newValue) {
@@ -261,11 +327,11 @@
 
   bool deliverListChanges() {
     if (_listRecords == null) return false;
-    List<ListChangeRecord> records = projectListSplices(this, _listRecords);
+    final records = projectListSplices<E>(this, _listRecords);
     _listRecords = null;
 
     if (hasListObservers && records.isNotEmpty) {
-      _listChanges.add(new UnmodifiableListView<ListChangeRecord>(records));
+      _listChanges.add(new UnmodifiableListView<ListChangeRecord<E>>(records));
       return true;
     }
     return false;
@@ -283,9 +349,12 @@
   ///
   /// Complexity is `O(l * p)` where `l` is the length of the current list and
   /// `p` is the length of the old list.
-  static List<ListChangeRecord> calculateChangeRecords(
-          List<Object> oldValue, List<Object> newValue) =>
-      calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length);
+  static List<ListChangeRecord<E>> calculateChangeRecords<E>(
+    List<E> oldValue,
+    List<E> newValue,
+  ) {
+    return new ListDiffer<E>().diff(oldValue, newValue);
+  }
 
   /// Updates the [previous] list using the [changeRecords]. For added items,
   /// the [current] list is used to find the current value.
diff --git a/packages/observable/lib/src/observable_map.dart b/packages/observable/lib/src/observable_map.dart
index 23a7f11..bb1c844 100644
--- a/packages/observable/lib/src/observable_map.dart
+++ b/packages/observable/lib/src/observable_map.dart
@@ -6,9 +6,9 @@
 
 import 'dart:collection';
 
-import 'change_record.dart' show ChangeRecord;
-import 'observable.dart' show Observable;
-import 'property_change_record.dart' show PropertyChangeRecord;
+import 'observable.dart';
+import 'records.dart';
+import 'to_observable.dart';
 
 // TODO(jmesserly): this needs to be faster. We currently require multiple
 // lookups per key to get the old value.
@@ -16,50 +16,28 @@
 // LinkedHashMap, SplayTreeMap or HashMap. However it can use them for the
 // backing store.
 
-// TODO(jmesserly): should we summarize map changes like we do for list changes?
-class MapChangeRecord<K, V> extends ChangeRecord {
-  // TODO(jmesserly): we could store this more compactly if it matters, with
-  // subtypes for inserted and removed.
-
-  /// The map key that changed.
-  final K key;
-
-  /// The previous value associated with this key.
-  final V oldValue;
-
-  /// The new value associated with this key.
-  final V newValue;
-
-  /// True if this key was inserted.
-  final bool isInsert;
-
-  /// True if this key was removed.
-  final bool isRemove;
-
-  MapChangeRecord(this.key, this.oldValue, this.newValue)
-      : isInsert = false,
-        isRemove = false;
-
-  MapChangeRecord.insert(this.key, this.newValue)
-      : isInsert = true,
-        isRemove = false,
-        oldValue = null;
-
-  MapChangeRecord.remove(this.key, this.oldValue)
-      : isInsert = false,
-        isRemove = true,
-        newValue = null;
-
-  String toString() {
-    var kind = isInsert ? 'insert' : isRemove ? 'remove' : 'set';
-    return '#<MapChangeRecord $kind $key from: $oldValue to: $newValue>';
-  }
-}
-
 /// Represents an observable map of model values. If any items are added,
 /// removed, or replaced, then observers that are listening to [changes]
 /// will be notified.
 class ObservableMap<K, V> extends Observable implements Map<K, V> {
+  /// Adapts [source] to be a `ObservableMap<K2, V2>`.
+  ///
+  /// Any time the map would produce a key or value that is not a [K2] or [V2]
+  /// the access will throw.
+  ///
+  /// Any time [K2] key or [V2] value is attempted added into the adapted map,
+  /// the store will throw unless the key is also an instance of [K] and the
+  /// value is also an instance of [V].
+  ///
+  /// If all accessed entries of [source] have [K2] keys and [V2] values and if
+  /// all entries added to the returned map have [K] keys and [V] values, then
+  /// the returned map can be used as a `Map<K2, V2>`.
+  static ObservableMap<K2, V2> castFrom<K, V, K2, V2>(
+    ObservableMap<K, V> source,
+  ) {
+    return new ObservableMap<K2, V2>.spy(source._map.cast<K2, V2>());
+  }
+
   final Map<K, V> _map;
 
   /// Creates an observable map.
@@ -95,22 +73,34 @@
     return result;
   }
 
+  /// Creates a new observable map wrapping [other].
+  ObservableMap.spy(Map<K, V> other) : _map = other;
+
+  @override
   Iterable<K> get keys => _map.keys;
 
+  @override
   Iterable<V> get values => _map.values;
 
+  @override
   int get length => _map.length;
 
+  @override
   bool get isEmpty => length == 0;
 
+  @override
   bool get isNotEmpty => !isEmpty;
 
+  @override
   bool containsValue(Object value) => _map.containsValue(value);
 
+  @override
   bool containsKey(Object key) => _map.containsKey(key);
 
+  @override
   V operator [](Object key) => _map[key];
 
+  @override
   void operator []=(K key, V value) {
     if (!hasObservers) {
       _map[key] = value;
@@ -132,12 +122,14 @@
     }
   }
 
+  @override
   void addAll(Map<K, V> other) {
     other.forEach((K key, V value) {
       this[key] = value;
     });
   }
 
+  @override
   V putIfAbsent(K key, V ifAbsent()) {
     int len = _map.length;
     V result = _map.putIfAbsent(key, ifAbsent);
@@ -149,6 +141,7 @@
     return result;
   }
 
+  @override
   V remove(Object key) {
     int len = _map.length;
     V result = _map.remove(key);
@@ -160,6 +153,7 @@
     return result;
   }
 
+  @override
   void clear() {
     int len = _map.length;
     if (hasObservers && len > 0) {
@@ -172,9 +166,47 @@
     _map.clear();
   }
 
+  @override
   void forEach(void f(K key, V value)) => _map.forEach(f);
 
-  String toString() => Maps.mapToString(this);
+  @override
+  String toString() => MapBase.mapToString(this);
+
+  @override
+  ObservableMap<K2, V2> cast<K2, V2>() {
+    return ObservableMap.castFrom<K, V, K2, V2>(this);
+  }
+
+  @deprecated
+  @override
+  // ignore: override_on_non_overriding_method
+  ObservableMap<K2, V2> retype<K2, V2>() {
+    return ObservableMap.castFrom<K, V, K2, V2>(this);
+  }
+
+  @override
+  Iterable<MapEntry<K, V>> get entries => _map.entries;
+
+  @override
+  void addEntries(Iterable<MapEntry<K, V>> entries) {
+    _map.addEntries(entries);
+  }
+
+  @override
+  Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(K key, V value)) {
+    return _map.map(transform);
+  }
+
+  @override
+  V update(K key, V update(V value), {V ifAbsent()}) {
+    return _map.update(key, update, ifAbsent: ifAbsent);
+  }
+
+  @override
+  void updateAll(V update(K key, V value)) => _map.updateAll(update);
+
+  @override
+  void removeWhere(bool test(K key, V value)) => _map.removeWhere(test);
 
   // Note: we don't really have a reasonable old/new value to use here.
   // But this should fix "keys" and "values" in templates with minimal overhead.
diff --git a/packages/observable/lib/src/property_change_record.dart b/packages/observable/lib/src/property_change_record.dart
deleted file mode 100644
index a09c196..0000000
--- a/packages/observable/lib/src/property_change_record.dart
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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.
-
-library observable.src.property_change_record;
-
-import 'change_record.dart';
-
-/// A change record to a field of an [Observable] object.
-class PropertyChangeRecord<T> extends ChangeRecord {
-  /// The object that changed.
-  final Object object;
-
-  /// The name of the property that changed.
-  final Symbol name;
-
-  /// The previous value of the property.
-  final T oldValue;
-
-  /// The new value of the property.
-  final T newValue;
-
-  PropertyChangeRecord(this.object, this.name, this.oldValue, this.newValue);
-
-  String toString() =>
-      '#<PropertyChangeRecord $name from: $oldValue to: $newValue>';
-}
diff --git a/packages/observable/lib/src/records.dart b/packages/observable/lib/src/records.dart
new file mode 100644
index 0000000..87d7d4f
--- /dev/null
+++ b/packages/observable/lib/src/records.dart
@@ -0,0 +1,28 @@
+// 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.
+
+library observable.src.records;
+
+import 'package:collection/collection.dart';
+import 'package:quiver/core.dart';
+
+import 'internal.dart';
+
+part 'records/list_change_record.dart';
+part 'records/map_change_record.dart';
+part 'records/property_change_record.dart';
+
+/// Result of a change to an observed object.
+class ChangeRecord {
+  /// Signifies a change occurred, but without details of the specific change.
+  ///
+  /// May be used to produce lower-GC-pressure records where more verbose change
+  /// records will not be used directly.
+  static const List<ChangeRecord> ANY = const [const ChangeRecord()];
+
+  /// Signifies no changes occurred.
+  static const List<ChangeRecord> NONE = const [];
+
+  const ChangeRecord();
+}
diff --git a/packages/observable/lib/src/records/list_change_record.dart b/packages/observable/lib/src/records/list_change_record.dart
new file mode 100644
index 0000000..321a949
--- /dev/null
+++ b/packages/observable/lib/src/records/list_change_record.dart
@@ -0,0 +1,134 @@
+// 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.
+
+part of observable.src.records;
+
+/// A [ChangeRecord] that denotes adding or removing nodes at [index].
+///
+/// It should be assumed that elements are [removed] *before* being added.
+///
+/// A [List<ListChangeRecord>] can be "played back" against the [List] using
+/// the final list positions to figure out which item was added - this removes
+/// the need to incur costly GC on the most common operation (adding).
+class ListChangeRecord<E> implements ChangeRecord {
+  /// How many elements were added at [index] (after removing elements).
+  final int addedCount;
+
+  /// Index of where the change occurred.
+  final int index;
+
+  /// List that changed.
+  final List<E> object;
+
+  /// Elements that were removed starting at [index] (before adding elements).
+  final List<E> removed;
+
+  factory ListChangeRecord(
+    List<E> object,
+    int index, {
+    List<E> removed,
+    int addedCount: 0,
+  }) {
+    return new ListChangeRecord._(
+        object, index, removed ?? new UnmodifiableListView([]), addedCount);
+  }
+
+  /// Records an `add` operation at `object[index]` of [addedCount] elements.
+  ListChangeRecord.add(this.object, this.index, this.addedCount)
+      : removed = new UnmodifiableListView([]) {
+    _assertValidState();
+  }
+
+  /// Records a `remove` operation at `object[index]` of [removed] elements.
+  ListChangeRecord.remove(this.object, this.index, List<E> removed)
+      : this.removed = freezeInDevMode<E>(removed),
+        this.addedCount = 0 {
+    _assertValidState();
+  }
+
+  /// Records a `replace` operation at `object[index]` of [removed] elements.
+  ///
+  /// If [addedCount] is not specified it defaults to `removed.length`.
+  ListChangeRecord.replace(this.object, this.index, List<E> removed,
+      [int addedCount])
+      : this.removed = freezeInDevMode<E>(removed),
+        this.addedCount = addedCount ?? removed.length {
+    _assertValidState();
+  }
+
+  ListChangeRecord._(
+    this.object,
+    this.index,
+    this.removed,
+    this.addedCount,
+  ) {
+    _assertValidState();
+  }
+
+  /// What elements were added to [object].
+  Iterable<E> get added {
+    return addedCount == 0
+        ? const []
+        : object.getRange(index, index + addedCount);
+  }
+
+  /// Apply this change record to [list].
+  void apply(List<E> list) {
+    list
+      ..removeRange(index, index + removed.length)
+      ..insertAll(index, object.getRange(index, index + addedCount));
+  }
+
+  void _assertValidState() {
+    assert(() {
+      if (object == null) {
+        throw new ArgumentError.notNull('object');
+      }
+      if (index == null) {
+        throw new ArgumentError.notNull('index');
+      }
+      if (removed == null) {
+        throw new ArgumentError.notNull('removed');
+      }
+      if (addedCount == null || addedCount < 0) {
+        throw new ArgumentError('Invalid `addedCount`: $addedCount');
+      }
+      return true;
+    }());
+  }
+
+  /// Returns whether [reference] index was changed in this operation.
+  bool indexChanged(int reference) {
+    // If reference was before the change then it wasn't changed.
+    if (reference < index) return false;
+
+    // If this was a shift operation anything after index is changed.
+    if (addedCount != removed.length) return true;
+
+    // Otherwise anything in the update range was changed.
+    return reference < index + addedCount;
+  }
+
+  @override
+  bool operator ==(Object o) {
+    if (o is ListChangeRecord<E>) {
+      return identical(object, o.object) &&
+          index == o.index &&
+          addedCount == o.addedCount &&
+          const ListEquality().equals(removed, o.removed);
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    return hash4(object, index, addedCount, const ListEquality().hash(removed));
+  }
+
+  @override
+  String toString() => ''
+      '#<$ListChangeRecord index: $index, '
+      'removed: $removed, '
+      'addedCount: $addedCount>';
+}
diff --git a/packages/observable/lib/src/records/map_change_record.dart b/packages/observable/lib/src/records/map_change_record.dart
new file mode 100644
index 0000000..cd37fc9
--- /dev/null
+++ b/packages/observable/lib/src/records/map_change_record.dart
@@ -0,0 +1,82 @@
+// 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.
+
+part of observable.src.records;
+
+/// A [ChangeRecord] that denotes adding, removing, or updating a map.
+class MapChangeRecord<K, V> implements ChangeRecord {
+  /// The map key that changed.
+  final K key;
+
+  /// The previous value associated with this key.
+  ///
+  /// Is always `null` if [isInsert].
+  final V oldValue;
+
+  /// The new value associated with this key.
+  ///
+  /// Is always `null` if [isRemove].
+  final V newValue;
+
+  /// True if this key was inserted.
+  final bool isInsert;
+
+  /// True if this key was removed.
+  final bool isRemove;
+
+  /// Create an update record of [key] from [oldValue] to [newValue].
+  const MapChangeRecord(this.key, this.oldValue, this.newValue)
+      : isInsert = false,
+        isRemove = false;
+
+  /// Create an insert record of [key] and [newValue].
+  const MapChangeRecord.insert(this.key, this.newValue)
+      : isInsert = true,
+        isRemove = false,
+        oldValue = null;
+
+  /// Create a remove record of [key] with a former [oldValue].
+  const MapChangeRecord.remove(this.key, this.oldValue)
+      : isInsert = false,
+        isRemove = true,
+        newValue = null;
+
+  /// Apply this change record to [map].
+  void apply(Map<K, V> map) {
+    if (isRemove) {
+      map.remove(key);
+    } else {
+      map[key] = newValue;
+    }
+  }
+
+  @override
+  bool operator ==(Object o) {
+    if (o is MapChangeRecord<K, V>) {
+      return key == o.key &&
+          oldValue == o.oldValue &&
+          newValue == o.newValue &&
+          isInsert == o.isInsert &&
+          isRemove == o.isRemove;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    return hashObjects([
+      key,
+      oldValue,
+      newValue,
+      isInsert,
+      isRemove,
+    ]);
+  }
+
+  @override
+  String toString() {
+    final kind = isInsert ? 'insert' : isRemove ? 'remove' : 'set';
+    return '#<MapChangeRecord $kind $key from $oldValue to $newValue';
+  }
+}
diff --git a/packages/observable/lib/src/records/property_change_record.dart b/packages/observable/lib/src/records/property_change_record.dart
new file mode 100644
index 0000000..74566d5
--- /dev/null
+++ b/packages/observable/lib/src/records/property_change_record.dart
@@ -0,0 +1,31 @@
+// 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.
+
+part of observable.src.records;
+
+/// A change record to a field of a generic observable object.
+class PropertyChangeRecord<T> implements ChangeRecord {
+  /// Object that changed.
+  final Object object;
+
+  /// Name of the property that changed.
+  final Symbol name;
+
+  /// Previous value of the property.
+  final T oldValue;
+
+  /// New value of the property.
+  final T newValue;
+
+  const PropertyChangeRecord(
+    this.object,
+    this.name,
+    this.oldValue,
+    this.newValue,
+  );
+
+  @override
+  String toString() => ''
+      '#<$PropertyChangeRecord $name from $oldValue to: $newValue';
+}
diff --git a/packages/observable/lib/src/to_observable.dart b/packages/observable/lib/src/to_observable.dart
index 0a0f316..c1ddc96 100644
--- a/packages/observable/lib/src/to_observable.dart
+++ b/packages/observable/lib/src/to_observable.dart
@@ -4,6 +4,10 @@
 
 library observable.src.to_observable;
 
+import 'dart:collection';
+
+import 'package:dart_internal/extract_type_arguments.dart';
+
 import 'observable.dart' show Observable;
 import 'observable_list.dart' show ObservableList;
 import 'observable_map.dart' show ObservableMap;
@@ -28,22 +32,42 @@
 
 dynamic _toObservableShallow(dynamic value) {
   if (value is Observable) return value;
-  if (value is Map) return new ObservableMap.from(value);
-  if (value is Iterable) return new ObservableList.from(value);
+
+  if (value is Map) {
+    return extractMapTypeArguments(
+        value, <K, V>() => new ObservableMap<K, V>.from(value));
+  }
+
+  if (value is Iterable) {
+    return extractIterableTypeArgument(
+        value, <T>() => new ObservableList<T>.from(value));
+  }
+
   return value;
 }
 
 dynamic _toObservableDeep(dynamic value) {
   if (value is Observable) return value;
+
   if (value is Map) {
-    var result = new ObservableMap.createFromType(value);
-    value.forEach((k, v) {
-      result[_toObservableDeep(k)] = _toObservableDeep(v);
+    return extractMapTypeArguments(value, <K, V>() {
+      var result = new ObservableMap<K, V>.createFromType(value);
+      value.forEach((k, v) {
+        result[_toObservableDeep(k)] = _toObservableDeep(v);
+      });
+      return result;
     });
-    return result;
   }
+
   if (value is Iterable) {
-    return new ObservableList.from(value.map(_toObservableDeep));
+    return extractIterableTypeArgument(value, <T>() {
+      var result = new ObservableList<T>();
+      for (var element in value) {
+        result.add(_toObservableDeep(element));
+      }
+      return result;
+    });
   }
+
   return value;
 }
diff --git a/packages/observable/pubspec.yaml b/packages/observable/pubspec.yaml
index b030500..593b4a7 100644
--- a/packages/observable/pubspec.yaml
+++ b/packages/observable/pubspec.yaml
@@ -1,11 +1,18 @@
 name: observable
-version: 0.14.0+1
+version: 0.22.1+3
 author: Dart Team <misc@dartlang.org>
 description: Support for marking objects as observable
 homepage: https://github.com/dart-lang/observable
 environment:
-  sdk: '>=1.19.0 <2.0.0'
+  sdk: '>=2.0.0-dev.55.0 <2.0.0'
 dependencies:
+  collection: '^1.11.0'
+  dart_internal: '^0.1.1'
   meta: '^1.0.4'
+  quiver: '>=0.24.0 <0.30.0'
 dev_dependencies:
-  test: '^0.12.0'
+  dart_style: '^1.0.9'
+  build_runner: ^0.7.11
+  build_test: ^0.10.0
+  build_web_compilers: ^0.3.1
+  test: ^0.12.0
diff --git a/packages/observable/test/list_change_test.dart b/packages/observable/test/list_change_test.dart
index b07c0b8..858b27b 100644
--- a/packages/observable/test/list_change_test.dart
+++ b/packages/observable/test/list_change_test.dart
@@ -20,7 +20,7 @@
   var model;
 
   tearDown(() {
-    sub.cancel();
+    sub?.cancel();
     model = null;
   });
 
@@ -37,7 +37,11 @@
     model.add(2);
 
     expect(summary, null);
-    return new Future(() => expectChanges(summary, [_delta(1, [], 2)]));
+    return new Future(() {
+      expectChanges(summary, [_delta(1, [], 2)]);
+      expect(summary[0].added, [1, 2]);
+      expect(summary[0].removed, []);
+    });
   });
 
   test('List Splice Truncate And Expand With Length', () {
@@ -47,29 +51,28 @@
     sub = model.listChanges.listen((r) => summary = r);
 
     model.length = 2;
-
     return new Future(() {
       expectChanges(summary, [
         _delta(2, ['c', 'd', 'e'], 0)
       ]);
+      expect(summary[0].added, []);
+      expect(summary[0].removed, ['c', 'd', 'e']);
       summary = null;
       model.length = 5;
     }).then(newMicrotask).then((_) {
       expectChanges(summary, [_delta(2, [], 3)]);
+      expect(summary[0].added, [null, null, null]);
+      expect(summary[0].removed, []);
     });
   });
 
   group('List deltas can be applied', () {
     applyAndCheckDeltas(model, copy, changes) => changes.then((summary) {
           // apply deltas to the copy
-          for (var delta in summary) {
-            copy.removeRange(delta.index, delta.index + delta.removed.length);
-            for (int i = delta.addedCount - 1; i >= 0; i--) {
-              copy.insert(delta.index, model[delta.index + i]);
-            }
+          for (ListChangeRecord delta in summary) {
+            delta.apply(copy);
           }
 
-          // Note: compare strings for easier debugging.
           expect('$copy', '$model', reason: 'summary $summary');
         });
 
@@ -87,7 +90,7 @@
     });
 
     test('Delete Empty', () {
-      var model = toObservable([1]);
+      var model = toObservable(<dynamic>[1]);
       var copy = model.toList();
       var changes = model.listChanges.first;
 
diff --git a/packages/observable/test/map_differ_test.dart b/packages/observable/test/map_differ_test.dart
new file mode 100644
index 0000000..7fb9ca9
--- /dev/null
+++ b/packages/observable/test/map_differ_test.dart
@@ -0,0 +1,115 @@
+import 'package:observable/observable.dart';
+import 'package:test/test.dart';
+
+main() {
+  group('$MapDiffer', () {
+    final diff = const MapDiffer<String, String>().diff;
+
+    test('should emit no changes for identical maps', () {
+      final map = new Map<String, String>.fromIterable(
+        new Iterable.generate(10, (i) => '$i'),
+      );
+      expect(diff(map, map), isEmpty);
+    });
+
+    test('should emit no changes for maps with identical content', () {
+      final map1 = new Map<String, String>.fromIterable(
+        new Iterable.generate(10, (i) => '$i'),
+      );
+      final map2 = new Map<String, String>.fromIterable(
+        new Iterable.generate(10, (i) => '$i'),
+      );
+      expect(diff(map1, map2), isEmpty);
+    });
+
+    test('should detect insertions', () {
+      expect(
+        diff({
+          'key-a': 'value-a',
+          'key-b': 'value-b',
+        }, {
+          'key-a': 'value-a',
+          'key-b': 'value-b',
+          'key-c': 'value-c',
+        }),
+        [
+          new MapChangeRecord.insert('key-c', 'value-c'),
+        ],
+      );
+    });
+
+    test('should detect removals', () {
+      expect(
+        diff({
+          'key-a': 'value-a',
+          'key-b': 'value-b',
+          'key-c': 'value-c',
+        }, {
+          'key-a': 'value-a',
+          'key-b': 'value-b',
+        }),
+        [
+          new MapChangeRecord.remove('key-c', 'value-c'),
+        ],
+      );
+    });
+
+    test('should detect updates', () {
+      expect(
+        diff({
+          'key-a': 'value-a',
+          'key-b': 'value-b-old',
+        }, {
+          'key-a': 'value-a',
+          'key-b': 'value-b-new',
+        }),
+        [
+          new MapChangeRecord('key-b', 'value-b-old', 'value-b-new'),
+        ],
+      );
+    });
+  });
+
+  group('$MapChangeRecord', () {
+    test('should reply an insertion', () {
+      final map1 = {
+        'key-a': 'value-a',
+        'key-b': 'value-b',
+      };
+      final map2 = {
+        'key-a': 'value-a',
+        'key-b': 'value-b',
+        'key-c': 'value-c',
+      };
+      new MapChangeRecord.insert('key-c', 'value-c').apply(map1);
+      expect(map1, map2);
+    });
+
+    test('should replay a removal', () {
+      final map1 = {
+        'key-a': 'value-a',
+        'key-b': 'value-b',
+        'key-c': 'value-c',
+      };
+      final map2 = {
+        'key-a': 'value-a',
+        'key-b': 'value-b',
+      };
+      new MapChangeRecord.remove('key-c', 'value-c').apply(map1);
+      expect(map1, map2);
+    });
+
+    test('should replay an update', () {
+      final map1 = {
+        'key-a': 'value-a',
+        'key-b': 'value-b-old',
+      };
+      final map2 = {
+        'key-a': 'value-a',
+        'key-b': 'value-b-new',
+      };
+      new MapChangeRecord('key-b', 'value-b-old', 'value-b-new').apply(map1);
+      expect(map1, map2);
+    });
+  });
+}
diff --git a/packages/observable/test/observable_list_test.dart b/packages/observable/test/observable_list_test.dart
index 0a29fa3..b999cfd 100644
--- a/packages/observable/test/observable_list_test.dart
+++ b/packages/observable/test/observable_list_test.dart
@@ -65,6 +65,15 @@
       });
     });
 
+    test('removeWhere changes length', () {
+      list.add(2);
+      list.removeWhere((e) => e == 2);
+      expect(list, [1, 3]);
+      return new Future(() {
+        expectChanges(changes, [_lengthChange(3, 4), _lengthChange(4, 2)]);
+      });
+    });
+
     test('length= changes length', () {
       list.length = 5;
       expect(list, [1, 2, 3, null, null]);
@@ -266,6 +275,19 @@
       });
     });
 
+    test('removeWhere', () {
+      list.removeWhere((e) => e == 3);
+      expect(list, orderedEquals([1, 2, 1, 4]));
+
+      return new Future(() {
+        expectChanges(propRecords, [_lengthChange(6, 4)]);
+        expectChanges(listRecords, [
+          _change(2, removed: [3]),
+          _change(3, removed: [3])
+        ]);
+      });
+    });
+
     test('sort', () {
       list.sort((x, y) => x - y);
       expect(list, orderedEquals([1, 1, 2, 3, 3, 4]));
@@ -311,7 +333,7 @@
   });
 }
 
-ObservableList list;
+ObservableList<int> list;
 
 PropertyChangeRecord _lengthChange(int oldValue, int newValue) =>
     new PropertyChangeRecord(list, #length, oldValue, newValue);
diff --git a/packages/observable/test/observable_map_test.dart b/packages/observable/test/observable_map_test.dart
index a424ce1..04aeccc 100644
--- a/packages/observable/test/observable_map_test.dart
+++ b/packages/observable/test/observable_map_test.dart
@@ -362,6 +362,21 @@
       });
     });
   });
+
+  group('Updates delegate as a spy', () {
+    Map delegate;
+    ObservableMap map;
+
+    setUp(() {
+      delegate = {};
+      map = new ObservableMap.spy(delegate);
+    });
+
+    test('[]=', () {
+      map['a'] = 42;
+      expect(delegate, {'a': 42});
+    });
+  });
 }
 
 _lengthChange(map, int oldValue, int newValue) =>
diff --git a/packages/observable/test/observable_test.dart b/packages/observable/test/observable_test.dart
index b89d654..3a05e2d 100644
--- a/packages/observable/test/observable_test.dart
+++ b/packages/observable/test/observable_test.dart
@@ -9,7 +9,7 @@
 
 import 'observable_test_utils.dart';
 
-main() => observableTests();
+void main() => observableTests();
 
 void observableTests() {
   // Track the subscriptions so we can clean them up in tearDown.
@@ -206,7 +206,7 @@
 
 createModel(int number) => new ObservableSubclass(number);
 
-class ObservableSubclass<T> extends Observable {
+class ObservableSubclass<T> extends PropertyChangeNotifier {
   ObservableSubclass([T initialValue]) : _value = initialValue;
 
   T get value => _value;
@@ -218,5 +218,6 @@
 
   T _value;
 
+  @override
   String toString() => '#<$runtimeType value: $value>';
 }
diff --git a/packages/observable/test/observable_test_utils.dart b/packages/observable/test/observable_test_utils.dart
index 91d4d8f..990cc86 100644
--- a/packages/observable/test/observable_test_utils.dart
+++ b/packages/observable/test/observable_test_utils.dart
@@ -13,8 +13,6 @@
 /// to happen in the next microtask:
 ///
 ///     future.then(newMicrotask).then(...)
-///
-/// Uses [mu].
 newMicrotask(_) => new Future.value();
 
 // TODO(jmesserly): use matchers when we have a way to compare ChangeRecords.
diff --git a/packages/observable/tool/travis.sh b/packages/observable/tool/travis.sh
new file mode 100755
index 0000000..0e584b9
--- /dev/null
+++ b/packages/observable/tool/travis.sh
@@ -0,0 +1,65 @@
+# Copyright 2018 the Dart project authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#!/bin/bash
+
+if [ "$#" == "0" ]; then
+  echo -e '\033[31mAt least one task argument must be provided!\033[0m'
+  exit 1
+fi
+
+EXIT_CODE=0
+
+while (( "$#" )); do
+  TASK=$1
+  case $TASK in
+  dartfmt) echo
+    echo -e '\033[1mTASK: dartfmt\033[22m'
+    echo -e 'dartfmt -n --set-exit-if-changed .'
+    dartfmt -n --set-exit-if-changed . || EXIT_CODE=$?
+    ;;
+  dartanalyzer) echo
+    echo -e '\033[1mTASK: dartanalyzer\033[22m'
+    echo -e 'dartanalyzer --fatal-warnings .'
+    dartanalyzer --fatal-warnings . || EXIT_CODE=$?
+    ;;
+  vm_test) echo
+    echo -e '\033[1mTASK: vm_test\033[22m'
+    echo -e 'pub run test -P travis -p vm -x requires-dart2'
+    pub run test -p vm || EXIT_CODE=$?
+    ;;
+  dartdevc_build) echo
+    echo -e '\033[1mTASK: build\033[22m'
+    echo -e 'pub run build_runner build --fail-on-severe'
+    pub run build_runner build --fail-on-severe || EXIT_CODE=$?
+    ;;
+  dartdevc_test) echo
+    echo -e '\033[1mTASK: dartdevc_test\033[22m'
+    echo -e 'pub run build_runner test -- -P travis -p chrome'
+    pub run build_runner test -- -p chrome || EXIT_CODE=$?
+    ;;
+  dart2js_test) echo
+    echo -e '\033[1mTASK: dart2js_test\033[22m'
+    echo -e 'pub run test -P travis -p chrome -x requires-dart2'
+    pub run test -p chrome || EXIT_CODE=$?
+    ;;
+  *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m"
+    EXIT_CODE=1
+    ;;
+  esac
+
+  shift
+done
+
+exit $EXIT_CODE
diff --git a/packages/path/.gitignore b/packages/path/.gitignore
index 7dbf035..813a31e 100644
--- a/packages/path/.gitignore
+++ b/packages/path/.gitignore
@@ -1,6 +1,7 @@
 # Don’t commit the following directories created by pub.
 .buildlog
 .pub/
+.dart_tool/
 build/
 packages
 .packages
@@ -12,4 +13,4 @@
 *.js.map
 
 # Include when developing application packages.
-pubspec.lock
\ No newline at end of file
+pubspec.lock
diff --git a/packages/path/.travis.yml b/packages/path/.travis.yml
index 63f5e5e..fc8e768 100644
--- a/packages/path/.travis.yml
+++ b/packages/path/.travis.yml
@@ -2,16 +2,11 @@
 
 dart:
   - dev
-  - stable
 
 dart_task:
   - test
-  - dartanalyzer
-
-matrix:
-  include:
-    - dart: dev
-      dart_task: dartfmt
+  - dartfmt
+  - dartanalyze
 
 # Only building master means that we don't run two builds for each pull request.
 branches:
diff --git a/packages/path/CHANGELOG.md b/packages/path/CHANGELOG.md
index e23e380..42731ad 100644
--- a/packages/path/CHANGELOG.md
+++ b/packages/path/CHANGELOG.md
@@ -1,3 +1,13 @@
+## 1.6.1
+
+* Drop the `retype` implementation for compatibility with the latest SDK.
+
+## 1.6.0
+
+* Add a `PathMap` class that uses path equality for its keys.
+
+* Add a `PathSet` class that uses path equality for its contents.
+
 ## 1.5.1
 
 * Fix a number of bugs that occurred when the current working directory was `/`
diff --git a/packages/path/lib/path.dart b/packages/path/lib/path.dart
index 9885859..ed70f9d 100644
--- a/packages/path/lib/path.dart
+++ b/packages/path/lib/path.dart
@@ -49,6 +49,8 @@
 
 export 'src/context.dart' hide createInternal;
 export 'src/path_exception.dart';
+export 'src/path_map.dart';
+export 'src/path_set.dart';
 export 'src/style.dart';
 
 /// A default context for manipulating POSIX paths.
diff --git a/packages/path/lib/src/path_map.dart b/packages/path/lib/src/path_map.dart
new file mode 100644
index 0000000..53205ad
--- /dev/null
+++ b/packages/path/lib/src/path_map.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, 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 '../path.dart' as p;
+
+/// A map whose keys are paths, compared using [equals] and [hash].
+class PathMap<V> extends MapView<String, V> {
+  /// Creates an empty [PathMap] whose keys are compared using `context.equals`
+  /// and `context.hash`.
+  ///
+  /// The [context] defaults to the current path context.
+  PathMap({p.Context context}) : super(_create(context));
+
+  /// Creates a [PathMap] with the same keys and values as [other] whose keys
+  /// are compared using `context.equals` and `context.hash`.
+  ///
+  /// The [context] defaults to the current path context. If multiple keys in
+  /// [other] represent the same logical path, the last key's value will be
+  /// used.
+  PathMap.of(Map<String, V> other, {p.Context context})
+      : super(_create(context)..addAll(other));
+
+  /// Creates a map that uses [context] for equality and hashing.
+  static Map<String, V> _create<V>(p.Context context) {
+    context ??= p.context;
+    return new LinkedHashMap(
+        equals: (path1, path2) {
+          if (path1 == null) return path2 == null;
+          if (path2 == null) return false;
+          return context.equals(path1, path2);
+        },
+        hashCode: (path) => path == null ? 0 : context.hash(path),
+        isValidKey: (path) => path is String || path == null);
+  }
+}
diff --git a/packages/path/lib/src/path_set.dart b/packages/path/lib/src/path_set.dart
new file mode 100644
index 0000000..e3b9461
--- /dev/null
+++ b/packages/path/lib/src/path_set.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2018, 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 '../path.dart' as p;
+
+/// A set containing paths, compared using [equals] and [hash].
+class PathSet extends IterableBase<String> implements Set<String> {
+  /// The set to which we forward implementation methods.
+  final Set<String> _inner;
+
+  /// Creates an empty [PathSet] whose contents are compared using
+  /// `context.equals` and `context.hash`.
+  ///
+  /// The [context] defaults to the current path context.
+  PathSet({p.Context context}) : _inner = _create(context);
+
+  /// Creates a [PathSet] with the same contents as [other] whose elements are
+  /// compared using `context.equals` and `context.hash`.
+  ///
+  /// The [context] defaults to the current path context. If multiple elements
+  /// in [other] represent the same logical path, the first value will be
+  /// used.
+  PathSet.of(Iterable<String> other, {p.Context context})
+      : _inner = _create(context)..addAll(other);
+
+  /// Creates a set that uses [context] for equality and hashing.
+  static Set<String> _create(p.Context context) {
+    context ??= p.context;
+    return new LinkedHashSet(
+        equals: (path1, path2) {
+          if (path1 == null) return path2 == null;
+          if (path2 == null) return false;
+          return context.equals(path1, path2);
+        },
+        hashCode: (path) => path == null ? 0 : context.hash(path),
+        isValidKey: (path) => path is String || path == null);
+  }
+
+  // Normally we'd use DelegatingSetView from the collection package to
+  // implement these, but we want to avoid adding dependencies from path because
+  // it's so widely used that even brief version skew can be very painful.
+
+  Iterator<String> get iterator => _inner.iterator;
+
+  int get length => _inner.length;
+
+  bool add(String value) => _inner.add(value);
+
+  void addAll(Iterable<String> elements) => _inner.addAll(elements);
+
+  Set<T> cast<T>() => _inner.cast<T>();
+
+  void clear() => _inner.clear();
+
+  bool contains(Object other) => _inner.contains(other);
+
+  bool containsAll(Iterable<Object> other) => _inner.containsAll(other);
+
+  Set<String> difference(Set<Object> other) => _inner.difference(other);
+
+  Set<String> intersection(Set<Object> other) => _inner.intersection(other);
+
+  String lookup(Object element) => _inner.lookup(element);
+
+  bool remove(Object value) => _inner.remove(value);
+
+  void removeAll(Iterable<Object> elements) => _inner.removeAll(elements);
+
+  void removeWhere(bool test(String element)) => _inner.removeWhere(test);
+
+  void retainAll(Iterable<Object> elements) => _inner.retainAll(elements);
+
+  void retainWhere(bool test(String element)) => _inner.retainWhere(test);
+
+  Set<String> union(Set<String> other) => _inner.union(other);
+
+  Set<String> toSet() => _inner.toSet();
+}
diff --git a/packages/path/pubspec.yaml b/packages/path/pubspec.yaml
index 30a8dff..ece564f 100644
--- a/packages/path/pubspec.yaml
+++ b/packages/path/pubspec.yaml
@@ -1,5 +1,5 @@
 name: path
-version: 1.5.1
+version: 1.6.1
 author: Dart Team <misc@dartlang.org>
 description: >
  A string-based path manipulation library. All of the path operations you know
@@ -9,4 +9,4 @@
 dev_dependencies:
   test: ">=0.12.0 <0.13.0"
 environment:
-  sdk: ">=1.0.0 <2.0.0"
+  sdk: ">=2.0.0-dev.62.0 <2.0.0"
diff --git a/packages/path/test/path_map_test.dart b/packages/path/test/path_map_test.dart
new file mode 100644
index 0000000..ce025db
--- /dev/null
+++ b/packages/path/test/path_map_test.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2018, 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';
+
+import 'package:path/path.dart';
+
+void main() {
+  group("considers equal", () {
+    test("two identical paths", () {
+      var map = new PathMap<int>();
+      map[join("foo", "bar")] = 1;
+      map[join("foo", "bar")] = 2;
+      expect(map, hasLength(1));
+      expect(map, containsPair(join("foo", "bar"), 2));
+    });
+
+    test("two logically equivalent paths", () {
+      var map = new PathMap<int>();
+      map["foo"] = 1;
+      map[absolute("foo")] = 2;
+      expect(map, hasLength(1));
+      expect(map, containsPair("foo", 2));
+      expect(map, containsPair(absolute("foo"), 2));
+    });
+
+    test("two nulls", () {
+      var map = new PathMap<int>();
+      map[null] = 1;
+      map[null] = 2;
+      expect(map, hasLength(1));
+      expect(map, containsPair(null, 2));
+    });
+  });
+
+  group("considers unequal", () {
+    test("two distinct paths", () {
+      var map = new PathMap<int>();
+      map["foo"] = 1;
+      map["bar"] = 2;
+      expect(map, hasLength(2));
+      expect(map, containsPair("foo", 1));
+      expect(map, containsPair("bar", 2));
+    });
+
+    test("a path and null", () {
+      var map = new PathMap<int>();
+      map["foo"] = 1;
+      map[null] = 2;
+      expect(map, hasLength(2));
+      expect(map, containsPair("foo", 1));
+      expect(map, containsPair(null, 2));
+    });
+  });
+
+  test("uses the custom context", () {
+    var map = new PathMap<int>(context: windows);
+    map["FOO"] = 1;
+    map["foo"] = 2;
+    expect(map, hasLength(1));
+    expect(map, containsPair("fOo", 2));
+  });
+
+  group(".of()", () {
+    test("copies the existing map's keys", () {
+      var map = new PathMap.of({"foo": 1, "bar": 2});
+      expect(map, hasLength(2));
+      expect(map, containsPair("foo", 1));
+      expect(map, containsPair("bar", 2));
+    });
+
+    test("uses the second value in the case of duplicates", () {
+      var map = new PathMap.of({"foo": 1, absolute("foo"): 2});
+      expect(map, hasLength(1));
+      expect(map, containsPair("foo", 2));
+      expect(map, containsPair(absolute("foo"), 2));
+    });
+  });
+}
diff --git a/packages/path/test/path_set_test.dart b/packages/path/test/path_set_test.dart
new file mode 100644
index 0000000..884c184
--- /dev/null
+++ b/packages/path/test/path_set_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2018, 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';
+
+import 'package:path/path.dart';
+
+void main() {
+  group("considers equal", () {
+    test("two identical paths", () {
+      var set = new PathSet();
+      expect(set.add(join("foo", "bar")), isTrue);
+      expect(set.add(join("foo", "bar")), isFalse);
+      expect(set, hasLength(1));
+      expect(set, contains(join("foo", "bar")));
+    });
+
+    test("two logically equivalent paths", () {
+      var set = new PathSet();
+      expect(set.add("foo"), isTrue);
+      expect(set.add(absolute("foo")), isFalse);
+      expect(set, hasLength(1));
+      expect(set, contains("foo"));
+      expect(set, contains(absolute("foo")));
+    });
+
+    test("two nulls", () {
+      var set = new PathSet();
+      expect(set.add(null), isTrue);
+      expect(set.add(null), isFalse);
+      expect(set, hasLength(1));
+      expect(set, contains(null));
+    });
+  });
+
+  group("considers unequal", () {
+    test("two distinct paths", () {
+      var set = new PathSet();
+      expect(set.add("foo"), isTrue);
+      expect(set.add("bar"), isTrue);
+      expect(set, hasLength(2));
+      expect(set, contains("foo"));
+      expect(set, contains("bar"));
+    });
+
+    test("a path and null", () {
+      var set = new PathSet();
+      expect(set.add("foo"), isTrue);
+      expect(set.add(null), isTrue);
+      expect(set, hasLength(2));
+      expect(set, contains("foo"));
+      expect(set, contains(null));
+    });
+  });
+
+  test("uses the custom context", () {
+    var set = new PathSet(context: windows);
+    expect(set.add("FOO"), isTrue);
+    expect(set.add("foo"), isFalse);
+    expect(set, hasLength(1));
+    expect(set, contains("fOo"));
+  });
+
+  group(".of()", () {
+    test("copies the existing set's keys", () {
+      var set = new PathSet.of(["foo", "bar"]);
+      expect(set, hasLength(2));
+      expect(set, contains("foo"));
+      expect(set, contains("bar"));
+    });
+
+    test("uses the first value in the case of duplicates", () {
+      var set = new PathSet.of(["foo", absolute("foo")]);
+      expect(set, hasLength(1));
+      expect(set, contains("foo"));
+      expect(set, contains(absolute("foo")));
+      expect(set.first, "foo");
+    });
+  });
+}
diff --git a/packages/usage/.gitignore b/packages/usage/.gitignore
index a9df6e6..fd4bf9d 100644
--- a/packages/usage/.gitignore
+++ b/packages/usage/.gitignore
@@ -1,6 +1,7 @@
 .packages
 .idea/
 .pub/
+.dart_tool/
 build/
 doc/api/
 pubspec.lock
diff --git a/packages/usage/PATENTS b/packages/usage/PATENTS
deleted file mode 100644
index 36a3874..0000000
--- a/packages/usage/PATENTS
+++ /dev/null
@@ -1,24 +0,0 @@
-Additional IP Rights Grant (Patents)
-
-"This implementation" means the copyrightable works distributed by
-Google as part of the Dart Project.
-
-Google hereby grants to you a perpetual, worldwide, non-exclusive,
-no-charge, royalty-free, irrevocable (except as stated in this
-section) patent license to make, have made, use, offer to sell, sell,
-import, transfer, and otherwise run, modify and propagate the contents
-of this implementation of Dart, where such license applies only to
-those patent claims, both currently owned by Google and acquired in
-the future, licensable by Google that are necessarily infringed by
-this implementation of Dart. This grant does not include claims that
-would be infringed only as a consequence of further modification of
-this implementation. If you or your agent or exclusive licensee
-institute or order or agree to the institution of patent litigation
-against any entity (including a cross-claim or counterclaim in a
-lawsuit) alleging that this implementation of Dart or any code
-incorporated within this implementation of Dart constitutes direct or
-contributory patent infringement, or inducement of patent
-infringement, then any patent rights granted to you under this License
-for this implementation of Dart shall terminate as of the date such
-litigation is filed.
-
diff --git a/packages/usage/analysis_options.yaml b/packages/usage/analysis_options.yaml
index 2ef22c9..d7bffe1 100644
--- a/packages/usage/analysis_options.yaml
+++ b/packages/usage/analysis_options.yaml
@@ -3,6 +3,29 @@
 linter:
   rules:
     - annotate_overrides
+    - avoid_empty_else
+    - avoid_init_to_null
+    - avoid_return_types_on_setters
+    - await_only_futures
+    - camel_case_types
+    - control_flow_in_finally
     - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
     - empty_constructor_bodies
     - empty_statements
+    - empty_statements
+    - implementation_imports
+    - library_names
+    - library_prefixes
+    - non_constant_identifier_names
+    - only_throw_errors
+    - prefer_final_fields
+    - prefer_is_not_empty
+    - prefer_single_quotes
+    - slash_for_doc_comments
+    - test_types_in_equals
+    - throw_in_finally
+    - type_init_formals
+    - unrelated_type_equality_checks
+    - valid_regexps
diff --git a/packages/usage/changelog.md b/packages/usage/changelog.md
index 351cb38..e43adb3 100644
--- a/packages/usage/changelog.md
+++ b/packages/usage/changelog.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 3.4.0
+- bump our minimum SDK constraint to `>=2.0.0-dev.30`
+- change to using non-deprecated dart:convert constants
+
 ## 3.3.0
 - added a `close()` method to the `Analytics` class
 - change our minimum SDK from `1.24.0-dev` to `1.24.0` stable
diff --git a/packages/usage/example/ga.dart b/packages/usage/example/ga.dart
index ad64249..3df1573 100644
--- a/packages/usage/example/ga.dart
+++ b/packages/usage/example/ga.dart
@@ -8,16 +8,16 @@
 import 'package:usage/usage_io.dart';
 
 main(List args) async {
-  final String DEFAULT_UA = 'UA-55029513-1';
+  final String defaultUA = 'UA-55029513-1';
 
   if (args.isEmpty) {
     print('usage: dart ga <GA tracking ID>');
-    print('pinging default UA value (${DEFAULT_UA})');
+    print('pinging default UA value (${defaultUA})');
   } else {
     print('pinging ${args.first}');
   }
 
-  String ua = args.isEmpty ? DEFAULT_UA : args.first;
+  String ua = args.isEmpty ? defaultUA : args.first;
 
   Analytics ga = new AnalyticsIO(ua, 'ga_test', '3.0');
 
diff --git a/packages/usage/lib/src/usage_impl.dart b/packages/usage/lib/src/usage_impl.dart
index df529e3..7a8cf21 100644
--- a/packages/usage/lib/src/usage_impl.dart
+++ b/packages/usage/lib/src/usage_impl.dart
@@ -12,18 +12,16 @@
   // &foo=bar
   return map.keys.map((key) {
     String value = '${map[key]}';
-    return "${key}=${Uri.encodeComponent(value)}";
+    return '${key}=${Uri.encodeComponent(value)}';
   }).join('&');
 }
 
-/**
- * A throttling algorithm. This models the throttling after a bucket with
- * water dripping into it at the rate of 1 drop per second. If the bucket has
- * water when an operation is requested, 1 drop of water is removed and the
- * operation is performed. If not the operation is skipped. This algorithm
- * lets operations be performed in bursts without throttling, but holds the
- * overall average rate of operations to 1 per second.
- */
+/// A throttling algorithm. This models the throttling after a bucket with
+/// water dripping into it at the rate of 1 drop per second. If the bucket has
+/// water when an operation is requested, 1 drop of water is removed and the
+/// operation is performed. If not the operation is skipped. This algorithm
+/// lets operations be performed in bursts without throttling, but holds the
+/// overall average rate of operations to 1 per second.
 class ThrottlingBucket {
   final int startingCount;
   int drops;
@@ -80,7 +78,7 @@
 
   String _url;
 
-  StreamController<Map<String, dynamic>> _sendController =
+  final StreamController<Map<String, dynamic>> _sendController =
       new StreamController.broadcast(sync: true);
 
   AnalyticsImpl(this.trackingId, this.properties, this.postHandler,
@@ -220,21 +218,17 @@
   @override
   String get clientId => properties['clientId'] ??= new Uuid().generateV4();
 
-  /**
-   * Send raw data to analytics. Callers should generally use one of the typed
-   * methods (`sendScreenView`, `sendEvent`, ...).
-   *
-   * Valid values for [hitType] are: 'pageview', 'screenview', 'event',
-   * 'transaction', 'item', 'social', 'exception', and 'timing'.
-   */
+  /// Send raw data to analytics. Callers should generally use one of the typed
+  /// methods (`sendScreenView`, `sendEvent`, ...).
+  ///
+  /// Valid values for [hitType] are: 'pageview', 'screenview', 'event',
+  /// 'transaction', 'item', 'social', 'exception', and 'timing'.
   Future sendRaw(String hitType, Map<String, dynamic> args) {
     return _sendPayload(hitType, args);
   }
 
-  /**
-   * Valid values for [hitType] are: 'pageview', 'screenview', 'event',
-   * 'transaction', 'item', 'social', 'exception', and 'timing'.
-   */
+  /// Valid values for [hitType] are: 'pageview', 'screenview', 'event',
+  /// 'transaction', 'item', 'social', 'exception', and 'timing'.
   Future _sendPayload(String hitType, Map<String, dynamic> args) {
     if (!enabled) return new Future.value();
 
@@ -262,14 +256,13 @@
   }
 }
 
-/**
- * A persistent key/value store. An [AnalyticsImpl] instance expects to have one
- * of these injected into it. There are default implementations for `dart:io`
- * and `dart:html` clients.
- *
- * The [name] parameter is used to uniquely store these properties on disk /
- * persistent storage.
- */
+/// A persistent key/value store. An [AnalyticsImpl] instance expects to have
+/// one of these injected into it.
+///
+/// There are default implementations for `dart:io` and `dart:html` clients.
+///
+/// The [name] parameter is used to uniquely store these properties on disk /
+/// persistent storage.
 abstract class PersistentProperties {
   final String name;
 
@@ -283,15 +276,15 @@
   void syncSettings();
 }
 
-/**
- * A utility class to perform HTTP POSTs. An [AnalyticsImpl] instance expects to
- * have one of these injected into it. There are default implementations for
- * `dart:io` and `dart:html` clients.
- *
- * The POST information should be sent on a best-effort basis. The `Future` from
- * [sendPost] should complete when the operation is finished, but failures to
- * send the information should be silent.
- */
+/// A utility class to perform HTTP POSTs.
+///
+/// An [AnalyticsImpl] instance expects to have one of these injected into it.
+/// There are default implementations for `dart:io` and `dart:html` clients.
+///
+/// The POST information should be sent on a best-effort basis.
+///
+/// The `Future` from [sendPost] should complete when the operation is finished,
+/// but failures to send the information should be silent.
 abstract class PostHandler {
   Future sendPost(String url, Map<String, dynamic> parameters);
 
diff --git a/packages/usage/lib/src/usage_impl_html.dart b/packages/usage/lib/src/usage_impl_html.dart
index c15cde6..010b508 100644
--- a/packages/usage/lib/src/usage_impl_html.dart
+++ b/packages/usage/lib/src/usage_impl_html.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert' show JSON;
+import 'dart:convert' show jsonEncode, jsonDecode;
 import 'dart:html';
 
 import 'usage_impl.dart';
@@ -64,7 +64,7 @@
   HtmlPersistentProperties(String name) : super(name) {
     String str = window.localStorage[name];
     if (str == null || str.isEmpty) str = '{}';
-    _map = JSON.decode(str);
+    _map = jsonDecode(str);
   }
 
   @override
@@ -78,7 +78,7 @@
       _map[key] = value;
     }
 
-    window.localStorage[name] = JSON.encode(_map);
+    window.localStorage[name] = jsonEncode(_map);
   }
 
   @override
diff --git a/packages/usage/lib/src/usage_impl_io.dart b/packages/usage/lib/src/usage_impl_io.dart
index 6e82d41..53aa967 100644
--- a/packages/usage/lib/src/usage_impl_io.dart
+++ b/packages/usage/lib/src/usage_impl_io.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert' show JSON, JsonEncoder;
+import 'dart:convert' show jsonDecode, JsonEncoder;
 import 'dart:io';
 
 import 'package:path/path.dart' as path;
@@ -56,7 +56,7 @@
   } else {
     // Dart/1.8.0 (macos; macos; macos; en_US)
     String os = Platform.operatingSystem;
-    return "Dart/${getDartVersion()} (${os}; ${os}; ${os}; ${locale})";
+    return 'Dart/${getDartVersion()} (${os}; ${os}; ${os}; ${locale})';
   }
 }
 
@@ -153,7 +153,7 @@
     try {
       String contents = _file.readAsStringSync();
       if (contents.isEmpty) contents = '{}';
-      _map = JSON.decode(contents);
+      _map = jsonDecode(contents);
     } catch (_) {
       _map = {};
     }
diff --git a/packages/usage/lib/usage.dart b/packages/usage/lib/usage.dart
index e79701c..0622335 100644
--- a/packages/usage/lib/usage.dart
+++ b/packages/usage/lib/usage.dart
@@ -2,26 +2,24 @@
 // 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.
 
-/**
- * `usage` is a wrapper around Google Analytics for both command-line apps
- * and web apps.
- *
- * In order to use this library as a web app, import the `analytics_html.dart`
- * library and instantiate the [AnalyticsHtml] class.
- *
- * In order to use this library as a command-line app, import the
- * `analytics_io.dart` library and instantiate the [AnalyticsIO] class.
- *
- * For both classes, you need to provide a Google Analytics tracking ID, the
- * application name, and the application version.
- *
- * Your application should provide an opt-in option for the user. If they
- * opt-in, set the [optIn] field to `true`. This setting will persist across
- * sessions automatically.
- *
- * For more information, please see the Google Analytics Measurement Protocol
- * [Policy](https://developers.google.com/analytics/devguides/collection/protocol/policy).
- */
+/// `usage` is a wrapper around Google Analytics for both command-line apps
+/// and web apps.
+///
+/// In order to use this library as a web app, import the `analytics_html.dart`
+/// library and instantiate the [AnalyticsHtml] class.
+///
+/// In order to use this library as a command-line app, import the
+/// `analytics_io.dart` library and instantiate the [AnalyticsIO] class.
+///
+/// For both classes, you need to provide a Google Analytics tracking ID, the
+/// application name, and the application version.
+///
+/// Your application should provide an opt-in option for the user. If they
+/// opt-in, set the [optIn] field to `true`. This setting will persist across
+/// sessions automatically.
+///
+/// For more information, please see the Google Analytics Measurement Protocol
+/// [Policy](https://developers.google.com/analytics/devguides/collection/protocol/policy).
 library usage;
 
 import 'dart:async';
@@ -32,19 +30,17 @@
 // Match multiple tabs or spaces.
 final RegExp _tabOrSpaceRegex = new RegExp(r'[\t ]+');
 
-/**
- * An interface to a Google Analytics session. [AnalyticsHtml] and [AnalyticsIO]
- * are concrete implementations of this interface. [AnalyticsMock] can be used
- * for testing or for some variants of an opt-in workflow.
- *
- * The analytics information is sent on a best-effort basis. So, failures to
- * send the GA information will not result in errors from the asynchronous
- * `send` methods.
- */
+/// An interface to a Google Analytics session.
+///
+/// [AnalyticsHtml] and [AnalyticsIO] are concrete implementations of this
+/// interface. [AnalyticsMock] can be used for testing or for some variants of
+/// an opt-in workflow.
+///
+/// The analytics information is sent on a best-effort basis. So, failures to
+/// send the GA information will not result in errors from the asynchronous
+/// `send` methods.
 abstract class Analytics {
-  /**
-   * Tracking ID / Property ID.
-   */
+  /// Tracking ID / Property ID.
   String get trackingId;
 
   /// The application name.
@@ -53,120 +49,93 @@
   /// The application version.
   String get applicationVersion;
 
-  /**
-   * Is this the first time the tool has run?
-   */
+  /// Is this the first time the tool has run?
   bool get firstRun;
 
-  /**
-   * Whether the [Analytics] instance is configured in an opt-in or opt-out manner.
-   */
+  /// Whether the [Analytics] instance is configured in an opt-in or opt-out
+  /// manner.
   AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
 
-  /**
-   * Will analytics data be sent.
-   */
+  /// Will analytics data be sent.
   bool get enabled;
 
-  /**
-   * Enable or disable sending of analytics data.
-   */
+  /// Enable or disable sending of analytics data.
   set enabled(bool value);
 
-  /**
-   * Anonymous client ID in UUID v4 format.
-   *
-   * The value is randomly-generated and should be reasonably stable for the
-   * computer sending analytics data.
-   */
+  /// Anonymous client ID in UUID v4 format.
+  ///
+  /// The value is randomly-generated and should be reasonably stable for the
+  /// computer sending analytics data.
   String get clientId;
 
-  /**
-   * Sends a screen view hit to Google Analytics.
-   *
-   * [parameters] can be any analytics key/value pair. Useful
-   * for custom dimensions, etc.
-   */
+  /// Sends a screen view hit to Google Analytics.
+  ///
+  /// [parameters] can be any analytics key/value pair. Useful
+  /// for custom dimensions, etc.
   Future sendScreenView(String viewName, {Map<String, String> parameters});
 
-  /**
-   * Sends an Event hit to Google Analytics. [label] specifies the event label.
-   * [value] specifies the event value. Values must be non-negative.
-   *
-   * [parameters] can be any analytics key/value pair. Useful
-   * for custom dimensions, etc.
-   */
+  /// Sends an Event hit to Google Analytics. [label] specifies the event label.
+  /// [value] specifies the event value. Values must be non-negative.
+  ///
+  /// [parameters] can be any analytics key/value pair. Useful
+  /// for custom dimensions, etc.
   Future sendEvent(String category, String action,
       {String label, int value, Map<String, String> parameters});
 
-  /**
-   * Sends a Social hit to Google Analytics. [network] specifies the social
-   * network, for example Facebook or Google Plus. [action] specifies the social
-   * interaction action. For example on Google Plus when a user clicks the +1
-   * button, the social action is 'plus'. [target] specifies the target of a
-   * social interaction. This value is typically a URL but can be any text.
-   */
+  /// Sends a Social hit to Google Analytics.
+  ///
+  /// [network] specifies the social network, for example Facebook or Google
+  /// Plus. [action] specifies the social interaction action. For example on
+  /// Google Plus when a user clicks the +1 button, the social action is 'plus'.
+  /// [target] specifies the target of a
+  /// social interaction. This value is typically a URL but can be any text.
   Future sendSocial(String network, String action, String target);
 
-  /**
-   * Sends a Timing hit to Google Analytics. [variableName] specifies the
-   * variable name of the timing. [time] specifies the user timing value (in
-   * milliseconds). [category] specifies the category of the timing. [label]
-   * specifies the label of the timing.
-   */
+  /// Sends a Timing hit to Google Analytics. [variableName] specifies the
+  /// variable name of the timing. [time] specifies the user timing value (in
+  /// milliseconds). [category] specifies the category of the timing. [label]
+  /// specifies the label of the timing.
   Future sendTiming(String variableName, int time,
       {String category, String label});
 
-  /**
-   * Start a timer. The time won't be calculated, and the analytics information
-   * sent, until the [AnalyticsTimer.finish] method is called.
-   */
+  /// Start a timer. The time won't be calculated, and the analytics information
+  /// sent, until the [AnalyticsTimer.finish] method is called.
   AnalyticsTimer startTimer(String variableName,
       {String category, String label});
 
-  /**
-   * In order to avoid sending any personally identifying information, the
-   * [description] field must not contain the exception message. In addition,
-   * only the first 100 chars of the description will be sent.
-   */
+  /// In order to avoid sending any personally identifying information, the
+  /// [description] field must not contain the exception message. In addition,
+  /// only the first 100 chars of the description will be sent.
   Future sendException(String description, {bool fatal});
 
-  /**
-   * Gets a session variable value.
-   */
+  /// Gets a session variable value.
   dynamic getSessionValue(String param);
 
-  /**
-   * Sets a session variable value. The value is persistent for the life of the
-   * [Analytics] instance. This variable will be sent in with every analytics
-   * hit. A list of valid variable names can be found here:
-   * https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters.
-   */
+  /// Sets a session variable value. The value is persistent for the life of the
+  /// [Analytics] instance. This variable will be sent in with every analytics
+  /// hit. A list of valid variable names can be found here:
+  /// https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters.
   void setSessionValue(String param, dynamic value);
 
-  /**
-   * Fires events when the usage library sends any data over the network. This
-   * will not fire if analytics has been disabled or if the throttling algorithm
-   * has been engaged.
-   *
-   * This method is public to allow library clients to more easily test their
-   * analytics implementations.
-   */
+  /// Fires events when the usage library sends any data over the network. This
+  /// will not fire if analytics has been disabled or if the throttling
+  /// algorithm has been engaged.
+  ///
+  /// This method is public to allow library clients to more easily test their
+  /// analytics implementations.
   Stream<Map<String, dynamic>> get onSend;
 
-  /**
-   * Wait for all of the outstanding analytics pings to complete. The returned
-   * `Future` will always complete without errors. You can pass in an optional
-   * `Duration` to specify to only wait for a certain amount of time.
-   *
-   * This method is particularly useful for command-line clients. Outstanding
-   * I/O requests will cause the VM to delay terminating the process. Generally,
-   * users won't want their CLI app to pause at the end of the process waiting
-   * for Google analytics requests to complete. This method allows CLI apps to
-   * delay for a short time waiting for GA requests to complete, and then do
-   * something like call `dart:io`'s `exit()` explicitly themselves (or the
-   * [close] method below).
-   */
+  /// Wait for all of the outstanding analytics pings to complete. The returned
+  /// `Future` will always complete without errors. You can pass in an optional
+  /// `Duration` to specify to only wait for a certain amount of time.
+  ///
+  /// This method is particularly useful for command-line clients. Outstanding
+  /// I/O requests will cause the VM to delay terminating the process.
+  /// Generally, users won't want their CLI app to pause at the end of the
+  /// process waiting for Google analytics requests to complete. This method
+  /// allows CLI apps to delay for a short time waiting for GA requests to
+  /// complete, and then do something like call `dart:io`'s `exit()` explicitly
+  /// themselves (or the [close] method below).
   Future waitForLastPing({Duration timeout});
 
   /// Free any used resources.
@@ -176,21 +145,15 @@
 }
 
 enum AnalyticsOpt {
-  /**
-   * Users must opt-in before any analytics data is sent.
-   */
+  /// Users must opt-in before any analytics data is sent.
   optIn,
 
-  /**
-   * Users must opt-out for analytics data to not be sent.
-   */
+  /// Users must opt-out for analytics data to not be sent.
   optOut
 }
 
-/**
- * An object, returned by [Analytics.startTimer], that is used to measure an
- * asynchronous process.
- */
+/// An object, returned by [Analytics.startTimer], that is used to measure an
+/// asynchronous process.
 class AnalyticsTimer {
   final Analytics analytics;
   final String variableName;
@@ -213,10 +176,8 @@
     }
   }
 
-  /**
-   * Finish the timer, calculate the elapsed time, and send the information to
-   * analytics. Once this is called, any future invocations are no-ops.
-   */
+  /// Finish the timer, calculate the elapsed time, and send the information to
+  /// analytics. Once this is called, any future invocations are no-ops.
   Future finish() {
     if (_endMillis != null) return new Future.value();
 
@@ -226,10 +187,8 @@
   }
 }
 
-/**
- * A no-op implementation of the [Analytics] class. This can be used as a
- * stand-in for that will never ping the GA server, or as a mock in test code.
- */
+/// A no-op implementation of the [Analytics] class. This can be used as a
+/// stand-in for that will never ping the GA server, or as a mock in test code.
 class AnalyticsMock implements Analytics {
   @override
   String get trackingId => 'UA-0';
@@ -240,16 +199,12 @@
 
   final bool logCalls;
 
-  /**
-   * Events are never added to this controller for the mock implementation.
-   */
-  StreamController<Map<String, dynamic>> _sendController =
+  /// Events are never added to this controller for the mock implementation.
+  final StreamController<Map<String, dynamic>> _sendController =
       new StreamController.broadcast();
 
-  /**
-   * Create a new [AnalyticsMock]. If [logCalls] is true, all calls will be
-   * logged to stdout.
-   */
+  /// Create a new [AnalyticsMock]. If [logCalls] is true, all calls will be
+  /// logged to stdout.
   AnalyticsMock([this.logCalls = false]);
 
   @override
@@ -331,16 +286,14 @@
   }
 }
 
-/**
- * Sanitize a stacktrace. This will shorten file paths in order to remove any
- * PII that may be contained in the full file path. For example, this will
- * shorten `file:///Users/foobar/tmp/error.dart` to `error.dart`.
- *
- * If [shorten] is `true`, this method will also attempt to compress the text
- * of the stacktrace. GA has a 100 char limit on the text that can be sent for
- * an exception. This will try and make those first 100 chars contain
- * information useful to debugging the issue.
- */
+/// Sanitize a stacktrace. This will shorten file paths in order to remove any
+/// PII that may be contained in the full file path. For example, this will
+/// shorten `file:///Users/foobar/tmp/error.dart` to `error.dart`.
+///
+/// If [shorten] is `true`, this method will also attempt to compress the text
+/// of the stacktrace. GA has a 100 char limit on the text that can be sent for
+/// an exception. This will try and make those first 100 chars contain
+/// information useful to debugging the issue.
 String sanitizeStacktrace(dynamic st, {bool shorten: true}) {
   String str = '${st}';
 
diff --git a/packages/usage/pubspec.yaml b/packages/usage/pubspec.yaml
index 564ba2e..b6f0f6e 100644
--- a/packages/usage/pubspec.yaml
+++ b/packages/usage/pubspec.yaml
@@ -3,13 +3,13 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: usage
-version: 3.3.0
+version: 3.4.0
 description: A Google Analytics wrapper for both command-line, web, and Flutter apps.
 homepage: https://github.com/dart-lang/usage
 author: Dart Team <misc@dartlang.org>
 
 environment:
-  sdk: '>=1.24.0 <2.0.0'
+  sdk: '>=2.0.0-dev.30 <2.0.0'
 
 dependencies:
   path: ^1.4.0
diff --git a/packages/usage/test/hit_types_test.dart b/packages/usage/test/hit_types_test.dart
index e8bf241..a7ee490 100644
--- a/packages/usage/test/hit_types_test.dart
+++ b/packages/usage/test/hit_types_test.dart
@@ -11,7 +11,7 @@
 
 import 'src/common.dart';
 
-main() => defineTests();
+void main() => defineTests();
 
 void defineTests() {
   group('screenView', () {
diff --git a/packages/usage/test/src/common.dart b/packages/usage/test/src/common.dart
index 7799394..c2f1a5e 100644
--- a/packages/usage/test/src/common.dart
+++ b/packages/usage/test/src/common.dart
@@ -12,9 +12,9 @@
 AnalyticsImplMock createMock({Map<String, dynamic> props}) =>
     new AnalyticsImplMock('UA-0', props: props);
 
-was(Map m, String type) => expect(m['t'], type);
-has(Map m, String key) => expect(m[key], isNotNull);
-hasnt(Map m, String key) => expect(m[key], isNull);
+void was(Map m, String type) => expect(m['t'], type);
+void has(Map m, String key) => expect(m[key], isNotNull);
+void hasnt(Map m, String key) => expect(m[key], isNull);
 
 class AnalyticsImplMock extends AnalyticsImpl {
   MockProperties get mockProperties => properties;
diff --git a/packages/usage/test/usage_impl_io_test.dart b/packages/usage/test/usage_impl_io_test.dart
index 8889077..a9c30d4 100644
--- a/packages/usage/test/usage_impl_io_test.dart
+++ b/packages/usage/test/usage_impl_io_test.dart
@@ -11,7 +11,7 @@
 import 'package:test/test.dart';
 import 'package:usage/src/usage_impl_io.dart';
 
-main() => defineTests();
+void main() => defineTests();
 
 void defineTests() {
   group('IOPostHandler', () {
@@ -93,7 +93,7 @@
   MockHttpClientResponse(this.client);
 
   @override
-  Future/*<E>*/ drain/*<E>*/([/*=E*/ futureValue]) {
+  Future<E> drain<E>([E futureValue]) {
     client.sendCount++;
     return new Future.value();
   }
diff --git a/packages/usage/test/usage_impl_test.dart b/packages/usage/test/usage_impl_test.dart
index a4ca3ac..2a3ea35 100644
--- a/packages/usage/test/usage_impl_test.dart
+++ b/packages/usage/test/usage_impl_test.dart
@@ -9,7 +9,7 @@
 
 import 'src/common.dart';
 
-main() => defineTests();
+void main() => defineTests();
 
 void defineTests() {
   group('ThrottlingBucket', () {
diff --git a/packages/usage/test/usage_test.dart b/packages/usage/test/usage_test.dart
index 37500a9..d97d7b4 100644
--- a/packages/usage/test/usage_test.dart
+++ b/packages/usage/test/usage_test.dart
@@ -7,7 +7,7 @@
 import 'package:test/test.dart';
 import 'package:usage/usage.dart';
 
-main() => defineTests();
+void main() => defineTests();
 
 void defineTests() {
   group('AnalyticsMock', () {
diff --git a/packages/usage/test/uuid_test.dart b/packages/usage/test/uuid_test.dart
index 98dbab8..81762e7 100644
--- a/packages/usage/test/uuid_test.dart
+++ b/packages/usage/test/uuid_test.dart
@@ -7,7 +7,7 @@
 import 'package:test/test.dart';
 import 'package:usage/uuid/uuid.dart';
 
-main() => defineTests();
+void main() => defineTests();
 
 void defineTests() {
   group('uuid', () {
diff --git a/packages/usage/test/web_test.dart b/packages/usage/test/web_test.dart
index 8d91ae9..1162559 100644
--- a/packages/usage/test/web_test.dart
+++ b/packages/usage/test/web_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-@TestOn("browser")
+@TestOn('browser')
 library usage.web_test;
 
 import 'dart:async';
diff --git a/pubspec.yaml b/pubspec.yaml
index 84c91cf..2042c4e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,10 +1,10 @@
 name: observatory
 dependencies:
-  charted: any
-  logging: any
   stack_trace: any
   unittest: any
   usage: any
-
-  # charted neglects to declare this dependency
-  collection: any
+  # This will be charted 0.6.1 when pushed to pub.
+  charted:
+    git:
+      url: git://github.com/google/charted
+      ref: b847ace40c4193ce3df30c00032d16d00d1a59b7