| // 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. |
| |
| part of _js_helper; |
| |
| class ConstantMapView<K, V> extends UnmodifiableMapView<K, V> |
| implements ConstantMap<K, V> { |
| ConstantMapView(Map<K, V> base) : super(base); |
| } |
| |
| abstract class ConstantMap<K, V> implements Map<K, V> { |
| // Used to create unmodifiable maps from other maps. |
| factory ConstantMap.from(Map other) { |
| final keys = List<K>.from(other.keys); |
| bool allStrings = true; |
| for (var k in keys) { |
| if (k is! String || '__proto__' == k) { |
| allStrings = false; |
| break; |
| } |
| } |
| if (allStrings) { |
| var object = JS('=Object', '{}'); |
| int index = 0; |
| for (final k in keys) { |
| V v = other[k]; |
| JS('void', '#[#] = #', object, k, index++); |
| } |
| final values = List<V>.from(other.values); |
| final map = |
| ConstantStringMap<K, V>._(object, JS<JSArray>('', '#', values)); |
| map._setKeys(keys); |
| return map; |
| } |
| // TODO(lrn): Make a proper unmodifiable map implementation. |
| return ConstantMapView<K, V>(Map.from(other)); |
| } |
| |
| const ConstantMap._(); |
| |
| Map<RK, RV> cast<RK, RV>() => Map.castFrom<K, V, RK, RV>(this); |
| bool get isEmpty => length == 0; |
| |
| bool get isNotEmpty => !isEmpty; |
| |
| String toString() => MapBase.mapToString(this); |
| |
| static Never _throwUnmodifiable() { |
| throw UnsupportedError('Cannot modify unmodifiable Map'); |
| } |
| |
| void operator []=(K key, V value) { |
| _throwUnmodifiable(); |
| } |
| |
| V putIfAbsent(K key, V ifAbsent()) { |
| _throwUnmodifiable(); |
| } |
| |
| V? remove(Object? key) { |
| _throwUnmodifiable(); |
| } |
| |
| void clear() { |
| _throwUnmodifiable(); |
| } |
| |
| void addAll(Map<K, V> other) { |
| _throwUnmodifiable(); |
| } |
| |
| Iterable<MapEntry<K, V>> get entries sync* { |
| // `this[key]` has static type `V?` but is always `V`. Rather than `as V`, |
| // we use `as dynamic` so the upcast requires no checking and the implicit |
| // downcast to `V` will be discarded in production. |
| for (var key in keys) yield MapEntry<K, V>(key, this[key] as dynamic); |
| } |
| |
| void addEntries(Iterable<MapEntry<K, V>> entries) { |
| for (var entry in entries) this[entry.key] = entry.value; |
| } |
| |
| Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(K key, V value)) { |
| var result = <K2, V2>{}; |
| this.forEach((K key, V value) { |
| var entry = transform(key, value); |
| result[entry.key] = entry.value; |
| }); |
| return result; |
| } |
| |
| V update(K key, V update(V value), {V ifAbsent()?}) { |
| _throwUnmodifiable(); |
| } |
| |
| void updateAll(V update(K key, V value)) { |
| _throwUnmodifiable(); |
| } |
| |
| void removeWhere(bool test(K key, V value)) { |
| _throwUnmodifiable(); |
| } |
| } |
| |
| class ConstantStringMap<K, V> extends ConstantMap<K, V> { |
| const ConstantStringMap._(this._jsIndex, this._values) : super._(); |
| |
| // A ConstantStringMap is backed by a JavaScript Object mapping a String to an |
| // index into the `_values` Array. This is valid only for keys where the order |
| // of keys is preserved by JavaScript, either by one-at-a-time insertion or a |
| // JavaScript Object initializer. |
| final Object _jsIndex; |
| final JSArray _values; |
| |
| int get length => _values.length; |
| |
| JSArray get _keys { |
| var keys = JS('', r'#.$keys', this); |
| if (keys == null) { |
| keys = _keysFromIndex(_jsIndex); |
| _setKeys(keys); |
| } |
| return JS('JSUnmodifiableArray', '#', keys); |
| } |
| |
| ConstantStringMap<K, V> _setKeys(Object? keys) { |
| JS('', r'#.$keys = #', this, keys); |
| return this; // Allow chaining in JavaScript of constant pool code. |
| } |
| |
| bool containsValue(Object? needle) { |
| return _values.contains(needle); |
| } |
| |
| bool containsKey(Object? key) { |
| if (key is! String) return false; |
| if ('__proto__' == key) return false; |
| return jsHasOwnProperty(_jsIndex, key); |
| } |
| |
| V? operator [](Object? key) { |
| if (!containsKey(key)) return null; |
| int index = JS('', '#[#]', _jsIndex, key); |
| return JS('', '#[#]', _values, index); |
| } |
| |
| void forEach(void f(K key, V value)) { |
| final keys = _keys; |
| final values = _values; |
| for (int i = 0; i < keys.length; i++) { |
| K key = JS('', '#[#]', keys, i); |
| V value = JS('', '#[#]', values, i); |
| f(key, value); |
| } |
| } |
| |
| Iterable<K> get keys => _KeysOrValues<K>(_keys); |
| |
| Iterable<V> get values => _KeysOrValues<V>(_values); |
| } |
| |
| /// Converts a JavaScript index object to an untyped Array of the String keys. |
| /// |
| /// The [index] is a JavaScript object used for lookup of String keys. The own |
| /// property names are the keys. The index object maps these names to a position |
| /// in the sequence of entries of the Map or elements of the Set. This is a |
| /// compact representation since the positions (values of the JavaScript |
| /// properties) are small integers. |
| // |
| /// For Sets we don't need the property values to be positions, but using the |
| /// same representation allows sharing of indexes between Map and Set constants |
| /// and allows the enhancement below. |
| JSArray _keysFromIndex(Object? index) { |
| return JS('', 'Object.keys(#)', index); |
| |
| // Currently the compiler ensures that the JavaScript object literal has its |
| // properties ordered so that the first one has value `0`, the second has |
| // value `1`, etc. If the Dart collection's ordering cannot be expressed as a |
| // JavaScript object (a problem only for String keys 'integers', e.g. `"1"` |
| // following `"2"`), a different, more general representation is chosen. |
| // |
| // We could instead ensure that the keys are sorted by position by 'sorting' |
| // the keys by the property value. This would be a single pass to assign keys |
| // to their correct positions in a new Array. |
| } |
| |
| /// An Iterable that wraps a JavaScript Array to provide a type and other |
| /// operations. |
| class _KeysOrValues<E> extends Iterable<E> { |
| final JSArray _elements; |
| _KeysOrValues(this._elements); |
| |
| int get length => _elements.length; |
| bool get isEmpty => 0 == length; |
| bool get isNotEmpty => 0 != length; |
| |
| _KeysOrValuesOrElementsIterator<E> get iterator => |
| _KeysOrValuesOrElementsIterator<E>(this._elements); |
| } |
| |
| /// A typed Iterator over an untyped but unmodified Array. |
| class _KeysOrValuesOrElementsIterator<E> implements Iterator<E> { |
| final JSArray _elements; |
| final int _length; |
| int _index = 0; |
| E? _current; |
| _KeysOrValuesOrElementsIterator(this._elements) : _length = _elements.length; |
| |
| E get current => _current as E; |
| |
| bool moveNext() { |
| if (_index >= _length) { |
| _current = null; |
| return false; |
| } |
| _current = JS<E>('', '#[#]', _elements, _index); |
| _index++; |
| return true; |
| } |
| |
| // This Iterator is rather like ArrayIterator. Could ArrayIterator be modified |
| // so that we can use it instead? That might open the possibility of using the |
| // special optimizations for for-in on Arrays. One problem is that here we are |
| // wrapping Arrays that never change but there is no signal that they are |
| // fixed length. |
| } |
| |
| class GeneralConstantMap<K, V> extends ConstantMap<K, V> { |
| // This constructor is not used. The instantiation is shortcut by the |
| // compiler. It is here to make the uninitialized final fields legal. |
| GeneralConstantMap(this._jsData) : super._(); |
| |
| // [_jsData] holds a key-value pair list. |
| final _jsData; |
| |
| // We cannot create the backing map on creation since hashCode interceptors |
| // have not been defined when constants are created. It is also not desirable |
| // to add this execution cost to initial program load. |
| Map<K, V> _getMap() { |
| LinkedHashMap<K, V>? backingMap = JS('LinkedHashMap|Null', r'#.$map', this); |
| if (backingMap == null) { |
| backingMap = JsConstantLinkedHashMap<K, V>(); |
| fillLiteralMap(_jsData, backingMap); |
| JS('', r'#.$map = #', this, backingMap); |
| } |
| return backingMap; |
| } |
| |
| static bool Function(Object?) _typeTest<T>() => (Object? o) => o is T; |
| |
| bool containsValue(Object? needle) { |
| return _getMap().containsValue(needle); |
| } |
| |
| bool containsKey(Object? key) { |
| return _getMap().containsKey(key); |
| } |
| |
| V? operator [](Object? key) { |
| return _getMap()[key]; |
| } |
| |
| void forEach(void f(K key, V value)) { |
| _getMap().forEach(f); |
| } |
| |
| Iterable<K> get keys { |
| return _getMap().keys; |
| } |
| |
| Iterable<V> get values { |
| return _getMap().values; |
| } |
| |
| int get length => _getMap().length; |
| } |
| |
| abstract class ConstantSet<E> extends SetBase<E> { |
| const ConstantSet(); |
| |
| static Never _throwUnmodifiable() { |
| throw UnsupportedError('Cannot modify constant Set'); |
| } |
| |
| void clear() { |
| _throwUnmodifiable(); |
| } |
| |
| bool add(E value) { |
| _throwUnmodifiable(); |
| } |
| |
| void addAll(Iterable<E> elements) { |
| _throwUnmodifiable(); |
| } |
| |
| bool remove(Object? value) { |
| _throwUnmodifiable(); |
| } |
| |
| void removeAll(Iterable<Object?> elements) { |
| _throwUnmodifiable(); |
| } |
| |
| void removeWhere(bool test(E element)) { |
| _throwUnmodifiable(); |
| } |
| |
| void retainAll(Iterable<Object?> elements) { |
| _throwUnmodifiable(); |
| } |
| |
| void retainWhere(bool test(E element)) { |
| _throwUnmodifiable(); |
| } |
| } |
| |
| class ConstantStringSet<E> extends ConstantSet<E> { |
| // A ConstantStringSet is backed by a JavaScript Object whose properties are |
| // the elements of the Set. This is valid only for sets where the order of |
| // elements is preserved by JavaScript, either by one-at-a-time insertion or a |
| // JavaScript Object initializer. |
| final Object? _jsIndex; |
| final int _length; |
| |
| const ConstantStringSet(this._jsIndex, this._length); |
| |
| int get length => JS('JSUInt31', '#', _length); |
| bool get isEmpty => _length == 0; |
| bool get isNotEmpty => !isEmpty; |
| |
| JSArray get _keys { |
| var keys = JS('', r'#.$keys', this); |
| if (keys == null) { |
| keys = _keysFromIndex(_jsIndex); |
| _setKeys(keys); |
| } |
| return JS('JSUnmodifiableArray', '#', keys); |
| } |
| |
| ConstantStringSet<E> _setKeys(Object? keys) { |
| JS('', r'#.$keys = #', this, keys); |
| return this; // Allow chaining in JavaScript of constant pool code. |
| } |
| |
| Iterator<E> get iterator => _KeysOrValuesOrElementsIterator<E>(_keys); |
| |
| bool contains(Object? key) { |
| if (key is! String) return false; |
| if ('__proto__' == key) return false; |
| return jsHasOwnProperty(_jsIndex, key); |
| } |
| |
| E? lookup(Object? element) { |
| // There is no way to tell the Set element from [element] for strings, so we |
| // don't bother to return the stored element. If the set contains the |
| // element, it must be of type `E`, so use `JS` for a free cast. |
| return contains(element) ? JS('', '#', element) : null; |
| } |
| |
| // TODO(sra): Use the `_keys` Array. |
| Set<E> toSet() => Set.of(this); |
| |
| // Consider implementations of operations that can directly use the untyped |
| // elements list. |
| // |
| // List<E> toList({bool growable = true)); |
| // String join([String separator = '']); |
| } |
| |
| class GeneralConstantSet<E> extends ConstantSet<E> { |
| final JSArray _elements; |
| const GeneralConstantSet(this._elements); |
| |
| int get length => _elements.length; |
| bool get isEmpty => length == 0; |
| bool get isNotEmpty => !isEmpty; |
| |
| Iterator<E> get iterator => _KeysOrValuesOrElementsIterator(_elements); |
| |
| // We cannot create the backing map on creation since hashCode interceptors |
| // have not been defined when constants are created. It is also not desirable |
| // to add this execution cost to initial program load. |
| Map<E, E> _getMap() { |
| LinkedHashMap<E, E>? backingMap = JS('LinkedHashMap|Null', r'#.$map', this); |
| if (backingMap == null) { |
| backingMap = JsConstantLinkedHashMap<E, E>(); |
| for (final element in _elements) { |
| E key = JS('', '#', element); |
| backingMap[key] = key; |
| } |
| JS('', r'#.$map = #', this, backingMap); |
| } |
| return backingMap; |
| } |
| |
| bool contains(Object? key) { |
| return _getMap().containsKey(key); |
| } |
| |
| E? lookup(Object? element) => _getMap()[element]; |
| |
| Set<E> toSet() => Set.of(this); |
| } |