Hide the existence of EfficientLengthIterable better.

The `EfficientLengthIterable` is an internal marker interface
that allows the SDK to more efficiently check, effectively,
`v is List || v is Set || v is Queue`, and some other
known internal types, which allows it to assume that `.length`
is efficient and doesn't iterate the iterable.

It's not intended for external use, but the current design
both has the name mentioned specifically in the declaration
of the public types `List`, `Set` and `Queue`,
and possibly allows the type to leak through the
least-upper-bound algorithm.

This change moves the mention of `EfficientLengthIterable`
from the public types to an anonymously named private type,
and ensures that the private type is never the result of
a least-upper-bound computation, by adding another
interface with the same depth that all the public
types implementing `EfficientLengthIterable` also implement.

(A longer term solution to `EfficientLengthIterable` looking
like it's a public name could be combining the collection-
related code from `dart:collection`, `dart:core` and
`dart:_internal` into a single `dart:_collection_impl`,
and exporting the relevant types from there. Then
we could make the interface be `_EfficientLengthIterable`
again.)

Change-Id: I717743f0ca253782162be0ad9ff05036fdf57159
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/322320
Reviewed-by: Nate Bosch <nbosch@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Lasse Nielsen <lrn@google.com>
This commit is contained in:
Lasse R.H. Nielsen 2023-09-12 11:08:42 +00:00 committed by Commit Queue
parent f7090efca0
commit c15f054809
23 changed files with 77 additions and 49 deletions

View file

@ -9,7 +9,6 @@ library /*isLegacy*/;
import self as self;
import "dart:core" as core;
import "dart:collection" as col;
import "dart:_internal" as _in;
static method useAddAll() → void {
dynamic dynamicSet1 = block {
@ -327,8 +326,8 @@ static method main() → dynamic {
self::useAddAll();
}
static method expect(core::Set<dynamic>* set1, core::Set<dynamic>* set2) → void {
if(!(set1.{_in::EfficientLengthIterable::length}{core::int*} =={core::num::==}{(core::Object*) →* core::bool*} set2.{_in::EfficientLengthIterable::length}{core::int*})) {
throw "Unexpected length. Expected ${set1.{_in::EfficientLengthIterable::length}{core::int*}}, actual ${set2.{_in::EfficientLengthIterable::length}{core::int*}}.";
if(!(set1.{core::Iterable::length}{core::int*} =={core::num::==}{(core::Object*) →* core::bool*} set2.{core::Iterable::length}{core::int*})) {
throw "Unexpected length. Expected ${set1.{core::Iterable::length}{core::int*}}, actual ${set2.{core::Iterable::length}{core::int*}}.";
}
for (dynamic element in set1) {
if(!set2.{core::Set::contains}(element){(core::Object*) →* core::bool*}) {

View file

@ -9,7 +9,6 @@ library /*isLegacy*/;
import self as self;
import "dart:core" as core;
import "dart:collection" as col;
import "dart:_internal" as _in;
static method useAddAll() → void {
dynamic dynamicSet1 = block {
@ -481,8 +480,8 @@ static method main() → dynamic {
self::useAddAll();
}
static method expect(core::Set<dynamic>* set1, core::Set<dynamic>* set2) → void {
if(!(set1.{_in::EfficientLengthIterable::length}{core::int*} =={core::num::==}{(core::Object*) →* core::bool*} set2.{_in::EfficientLengthIterable::length}{core::int*})) {
throw "Unexpected length. Expected ${set1.{_in::EfficientLengthIterable::length}{core::int*}}, actual ${set2.{_in::EfficientLengthIterable::length}{core::int*}}.";
if(!(set1.{core::Iterable::length}{core::int*} =={core::num::==}{(core::Object*) →* core::bool*} set2.{core::Iterable::length}{core::int*})) {
throw "Unexpected length. Expected ${set1.{core::Iterable::length}{core::int*}}, actual ${set2.{core::Iterable::length}{core::int*}}.";
}
{
synthesized core::Iterator<dynamic>* :sync-for-iterator = set1.{core::Iterable::iterator}{core::Iterator<dynamic>*};

View file

@ -2,7 +2,6 @@ library /*isLegacy*/;
import self as self;
import "dart:core" as core;
import "dart:collection" as col;
import "dart:_internal" as _in;
static method useAddAll() → void {
dynamic dynamicSet1 = block {
@ -320,8 +319,8 @@ static method main() → dynamic {
self::useAddAll();
}
static method expect(core::Set<dynamic>* set1, core::Set<dynamic>* set2) → void {
if(!(set1.{_in::EfficientLengthIterable::length}{core::int*} =={core::num::==}{(core::Object*) →* core::bool*} set2.{_in::EfficientLengthIterable::length}{core::int*})) {
throw "Unexpected length. Expected ${set1.{_in::EfficientLengthIterable::length}{core::int*}}, actual ${set2.{_in::EfficientLengthIterable::length}{core::int*}}.";
if(!(set1.{core::Iterable::length}{core::int*} =={core::num::==}{(core::Object*) →* core::bool*} set2.{core::Iterable::length}{core::int*})) {
throw "Unexpected length. Expected ${set1.{core::Iterable::length}{core::int*}}, actual ${set2.{core::Iterable::length}{core::int*}}.";
}
for (dynamic element in set1) {
if(!set2.{core::Set::contains}(element){(core::Object*) →* core::bool*}) {

View file

@ -2,7 +2,6 @@ library /*isLegacy*/;
import self as self;
import "dart:core" as core;
import "dart:collection" as col;
import "dart:_internal" as _in;
static method useAddAll() → void {
dynamic dynamicSet1 = block {
@ -320,8 +319,8 @@ static method main() → dynamic {
self::useAddAll();
}
static method expect(core::Set<dynamic>* set1, core::Set<dynamic>* set2) → void {
if(!(set1.{_in::EfficientLengthIterable::length}{core::int*} =={core::num::==}{(core::Object*) →* core::bool*} set2.{_in::EfficientLengthIterable::length}{core::int*})) {
throw "Unexpected length. Expected ${set1.{_in::EfficientLengthIterable::length}{core::int*}}, actual ${set2.{_in::EfficientLengthIterable::length}{core::int*}}.";
if(!(set1.{core::Iterable::length}{core::int*} =={core::num::==}{(core::Object*) →* core::bool*} set2.{core::Iterable::length}{core::int*})) {
throw "Unexpected length. Expected ${set1.{core::Iterable::length}{core::int*}}, actual ${set2.{core::Iterable::length}{core::int*}}.";
}
for (dynamic element in set1) {
if(!set2.{core::Set::contains}(element){(core::Object*) →* core::bool*}) {

View file

@ -2,7 +2,6 @@ library /*isLegacy*/;
import self as self;
import "dart:core" as core;
import "dart:collection" as col;
import "dart:_internal" as _in;
static method useAddAll() → void {
dynamic dynamicSet1 = block {
@ -474,8 +473,8 @@ static method main() → dynamic {
self::useAddAll();
}
static method expect(core::Set<dynamic>* set1, core::Set<dynamic>* set2) → void {
if(!(set1.{_in::EfficientLengthIterable::length}{core::int*} =={core::num::==}{(core::Object*) →* core::bool*} set2.{_in::EfficientLengthIterable::length}{core::int*})) {
throw "Unexpected length. Expected ${set1.{_in::EfficientLengthIterable::length}{core::int*}}, actual ${set2.{_in::EfficientLengthIterable::length}{core::int*}}.";
if(!(set1.{core::Iterable::length}{core::int*} =={core::num::==}{(core::Object*) →* core::bool*} set2.{core::Iterable::length}{core::int*})) {
throw "Unexpected length. Expected ${set1.{core::Iterable::length}{core::int*}}, actual ${set2.{core::Iterable::length}{core::int*}}.";
}
{
synthesized core::Iterator<dynamic>* :sync-for-iterator = set1.{core::Iterable::iterator}{core::Iterator<dynamic>*};

View file

@ -2,7 +2,6 @@ library;
import self as self;
import "dart:core" as core;
import "dart:collection" as col;
import "dart:_internal" as _in;
static method useAddAll() → void {
dynamic dynamicSet1 = block {
@ -257,8 +256,8 @@ static method main() → dynamic {
self::useAddAllNullable();
}
static method expect(core::Set<dynamic> set1, core::Set<dynamic> set2) → void {
if(!(set1.{_in::EfficientLengthIterable::length}{core::int} =={core::num::==}{(core::Object) → core::bool} set2.{_in::EfficientLengthIterable::length}{core::int})) {
throw "Unexpected length. Expected ${set1.{_in::EfficientLengthIterable::length}{core::int}}, actual ${set2.{_in::EfficientLengthIterable::length}{core::int}}.";
if(!(set1.{core::Iterable::length}{core::int} =={core::num::==}{(core::Object) → core::bool} set2.{core::Iterable::length}{core::int})) {
throw "Unexpected length. Expected ${set1.{core::Iterable::length}{core::int}}, actual ${set2.{core::Iterable::length}{core::int}}.";
}
for (dynamic element in set1) {
if(!set2.{core::Set::contains}(element){(core::Object?) → core::bool}) {

View file

@ -2,7 +2,6 @@ library;
import self as self;
import "dart:core" as core;
import "dart:collection" as col;
import "dart:_internal" as _in;
static method useAddAll() → void {
dynamic dynamicSet1 = block {
@ -356,8 +355,8 @@ static method main() → dynamic {
self::useAddAllNullable();
}
static method expect(core::Set<dynamic> set1, core::Set<dynamic> set2) → void {
if(!(set1.{_in::EfficientLengthIterable::length}{core::int} =={core::num::==}{(core::Object) → core::bool} set2.{_in::EfficientLengthIterable::length}{core::int})) {
throw "Unexpected length. Expected ${set1.{_in::EfficientLengthIterable::length}{core::int}}, actual ${set2.{_in::EfficientLengthIterable::length}{core::int}}.";
if(!(set1.{core::Iterable::length}{core::int} =={core::num::==}{(core::Object) → core::bool} set2.{core::Iterable::length}{core::int})) {
throw "Unexpected length. Expected ${set1.{core::Iterable::length}{core::int}}, actual ${set2.{core::Iterable::length}{core::int}}.";
}
{
synthesized core::Iterator<dynamic> :sync-for-iterator = set1.{core::Iterable::iterator}{core::Iterator<dynamic>};

View file

@ -2,7 +2,6 @@ library;
import self as self;
import "dart:core" as core;
import "dart:collection" as col;
import "dart:_internal" as _in;
static method useAddAll() → void {
dynamic dynamicSet1 = block {
@ -257,8 +256,8 @@ static method main() → dynamic {
self::useAddAllNullable();
}
static method expect(core::Set<dynamic> set1, core::Set<dynamic> set2) → void {
if(!(set1.{_in::EfficientLengthIterable::length}{core::int} =={core::num::==}{(core::Object) → core::bool} set2.{_in::EfficientLengthIterable::length}{core::int})) {
throw "Unexpected length. Expected ${set1.{_in::EfficientLengthIterable::length}{core::int}}, actual ${set2.{_in::EfficientLengthIterable::length}{core::int}}.";
if(!(set1.{core::Iterable::length}{core::int} =={core::num::==}{(core::Object) → core::bool} set2.{core::Iterable::length}{core::int})) {
throw "Unexpected length. Expected ${set1.{core::Iterable::length}{core::int}}, actual ${set2.{core::Iterable::length}{core::int}}.";
}
for (dynamic element in set1) {
if(!set2.{core::Set::contains}(element){(core::Object?) → core::bool}) {

View file

@ -2,7 +2,6 @@ library;
import self as self;
import "dart:core" as core;
import "dart:collection" as col;
import "dart:_internal" as _in;
static method useAddAll() → void {
dynamic dynamicSet1 = block {
@ -257,8 +256,8 @@ static method main() → dynamic {
self::useAddAllNullable();
}
static method expect(core::Set<dynamic> set1, core::Set<dynamic> set2) → void {
if(!(set1.{_in::EfficientLengthIterable::length}{core::int} =={core::num::==}{(core::Object) → core::bool} set2.{_in::EfficientLengthIterable::length}{core::int})) {
throw "Unexpected length. Expected ${set1.{_in::EfficientLengthIterable::length}{core::int}}, actual ${set2.{_in::EfficientLengthIterable::length}{core::int}}.";
if(!(set1.{core::Iterable::length}{core::int} =={core::num::==}{(core::Object) → core::bool} set2.{core::Iterable::length}{core::int})) {
throw "Unexpected length. Expected ${set1.{core::Iterable::length}{core::int}}, actual ${set2.{core::Iterable::length}{core::int}}.";
}
for (dynamic element in set1) {
if(!set2.{core::Set::contains}(element){(core::Object?) → core::bool}) {

View file

@ -2,7 +2,6 @@ library;
import self as self;
import "dart:core" as core;
import "dart:collection" as col;
import "dart:_internal" as _in;
static method useAddAll() → void {
dynamic dynamicSet1 = block {
@ -356,8 +355,8 @@ static method main() → dynamic {
self::useAddAllNullable();
}
static method expect(core::Set<dynamic> set1, core::Set<dynamic> set2) → void {
if(!(set1.{_in::EfficientLengthIterable::length}{core::int} =={core::num::==}{(core::Object) → core::bool} set2.{_in::EfficientLengthIterable::length}{core::int})) {
throw "Unexpected length. Expected ${set1.{_in::EfficientLengthIterable::length}{core::int}}, actual ${set2.{_in::EfficientLengthIterable::length}{core::int}}.";
if(!(set1.{core::Iterable::length}{core::int} =={core::num::==}{(core::Object) → core::bool} set2.{core::Iterable::length}{core::int})) {
throw "Unexpected length. Expected ${set1.{core::Iterable::length}{core::int}}, actual ${set2.{core::Iterable::length}{core::int}}.";
}
{
synthesized core::Iterator<dynamic> :sync-for-iterator = set1.{core::Iterable::iterator}{core::Iterator<dynamic>};

View file

@ -95,7 +95,8 @@ base class IdentityMap<K, V> extends InternalMap<K, V> {
}
}
class _JSMapIterable<E> extends EfficientLengthIterable<E> {
class _JSMapIterable<E> extends EfficientLengthIterable<E>
implements HideEfficientLengthIterable<E> {
final InternalMap _map;
@notNull
final bool _isKeys;

View file

@ -13,6 +13,7 @@ import 'dart:_interceptors';
import 'dart:_internal'
show
EfficientLengthIterable,
HideEfficientLengthIterable,
MappedIterable,
IterableElementError,
SubListIterable,

View file

@ -443,7 +443,8 @@ base class _CustomHashMap<K, V> extends _HashMap<K, V> {
}
}
class _HashMapKeyIterable<E> extends EfficientLengthIterable<E> {
class _HashMapKeyIterable<E> extends EfficientLengthIterable<E>
implements HideEfficientLengthIterable<E> {
final _HashMap _map;
_HashMapKeyIterable(this._map);

View file

@ -53,6 +53,7 @@ import 'dart:_internal'
show
checkNotNullable,
EfficientLengthIterable,
HideEfficientLengthIterable,
MappedIterable,
IterableElementError,
SubListIterable;

View file

@ -339,7 +339,8 @@ class LinkedHashMapCell {
LinkedHashMapCell(this.hashMapCellKey, this.hashMapCellValue);
}
class LinkedHashMapKeyIterable<E> extends EfficientLengthIterable<E> {
class LinkedHashMapKeyIterable<E> extends EfficientLengthIterable<E>
implements HideEfficientLengthIterable<E> {
final JsLinkedHashMap _map;
LinkedHashMapKeyIterable(this._map);

View file

@ -125,7 +125,8 @@ class JSArrayIteratorAdapter<T> implements Iterator<T> {
/// [JSArrayIterableAdapter] lazily adapts a [JSArray] to Dart's [Iterable]
/// interface.
class JSArrayIterableAdapter<T> extends EfficientLengthIterable<T> {
class JSArrayIterableAdapter<T> extends EfficientLengthIterable<T>
implements HideEfficientLengthIterable<T> {
final JSArray array;
JSArrayIterableAdapter(this.array);

View file

@ -224,7 +224,8 @@ abstract class UnmodifiableMapBase<K, V> = MapBase<K, V>
/// Iterable that iterates over the values of a `Map`.
/// It accesses the values by iterating over the keys of the map, and using the
/// map's `operator[]` to lookup the keys.
class _MapBaseValueIterable<K, V> extends EfficientLengthIterable<V> {
class _MapBaseValueIterable<K, V> extends EfficientLengthIterable<V>
implements HideEfficientLengthIterable<V> {
final Map<K, V> _map;
_MapBaseValueIterable(this._map);

View file

@ -4,6 +4,11 @@
part of dart.collection;
/// Helper interface to hide [EfficientLengthIterable] from the public
/// declaration of [Queue].
abstract class _QueueIterable<E>
implements EfficientLengthIterable<E>, HideEfficientLengthIterable<E> {}
/// A [Queue] is a collection that can be manipulated at both ends. One
/// can iterate over the elements of a queue through [forEach] or with
/// an [Iterator].
@ -32,7 +37,7 @@ part of dart.collection;
/// queue.removeLast();
/// print(queue); // {1, 2, 3}
/// ```
abstract interface class Queue<E> implements EfficientLengthIterable<E> {
abstract interface class Queue<E> implements Iterable<E>, _QueueIterable<E> {
/// Creates a queue.
factory Queue() = ListQueue<E>;

View file

@ -784,7 +784,8 @@ abstract class _SplayTreeIterator<K, Node extends _SplayTreeNode<K, Node>, T>
}
class _SplayTreeKeyIterable<K, Node extends _SplayTreeNode<K, Node>>
extends EfficientLengthIterable<K> {
extends EfficientLengthIterable<K>
implements HideEfficientLengthIterable<K> {
_SplayTree<K, Node> _tree;
_SplayTreeKeyIterable(this._tree);
int get length => _tree._count;
@ -801,7 +802,8 @@ class _SplayTreeKeyIterable<K, Node extends _SplayTreeNode<K, Node>>
}
}
class _SplayTreeValueIterable<K, V> extends EfficientLengthIterable<V> {
class _SplayTreeValueIterable<K, V> extends EfficientLengthIterable<V>
implements HideEfficientLengthIterable<V> {
SplayTreeMap<K, V> _map;
_SplayTreeValueIterable(this._map);
int get length => _map._count;
@ -810,7 +812,8 @@ class _SplayTreeValueIterable<K, V> extends EfficientLengthIterable<V> {
}
class _SplayTreeMapEntryIterable<K, V>
extends EfficientLengthIterable<MapEntry<K, V>> {
extends EfficientLengthIterable<MapEntry<K, V>>
implements HideEfficientLengthIterable<MapEntry<K, V>> {
SplayTreeMap<K, V> _map;
_SplayTreeMapEntryIterable(this._map);
int get length => _map._count;

View file

@ -4,6 +4,11 @@
part of dart.core;
/// Helper interface to hide [EfficientLengthIterable] from the public
/// declaration of [List].
abstract class _ListIterable<E>
implements EfficientLengthIterable<E>, HideEfficientLengthIterable<E> {}
/// An indexable collection of objects with a length.
///
/// Subclasses of this class implement different kinds of lists.
@ -112,7 +117,7 @@ part of dart.core;
/// Changing the list's length while it is being iterated, either by iterating it
/// directly or through iterating an [Iterable] that is backed by the list, will
/// break the iteration.
abstract interface class List<E> implements EfficientLengthIterable<E> {
abstract interface class List<E> implements Iterable<E>, _ListIterable<E> {
/// Creates a list of the given length with [fill] at each position.
///
/// The [length] must be a non-negative integer.

View file

@ -4,6 +4,11 @@
part of dart.core;
/// Helper interface to hide [EfficientLengthIterable] from the public
/// declaration of [Set].
abstract class _SetIterable<E>
implements EfficientLengthIterable<E>, HideEfficientLengthIterable<E> {}
/// A collection of objects in which each object can occur only once.
///
/// That is, for each object of the element type, the object is either considered
@ -33,7 +38,7 @@ part of dart.core;
/// It is generally not allowed to modify the equality of elements (and thus not
/// their hashcode) while they are in the set. Some specialized subtypes may be
/// more permissive, in which case they should document this behavior.
abstract interface class Set<E> extends EfficientLengthIterable<E> {
abstract interface class Set<E> implements Iterable<E>, _SetIterable<E> {
/// Creates an empty [Set].
///
/// The created [Set] is a plain [LinkedHashSet].

View file

@ -78,7 +78,7 @@ class CastIterable<S, T> extends _CastIterableBase<S, T> {
}
class _EfficientLengthCastIterable<S, T> extends CastIterable<S, T>
implements EfficientLengthIterable<T> {
implements EfficientLengthIterable<T>, HideEfficientLengthIterable<T> {
_EfficientLengthCastIterable(EfficientLengthIterable<S> source)
: super._(source);
}

View file

@ -19,13 +19,23 @@ abstract class EfficientLengthIterable<T> extends Iterable<T> {
int get length;
}
/// An interface which hides [EfficientLengthIterable] from upper bounds.
///
/// Every type which implements [EfficientLengthIterable] also implements
/// this interface, and they have the same *depth*, so it's impossible
/// for the upper-bound algorithm to get [EfficientLengthIterable]
/// as the result.
abstract interface class HideEfficientLengthIterable<T>
implements Iterable<T> {}
/**
* An [Iterable] for classes that have efficient [length] and [elementAt].
*
* All other methods are implemented in terms of [length] and [elementAt],
* including [iterator].
*/
abstract class ListIterable<E> extends EfficientLengthIterable<E> {
abstract class ListIterable<E> extends EfficientLengthIterable<E>
implements HideEfficientLengthIterable<E> {
int get length;
E elementAt(int i);
@ -376,7 +386,7 @@ class MappedIterable<S, T> extends Iterable<T> {
}
class EfficientLengthMappedIterable<S, T> extends MappedIterable<S, T>
implements EfficientLengthIterable<T> {
implements EfficientLengthIterable<T>, HideEfficientLengthIterable<T> {
EfficientLengthMappedIterable(Iterable<S> iterable, T function(S value))
: super._(iterable, function);
}
@ -511,7 +521,7 @@ class TakeIterable<E> extends Iterable<E> {
}
class EfficientLengthTakeIterable<E> extends TakeIterable<E>
implements EfficientLengthIterable<E> {
implements EfficientLengthIterable<E>, HideEfficientLengthIterable<E> {
EfficientLengthTakeIterable(Iterable<E> iterable, int takeCount)
: super._(iterable, takeCount);
@ -605,7 +615,7 @@ class SkipIterable<E> extends Iterable<E> {
}
class EfficientLengthSkipIterable<E> extends SkipIterable<E>
implements EfficientLengthIterable<E> {
implements EfficientLengthIterable<E>, HideEfficientLengthIterable<E> {
factory EfficientLengthSkipIterable(Iterable<E> iterable, int count) {
return EfficientLengthSkipIterable<E>._(iterable, _checkCount(count));
}
@ -682,7 +692,8 @@ class SkipWhileIterator<E> implements Iterator<E> {
/**
* The always empty [Iterable].
*/
class EmptyIterable<E> extends EfficientLengthIterable<E> {
class EmptyIterable<E> extends EfficientLengthIterable<E>
implements HideEfficientLengthIterable<E> {
const EmptyIterable();
Iterator<E> get iterator => const EmptyIterator<Never>();
@ -816,7 +827,7 @@ class FollowedByIterable<E> extends Iterable<E> {
}
class EfficientLengthFollowedByIterable<E> extends FollowedByIterable<E>
implements EfficientLengthIterable<E> {
implements EfficientLengthIterable<E>, HideEfficientLengthIterable<E> {
EfficientLengthFollowedByIterable(
EfficientLengthIterable<E> first, EfficientLengthIterable<E> second)
: super(first, second);
@ -978,7 +989,9 @@ class IndexedIterable<T> extends Iterable<(int, T)> {
}
class EfficientLengthIndexedIterable<T> extends IndexedIterable<T>
implements EfficientLengthIterable<(int, T)> {
implements
EfficientLengthIterable<(int, T)>,
HideEfficientLengthIterable<(int, T)> {
EfficientLengthIndexedIterable(super._source, super._start) : super._();
(int, T) get last {