mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 16:13:19 +00:00
[dartdevc] Moving DDC Set implementations into DDC's private runtime.
This allows internal Set classes to be referenced by our runtime. Context: incoming changes to our generic types (required for hot reload) requires that RTIs be passed to generic classes on instantiation. Moving our Set implementation into our private runtime and making their classes public allows us to directly reference them without clobbering names externally. Change-Id: Ie47b3263ebbf2650d314b5285a2d50f3abd1a664 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373327 Commit-Queue: Mark Zhou <markzipan@google.com> Reviewed-by: Jens Johansen <jensj@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com>
This commit is contained in:
parent
59add4f01e
commit
72ee2943fd
|
@ -366,9 +366,9 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
|||
_identityHashMapImplClass =
|
||||
sdk.getClass('dart:_js_helper', 'IdentityMap'),
|
||||
_linkedHashSetClass = sdk.getClass('dart:collection', 'LinkedHashSet'),
|
||||
_linkedHashSetImplClass = sdk.getClass('dart:collection', '_HashSet'),
|
||||
_linkedHashSetImplClass = sdk.getClass('dart:_js_helper', 'LinkedSet'),
|
||||
_identityHashSetImplClass =
|
||||
sdk.getClass('dart:collection', '_IdentityHashSet'),
|
||||
sdk.getClass('dart:_js_helper', 'IdentitySet'),
|
||||
_syncIterableClass = sdk.getClass('dart:_js_helper', 'SyncIterable'),
|
||||
_asyncStarImplClass = sdk.getClass('dart:async', '_AsyncStarImpl'),
|
||||
_assertInteropMethod = sdk.getTopLevelMember(
|
||||
|
|
|
@ -321,8 +321,8 @@ void runSharedTests(
|
|||
breakpointId: 'BP',
|
||||
expression: 'dart.getObjectMetadata(set)',
|
||||
expectedResult: {
|
||||
'className': '_HashSet<String>',
|
||||
'libraryId': 'dart:collection',
|
||||
'className': 'LinkedSet<String>',
|
||||
'libraryId': 'dart:_js_helper',
|
||||
'runtimeKind': 'set',
|
||||
'length': 3,
|
||||
});
|
||||
|
|
|
@ -32,8 +32,6 @@ final Map<String, List<String>> additionalRequiredClasses = {
|
|||
'ListBase',
|
||||
'MapBase',
|
||||
'LinkedHashSet',
|
||||
'_HashSet',
|
||||
'_IdentityHashSet',
|
||||
],
|
||||
'dart:math': ['Rectangle'],
|
||||
'dart:html': [],
|
||||
|
@ -46,6 +44,8 @@ final Map<String, List<String>> additionalRequiredClasses = {
|
|||
'PrivateSymbol',
|
||||
'LinkedMap',
|
||||
'IdentityMap',
|
||||
'LinkedSet',
|
||||
'IdentitySet',
|
||||
'SyncIterable',
|
||||
],
|
||||
};
|
||||
|
|
|
@ -13,6 +13,10 @@ import 'dart:_js_helper'
|
|||
IdentityMap,
|
||||
CustomHashMap,
|
||||
CustomKeyHashMap,
|
||||
LinkedSet,
|
||||
IdentitySet,
|
||||
CustomHashSet,
|
||||
CustomKeyHashSet,
|
||||
DartIterator,
|
||||
notNull,
|
||||
putLinkedMapKey;
|
||||
|
@ -88,23 +92,22 @@ class HashSet<E> {
|
|||
if (hashCode == null) {
|
||||
if (equals == null) {
|
||||
if (identical(E, String) || identical(E, int)) {
|
||||
return _IdentityHashSet<E>();
|
||||
return IdentitySet<E>();
|
||||
}
|
||||
return _HashSet<E>();
|
||||
return LinkedSet<E>();
|
||||
}
|
||||
} else if (identical(identityHashCode, hashCode) &&
|
||||
identical(identical, equals)) {
|
||||
return _IdentityHashSet<E>();
|
||||
return IdentitySet<E>();
|
||||
}
|
||||
return _CustomHashSet<E>(
|
||||
equals ?? dart.equals, hashCode ?? dart.hashCode);
|
||||
return CustomHashSet<E>(equals ?? dart.equals, hashCode ?? dart.hashCode);
|
||||
}
|
||||
return _CustomKeyHashSet<E>(
|
||||
return CustomKeyHashSet<E>(
|
||||
equals ?? dart.equals, hashCode ?? dart.hashCode, isValidKey);
|
||||
}
|
||||
|
||||
@patch
|
||||
factory HashSet.identity() = _IdentityHashSet<E>;
|
||||
factory HashSet.identity() = IdentitySet<E>;
|
||||
}
|
||||
|
||||
@patch
|
||||
|
@ -118,460 +121,21 @@ class LinkedHashSet<E> {
|
|||
if (hashCode == null) {
|
||||
if (equals == null) {
|
||||
if (identical(E, String) || identical(E, int)) {
|
||||
return _IdentityHashSet<E>();
|
||||
return IdentitySet<E>();
|
||||
}
|
||||
return _HashSet<E>();
|
||||
return LinkedSet<E>();
|
||||
}
|
||||
hashCode = dart.hashCode;
|
||||
} else if (identical(identityHashCode, hashCode) &&
|
||||
identical(identical, equals)) {
|
||||
return _IdentityHashSet<E>();
|
||||
return IdentitySet<E>();
|
||||
}
|
||||
return _CustomHashSet<E>(equals ?? dart.equals, hashCode);
|
||||
return CustomHashSet<E>(equals ?? dart.equals, hashCode);
|
||||
}
|
||||
return _CustomKeyHashSet<E>(
|
||||
return CustomKeyHashSet<E>(
|
||||
equals ?? dart.equals, hashCode ?? dart.hashCode, isValidKey);
|
||||
}
|
||||
|
||||
@patch
|
||||
factory LinkedHashSet.identity() = _IdentityHashSet<E>;
|
||||
}
|
||||
|
||||
base class _HashSet<E> extends _InternalSet<E>
|
||||
implements HashSet<E>, LinkedHashSet<E> {
|
||||
/// The backing store for this set.
|
||||
///
|
||||
/// 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 Set()');
|
||||
|
||||
/// 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 required 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;
|
||||
|
||||
_HashSet();
|
||||
|
||||
Set<E> _newSet() => _HashSet<E>();
|
||||
|
||||
Set<R> _newSimilarSet<R>() => _HashSet<R>();
|
||||
|
||||
bool contains(Object? key) {
|
||||
if (key == null) {
|
||||
// Convert undefined to null, if needed.
|
||||
key = null;
|
||||
} else if (JS<bool>('!', '#[#] !== #', key, dart.extensionSymbol('_equals'),
|
||||
dart.identityEquals)) {
|
||||
@notNull
|
||||
Object? k = key;
|
||||
var buckets = JS('', '#.get(# & 0x3fffffff)', _keyMap, k.hashCode);
|
||||
if (buckets != null) {
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
k = JS('', '#[#]', buckets, i);
|
||||
if (k == key) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return JS<bool>('!', '#.has(#)', _map, key);
|
||||
}
|
||||
|
||||
E? lookup(Object? key) {
|
||||
if (key == null) return null;
|
||||
if (JS<bool>('!', '#[#] !== #', key, dart.extensionSymbol('_equals'),
|
||||
dart.identityEquals)) {
|
||||
@notNull
|
||||
Object? k = key;
|
||||
var buckets = JS('', '#.get(# & 0x3fffffff)', _keyMap, k.hashCode);
|
||||
if (buckets != null) {
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
k = JS('', '#[#]', buckets, i);
|
||||
if (k == key) return JS('', '#', k);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return JS('', '#.has(#) ? # : null', _map, key, key);
|
||||
}
|
||||
|
||||
bool add(E key) {
|
||||
var map = _map;
|
||||
if (key == null) {
|
||||
if (JS<bool>('!', '#.has(null)', map)) return false;
|
||||
// Convert undefined to null, if needed.
|
||||
JS('', '# = null', key);
|
||||
} else if (JS<bool>('!', '#[#] !== #', key, dart.extensionSymbol('_equals'),
|
||||
dart.identityEquals)) {
|
||||
var keyMap = _keyMap;
|
||||
@notNull
|
||||
var k = key;
|
||||
int hash = JS('!', '# & 0x3fffffff', k.hashCode);
|
||||
var buckets = JS('', '#.get(#)', keyMap, hash);
|
||||
if (buckets == null) {
|
||||
JS('', '#.set(#, [#])', keyMap, hash, key);
|
||||
} else {
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
k = JS('', '#[#]', buckets, i);
|
||||
if (k == key) return false;
|
||||
}
|
||||
JS('', '#.push(#)', buckets, key);
|
||||
}
|
||||
} else if (JS<bool>('!', '#.has(#)', map, key)) {
|
||||
return false;
|
||||
}
|
||||
JS('', '#.add(#)', map, key);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
|
||||
void addAll(Iterable<E> objects) {
|
||||
var map = _map;
|
||||
int length = JS('', '#.size', map);
|
||||
for (E key in objects) {
|
||||
if (key == null) {
|
||||
// Convert undefined to null, if needed.
|
||||
JS('', '# = null', key);
|
||||
} else if (JS<bool>('!', '#[#] !== #', key,
|
||||
dart.extensionSymbol('_equals'), dart.identityEquals)) {
|
||||
key = putLinkedMapKey(key, _keyMap);
|
||||
}
|
||||
JS('', '#.add(#)', map, key);
|
||||
}
|
||||
if (length != JS<int>('!', '#.size', map)) {
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
}
|
||||
}
|
||||
|
||||
bool remove(Object? key) {
|
||||
if (key == null) {
|
||||
// Convert undefined to null, if needed.
|
||||
key = null;
|
||||
} else if (JS<bool>('!', '#[#] !== #', key, dart.extensionSymbol('_equals'),
|
||||
dart.identityEquals)) {
|
||||
@notNull
|
||||
Object? k = key;
|
||||
int hash = JS('!', '# & 0x3fffffff', k.hashCode);
|
||||
var buckets = JS('', '#.get(#)', _keyMap, hash);
|
||||
if (buckets == null) return false; // not found
|
||||
for (int i = 0, n = JS('!', '#.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 false; // not found
|
||||
}
|
||||
}
|
||||
var map = _map;
|
||||
if (JS<bool>('!', '#.delete(#)', map, key)) {
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
var map = _map;
|
||||
if (JS<int>('!', '#.size', map) > 0) {
|
||||
JS('', '#.clear()', map);
|
||||
JS('', '#.clear()', _keyMap);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used for DDC const sets.
|
||||
base class _ImmutableSet<E> extends _HashSet<E> {
|
||||
_ImmutableSet.from(JSArray<E> entries) {
|
||||
var map = _map;
|
||||
for (var key in entries) {
|
||||
if (key == null) {
|
||||
// Convert undefined to null, if needed.
|
||||
JS('', '# = null', key);
|
||||
} else if (JS<bool>('!', '#[#] !== #', key,
|
||||
dart.extensionSymbol('_equals'), dart.identityEquals)) {
|
||||
key = putLinkedMapKey(key, _keyMap);
|
||||
}
|
||||
JS('', '#.add(#)', map, key);
|
||||
}
|
||||
}
|
||||
|
||||
bool add(E value) => throw _unsupported();
|
||||
void addAll(Iterable<E> elements) => throw _unsupported();
|
||||
void clear() => throw _unsupported();
|
||||
bool remove(Object? value) => throw _unsupported();
|
||||
|
||||
static Error _unsupported() =>
|
||||
UnsupportedError("Cannot modify unmodifiable set");
|
||||
}
|
||||
|
||||
base class _IdentityHashSet<E> extends _InternalSet<E>
|
||||
implements HashSet<E>, LinkedHashSet<E> {
|
||||
/// The backing store for this set.
|
||||
@notNull
|
||||
final _map = JS('', 'new Set()');
|
||||
|
||||
@notNull
|
||||
int _modifications = 0;
|
||||
|
||||
_IdentityHashSet();
|
||||
|
||||
Set<E> _newSet() => _IdentityHashSet<E>();
|
||||
|
||||
Set<R> _newSimilarSet<R>() => _IdentityHashSet<R>();
|
||||
|
||||
bool contains(Object? element) {
|
||||
return JS<bool>('!', '#.has(#)', _map, element);
|
||||
}
|
||||
|
||||
E? lookup(Object? element) {
|
||||
return element is E && JS<bool>('!', '#.has(#)', _map, element)
|
||||
? element
|
||||
: null;
|
||||
}
|
||||
|
||||
bool add(E element) {
|
||||
var map = _map;
|
||||
if (JS<bool>('!', '#.has(#)', map, element)) return false;
|
||||
JS('', '#.add(#)', map, element);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
|
||||
void addAll(Iterable<E> objects) {
|
||||
var map = _map;
|
||||
int length = JS('', '#.size', map);
|
||||
for (E key in objects) {
|
||||
JS('', '#.add(#)', map, key);
|
||||
}
|
||||
if (length != JS<int>('!', '#.size', map)) {
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
}
|
||||
}
|
||||
|
||||
bool remove(Object? element) {
|
||||
if (JS<bool>('!', '#.delete(#)', _map, element)) {
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
var map = _map;
|
||||
if (JS<int>('!', '#.size', map) > 0) {
|
||||
JS('', '#.clear()', map);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base class _CustomKeyHashSet<E> extends _CustomHashSet<E> {
|
||||
_Predicate<Object?> _validKey;
|
||||
_CustomKeyHashSet(_Equality<E> equals, _Hasher<E> hashCode, this._validKey)
|
||||
: super(equals, hashCode);
|
||||
|
||||
Set<E> _newSet() => _CustomKeyHashSet<E>(_equals, _hashCode, _validKey);
|
||||
|
||||
Set<R> _newSimilarSet<R>() => _HashSet<R>();
|
||||
|
||||
bool contains(Object? element) {
|
||||
// TODO(jmesserly): there is a subtle difference here compared to Dart 1.
|
||||
// See the comment on CustomKeyHashMap.containsKey for more information.
|
||||
// Treatment of `null` is different due to strong mode's requirement to
|
||||
// perform an `element is E` check before calling equals/hashCode.
|
||||
if (!_validKey(element)) return false;
|
||||
return super.contains(element);
|
||||
}
|
||||
|
||||
E? lookup(Object? element) {
|
||||
if (!_validKey(element)) return null;
|
||||
return super.lookup(element);
|
||||
}
|
||||
|
||||
bool remove(Object? element) {
|
||||
if (!_validKey(element)) return false;
|
||||
return super.remove(element);
|
||||
}
|
||||
}
|
||||
|
||||
base class _CustomHashSet<E> extends _InternalSet<E>
|
||||
implements HashSet<E>, LinkedHashSet<E> {
|
||||
_Equality<E> _equals;
|
||||
_Hasher<E> _hashCode;
|
||||
|
||||
// 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;
|
||||
|
||||
/// The backing store for this set, used to handle ordering.
|
||||
// TODO(jmesserly): a non-linked custom hash set could skip this.
|
||||
@notNull
|
||||
final _map = JS('', 'new Set()');
|
||||
|
||||
/// Our map used to map keys onto the canonical key that is stored in [_map].
|
||||
@notNull
|
||||
final _keyMap = JS('', 'new Map()');
|
||||
|
||||
_CustomHashSet(this._equals, this._hashCode);
|
||||
|
||||
Set<E> _newSet() => _CustomHashSet<E>(_equals, _hashCode);
|
||||
Set<R> _newSimilarSet<R>() => _HashSet<R>();
|
||||
|
||||
bool contains(Object? key) {
|
||||
if (key is E) {
|
||||
var buckets = JS('', '#.get(# & 0x3fffffff)', _keyMap, _hashCode(key));
|
||||
if (buckets != null) {
|
||||
var equals = _equals;
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
E k = JS('', '#[#]', buckets, i);
|
||||
if (equals(k, key)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
E? lookup(Object? key) {
|
||||
if (key is E) {
|
||||
var buckets = JS('', '#.get(# & 0x3fffffff)', _keyMap, _hashCode(key));
|
||||
if (buckets != null) {
|
||||
var equals = _equals;
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
E k = JS('', '#[#]', buckets, i);
|
||||
if (equals(k, key)) return k;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool add(E key) {
|
||||
var keyMap = _keyMap;
|
||||
var hash = JS<int>('!', '# & 0x3fffffff', _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('!', '#.length', buckets); i < n; i++) {
|
||||
E k = JS('', '#[#]', buckets, i);
|
||||
if (equals(k, key)) return false;
|
||||
}
|
||||
JS('', '#.push(#)', buckets, key);
|
||||
}
|
||||
JS('', '#.add(#)', _map, key);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
|
||||
void addAll(Iterable<E> objects) {
|
||||
// TODO(jmesserly): it'd be nice to skip the covariance check here.
|
||||
for (E element in objects) add(element);
|
||||
}
|
||||
|
||||
bool remove(Object? key) {
|
||||
if (key is E) {
|
||||
var hash = JS<int>('!', '# & 0x3fffffff', _hashCode(key));
|
||||
var keyMap = _keyMap;
|
||||
var buckets = JS('', '#.get(#)', keyMap, hash);
|
||||
if (buckets == null) return false; // not found
|
||||
var equals = _equals;
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
E k = JS('', '#[#]', buckets, i);
|
||||
if (equals(k, key)) {
|
||||
if (n == 1) {
|
||||
JS('', '#.delete(#)', keyMap, hash);
|
||||
} else {
|
||||
JS('', '#.splice(#, 1)', buckets, i);
|
||||
}
|
||||
JS('', '#.delete(#)', _map, k);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
var map = _map;
|
||||
if (JS<int>('!', '#.size', map) > 0) {
|
||||
JS('', '#.clear()', map);
|
||||
JS('', '#.clear()', _keyMap);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Base class for our internal [LinkedHashSet]/[HashSet] implementations.
|
||||
///
|
||||
/// This implements the common functionality.
|
||||
abstract base class _InternalSet<E> extends _SetBase<E> {
|
||||
@notNull
|
||||
get _map;
|
||||
|
||||
@notNull
|
||||
int get _modifications;
|
||||
|
||||
@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);
|
||||
|
||||
Iterator<E> get iterator => DartIterator<E>(_jsIterator());
|
||||
|
||||
@JSExportName('Symbol.iterator')
|
||||
_jsIterator() {
|
||||
var self = this;
|
||||
var iterator = JS('', '#.values()', self._map);
|
||||
int modifications = self._modifications;
|
||||
return JS(
|
||||
'',
|
||||
'''{
|
||||
next() {
|
||||
if (# != #) {
|
||||
throw #;
|
||||
}
|
||||
return #.next();
|
||||
}
|
||||
}''',
|
||||
modifications,
|
||||
self._modifications,
|
||||
ConcurrentModificationError(self),
|
||||
iterator);
|
||||
}
|
||||
factory LinkedHashSet.identity() = IdentitySet<E>;
|
||||
}
|
||||
|
|
154
sdk/lib/_internal/js_dev_runtime/private/custom_hash_set.dart
Normal file
154
sdk/lib/_internal/js_dev_runtime/private/custom_hash_set.dart
Normal file
|
@ -0,0 +1,154 @@
|
|||
// Copyright (c) 2024, 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;
|
||||
|
||||
base class CustomKeyHashSet<E> extends CustomHashSet<E> {
|
||||
final _Predicate<Object?> _validKey;
|
||||
CustomKeyHashSet(_Equality<E> equals, _Hasher<E> hashCode, this._validKey)
|
||||
: super(equals, hashCode);
|
||||
|
||||
Set<E> _newSet() => CustomKeyHashSet<E>(_equals, _hashCode, _validKey);
|
||||
|
||||
Set<R> _newSimilarSet<R>() => LinkedSet<R>();
|
||||
|
||||
@override
|
||||
@notNull
|
||||
bool contains(Object? element) {
|
||||
// TODO(jmesserly): there is a subtle difference here compared to Dart 1.
|
||||
// See the comment on CustomKeyHashMap.containsKey for more information.
|
||||
// Treatment of `null` is different due to strong mode's requirement to
|
||||
// perform an `element is E` check before calling equals/hashCode.
|
||||
if (!_validKey(element)) return false;
|
||||
return super.contains(element);
|
||||
}
|
||||
|
||||
@override
|
||||
E? lookup(Object? element) {
|
||||
if (!_validKey(element)) return null;
|
||||
return super.lookup(element);
|
||||
}
|
||||
|
||||
@override
|
||||
bool remove(Object? element) {
|
||||
if (!_validKey(element)) return false;
|
||||
return super.remove(element);
|
||||
}
|
||||
}
|
||||
|
||||
base class CustomHashSet<E> extends InternalSet<E> {
|
||||
_Equality<E> _equals;
|
||||
_Hasher<E> _hashCode;
|
||||
|
||||
// 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;
|
||||
|
||||
/// The backing store for this set, used to handle ordering.
|
||||
// TODO(jmesserly): a non-linked custom hash set could skip this.
|
||||
@notNull
|
||||
final _map = JS('', 'new Set()');
|
||||
|
||||
/// Our map used to map keys onto the canonical key that is stored in [_map].
|
||||
@notNull
|
||||
final _keyMap = JS('', 'new Map()');
|
||||
|
||||
CustomHashSet(this._equals, this._hashCode);
|
||||
|
||||
Set<E> _newSet() => CustomHashSet<E>(_equals, _hashCode);
|
||||
Set<R> _newSimilarSet<R>() => LinkedSet<R>();
|
||||
|
||||
@notNull
|
||||
bool contains(Object? key) {
|
||||
if (key is E) {
|
||||
var buckets = JS('', '#.get(# & 0x3fffffff)', _keyMap, _hashCode(key));
|
||||
if (buckets != null) {
|
||||
var equals = _equals;
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
E k = JS('', '#[#]', buckets, i);
|
||||
if (equals(k, key)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
E? lookup(Object? key) {
|
||||
if (key is E) {
|
||||
var buckets = JS('', '#.get(# & 0x3fffffff)', _keyMap, _hashCode(key));
|
||||
if (buckets != null) {
|
||||
var equals = _equals;
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
E k = JS('', '#[#]', buckets, i);
|
||||
if (equals(k, key)) return k;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool add(E key) {
|
||||
var keyMap = _keyMap;
|
||||
var hash = JS<int>('!', '# & 0x3fffffff', _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('!', '#.length', buckets); i < n; i++) {
|
||||
E k = JS('', '#[#]', buckets, i);
|
||||
if (equals(k, key)) return false;
|
||||
}
|
||||
JS('', '#.push(#)', buckets, key);
|
||||
}
|
||||
JS('', '#.add(#)', _map, key);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
|
||||
void addAll(Iterable<E> objects) {
|
||||
// TODO(jmesserly): it'd be nice to skip the covariance check here.
|
||||
for (E element in objects) add(element);
|
||||
}
|
||||
|
||||
bool remove(Object? key) {
|
||||
if (key is E) {
|
||||
var hash = JS<int>('!', '# & 0x3fffffff', _hashCode(key));
|
||||
var keyMap = _keyMap;
|
||||
var buckets = JS('', '#.get(#)', keyMap, hash);
|
||||
if (buckets == null) return false; // not found
|
||||
var equals = _equals;
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
E k = JS('', '#[#]', buckets, i);
|
||||
if (equals(k, key)) {
|
||||
if (n == 1) {
|
||||
JS('', '#.delete(#)', keyMap, hash);
|
||||
} else {
|
||||
JS('', '#.splice(#, 1)', buckets, i);
|
||||
}
|
||||
JS('', '#.delete(#)', _map, k);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
var map = _map;
|
||||
if (JS<int>('!', '#.size', map) > 0) {
|
||||
JS('', '#.clear()', map);
|
||||
JS('', '#.clear()', _keyMap);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -636,14 +636,6 @@ Map<K, V> constMap<K, V>(JSArray elements) {
|
|||
}
|
||||
|
||||
final constantSets = JS<Object>('!', 'new Map()');
|
||||
var _immutableSetConstructor;
|
||||
|
||||
// We cannot invoke private class constructors directly in Dart.
|
||||
Set<E> _createImmutableSet<E>(JSArray<E> elements) {
|
||||
_immutableSetConstructor ??=
|
||||
JS('', '#.#', getLibrary('dart:collection'), '_ImmutableSet\$');
|
||||
return JS('', 'new (#(#)).from(#)', _immutableSetConstructor, E, elements);
|
||||
}
|
||||
|
||||
Set<E> constSet<E>(JSArray<E> elements) {
|
||||
var count = elements.length;
|
||||
|
@ -653,7 +645,7 @@ Set<E> constSet<E>(JSArray<E> elements) {
|
|||
}
|
||||
Set<E>? result = JS('', '#.get(#)', map, E);
|
||||
if (result != null) return result;
|
||||
result = _createImmutableSet<E>(elements);
|
||||
result = ImmutableSet<E>.from(elements);
|
||||
JS('', '#.set(#, #)', map, E, result);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import 'dart:_js_helper'
|
|||
DeferredNotLoadedError,
|
||||
getRtiForRecord,
|
||||
ImmutableMap,
|
||||
ImmutableSet,
|
||||
JsLinkedHashMap,
|
||||
jsObjectGetPrototypeOf,
|
||||
jsObjectSetPrototypeOf,
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) 2024, 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;
|
||||
|
||||
base class IdentitySet<E> extends InternalSet<E> {
|
||||
/// The backing store for this set.
|
||||
@notNull
|
||||
final _map = JS('', 'new Set()');
|
||||
|
||||
@notNull
|
||||
int _modifications = 0;
|
||||
|
||||
IdentitySet();
|
||||
|
||||
Set<E> _newSet() => IdentitySet<E>();
|
||||
|
||||
Set<R> _newSimilarSet<R>() => IdentitySet<R>();
|
||||
|
||||
bool contains(Object? element) {
|
||||
return JS<bool>('!', '#.has(#)', _map, element);
|
||||
}
|
||||
|
||||
E? lookup(Object? element) {
|
||||
return element is E && JS<bool>('!', '#.has(#)', _map, element)
|
||||
? element
|
||||
: null;
|
||||
}
|
||||
|
||||
bool add(E element) {
|
||||
var map = _map;
|
||||
if (JS<bool>('!', '#.has(#)', map, element)) return false;
|
||||
JS('', '#.add(#)', map, element);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
|
||||
void addAll(Iterable<E> objects) {
|
||||
var map = _map;
|
||||
int length = JS('', '#.size', map);
|
||||
for (E key in objects) {
|
||||
JS('', '#.add(#)', map, key);
|
||||
}
|
||||
if (length != JS<int>('!', '#.size', map)) {
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
}
|
||||
}
|
||||
|
||||
bool remove(Object? element) {
|
||||
if (JS<bool>('!', '#.delete(#)', _map, element)) {
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
var map = _map;
|
||||
if (JS<int>('!', '#.size', map) > 0) {
|
||||
JS('', '#.clear()', map);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,8 +25,11 @@ import 'dart:_runtime' as dart;
|
|||
|
||||
part 'annotations.dart';
|
||||
part 'linked_hash_map.dart';
|
||||
part 'linked_hash_set.dart';
|
||||
part 'identity_hash_map.dart';
|
||||
part 'identity_hash_set.dart';
|
||||
part 'custom_hash_map.dart';
|
||||
part 'custom_hash_set.dart';
|
||||
part 'native_helper.dart';
|
||||
part 'regexp_helper.dart';
|
||||
part 'string_helper.dart';
|
||||
|
|
260
sdk/lib/_internal/js_dev_runtime/private/linked_hash_set.dart
Normal file
260
sdk/lib/_internal/js_dev_runtime/private/linked_hash_set.dart
Normal file
|
@ -0,0 +1,260 @@
|
|||
// Copyright (c) 2024, 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.
|
||||
|
||||
// Efficient JavaScript based implementation of a linked hash set used as a
|
||||
// backing map for constant sets and the [LinkedHashSet] patch.
|
||||
|
||||
part of dart._js_helper;
|
||||
|
||||
/// Base class for our internal [LinkedHashSet]/[HashSet] implementations.
|
||||
///
|
||||
/// This implements the common functionality.
|
||||
abstract base class InternalSet<E> extends SetBase<E>
|
||||
implements LinkedHashSet<E>, HashSet<E> {
|
||||
@notNull
|
||||
get _map;
|
||||
|
||||
@notNull
|
||||
int get _modifications;
|
||||
|
||||
@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);
|
||||
|
||||
Iterator<E> get iterator => DartIterator<E>(_jsIterator());
|
||||
|
||||
@JSExportName('Symbol.iterator')
|
||||
_jsIterator() {
|
||||
var self = this;
|
||||
var iterator = JS('', '#.values()', self._map);
|
||||
int modifications = self._modifications;
|
||||
return JS(
|
||||
'',
|
||||
'''{
|
||||
next() {
|
||||
if (# != #) {
|
||||
throw #;
|
||||
}
|
||||
return #.next();
|
||||
}
|
||||
}''',
|
||||
modifications,
|
||||
self._modifications,
|
||||
ConcurrentModificationError(self),
|
||||
iterator);
|
||||
}
|
||||
|
||||
Set<E> _newSet() => LinkedSet<E>();
|
||||
|
||||
Set<R> _newSimilarSet<R>() => LinkedSet<R>();
|
||||
|
||||
Set<R> cast<R>() => Set.castFrom<E, R>(this, newSet: _newSimilarSet);
|
||||
|
||||
Set<E> difference(Set<Object?> other) {
|
||||
Set<E> result = _newSet();
|
||||
for (var element in this) {
|
||||
if (!other.contains(element)) result.add(element);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Set<E> intersection(Set<Object?> other) {
|
||||
Set<E> result = _newSet();
|
||||
for (var element in this) {
|
||||
if (other.contains(element)) result.add(element);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Set<E> toSet() => _newSet()..addAll(this);
|
||||
}
|
||||
|
||||
/// A linked hash set implementation based on ES6 Maps and Sets.
|
||||
base class LinkedSet<E> extends InternalSet<E> {
|
||||
/// The backing store for this set.
|
||||
///
|
||||
/// 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 Set()');
|
||||
|
||||
/// 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 required 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;
|
||||
|
||||
bool contains(Object? key) {
|
||||
if (key == null) {
|
||||
// Convert undefined to null, if needed.
|
||||
key = null;
|
||||
} else if (JS<bool>('!', '#[#] !== #', key, dart.extensionSymbol('_equals'),
|
||||
dart.identityEquals)) {
|
||||
@notNull
|
||||
Object? k = key;
|
||||
var buckets = JS('', '#.get(# & 0x3fffffff)', _keyMap, k.hashCode);
|
||||
if (buckets != null) {
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
k = JS('', '#[#]', buckets, i);
|
||||
if (k == key) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return JS<bool>('!', '#.has(#)', _map, key);
|
||||
}
|
||||
|
||||
E? lookup(Object? key) {
|
||||
if (key == null) return null;
|
||||
if (JS<bool>('!', '#[#] !== #', key, dart.extensionSymbol('_equals'),
|
||||
dart.identityEquals)) {
|
||||
@notNull
|
||||
Object? k = key;
|
||||
var buckets = JS('', '#.get(# & 0x3fffffff)', _keyMap, k.hashCode);
|
||||
if (buckets != null) {
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
k = JS('', '#[#]', buckets, i);
|
||||
if (k == key) return JS('', '#', k);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return JS('', '#.has(#) ? # : null', _map, key, key);
|
||||
}
|
||||
|
||||
bool add(E key) {
|
||||
var map = _map;
|
||||
if (key == null) {
|
||||
if (JS<bool>('!', '#.has(null)', map)) return false;
|
||||
// Convert undefined to null, if needed.
|
||||
JS('', '# = null', key);
|
||||
} else if (JS<bool>('!', '#[#] !== #', key, dart.extensionSymbol('_equals'),
|
||||
dart.identityEquals)) {
|
||||
var keyMap = _keyMap;
|
||||
@notNull
|
||||
var k = key;
|
||||
int hash = JS('!', '# & 0x3fffffff', k.hashCode);
|
||||
var buckets = JS('', '#.get(#)', keyMap, hash);
|
||||
if (buckets == null) {
|
||||
JS('', '#.set(#, [#])', keyMap, hash, key);
|
||||
} else {
|
||||
for (int i = 0, n = JS('!', '#.length', buckets); i < n; i++) {
|
||||
k = JS('', '#[#]', buckets, i);
|
||||
if (k == key) return false;
|
||||
}
|
||||
JS('', '#.push(#)', buckets, key);
|
||||
}
|
||||
} else if (JS<bool>('!', '#.has(#)', map, key)) {
|
||||
return false;
|
||||
}
|
||||
JS('', '#.add(#)', map, key);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
|
||||
void addAll(Iterable<E> objects) {
|
||||
var map = _map;
|
||||
int length = JS('', '#.size', map);
|
||||
for (E key in objects) {
|
||||
if (key == null) {
|
||||
// Convert undefined to null, if needed.
|
||||
JS('', '# = null', key);
|
||||
} else if (JS<bool>('!', '#[#] !== #', key,
|
||||
dart.extensionSymbol('_equals'), dart.identityEquals)) {
|
||||
key = putLinkedMapKey(key, _keyMap);
|
||||
}
|
||||
JS('', '#.add(#)', map, key);
|
||||
}
|
||||
if (length != JS<int>('!', '#.size', map)) {
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
}
|
||||
}
|
||||
|
||||
bool remove(Object? key) {
|
||||
if (key == null) {
|
||||
// Convert undefined to null, if needed.
|
||||
key = null;
|
||||
} else if (JS<bool>('!', '#[#] !== #', key, dart.extensionSymbol('_equals'),
|
||||
dart.identityEquals)) {
|
||||
@notNull
|
||||
Object? k = key;
|
||||
int hash = JS('!', '# & 0x3fffffff', k.hashCode);
|
||||
var buckets = JS('', '#.get(#)', _keyMap, hash);
|
||||
if (buckets == null) return false; // not found
|
||||
for (int i = 0, n = JS('!', '#.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 false; // not found
|
||||
}
|
||||
}
|
||||
var map = _map;
|
||||
if (JS<bool>('!', '#.delete(#)', map, key)) {
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
var map = _map;
|
||||
if (JS<int>('!', '#.size', map) > 0) {
|
||||
JS('', '#.clear()', map);
|
||||
JS('', '#.clear()', _keyMap);
|
||||
_modifications = (_modifications + 1) & 0x3fffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used for DDC const sets.
|
||||
base class ImmutableSet<E> extends LinkedSet<E> {
|
||||
ImmutableSet.from(JSArray<E> entries) {
|
||||
var map = _map;
|
||||
for (var key in entries) {
|
||||
if (key == null) {
|
||||
// Convert undefined to null, if needed.
|
||||
JS('', '# = null', key);
|
||||
} else if (JS<bool>('!', '#[#] !== #', key,
|
||||
dart.extensionSymbol('_equals'), dart.identityEquals)) {
|
||||
key = putLinkedMapKey(key, _keyMap);
|
||||
}
|
||||
JS('', '#.add(#)', map, key);
|
||||
}
|
||||
}
|
||||
|
||||
bool add(E value) => throw _unsupported();
|
||||
void addAll(Iterable<E> elements) => throw _unsupported();
|
||||
void clear() => throw _unsupported();
|
||||
bool remove(Object? value) => throw _unsupported();
|
||||
|
||||
static Error _unsupported() =>
|
||||
UnsupportedError("Cannot modify unmodifiable set");
|
||||
}
|
|
@ -4031,7 +4031,7 @@ Value:
|
|||
{
|
||||
"style": "background-color: #d9edf7;color: black"
|
||||
},
|
||||
"_HashSet<dynamic> length 3"
|
||||
"LinkedSet<dynamic> length 3"
|
||||
]
|
||||
-----------------------------------
|
||||
Test: Set instance body
|
||||
|
@ -4148,7 +4148,7 @@ Value:
|
|||
{
|
||||
"style": "background-color: #d9edf7;color: black"
|
||||
},
|
||||
"_HashSet<dynamic>"
|
||||
"LinkedSet<dynamic>"
|
||||
]
|
||||
-----------------------------------
|
||||
Test: Set definition formatting body
|
||||
|
@ -4279,58 +4279,6 @@ Value:
|
|||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
"li",
|
||||
{
|
||||
"style": "padding-left: 13px;"
|
||||
},
|
||||
[
|
||||
"span",
|
||||
{
|
||||
"style": "background-color: thistle; color: rgb(136, 19, 145); margin-right: -13px"
|
||||
},
|
||||
"_newSet: "
|
||||
],
|
||||
[
|
||||
"span",
|
||||
{
|
||||
"style": "margin-left: 13px"
|
||||
},
|
||||
[
|
||||
"object",
|
||||
{
|
||||
"object": "<OBJECT>",
|
||||
"config": {}
|
||||
}
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
"li",
|
||||
{
|
||||
"style": "padding-left: 13px;"
|
||||
},
|
||||
[
|
||||
"span",
|
||||
{
|
||||
"style": "background-color: thistle; color: rgb(136, 19, 145); margin-right: -13px"
|
||||
},
|
||||
"_newSimilarSet: "
|
||||
],
|
||||
[
|
||||
"span",
|
||||
{
|
||||
"style": "margin-left: 13px"
|
||||
},
|
||||
[
|
||||
"object",
|
||||
{
|
||||
"object": "<OBJECT>",
|
||||
"config": {}
|
||||
}
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
"li",
|
||||
{
|
||||
|
@ -7467,4 +7415,4 @@ Value:
|
|||
]
|
||||
]
|
||||
]
|
||||
-----------------------------------
|
||||
-----------------------------------
|
||||
|
|
Loading…
Reference in a new issue