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