Specialized JavaScript map for num & string keys (+ generic dart.map)
diff --git a/lib/runtime/dart/_operations.js b/lib/runtime/dart/_operations.js
index 082a0f4..7dd6af2 100644
--- a/lib/runtime/dart/_operations.js
+++ b/lib/runtime/dart/_operations.js
@@ -275,8 +275,11 @@
    * example `map()`.
    */
   // TODO(jmesserly): this could be faster
-  function map(values) {
-    let map = collection.LinkedHashMap.new();
+  function map(values, keyType, valueType) {
+    let ctor = (keyType && valueType)
+        ? core.Map$(keyType, valueType)
+        : core.Map;
+    let map = ctor.new();
     if (Array.isArray(values)) {
       for (let i = 0, end = values.length - 1; i < end; i += 2) {
         let key = values[i];
diff --git a/lib/runtime/dart/convert.js b/lib/runtime/dart/convert.js
index a535d9b..fd9c50e 100644
--- a/lib/runtime/dart/convert.js
+++ b/lib/runtime/dart/convert.js
@@ -644,7 +644,7 @@
   let _FusedConverter = _FusedConverter$();
   dart.defineLazyProperties(Encoding, {
     get _nameToEncoding() {
-      return dart.map({"iso_8859-1:1987": LATIN1, "iso-ir-100": LATIN1, "iso_8859-1": LATIN1, "iso-8859-1": LATIN1, latin1: LATIN1, l1: LATIN1, ibm819: LATIN1, cp819: LATIN1, csisolatin1: LATIN1, "iso-ir-6": ASCII, "ansi_x3.4-1968": ASCII, "ansi_x3.4-1986": ASCII, "iso_646.irv:1991": ASCII, "iso646-us": ASCII, "us-ascii": ASCII, us: ASCII, ibm367: ASCII, cp367: ASCII, csascii: ASCII, ascii: ASCII, csutf8: UTF8, "utf-8": UTF8});
+      return dart.map({"iso_8859-1:1987": LATIN1, "iso-ir-100": LATIN1, "iso_8859-1": LATIN1, "iso-8859-1": LATIN1, latin1: LATIN1, l1: LATIN1, ibm819: LATIN1, cp819: LATIN1, csisolatin1: LATIN1, "iso-ir-6": ASCII, "ansi_x3.4-1968": ASCII, "ansi_x3.4-1986": ASCII, "iso_646.irv:1991": ASCII, "iso646-us": ASCII, "us-ascii": ASCII, us: ASCII, ibm367: ASCII, cp367: ASCII, csascii: ASCII, ascii: ASCII, csutf8: UTF8, "utf-8": UTF8}, core.String, Encoding);
     },
     set _nameToEncoding(_) {}
   });
diff --git a/lib/runtime/dart/js.js b/lib/runtime/dart/js.js
index db2f65e..48a06e1 100644
--- a/lib/runtime/dart/js.js
+++ b/lib/runtime/dart/js.js
@@ -116,7 +116,7 @@
         args = core.List.from(args[dartx.map](_convertToJS));
       let fn = this[_jsObject][method];
       if (!(fn instanceof Function)) {
-        dart.throw(new core.NoSuchMethodError(this[_jsObject], core.Symbol.new(dart.as(method, core.String)), args, dart.map()));
+        dart.throw(new core.NoSuchMethodError(this[_jsObject], core.Symbol.new(dart.as(method, core.String)), args, dart.map([], core.Symbol, dart.dynamic)));
       }
       return _convertToDart(fn.apply(this[_jsObject], args));
     }
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
index 0ff3baa..bd3542d 100644
--- a/lib/src/codegen/js_codegen.dart
+++ b/lib/src/codegen/js_codegen.dart
@@ -3216,10 +3216,11 @@
     // TODO(jmesserly): we can likely make these faster.
     JS.Expression emitMap() {
       var entries = node.entries;
+      if (entries.isEmpty && node.typeArguments == null) {
+        return js.call('dart.map()');
+      }
       var mapArguments = null;
-      if (entries.isEmpty) {
-        mapArguments = [];
-      } else if (entries.every((e) => e.key is StringLiteral)) {
+      if (entries.isNotEmpty && entries.every((e) => e.key is StringLiteral)) {
         // Use JS object literal notation if possible, otherwise use an array.
         // We could do this any time all keys are non-nullable String type.
         // For now, support StringLiteral as the common non-nullable String case.
@@ -3236,8 +3237,14 @@
         }
         mapArguments = new JS.ArrayInitializer(values);
       }
-      // TODO(jmesserly): add generic types args.
-      return js.call('dart.map(#)', [mapArguments]);
+      var args = [mapArguments];
+      if (node.typeArguments == null) {
+        return js.call('dart.map(#)', args);
+      } else {
+        args.addAll(node.typeArguments.arguments.map((a) => _emitTypeName(a.type)));
+        return js.call('dart.map(#, #, #)', args);
+      }
+
     }
     if (node.constKeyword != null) return _emitConst(emitMap);
     return emitMap();
diff --git a/tool/input_sdk/lib/core/core.dart b/tool/input_sdk/lib/core/core.dart
index 4b11edc..4cef7f9 100644
--- a/tool/input_sdk/lib/core/core.dart
+++ b/tool/input_sdk/lib/core/core.dart
@@ -157,6 +157,7 @@
 import "dart:_internal" as internal show Symbol;
 import "dart:convert" show UTF8, LATIN1, Encoding;
 import "dart:math" show Random;  // Used by List.shuffle.
+import 'dart:_foreign_helper' show JS;
 
 part "annotations.dart";
 part "bool.dart";
diff --git a/tool/input_sdk/lib/core/map.dart b/tool/input_sdk/lib/core/map.dart
index c011a20..b29bb44 100644
--- a/tool/input_sdk/lib/core/map.dart
+++ b/tool/input_sdk/lib/core/map.dart
@@ -4,6 +4,147 @@
 
 part of dart.core;
 
+const _NULL_KEY = "__dart_jsMap_null";
+const _PROTOTYPE_KEY = "__dart_jsMap_prototype";
+
+final _GLOBAL =
+    JS('', '(typeof window === "undefined" ? global : window)');
+
+final _Object_keys = JS('', '#["Object"].keys', _GLOBAL);
+final _String = JS('', '#.String', _GLOBAL);
+final _Number = JS('', '#.Number', _GLOBAL);
+
+class JsMap<K, V> implements Map<K, V> {
+  //static final JsObject _Object = context['Object'];
+  var _jsMap = JS('', 'new Map()');
+
+  final _keyCaster;
+
+  JsMap(this._keyCaster);
+
+  get _jsKeys => JS('', '#(#)', _Object_keys, _jsMap);
+
+  List<K> get _keysList {
+    var jsMap = _jsMap;
+    var jsKeys = _jsKeys;
+    var result = <K>[];
+    for (var i = 0, n = JS('int', '#.length', jsKeys); i < n; i++) {
+      result.add(JS('K', '#[#]', jsKeys, i));
+    }
+    return result;
+  }
+
+  _mangleKey(key) {
+    // assert(key is num || key is String);
+    if (key == null) {
+      return _NULL_KEY;
+    } else if (key == 'prototype') {
+      return _PROTOTYPE_KEY;
+    } else {
+      return key;
+    }
+  }
+
+  _demangleKey(key) {
+    if (key == _PROTOTYPE_KEY) {
+      return 'prototype';
+    } else if (key == _NULL_KEY) {
+      return null;
+    } else {
+      return JS('', '#(#)', _keyCaster, key);
+    }
+  }
+
+  @override
+  V operator [](Object key) {
+    return JS('V', '#[#]', _jsMap, _mangleKey(key));
+  }
+
+  @override
+  void operator []=(K key, V value) {
+    JS('', '#[#] = #', _jsMap, _mangleKey(key), value);
+  }
+
+  @override
+  void addAll(Map<K, V> other) {
+    other.forEach((K key, V value) {
+      this[key] = value;
+    });
+  }
+
+  @override
+  void clear() {
+    _jsMap = JS('', 'new Map()');
+  }
+
+  @override
+  bool containsKey(Object key) {
+    return JS('bool', '# in #', _mangleKey(key), _jsMap);
+  }
+
+  @override
+  bool containsValue(Object value) {
+    for (var key in _keysList) {
+      var v = JS('V', '#[#]', _jsMap, key);
+      if (value == v) return true;
+    }
+    return false;
+  }
+
+  @override
+  void forEach(void f(K key, V value)) {
+    for (var key in _keysList) {
+      V value = JS('V', '#[#]', _jsMap, key);
+      f(_demangleKey(key), value);
+    }
+  }
+
+  @override
+  bool get isEmpty => JS('bool', '#.length == 0', _jsKeys);
+
+  @override
+  bool get isNotEmpty => !isEmpty;
+
+  // TODO: implement keys
+  @override
+  Iterable<K> get keys {
+    var result = <K>[];
+    for (var key in _keysList) {
+      result.add(_demangleKey(key));
+    }
+    return result;
+  }
+
+  // TODO: implement length
+  @override
+  int get length => _keysList.length;
+
+  @override
+  V putIfAbsent(K key, V ifAbsent()) {
+    var prop = _mangleKey(key);
+    return JS('V', '# in # ? #[#] : (#[#] = #())',
+        prop, _jsMap, _jsMap, prop, _jsMap, prop, ifAbsent);
+  }
+
+  @override
+  V remove(Object key) {
+    var prop = _mangleKey(key);
+    V value = JS('V', '#[#]', _jsMap, prop);
+    JS('', 'delete #[#]', _jsMap, prop);
+    return value;
+  }
+
+  // TODO: implement values
+  @override
+  Iterable<V> get values {
+    var result = <V>[];
+    for (var key in _keysList) {
+      result.add(JS('V', '#[#]', _jsMap, key));
+    }
+    return result;
+  }
+}
+
 /**
  * An collection of key-value pairs, from which you retrieve a value
  * using its associated key.
@@ -35,7 +176,16 @@
    * `operator==` and `hashCode`, and it allows null as a key.
    * It iterates in key insertion order.
    */
-  factory Map() = LinkedHashMap<K, V>;
+  //factory Map() = LinkedHashMap<K, V>;
+  factory Map() {
+    if (JS('', 'K && (K.name === "int" || K.name === "num" || K.name === "double")')) {
+      return new JsMap<K, V>(_Number);
+    } else if (JS('', 'K && (K.name === "String")')) {
+      return new JsMap<K, V>(_String);
+    } else {
+      return new LinkedHashMap<K, V>();
+    }
+  }
 
   /**
    * Creates a [LinkedHashMap] instance that contains all key-value pairs of