optimize maps/sets in DDC, fixes #29865

- string/int keys use identity maps
- construct the correct type rather than relying on factory constructors when possible
- checks if key has identity semantics and use that when possible
- avoid helper calls/casts/etc in implementations
- use native ES6 Map/Set key order tracking
- optimized constructor for map literals
- fixes #30466, const maps are now canonicalized correctly

Change-Id: Id44acf3d9bf8cb153a4755aa28df08fe91bfc633
Reviewed-on: https://dart-review.googlesource.com/8167
Reviewed-by: Vijay Menon <vsm@google.com>
This commit is contained in:
Jennifer Messerly 2017-10-04 19:04:27 +00:00
parent 082bbdb6a7
commit 3a79c35f3c
28 changed files with 7737 additions and 13530 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -128,7 +128,7 @@ class CodeGenerator extends Object
/// The type provider from the current Analysis [context].
final TypeProvider types;
final LibraryElement dartCoreLibrary;
final LibraryElement coreLibrary;
final LibraryElement dartJSLibrary;
/// The dart:async `StreamIterator<>` type.
@ -153,6 +153,10 @@ class CodeGenerator extends Object
final ClassElement stringClass;
final ClassElement functionClass;
final ClassElement privateSymbolClass;
final InterfaceType linkedHashMapImplType;
final InterfaceType identityHashMapImplType;
final InterfaceType linkedHashSetImplType;
final InterfaceType identityHashSetImplType;
ConstFieldVisitor _constants;
@ -216,7 +220,7 @@ class CodeGenerator extends Object
_jsNumber = _getLibrary(c, 'dart:_interceptors').getType('JSNumber'),
interceptorClass =
_getLibrary(c, 'dart:_interceptors').getType('Interceptor'),
dartCoreLibrary = _getLibrary(c, 'dart:core'),
coreLibrary = _getLibrary(c, 'dart:core'),
boolClass = _getLibrary(c, 'dart:core').getType('bool'),
intClass = _getLibrary(c, 'dart:core').getType('int'),
doubleClass = _getLibrary(c, 'dart:core').getType('double'),
@ -227,6 +231,14 @@ class CodeGenerator extends Object
functionClass = _getLibrary(c, 'dart:core').getType('Function'),
privateSymbolClass =
_getLibrary(c, 'dart:_internal').getType('PrivateSymbol'),
linkedHashMapImplType =
_getLibrary(c, 'dart:_js_helper').getType('LinkedMap').type,
identityHashMapImplType =
_getLibrary(c, 'dart:_js_helper').getType('IdentityMap').type,
linkedHashSetImplType =
_getLibrary(c, 'dart:collection').getType('_HashSet').type,
identityHashSetImplType =
_getLibrary(c, 'dart:collection').getType('_IdentityHashSet').type,
dartJSLibrary = _getLibrary(c, 'dart:js') {
typeRep = new JSTypeRep(rules, types);
}
@ -363,7 +375,7 @@ class CodeGenerator extends Object
}
// Add implicit dart:core dependency so it is first.
emitLibraryName(dartCoreLibrary);
emitLibraryName(coreLibrary);
// Visit each compilation unit and emit its code.
//
@ -510,8 +522,8 @@ class CodeGenerator extends Object
}
String coreModuleName;
if (!_libraries.containsKey(dartCoreLibrary)) {
coreModuleName = _libraryToModule(dartCoreLibrary);
if (!_libraries.containsKey(coreLibrary)) {
coreModuleName = _libraryToModule(coreLibrary);
}
modules.forEach((module, libraries) {
// Generate import directives.
@ -610,11 +622,12 @@ class CodeGenerator extends Object
for (var declaration in unit.declarations) {
if (declaration is TopLevelVariableDeclaration) {
inferNullableTypes(declaration);
if (isInternalSdk &&
(declaration.variables.isFinal || declaration.variables.isConst)) {
_emitInternalSdkFields(declaration.variables.variables);
} else {
(fields ??= []).addAll(declaration.variables.variables);
var lazyFields = declaration.variables.variables;
if (isInternalSdk) {
lazyFields = _emitInternalSdkFields(lazyFields);
}
if (lazyFields.isNotEmpty) {
(fields ??= []).addAll(lazyFields);
}
continue;
}
@ -2189,12 +2202,11 @@ class CodeGenerator extends Object
}
emitSignature('Constructor', constructors);
}
// Add static property dart._runtimeType to Object.
// All other Dart classes will (statically) inherit this property.
if (classElem == objectClass) {
body.add(_callHelperStatement('tagComputed(#, () => #.#);',
[className, emitLibraryName(dartCoreLibrary), 'Type']));
[className, emitLibraryName(coreLibrary), 'Type']));
}
}
@ -2777,7 +2789,7 @@ class CodeGenerator extends Object
var isSync = !(element.isAsynchronous || element.isGenerator);
var formals = _emitFormalParameterList(parameters, destructure: isSync);
var typeFormals = _emitTypeFormals(type.typeFormals);
formals.insertAll(0, typeFormals);
if (_reifyGeneric(element)) formals.insertAll(0, typeFormals);
JS.Block code = isSync
? _emitFunctionBody(element, parameters, body)
@ -3786,8 +3798,14 @@ class CodeGenerator extends Object
}
List<JS.Expression> _emitInvokeTypeArguments(InvocationExpression node) {
// add no reify generic check here: if (node.function)
// node is Identifier
var function = node.function;
if (function is Identifier && !_reifyGeneric(function.staticElement)) {
return null;
}
return _emitFunctionTypeArguments(
node.function.staticType, node.staticInvokeType, node.typeArguments);
function.staticType, node.staticInvokeType, node.typeArguments);
}
/// If `g` is a generic function type, and `f` is an instantiation of it,
@ -4194,17 +4212,30 @@ class CodeGenerator extends Object
/// Treat dart:_runtime fields as safe to eagerly evaluate.
// TODO(jmesserly): it'd be nice to avoid this special case.
void _emitInternalSdkFields(List<VariableDeclaration> fields) {
List<VariableDeclaration> _emitInternalSdkFields(
List<VariableDeclaration> fields) {
var lazyFields = <VariableDeclaration>[];
for (var field in fields) {
// Skip our magic undefined constant.
var element = field.element as TopLevelVariableElement;
if (element.name == 'undefined') continue;
_moduleItems.add(annotate(
js.statement('# = #;',
[_emitTopLevelName(field.element), _visitInitializer(field)]),
field,
field.element));
var init = field.initializer;
if (init == null ||
init is Literal ||
_isJSInvocation(init) ||
init is InstanceCreationExpression &&
isSdkInternalRuntime(init.staticElement.library)) {
_moduleItems.add(annotate(
js.statement('# = #;',
[_emitTopLevelName(field.element), _visitInitializer(field)]),
field,
field.element));
} else {
lazyFields.add(field);
}
}
return lazyFields;
}
JS.Expression _visitInitializer(VariableDeclaration node) {
@ -4280,31 +4311,56 @@ class CodeGenerator extends Object
SimpleIdentifier name,
ArgumentList argumentList,
bool isConst) {
if (element == null) {
return _throwUnsafe('unresolved constructor: ${type?.name ?? '<null>'}'
'.${name?.name ?? '<unnamed>'}');
}
var classElem = element.enclosingElement;
if (_isObjectLiteral(classElem)) {
return _emitObjectLiteral(argumentList);
}
JS.Expression emitNew() {
JS.Expression ctor;
bool isFactory = false;
bool isNative = false;
if (element == null) {
ctor = _throwUnsafe('unresolved constructor: ${type?.name ?? '<null>'}'
'.${name?.name ?? '<unnamed>'}');
} else {
ctor = _emitConstructorName(element, type);
isFactory = element.isFactory;
var classElem = element.enclosingElement;
isNative = _isJSNative(classElem);
}
var args = _emitArgumentList(argumentList);
if (argumentList.arguments.isEmpty && element.library.isInSdk) {
// Skip the slow SDK factory constructors when possible.
switch (classElem.name) {
case 'Map':
case 'HashMap':
case 'LinkedHashMap':
if (element.name == '') {
return js.call('new #.new()', _emitMapImplType(type));
} else if (element.name == 'identity') {
return js.call(
'new #.new()', _emitMapImplType(type, identity: true));
}
break;
case 'Set':
case 'HashSet':
case 'LinkedHashSet':
if (element.name == '') {
return js.call('new #.new()', _emitSetImplType(type));
} else if (element.name == 'identity') {
return js.call(
'new #.new()', _emitSetImplType(type, identity: true));
}
break;
case 'List':
if (element.name == '' && type is InterfaceType) {
return _emitList(type.typeArguments[0], []);
}
break;
}
}
// Native factory constructors are JS constructors - use new here.
return isFactory && !isNative
var ctor = _emitConstructorName(element, type);
return element.isFactory && !_isJSNative(classElem)
? new JS.Call(ctor, args)
: new JS.New(ctor, args);
}
if (element != null && _isObjectLiteral(element.enclosingElement)) {
return _emitObjectLiteral(argumentList);
}
if (isConst) return _emitConst(emitNew);
return emitNew();
return isConst ? _emitConst(emitNew) : emitNew();
}
bool _isObjectLiteral(Element classElem) {
@ -5499,23 +5555,41 @@ class CodeGenerator extends Object
@override
visitMapLiteral(MapLiteral node) {
var isConst = node.constKeyword != null;
// TODO(jmesserly): we can likely make these faster.
JS.Expression emitMap() {
var entries = node.entries;
var type = node.staticType as InterfaceType;
var elements = <JS.Expression>[];
for (var e in entries) {
elements.add(_visit(e.key));
elements.add(_visit(e.value));
emitEntries() {
var entries = <JS.Expression>[];
for (var e in node.entries) {
entries.add(_visit(e.key));
entries.add(_visit(e.value));
}
var args = type.typeArguments.map(_emitType).toList();
args.add(new JS.ArrayInitializer(elements));
return _callHelper(isConst ? 'constMap(#)' : 'map(#)', [args]);
return new JS.ArrayInitializer(entries);
}
return isConst ? _cacheConst(emitMap) : emitMap();
if (node.constKeyword == null) {
var mapType = _emitMapImplType(node.staticType);
if (node.entries.isEmpty) {
return js.call('new #.new()', [mapType]);
}
return js.call('new #.from(#)', [mapType, emitEntries()]);
}
var typeArgs = (node.staticType as InterfaceType).typeArguments;
return _cacheConst(() => _callHelper('constMap(#, #, #)',
[_emitType(typeArgs[0]), _emitType(typeArgs[1]), emitEntries()]));
}
JS.Expression _emitMapImplType(InterfaceType type, {bool identity}) {
var typeArgs = type.typeArguments;
if (typeArgs.isEmpty) return _emitType(type);
identity ??= isPrimitiveType(typeArgs[0]);
type = identity ? identityHashMapImplType : linkedHashMapImplType;
return _emitType(type.instantiate(typeArgs));
}
JS.Expression _emitSetImplType(InterfaceType type, {bool identity}) {
var typeArgs = type.typeArguments;
if (typeArgs.isEmpty) return _emitType(type);
identity ??= isPrimitiveType(typeArgs[0]);
type = identity ? identityHashSetImplType : linkedHashSetImplType;
return _emitType(type.instantiate(typeArgs));
}
@override
@ -6138,6 +6212,13 @@ bool _isDeferredLoadLibrary(Expression target, SimpleIdentifier name) {
bool _annotatedNullCheck(Element e) =>
e != null && findAnnotation(e, isNullCheckAnnotation) != null;
bool _reifyGeneric(Element e) =>
e == null ||
!e.library.isInSdk ||
findAnnotation(
e, (a) => isBuiltinAnnotation(a, '_js_helper', 'NoReifyGeneric')) ==
null;
final _friendlyOperatorName = {
'<': 'lessThan',
'>': 'greaterThan',

View file

@ -47,7 +47,7 @@ bool isPublicJSAnnotation(DartObjectImpl value) =>
bool isJSAnonymousAnnotation(DartObjectImpl value) =>
_isJsLibType('_Anonymous', value.type.element);
bool _isBuiltinAnnotation(
bool isBuiltinAnnotation(
DartObjectImpl value, String libraryName, String annotationName) {
var e = value?.type?.element;
if (e?.name != annotationName) return false;
@ -59,22 +59,22 @@ bool _isBuiltinAnnotation(
/// Whether [value] is a `@JSExportName` (internal annotation used in SDK
/// instead of `@JS` from `package:js`).
bool isJSExportNameAnnotation(DartObjectImpl value) =>
_isBuiltinAnnotation(value, '_foreign_helper', 'JSExportName');
isBuiltinAnnotation(value, '_foreign_helper', 'JSExportName');
bool isJsName(DartObjectImpl value) =>
_isBuiltinAnnotation(value, '_js_helper', 'JSName');
isBuiltinAnnotation(value, '_js_helper', 'JSName');
bool isJsPeerInterface(DartObjectImpl value) =>
_isBuiltinAnnotation(value, '_js_helper', 'JsPeerInterface');
isBuiltinAnnotation(value, '_js_helper', 'JsPeerInterface');
bool isNativeAnnotation(DartObjectImpl value) =>
_isBuiltinAnnotation(value, '_js_helper', 'Native');
isBuiltinAnnotation(value, '_js_helper', 'Native');
bool isNotNullAnnotation(DartObjectImpl value) =>
_isBuiltinAnnotation(value, '_js_helper', 'NotNull');
isBuiltinAnnotation(value, '_js_helper', 'NotNull');
bool isNullCheckAnnotation(DartObjectImpl value) =>
_isBuiltinAnnotation(value, '_js_helper', 'NullCheck');
isBuiltinAnnotation(value, '_js_helper', 'NullCheck');
/// Returns the name value of the `JSExportName` annotation (when compiling
/// the SDK), or `null` if there's none. This is used to control the name

View file

@ -24,7 +24,7 @@ import 'property_model.dart';
// TODO(vsm): Revisit whether we really need this when we get
// better non-nullability in the type system.
abstract class NullableTypeInference {
LibraryElement get dartCoreLibrary;
LibraryElement get coreLibrary;
VirtualFieldModel get virtualFields;
InterfaceType getImplementationType(DartType type);
@ -74,7 +74,7 @@ abstract class NullableTypeInference {
}
}
if (e.name == 'identical' && identical(e.library, dartCoreLibrary)) {
if (e.name == 'identical' && identical(e.library, coreLibrary)) {
return true;
}
// If this is a method call, check to see whether it is to a final
@ -87,7 +87,7 @@ abstract class NullableTypeInference {
DartType targetType = container.type;
InterfaceType implType = getImplementationType(targetType);
if (implType != null) {
MethodElement method = implType.lookUpMethod(e.name, dartCoreLibrary);
MethodElement method = implType.lookUpMethod(e.name, coreLibrary);
if (method != null) e = method;
}
}
@ -111,7 +111,7 @@ abstract class NullableTypeInference {
var targetType = container.type;
var implType = getImplementationType(targetType);
if (implType != null) {
var getter = implType.lookUpGetter(name, dartCoreLibrary);
var getter = implType.lookUpGetter(name, coreLibrary);
if (getter != null) element = getter;
}
}

View file

@ -419,5 +419,10 @@ class ClassPropertyModel {
if (!type.isObject) {
_collectNativeMembers(element.supertype, members);
}
if (element.isEnum) {
// TODO(jmesserly): analyzer does not create the synthetic element
// for the enum's `toString()` method, so we'll use the one on Object.
members.add('toString');
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@ import 'dart:_js_helper'
checkInt,
getRuntimeType,
getTraceFromException,
JsLinkedHashMap,
LinkedMap,
JSSyntaxRegExp,
NoInline,
notNull,
@ -390,7 +390,7 @@ class Map<K, V> {
}
@patch
factory Map() = JsLinkedHashMap<K, V>.es6;
factory Map() = LinkedMap<K, V>;
}
@patch

View file

@ -22,6 +22,14 @@ class NotNull {
const notNull = const NotNull();
/// Marks a generic function or static method API to be not reified.
/// ****CAUTION******
/// This is currently unchecked, and hence should be used very carefully for
/// internal SDK APIs only.
class NoReifyGeneric {
const NoReifyGeneric();
}
/// Tells the development compiler to check a variable for null at its
/// declaration point, and then to assume that the variable is non-null
/// from that point forward.

View file

@ -0,0 +1,195 @@
// 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.
part of dart._js_helper;
class CustomKeyHashMap<K, V> extends CustomHashMap<K, V> {
final _Predicate<Object> _validKey;
CustomKeyHashMap(_Equality<K> equals, _Hasher<K> hashCode, this._validKey)
: super(equals, hashCode);
@override
@notNull
bool containsKey(Object key) {
if (!_validKey(key)) return false;
return super.containsKey(key);
}
@override
V operator [](Object key) {
if (!_validKey(key)) return null;
return super[key];
}
@override
V remove(Object key) {
if (!_validKey(key)) return null;
return super.remove(key);
}
}
class CustomHashMap<K, V> extends InternalMap<K, V> {
/// The backing store for this map.
@notNull
final _map = JS('', 'new Map()');
/// Our map used to map keys onto the canonical key that is stored in [_map].
@notNull
final _keyMap = JS('', 'new Map()');
// We track the number of modifications done to the key set of the
// hash map to be able to throw when the map is modified while being
// iterated over.
//
// Value cycles after 2^30 modifications so that modification counts are
// always unboxed (Smi) values. Modification detection will be missed if you
// make exactly some multiple of 2^30 modifications between advances of an
// iterator.
@notNull
int _modifications = 0;
final _Equality<K> _equals;
final _Hasher<K> _hashCode;
CustomHashMap(this._equals, this._hashCode);
@notNull
int get length => JS('int', '#.size', _map);
@notNull
bool get isEmpty => JS('bool', '#.size == 0', _map);
@notNull
bool get isNotEmpty => JS('bool', '#.size != 0', _map);
Iterable<K> get keys => new _JSMapIterable<K>(this, true);
Iterable<V> get values => new _JSMapIterable<V>(this, false);
@notNull
bool containsKey(Object key) {
if (key is K) {
var buckets = JS('', '#.get(# & 0x3ffffff)', _keyMap, _hashCode(key));
if (buckets != null) {
var equals = _equals;
for (int i = 0, n = JS('int', '#.length', buckets); i < n; i++) {
K k = JS('', '#[#]', buckets, i);
if (equals(k, key)) return true;
}
}
}
return false;
}
bool containsValue(Object value) {
for (var v in JS('', '#.values()', _map)) {
if (value == v) return true;
}
return false;
}
void addAll(Map<K, V> other) {
other.forEach((K key, V value) {
this[key] = value;
});
}
V operator [](Object key) {
if (key is K) {
var buckets = JS('', '#.get(# & 0x3ffffff)', _keyMap, _hashCode(key));
if (buckets != null) {
var equals = _equals;
for (int i = 0, n = JS('int', '#.length', buckets); i < n; i++) {
K k = JS('', '#[#]', buckets, i);
if (equals(k, key)) return JS('', '#.get(#)', _map, k);
}
}
}
return null;
}
void operator []=(K key, V value) {
var keyMap = _keyMap;
var hash = JS('int', '# & 0x3ffffff', _hashCode(key));
var buckets = JS('', '#.get(#)', keyMap, hash);
if (buckets == null) {
JS('', '#.set(#, [#])', keyMap, hash, key);
} else {
var equals = _equals;
for (int i = 0, n = JS('int', '#.length', buckets);;) {
K k = JS('', '#[#]', buckets, i);
if (equals(k, key)) {
key = k;
break;
}
if (++i >= n) {
JS('', '#.push(#)', buckets, key);
break;
}
}
}
JS('', '#.set(#, #)', _map, key, value);
_modifications = (_modifications + 1) & 0x3ffffff;
}
V putIfAbsent(K key, V ifAbsent()) {
var keyMap = _keyMap;
var hash = JS('int', '# & 0x3ffffff', _hashCode(key));
var buckets = JS('', '#.get(#)', keyMap, hash);
if (buckets == null) {
JS('', '#.set(#, [#])', keyMap, hash, key);
} else {
var equals = _equals;
for (int i = 0, n = JS('int', '#.length', buckets); i < n; i++) {
K k = JS('', '#[#]', buckets, i);
if (equals(k, key)) return JS('', '#.get(#)', _map, k);
}
JS('', '#.push(#)', buckets, key);
}
V value = ifAbsent();
JS('', '#.set(#, #)', _map, key, value);
_modifications = (_modifications + 1) & 0x3ffffff;
return value;
}
V remove(Object key) {
if (key is K) {
var hash = JS('int', '# & 0x3ffffff', _hashCode(key));
var keyMap = _keyMap;
var buckets = JS('', '#.get(#)', keyMap, hash);
if (buckets == null) return null; // not found
var equals = _equals;
for (int i = 0, n = JS('int', '#.length', buckets); i < n; i++) {
K k = JS('', '#[#]', buckets, i);
if (equals(k, key)) {
if (n == 1) {
JS('', '#.delete(#)', keyMap, hash);
} else {
JS('', '#.splice(#, 1)', buckets, i);
}
var map = _map;
V value = JS('', '#.get(#)', map, k);
JS('', '#.delete(#)', map, k);
_modifications = (_modifications + 1) & 0x3ffffff;
return value;
}
}
}
return null;
}
void clear() {
var map = _map;
if (JS('int', '#.size', map) > 0) {
JS('', '#.clear()', map);
JS('', '#.clear()', _keyMap);
_modifications = (_modifications + 1) & 0x3ffffff;
}
}
String toString() => Maps.mapToString(this);
}
typedef bool _Equality<K>(K a, K b);
typedef int _Hasher<K>(K object);
typedef bool _Predicate<T>(T value);

View file

@ -350,6 +350,7 @@ void _installPropertiesForGlobalObject(jsProto) {
_installPropertiesForObject(jsProto);
// Use JS toString for JS objects, rather than the Dart one.
JS('', '#[dartx.toString] = function() { return this.toString(); }', jsProto);
identityEquals ??= JS('', '#[dartx._equals]', jsProto);
}
final _extensionMap = JS('', 'new Map()');
@ -522,3 +523,6 @@ final isStream = JS('', 'Symbol("_is_Stream")');
/// The well known symbol for testing `is StreamSubscription`
final isStreamSubscription = JS('', 'Symbol("_is_StreamSubscription")');
/// The default `operator ==` that calls [identical].
var identityEquals;

View file

@ -542,31 +542,6 @@ notNull(x) {
return x;
}
///
/// Creates a dart:collection LinkedHashMap.
///
/// For a map with string keys an object literal can be used, for example
/// `map({'hi': 1, 'there': 2})`.
///
/// Otherwise an array should be used, for example `map([1, 2, 3, 4])` will
/// create a map with keys [1, 3] and values [2, 4]. Each key-value pair
/// should be adjacent entries in the array.
///
/// For a map with no keys the function can be called with no arguments, for
/// example `map()`.
///
// TODO(jmesserly): this could be faster
// TODO(jmesserly): we can use default values `= dynamic` once #417 is fixed.
// TODO(jmesserly): move this to classes for consistency with list literals?
map<K, V>(JSArray elements) {
var map = new JsLinkedHashMap<K, V>();
if (elements == null) return map;
for (var i = 0, end = elements.length - 1; i < end; i += 2) {
map[JS('', '#[#]', elements, i)] = JS('', '#[#]', elements, i + 1);
}
return map;
}
/// The global constant map table.
final constantMaps = JS('', 'new Map()');
@ -585,7 +560,7 @@ constMap<K, V>(JSArray elements) {
map = lookupNonTerminal(map, K);
var result = JS('', '#.get(#)', map, V);
if (result != null) return result;
result = new ImmutableMap<K, V>(elements);
result = new ImmutableMap<K, V>.from(elements);
JS('', '#.set(#, #)', map, V, result);
return result;
}
@ -735,6 +710,8 @@ const_(obj) => JS('', '''(() => {
// Right now we use the (name,value) pairs in sequence, which prevents
// an object with incorrect field values being returned, but won't
// canonicalize correctly if key order is different.
//
// See issue https://github.com/dart-lang/sdk/issues/30876
for (let i = 0; i < count; i++) {
let name = names[i];
map = lookupNonTerminal(map, name);
@ -799,6 +776,17 @@ int hashCode(obj) {
return obj == null ? 0 : JS('int', '#[#]', obj, extensionSymbol('hashCode'));
}
hashKey(k) {
if (k == null) return 0;
switch (JS('String', 'typeof #', k)) {
case "object":
case "function":
return JS('int', '#[#] & 0x3ffffff', k, extensionSymbol('hashCode'));
}
// For primitive types we can store the key directly in an ES6 Map.
return k;
}
@JSExportName('toString')
String _toString(obj) {
if (obj == null) return "null";
@ -834,6 +822,7 @@ String str(strings, @rest values) => JS('', '''(() => {
final identityHashCode_ = JS('', 'Symbol("_identityHashCode")');
/// Adapts a Dart `get iterator` into a JS `[Symbol.iterator]`.
// TODO(jmesserly): instead of an adaptor, we could compile Dart iterators
// natively implementing the JS iterator protocol. This would allow us to
// optimize them a bit.

View file

@ -0,0 +1,131 @@
// 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.
part of dart._js_helper;
class IdentityMap<K, V> extends InternalMap<K, V> {
final _map = JS('', 'new Map()');
// We track the number of modifications done to the key set of the
// hash map to be able to throw when the map is modified while being
// iterated over.
//
// Value cycles after 2^30 modifications so that modification counts are
// always unboxed (Smi) values. Modification detection will be missed if you
// make exactly some multiple of 2^30 modifications between advances of an
// iterator.
@notNull
int _modifications = 0;
IdentityMap();
IdentityMap.from(JSArray entries) {
var map = _map;
for (int i = 0, n = JS('int', '#.length', entries); i < n; i += 2) {
JS('', '#.set(#[#], #[#])', map, entries, i, entries, i + 1);
}
}
int get length => JS('int', '#.size', _map);
bool get isEmpty => JS('bool', '#.size == 0', _map);
bool get isNotEmpty => JS('bool', '#.size != 0', _map);
Iterable<K> get keys => new _JSMapIterable<K>(this, true);
Iterable<V> get values => new _JSMapIterable<V>(this, false);
bool containsKey(Object key) {
return JS('bool', '#.has(#)', _map, key);
}
bool containsValue(Object value) {
for (var v in JS('', '#.values()', _map)) {
if (v == value) return true;
}
return false;
}
void addAll(Map<K, V> other) {
if (other.isNotEmpty) {
var map = _map;
other.forEach((key, value) {
JS('', '#.set(#, #)', map, key, value);
});
_modifications = (_modifications + 1) & 0x3ffffff;
}
}
V operator [](Object key) {
return JS('', '#.get(#)', _map, key);
}
void operator []=(K key, V value) {
JS('', '#.set(#, #)', _map, key, value);
_modifications = (_modifications + 1) & 0x3ffffff;
}
V putIfAbsent(K key, V ifAbsent()) {
if (JS('bool', '#.has(#)', _map, key)) return JS('', '#.get(#)', _map, key);
V value = ifAbsent();
JS('', '#.set(#, #)', _map, key, value);
_modifications = (_modifications + 1) & 0x3ffffff;
return value;
}
V remove(Object key) {
V value = JS('', '#.get(#)', _map, key);
if (JS('bool', '#.delete(#)', _map, key)) {
_modifications = (_modifications + 1) & 0x3ffffff;
}
return value;
}
void clear() {
if (JS('int', '#.size', _map) > 0) {
JS('', '#.clear()', _map);
_modifications = (_modifications + 1) & 0x3ffffff;
}
}
String toString() => Maps.mapToString(this);
}
class _JSMapIterable<E> extends EfficientLengthIterable<E> {
final InternalMap _map;
@notNull
final bool _isKeys;
_JSMapIterable(this._map, this._isKeys);
int get length => _map.length;
bool get isEmpty => _map.isEmpty;
@JSExportName('Symbol.iterator')
_jsIterator() {
var map = _map;
var iterator =
JS('', '# ? #.keys() : #.values()', _isKeys, map._map, map._map);
int modifications = map._modifications;
return JS(
'',
'''{
next() {
if (# != #) {
throw #;
}
return #.next();
}
}''',
modifications,
map._modifications,
new ConcurrentModificationError(map),
iterator);
}
Iterator<E> get iterator => new DartIterator<E>(_jsIterator());
bool contains(Object element) =>
_isKeys ? _map.containsKey(element) : _map.containsValue(element);
void forEach(void f(E element)) {
for (var entry in this) f(entry);
}
}

View file

@ -8,7 +8,7 @@ import 'dart:collection';
import 'dart:_debugger' show stackTraceMapper;
import 'dart:_foreign_helper' show JS, JS_STRING_CONCAT;
import 'dart:_foreign_helper' show JS, JS_STRING_CONCAT, JSExportName;
import 'dart:_interceptors';
import 'dart:_internal'
@ -19,6 +19,8 @@ import 'dart:_runtime' as dart;
part 'annotations.dart';
part 'linked_hash_map.dart';
part 'identity_hash_map.dart';
part 'custom_hash_map.dart';
part 'native_helper.dart';
part 'regexp_helper.dart';
part 'string_helper.dart';
@ -30,9 +32,22 @@ class _Patch {
const _Patch patch = const _Patch();
/// Marks the internal map in dart2js, so that internal libraries can is-check
// them.
abstract class InternalMap<K, V> implements Map<K, V> {}
/// Adapts a JS `[Symbol.iterator]` to a Dart `get iterator`.
///
/// This is the inverse of `JsIterator`, for classes where we can more
/// efficiently obtain a JS iterator instead of a Dart one.
class DartIterator<E> implements Iterator<E> {
DartIterator(jsIterator) {
JS('', 'this._current = null');
JS('', 'this._jsIterator = #', jsIterator);
}
E get current => JS('', 'this._current');
bool moveNext() {
var next = JS('', 'this._jsIterator.next()');
JS('', 'this._current = #.value', next);
return JS('bool', '!#.done', next);
}
}
class Primitives {
/// Isolate-unique ID for caching [JsClosureMirror.function].

View file

@ -1,4 +1,4 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// 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.
@ -7,452 +7,264 @@
part of dart._js_helper;
// DDC-specific, just use Object-backed maps
//const _USE_ES6_MAPS = const bool.fromEnvironment("dart2js.use.es6.maps");
class JsLinkedHashMap<K, V> implements LinkedHashMap<K, V>, InternalMap<K, V> {
/// Our internal LinkedHashMaps.
abstract class InternalMap<K, V> implements LinkedHashMap<K, V> {
@notNull
int _length = 0;
get _map;
// The hash map contents are divided into three parts: one part for
// string keys, one for numeric keys, and one for the rest. String
// and numeric keys map directly to their linked cells, but the rest
// of the entries are stored in bucket lists of the form:
//
// [cell-0, cell-1, ...]
//
// where all keys in the same bucket share the same hash code.
var _strings;
var _nums;
var _rest;
@notNull
int get _modifications;
// The keys and values are stored in cells that are linked together
// to form a double linked list.
LinkedHashMapCell<K, V> _first;
LinkedHashMapCell<K, V> _last;
void forEach(void action(K key, V value)) {
int modifications = _modifications;
for (var entry in JS('Iterable', '#.entries()', _map)) {
action(JS('', '#[0]', entry), JS('', '#[1]', entry));
if (modifications != _modifications) {
throw new ConcurrentModificationError(this);
}
}
}
}
/// A linked hash map implementation based on ES6 Map.
///
/// Items that can use identity semantics are stored directly in the backing
/// map.
///
/// Items that have a custom equality/hashCode are first canonicalized by
/// looking up the canonical key by its hashCode.
class LinkedMap<K, V> extends InternalMap<K, V> {
/// The backing store for this map.
///
/// Keys that use identity equality are stored directly. For other types of
/// keys, we first look them up (by hashCode) in the [_keyMap] map, then
/// we lookup the key in this map.
@notNull
final _map = JS('', 'new Map()');
/// Items that use custom equality semantics.
///
/// This maps from the item's hashCode to the canonical key, which is then
/// used to lookup the item in [_map]. Keeping the data in our primary backing
/// map gives us the ordering semantics requred by [LinkedHashMap], while
/// also providing convenient access to keys/values.
@notNull
final _keyMap = JS('', 'new Map()');
// We track the number of modifications done to the key set of the
// hash map to be able to throw when the map is modified while being
// iterated over.
//
// Value cycles after 2^30 modifications so that modification counts are
// always unboxed (Smi) values. Modification detection will be missed if you
// make exactly some multiple of 2^30 modifications between advances of an
// iterator.
@notNull
int _modifications = 0;
// static bool get _supportsEs6Maps {
// return JS('returns:bool;depends:none;effects:none;throws:never;gvn:true',
// 'typeof Map != "undefined"');
// }
LinkedMap();
JsLinkedHashMap();
/// If ES6 Maps are available returns a linked hash-map backed by an ES6 Map.
// @ForceInline()
factory JsLinkedHashMap.es6() {
// return (_USE_ES6_MAPS && JsLinkedHashMap._supportsEs6Maps)
// ? new Es6LinkedHashMap<K, V>()
// : new JsLinkedHashMap<K, V>();
return new JsLinkedHashMap<K, V>();
/// Called by generated code for a map literal.
LinkedMap.from(JSArray entries) {
var map = _map;
var keyMap = _keyMap;
for (int i = 0, n = JS('int', '#.length', entries); i < n; i += 2) {
K key = JS('', '#[#]', entries, i);
V value = JS('', '#[#]', entries, i + 1);
if (key == null) {
key = null;
} else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'),
dart.identityEquals)) {
key = putLinkedMapKey(key, keyMap);
}
JS('', '#.set(#, #)', map, key, value);
}
}
@notNull
int get length => _length;
@notNull
bool get isEmpty => _length == 0;
@notNull
bool get isNotEmpty => !isEmpty;
int get length => JS('int', '#.size', _map);
Iterable<K> get keys {
return new LinkedHashMapKeyIterable<K>(this);
}
@notNull
bool get isEmpty => JS('bool', '#.size == 0', _map);
Iterable<V> get values {
return new MappedIterable<K, V>(keys, (each) => this[each]);
}
@notNull
bool get isNotEmpty => JS('bool', '#.size != 0', _map);
Iterable<K> get keys => new _JSMapIterable<K>(this, true);
Iterable<V> get values => new _JSMapIterable<V>(this, false);
@notNull
bool containsKey(Object key) {
if (_isStringKey(key)) {
var strings = _strings;
if (strings == null) return false;
return _containsTableEntry(strings, key);
} else if (_isNumericKey(key)) {
var nums = _nums;
if (nums == null) return false;
return _containsTableEntry(nums, key);
} else {
return internalContainsKey(key);
if (key == null) {
key = null;
} else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'),
dart.identityEquals)) {
@notNull
var k = key;
var buckets = JS('', '#.get(# & 0x3ffffff)', _keyMap, k.hashCode);
if (buckets != null) {
for (int i = 0, n = JS('int', '#.length', buckets); i < n; i++) {
k = JS('', '#[#]', buckets, i);
if (k == key) return true;
}
}
return false;
}
}
@notNull
bool internalContainsKey(Object key) {
var rest = _rest;
if (rest == null) return false;
var bucket = _getBucket(rest, key);
return internalFindBucketIndex(bucket, key) >= 0;
return JS('bool', '#.has(#)', _map, key);
}
bool containsValue(Object value) {
return keys.any((each) => this[each] == value);
for (var v in JS('', '#.values()', _map)) {
if (v == value) return true;
}
return false;
}
void addAll(Map<K, V> other) {
var map = _map;
int length = JS('', '#.size', map);
other.forEach((K key, V value) {
this[key] = value;
if (key == null) {
key = null;
} else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'),
dart.identityEquals)) {
key = putLinkedMapKey(key, _keyMap);
}
JS('', '#.set(#, #)', _map, key, value);
});
if (length != JS('int', '#.size', map)) {
_modifications = (_modifications + 1) & 0x3ffffff;
}
}
V operator [](Object key) {
if (_isStringKey(key)) {
var strings = _strings;
if (strings == null) return null;
LinkedHashMapCell/*<K, V>*/ cell = _getTableCell(strings, key);
return (cell == null) ? null : cell.hashMapCellValue;
} else if (_isNumericKey(key)) {
var nums = _nums;
if (nums == null) return null;
LinkedHashMapCell/*<K, V>*/ cell = _getTableCell(nums, key);
return (cell == null) ? null : cell.hashMapCellValue;
} else {
return internalGet(key);
if (key == null) {
key = null;
} else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'),
dart.identityEquals)) {
@notNull
var k = key;
var buckets = JS('', '#.get(# & 0x3ffffff)', _keyMap, k.hashCode);
if (buckets != null) {
for (int i = 0, n = JS('int', '#.length', buckets); i < n; i++) {
k = JS('', '#[#]', buckets, i);
if (k == key) return JS('', '#.get(#)', _map, k);
}
}
return null;
}
}
V internalGet(Object key) {
var rest = _rest;
if (rest == null) return null;
var bucket = _getBucket(rest, key);
int index = internalFindBucketIndex(bucket, key);
if (index < 0) return null;
LinkedHashMapCell/*<K, V>*/ cell = JS('var', '#[#]', bucket, index);
return cell.hashMapCellValue;
return JS('', '#.get(#)', _map, key);
}
void operator []=(K key, V value) {
if (_isStringKey(key)) {
var strings = _strings;
if (strings == null) _strings = strings = _newHashTable();
_addHashTableEntry(strings, key, value);
} else if (_isNumericKey(key)) {
var nums = _nums;
if (nums == null) _nums = nums = _newHashTable();
_addHashTableEntry(nums, key, value);
} else {
internalSet(key, value);
if (key == null) {
key = null;
} else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'),
dart.identityEquals)) {
key = putLinkedMapKey(key, _keyMap);
}
}
void internalSet(K key, V value) {
var rest = _rest;
if (rest == null) _rest = rest = _newHashTable();
var hash = internalComputeHashCode(key);
var bucket = _getTableBucket(rest, hash);
if (bucket == null) {
LinkedHashMapCell/*<K, V>*/ cell = _newLinkedCell(key, value);
_setTableEntry(rest, hash, JS('var', '[#]', cell));
} else {
int index = internalFindBucketIndex(bucket, key);
if (index >= 0) {
LinkedHashMapCell/*<K, V>*/ cell = JS('var', '#[#]', bucket, index);
cell.hashMapCellValue = value;
} else {
LinkedHashMapCell/*<K, V>*/ cell = _newLinkedCell(key, value);
JS('void', '#.push(#)', bucket, cell);
}
var map = _map;
int length = JS('', '#.size', map);
JS('', '#.set(#, #)', map, key, value);
// TODO(jmesserly): can we remove this? Identity maps do not try to be as
// precise. We do pay a performance cost to support it.
if (length != JS('int', '#.size', map)) {
_modifications = (_modifications + 1) & 0x3ffffff;
}
}
V putIfAbsent(K key, V ifAbsent()) {
if (containsKey(key)) return this[key];
var map = _map;
if (key == null) {
key = null;
if (JS('bool', '#.has(null)', map)) return JS('', '#.get(null)', map);
} else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'),
dart.identityEquals)) {
@notNull
K k = key;
var hash = JS('int', '# & 0x3ffffff', k.hashCode);
var buckets = JS('', '#.get(#)', _keyMap, hash);
if (buckets == null) {
JS('', '#.set(#, [#])', _keyMap, hash, key);
} else {
for (int i = 0, n = JS('int', '#.length', buckets); i < n; i++) {
k = JS('', '#[#]', buckets, i);
if (k == key) return JS('', '#.get(#)', map, k);
}
JS('', '#.push(#)', buckets, key);
}
} else if (JS('bool', '#.has(#)', map, key)) {
return JS('', '#.get(#)', map, key);
}
V value = ifAbsent();
this[key] = value;
JS('', '#.set(#, #)', map, key, value);
_modifications = (_modifications + 1) & 0x3ffffff;
return value;
}
V remove(Object key) {
if (_isStringKey(key)) {
return _removeHashTableEntry(_strings, key);
} else if (_isNumericKey(key)) {
return _removeHashTableEntry(_nums, key);
} else {
return internalRemove(key);
if (key == null) {
key = null;
} else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'),
dart.identityEquals)) {
@notNull
var k = key;
var hash = JS('int', '# & 0x3ffffff', k.hashCode);
var buckets = JS('', '#.get(#)', _keyMap, hash);
if (buckets == null) return null; // not found
for (int i = 0, n = JS('int', '#.length', buckets);;) {
k = JS('', '#[#]', buckets, i);
if (k == key) {
key = k;
if (n == 1) {
JS('', '#.delete(#)', _keyMap, hash);
} else {
JS('', '#.splice(#, 1)', buckets, i);
}
break;
}
if (++i >= n) return null; // not found
}
}
}
V internalRemove(Object key) {
var rest = _rest;
if (rest == null) return null;
var bucket = _getBucket(rest, key);
int index = internalFindBucketIndex(bucket, key);
if (index < 0) return null;
// Use splice to remove the [cell] element at the index and
// unlink the cell before returning its value.
LinkedHashMapCell/*<K, V>*/ cell =
JS('var', '#.splice(#, 1)[0]', bucket, index);
_unlinkCell(cell);
// TODO(kasperl): Consider getting rid of the bucket list when
// the length reaches zero.
return cell.hashMapCellValue;
var map = _map;
V value = JS('', '#.get(#)', map, key);
if (JS('bool', '#.delete(#)', map, key)) {
_modifications = (_modifications + 1) & 0x3ffffff;
}
return value;
}
void clear() {
if (_length > 0) {
_strings = _nums = _rest = _first = _last = null;
_length = 0;
_modified();
var map = _map;
if (JS('int', '#.size', map) > 0) {
JS('', '#.clear()', map);
JS('', '#.clear()', _keyMap);
_modifications = (_modifications + 1) & 0x3ffffff;
}
}
void forEach(void action(K key, V value)) {
LinkedHashMapCell/*<K, V>*/ cell = _first;
int modifications = _modifications;
while (cell != null) {
action(cell.hashMapCellKey, cell.hashMapCellValue);
if (modifications != _modifications) {
throw new ConcurrentModificationError(this);
}
cell = cell._next;
}
}
void _addHashTableEntry(var table, K key, V value) {
LinkedHashMapCell/*<K, V>*/ cell = _getTableCell(table, key);
if (cell == null) {
_setTableEntry(table, key, _newLinkedCell(key, value));
} else {
cell.hashMapCellValue = value;
}
}
V _removeHashTableEntry(var table, Object key) {
if (table == null) return null;
LinkedHashMapCell/*<K, V>*/ cell = _getTableCell(table, key);
if (cell == null) return null;
_unlinkCell(cell);
_deleteTableEntry(table, key);
return cell.hashMapCellValue;
}
void _modified() {
// Value cycles after 2^30 modifications so that modification counts are
// always unboxed (Smi) values. Modification detection will be missed if you
// make exactly some multiple of 2^30 modifications between advances of an
// iterator.
_modifications = (_modifications + 1) & 0x3ffffff;
}
// Create a new cell and link it in as the last one in the list.
LinkedHashMapCell/*<K, V>*/ _newLinkedCell(K key, V value) {
LinkedHashMapCell/*<K, V>*/ cell =
new LinkedHashMapCell/*<K, V>*/(key, value);
if (_first == null) {
_first = _last = cell;
} else {
LinkedHashMapCell/*<K, V>*/ last = _last;
cell._previous = last;
_last = last._next = cell;
}
_length++;
_modified();
return cell;
}
// Unlink the given cell from the linked list of cells.
void _unlinkCell(LinkedHashMapCell/*<K, V>*/ cell) {
LinkedHashMapCell/*<K, V>*/ previous = cell._previous;
LinkedHashMapCell/*<K, V>*/ next = cell._next;
if (previous == null) {
assert(cell == _first);
_first = next;
} else {
previous._next = next;
}
if (next == null) {
assert(cell == _last);
_last = previous;
} else {
next._previous = previous;
}
_length--;
_modified();
}
@notNull
static bool _isStringKey(var key) {
return key is String;
}
@notNull
static bool _isNumericKey(var key) {
// Only treat unsigned 30-bit integers as numeric keys. This way,
// we avoid converting them to strings when we use them as keys in
// the JavaScript hash table object.
return key is num && JS('bool', '(# & 0x3ffffff) === #', key, key);
}
int internalComputeHashCode(var key) {
// We force the hash codes to be unsigned 30-bit integers to avoid
// issues with problematic keys like '__proto__'. Another option
// would be to throw an exception if the hash code isn't a number.
return JS('int', '# & 0x3ffffff', key.hashCode);
}
List<dynamic/*=LinkedHashMapCell<K, V>*/ > _getBucket(var table, var key) {
var hash = internalComputeHashCode(key);
return _getTableBucket(table, hash);
}
@notNull
int internalFindBucketIndex(var bucket, var key) {
if (bucket == null) return -1;
int length = JS('int', '#.length', bucket);
for (int i = 0; i < length; i++) {
LinkedHashMapCell/*<K, V>*/ cell = JS('var', '#[#]', bucket, i);
if (cell.hashMapCellKey == key) return i;
}
return -1;
}
String toString() => Maps.mapToString(this);
/*=LinkedHashMapCell<K, V>*/ _getTableCell(var table, var key) {
return JS('var', '#[#]', table, key);
}
/*=List<LinkedHashMapCell<K, V>>*/ _getTableBucket(var table, var key) {
return JS('var', '#[#]', table, key);
}
void _setTableEntry(var table, var key, var value) {
assert(value != null);
JS('void', '#[#] = #', table, key, value);
}
void _deleteTableEntry(var table, var key) {
JS('void', 'delete #[#]', table, key);
}
@notNull
bool _containsTableEntry(var table, var key) {
LinkedHashMapCell/*<K, V>*/ cell = _getTableCell(table, key);
return cell != null;
}
_newHashTable() {
// Create a new JavaScript object to be used as a hash table. Use
// Object.create to avoid the properties on Object.prototype
// showing up as entries.
var table = JS('var', 'Object.create(null)');
// Attempt to force the hash table into 'dictionary' mode by
// adding a property to it and deleting it again.
var temporaryKey = '<non-identifier-key>';
_setTableEntry(table, temporaryKey, table);
_deleteTableEntry(table, temporaryKey);
return table;
}
}
class Es6LinkedHashMap<K, V> extends JsLinkedHashMap<K, V> {
@override
/*=LinkedHashMapCell<K, V>*/ _getTableCell(var table, var key) {
return JS('var', '#.get(#)', table, key);
@NoReifyGeneric()
K putLinkedMapKey<K>(@notNull K key, keyMap) {
var hash = JS('int', '# & 0x3ffffff', key.hashCode);
var buckets = JS('', '#.get(#)', keyMap, hash);
if (buckets == null) {
JS('', '#.set(#, [#])', keyMap, hash, key);
return key;
}
@override
/*=List<LinkedHashMapCell<K, V>>*/ _getTableBucket(var table, var key) {
return JS('var', '#.get(#)', table, key);
}
@override
void _setTableEntry(var table, var key, var value) {
JS('void', '#.set(#, #)', table, key, value);
}
@override
void _deleteTableEntry(var table, var key) {
JS('void', '#.delete(#)', table, key);
}
@override
bool _containsTableEntry(var table, var key) {
return JS('bool', '#.has(#)', table, key);
}
@override
_newHashTable() {
return JS('var', 'new Map()');
for (int i = 0, n = JS('int', '#.length', buckets); i < n; i++) {
@notNull
K k = JS('', '#[#]', buckets, i);
if (k == key) return k;
}
JS('', '#.push(#)', buckets, key);
return key;
}
class LinkedHashMapCell<K, V> {
final dynamic/*=K*/ hashMapCellKey;
dynamic/*=V*/ hashMapCellValue;
LinkedHashMapCell/*<K, V>*/ _next;
LinkedHashMapCell/*<K, V>*/ _previous;
LinkedHashMapCell(this.hashMapCellKey, this.hashMapCellValue);
}
class LinkedHashMapKeyIterable<E> extends EfficientLengthIterable<E> {
final dynamic/*=JsLinkedHashMap<E, dynamic>*/ _map;
LinkedHashMapKeyIterable(this._map);
int get length => _map._length;
bool get isEmpty => _map._length == 0;
Iterator<E> get iterator {
return new LinkedHashMapKeyIterator<E>(_map, _map._modifications);
}
bool contains(Object element) {
return _map.containsKey(element);
}
void forEach(void f(E element)) {
LinkedHashMapCell/*<E, dynamic>*/ cell = _map._first;
int modifications = _map._modifications;
while (cell != null) {
f(cell.hashMapCellKey);
if (modifications != _map._modifications) {
throw new ConcurrentModificationError(_map);
}
cell = cell._next;
}
}
}
class LinkedHashMapKeyIterator<E> implements Iterator<E> {
final dynamic/*=JsLinkedHashMap<E, dynamic>*/ _map;
final int _modifications;
LinkedHashMapCell/*<E, dynamic>*/ _cell;
E _current;
LinkedHashMapKeyIterator(this._map, this._modifications) {
_cell = _map._first;
}
E get current => _current;
bool moveNext() {
if (_modifications != _map._modifications) {
throw new ConcurrentModificationError(_map);
} else if (_cell == null) {
_current = null;
return false;
} else {
_current = _cell.hashMapCellKey;
_cell = _cell._next;
return true;
}
}
}
class ImmutableMap<K, V> extends JsLinkedHashMap<K, V> {
ImmutableMap(JSArray elements) {
if (elements == null) return;
for (var i = 0, end = elements.length - 1; i < end; i += 2) {
super[JS('', '#[#]', elements, i)] = JS('', '#[#]', elements, i + 1);
}
}
class ImmutableMap<K, V> extends LinkedMap<K, V> {
ImmutableMap.from(JSArray entries) : super.from(entries);
void operator []=(Object key, Object value) {
throw _unsupported();

View file

@ -22,6 +22,9 @@ string_static_test: MissingCompileTimeError
[ !$checked && $compiler != dartdevc && $runtime != none ]
null_nosuchmethod_test: RuntimeError # needs Dart 2 or checked mode
[ $compiler != dartdevc && $runtime != none ]
map_keys2_test: RuntimeError # needs Dart 2 is checks
[ (!$checked && $runtime == vm) || (!$checked && $compiler == dart2js) || $compiler == precompiler ]
int_parse_radix_test/badTypes: RuntimeError # wrong exception returned
@ -105,7 +108,6 @@ iterable_to_list_test/*: RuntimeError
list_concurrent_modify_test: RuntimeError # DDC uses ES6 array iterators so it does not issue this
nan_infinity_test/01: RuntimeError # Issue 29921
main_test: RuntimeError # Issue 29921
map_keys2_test: RuntimeError # Issue 29921
null_nosuchmethod_test/01: RuntimeError # DDC checks type before too many arguments, so TypeError instead of NSM
regexp/bol-with-multiline_test: RuntimeError # Issue 29921
regexp/invalid-range-in-class_test: RuntimeError # Issue 29921
@ -364,7 +366,6 @@ big_integer_parsed_mul_div_vm_test: Pass, Timeout # --no_intrinsify
string_trimlr_test/02: RuntimeError # Issue 29060
iterable_to_set_test: RuntimeError # is-checks do not implement strong mode type system
[ $compiler == precompiler || $compiler == app_jit ]
string_trimlr_test/02: RuntimeError # Issue 29060

View file

@ -81,7 +81,6 @@ testWithHashMap() {
var otherMap = new HashMap.from(map);
Expect.isTrue(otherMap is Map);
Expect.isTrue(otherMap is HashMap);
Expect.isTrue(otherMap is! LinkedHashMap);
var i = 1;
for (var val in map.values) {
Expect.equals(i++, val);

View file

@ -15,16 +15,16 @@ main() {
var map6 = new Map<String, bool>();
Expect.isTrue(map1.keys is Iterable<String>);
Expect.isTrue(map1.keys is Iterable<bool>);
Expect.isFalse(map1.keys is Iterable<bool>);
Expect.isTrue(map2.keys is Iterable<String>);
Expect.isTrue(map2.keys is Iterable<bool>);
Expect.isFalse(map2.keys is Iterable<String>);
Expect.isFalse(map2.keys is Iterable<bool>);
Expect.isTrue(map3.keys is Iterable<String>);
Expect.isTrue(map3.keys is Iterable<bool>);
Expect.isFalse(map3.keys is Iterable<bool>);
Expect.isTrue(map4.keys is Iterable<String>);
Expect.isTrue(map4.keys is Iterable<bool>);
Expect.isFalse(map4.keys is Iterable<String>);
Expect.isFalse(map4.keys is Iterable<bool>);
Expect.isTrue(map5.keys is Iterable<String>);
Expect.isFalse(map5.keys is Iterable<bool>);

View file

@ -122,6 +122,19 @@ void main() {
testUnmodifiableMap(new UnmodifiableMapView({1: 37}));
testUnmodifiableMap(new UnmodifiableMapBaseMap([1, 37]));
testTypeAnnotations(new HashMap());
testTypeAnnotations(new LinkedHashMap());
testTypeAnnotations(new HashMap(equals: identical));
testTypeAnnotations(new LinkedHashMap(equals: identical));
testTypeAnnotations(new HashMap(
equals: (int a, int b) => a == b,
hashCode: (int a) => a.hashCode,
isValidKey: (a) => a is int));
testTypeAnnotations(new LinkedHashMap(
equals: (int a, int b) => a == b,
hashCode: (int a) => a.hashCode,
isValidKey: (a) => a is int));
testFrom();
}
@ -501,9 +514,12 @@ void testNumericKeys(Map map) {
}
void testNaNKeys(Map map) {
Object nan = double.NAN;
// Skip this test on platforms that use native-JS NaN semantics for speed.
if (!identical(nan, nan)) return;
Expect.isTrue(map.isEmpty);
// Test NaN.
var nan = double.NAN;
Expect.isFalse(map.containsKey(nan));
Expect.equals(null, map[nan]);
@ -993,3 +1009,20 @@ void testFrom() {
expectMap(superMap, new SplayTreeMap<Super, Super>.from(interfaceMap));
expectMap(superMap, new SplayTreeMap<Interface, Interface>.from(superMap));
}
void testTypeAnnotations(Map<int, int> map) {
map[0] = 100;
map[999] = 101;
map[0x800000000] = 102;
map[0x20000000000000] = 103;
Expect.isFalse(map.containsKey("not an it"));
Expect.isNull(map.remove("not an it"));
testLength(4, map);
Expect.equals(101, map.remove(999));
testLength(3, map);
Expect.equals(102, map.remove(0x800000000));
testLength(2, map);
Expect.equals(103, map.remove(0x20000000000000));
testLength(1, map);
}

View file

@ -178,8 +178,8 @@ branch_canonicalization_test: RuntimeError # Issue 29920
call_closurization_test: RuntimeError # Issue 29920
call_test: RuntimeError
canonical_const2_test: RuntimeError # Ints and doubles are unified.
compile_time_constant_d_test: RuntimeError # Issue 29920
compile_time_constant_e_test: RuntimeError # Issue 30466
compile_time_constant_d_test: RuntimeError # Issue 30876
compile_time_constant_e_test: RuntimeError # Issue 30876
const_switch_test/02: RuntimeError # Issue 29920
const_switch_test/04: RuntimeError # Ints and doubles are unified.
constructor12_test: RuntimeError # Issue 29920

View file

@ -8,9 +8,9 @@ import 'dart:async';
main() {
// Unique object.
var baz = new Object();
var baz = new Mimic(new Object());
// Not so unique object that thinks it's the same as baz.
var mimic = new Mimic(baz);
var mimic = new Mimic(baz.original);
// runGuarded calls run, captures the synchronous error (if any) and
// gives that one to handleUncaughtError.
@ -44,7 +44,7 @@ main() {
Expect.equals(499, forked[#foo]);
Expect.listEquals([], forked["bar"]);
Expect.equals("baz", forked[baz]);
Expect.isNull(Zone.current[mimic]);
Expect.equals("baz", forked[mimic]);
Expect.equals("zero!", forked[0]);
Expect.equals("zero!", forked[0.0]); // Lookup uses equality.
Expect.equals("zero!", forked[-0.0]);
@ -57,7 +57,7 @@ main() {
Expect.equals(499, Zone.current[#foo]);
Expect.listEquals([], Zone.current["bar"]);
Expect.equals("baz", Zone.current[baz]);
Expect.isNull(Zone.current[mimic]);
Expect.equals("baz", Zone.current[mimic]);
Expect.equals("zero!", Zone.current[0]);
Expect.equals("zero!", Zone.current[0.0]); // Lookup uses equality.
Expect.equals("zero!", Zone.current[-0.0]);
@ -84,7 +84,7 @@ main() {
Expect.equals(499, Zone.current[#foo]);
Expect.listEquals([42], Zone.current["bar"]);
Expect.equals("baz", Zone.current[baz]);
Expect.isNull(Zone.current[mimic]);
Expect.equals("baz", Zone.current[mimic]);
Expect.equals("zero!", Zone.current[0]);
Expect.equals(baz, Zone.current[null]);
Expect.isNull(Zone.current["qux"]);
@ -129,7 +129,7 @@ main() {
Expect.equals(499, forked[#foo]);
Expect.listEquals([42], forked["bar"]);
Expect.equals("baz", forked[baz]);
Expect.isNull(Zone.current[mimic]);
Expect.equals("baz", forked[mimic]);
Expect.equals("zero!", forked[0]);
Expect.equals("zero!", forked[0.0]); // Lookup uses equality.
Expect.equals("zero!", forked[-0.0]);