[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:
MarkZ 2024-07-02 07:27:12 +00:00 committed by Commit Queue
parent 59add4f01e
commit 72ee2943fd
11 changed files with 509 additions and 522 deletions

View file

@ -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(

View file

@ -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,
});

View file

@ -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',
],
};

View file

@ -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>;
}

View 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;
}
}
}

View file

@ -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;
}

View file

@ -43,6 +43,7 @@ import 'dart:_js_helper'
DeferredNotLoadedError,
getRtiForRecord,
ImmutableMap,
ImmutableSet,
JsLinkedHashMap,
jsObjectGetPrototypeOf,
jsObjectSetPrototypeOf,

View file

@ -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;
}
}
}

View file

@ -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';

View 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");
}

View file

@ -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:
]
]
]
-----------------------------------
-----------------------------------