Initial steps to add cast operation.

Start by adding static castTo method to the interfaces. This provides everybody with the
desired implementation without exposing the classes.
When implementing the cast method, the recommended way is to (extend the base class,
but if you choose not to ...) use the castTo method.

If we ever get a language feature, we can implement the castTo method using that,
without being stuck with an unnecessary class.

Change-Id: I53274e60c88a4de519bead85d2ca44c1065a9c1a
Reviewed-on: https://dart-review.googlesource.com/26620
Commit-Queue: Lasse R.H. Nielsen <lrn@google.com>
Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
Reviewed-by: Leaf Petersen <leafp@google.com>
This commit is contained in:
Lasse R.H. Nielsen 2018-01-03 11:29:18 +01:00 committed by commit-bot@chromium.org
parent 47d4534700
commit d1382f983c
10 changed files with 605 additions and 1 deletions

View file

@ -110,6 +110,17 @@ abstract class Iterable<E> {
*/
const factory Iterable.empty() = EmptyIterable<E>;
/**
* Adapts [source] to be an `Iterable<T>`.
*
* Any time the iterable would produce an element that is not a [T],
* the element access will throw. If all elements of [source] are actually
* instances of [T], or if only elements that are actually instances of [T]
* are accessed, then the resulting iterable can be used as an `Iterable<T>`.
*/
static Iterable<T> castTo<S, T>(Iterable<S> source) =>
new CastIterable<S, T>(source);
/**
* Returns a new `Iterator` that allows iterating the elements of this
* `Iterable`.

View file

@ -155,6 +155,22 @@ abstract class List<E> implements EfficientLengthIterable<E> {
*/
external factory List.unmodifiable(Iterable elements);
/**
* Adapts [source] to be a `List<T>`.
*
* Any time the list would produce an element that is not a [T],
* the element access will throw.
*
* Any time a [T] value is attempted stored into the adapted list,
* the store will throw unless the value is also an instance of [S].
*
* If all accessed elements of [source] are actually instances of [T],
* and if all elements stored into the returned list are actually instance
* of [S],
* then the returned list can be used as a `List<T>`.
*/
static List<T> castTo<S, T>(List<S> source) => new CastList<S, T>(source);
/**
* Returns the object at the given [index] in the list
* or throws a [RangeError] if [index] is out of bounds.

View file

@ -142,6 +142,23 @@ abstract class Map<K, V> {
factory Map.fromIterables(Iterable<K> keys, Iterable<V> values) =
LinkedHashMap<K, V>.fromIterables;
/**
* Adapts [source] to be a `Map<K2, V2>`.
*
* Any time the set would produce a key or value that is not a [K2] or [V2],
* the access will throw.
*
* Any time [K2] key or [V2] value is attempted added into the adapted map,
* the store will throw unless the key is also an instance of [K] and
* the value is also an instance of [V].
*
* If all accessed entries of [source] are have [K2] keys and [V2] values
* and if all entries added to the returned map have [K] keys and [V]] values,
* then the returned map can be used as a `Map<K2, V2>`.
*/
static Map<K2, V2> castTo<K, V, K2, V2>(Map<K, V> source) =>
new CastMap<K, V, K2, V2>(source);
/**
* Returns true if this map contains the given [value].
*

View file

@ -78,6 +78,29 @@ abstract class Set<E> extends EfficientLengthIterable<E> {
*/
factory Set.from(Iterable elements) = LinkedHashSet<E>.from;
/**
* Adapts [source] to be a `Set<T>`.
*
* If [newSet] is provided, it is used to create the new sets returned
* by [toSet], [union], and is also used for [intersection] and [difference].
* If [newSet] is omitted, it defaults to creating a new set using the
* default [Set] constructor, and [intersection] and [difference]
* returns an adapted version of calling the same method on the source.
*
* Any time the set would produce an element that is not a [T],
* the element access will throw.
*
* Any time a [T] value is attempted added into the adapted set,
* the store will throw unless the value is also an instance of [S].
*
* If all accessed elements of [source] are actually instances of [T],
* and if all elements added to the returned set are actually instance
* of [S],
* then the returned set can be used as a `Set<T>`.
*/
static Set<T> castTo<S, T>(Set<S> source, {Set<R> Function<R>() newSet}) =>
new CastSet<S, T>(source, newSet);
/**
* Provides an iterator that iterates over the elements of this set.
*

View file

@ -1694,7 +1694,7 @@ class _Uri implements Uri {
if (argumentError) {
throw new ArgumentError("Illegal character in path");
} else {
throw new UnsupportedError("Illegal character in path");
throw new UnsupportedError("Illegal character in path: $segment");
}
}
}

352
sdk/lib/internal/cast.dart Normal file
View file

@ -0,0 +1,352 @@
// 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 dart._internal;
// Casting wrappers for collections and asynchronous classes.
abstract class _CastIterableBase<S, T> extends Iterable<T> {
Iterable<S> get _source;
Iterator<T> get iterator => new CastIterator<S, T>(_source.iterator);
// The following members use the default implementation on the
// throwing iterator. These are all operations that have no more efficient
// implementation than visiting every element in order,
// or that has no more efficient way to get the correct type (toList, toSet).
//
// * map
// * where
// * expand
// * forEach
// * reduce
// * fold
// * every
// * any
// * join
// * toList
// * toSet
// * skipWhile
// * takeWhile
// * firstWhere
// * singleWhere
int get length => _source.length;
bool get isEmpty => _source.isEmpty;
bool get isNotEmpty => _source.isNotEmpty;
Iterable<T> skip(int count) => new CastIterable<S, T>(_source.skip(count));
Iterable<T> take(int count) => new CastIterable<S, T>(_source.take(count));
T elementAt(int index) => _source.elementAt(index) as T;
T get first => _source.first as T;
T get last => _source.last as T;
T get single => _source.single as T;
bool contains(Object other) => _source.contains(other);
// Might be implemented by testing backwards from the end,
// so use the _source's implementation.
T lastWhere(bool test(T element), {T orElse()}) =>
_source.lastWhere((S element) => test(element as T),
orElse: (orElse == null) ? null : () => orElse() as S) as T;
String toString() => _source.toString();
}
class CastIterator<S, T> implements Iterator<T> {
Iterator<S> _source;
CastIterator(this._source);
bool moveNext() => _source.moveNext();
T get current => _source.current as T;
}
class CastIterable<S, T> extends _CastIterableBase<S, T> {
final Iterable<S> _source;
CastIterable._(this._source);
factory CastIterable(Iterable<S> source) {
if (source is EfficientLengthIterable<S>) {
return new _EfficientLengthCastIterable<S, T>(source);
}
return new CastIterable<S, T>._(source);
}
Iterable<R> cast<R>() => new CastIterable<S, R>(_source);
}
class _EfficientLengthCastIterable<S, T> extends CastIterable<S, T>
implements EfficientLengthIterable<T> {
_EfficientLengthCastIterable(EfficientLengthIterable<S> source)
: super._(source);
}
abstract class _CastListBase<S, T> extends _CastIterableBase<S, T>
with ListMixin<T> {
List<S> get _source;
// Using the default implementation from ListMixin:
// * reversed
// * shuffle
// * indexOf
// * lastIndexOf
// * clear
// * sublist
// * asMap
T operator [](int index) => _source[index] as T;
void operator []=(int index, T value) {
_source[index] = value as S;
}
void set length(int length) {
_source.length = length;
}
void add(T value) {
_source.add(value as S);
}
void addAll(Iterable<T> values) {
_source.addAll(new CastIterable<T, S>(values));
}
void sort([int compare(T v1, T v2)]) {
_source.sort((S v1, S v2) => compare(v1 as T, v2 as T));
}
void shuffle([Random random]) {
_source.shuffle(random);
}
void insert(int index, T element) {
_source.insert(index, element as S);
}
void insertAll(int index, Iterable<T> elements) {
_source.insertAll(index, new CastIterable<T, S>(elements));
}
void setAll(int index, Iterable<T> elements) {
_source.setAll(index, new CastIterable<T, S>(elements));
}
bool remove(Object value) => _source.remove(value);
T removeAt(int index) => _source.removeAt(index) as T;
T removeLast() => _source.removeLast() as T;
void removeWhere(bool test(T element)) {
_source.removeWhere((S element) => test(element as T));
}
void retainWhere(bool test(T element)) {
_source.retainWhere((S element) => test(element as T));
}
Iterable<T> getRange(int start, int end) =>
new CastIterable<S, T>(_source.getRange(start, end));
void setRange(int start, int end, Iterable<T> iterable, [int skipCount = 0]) {
_source.setRange(start, end, new CastIterable<T, S>(iterable), skipCount);
}
void removeRange(int start, int end) {
_source.removeRange(start, end);
}
void fillRange(int start, int end, [T fillValue]) {
_source.fillRange(start, end, fillValue as S);
}
void replaceRange(int start, int end, Iterable<T> replacement) {
_source.replaceRange(start, end, new CastIterable<T, S>(replacement));
}
}
class CastList<S, T> extends _CastListBase<S, T> {
final List<S> _source;
CastList(this._source);
List<R> cast<R>() => new CastList<S, R>(_source);
}
class CastSet<S, T> extends _CastIterableBase<S, T> implements Set<T> {
final Set<S> _source;
/// Creates a new empty set of the same *kind* as [_source],
/// but with `<R>` as type argument.
/// Used by [toSet] (and indirectly by [union]).
final Set<R> Function<R>() _emptySet;
CastSet(this._source, this._emptySet);
static Set<R> _defaultEmptySet<R>() => new Set<R>();
Set<R> cast<R>() => new CastSet<S, R>(_source, _emptySet);
bool add(T value) => _source.add(value as S);
void addAll(Iterable<T> elements) {
_source.addAll(new CastIterable<T, S>(elements));
}
bool remove(Object object) => _source.remove(object);
void removeAll(Iterable<Object> objects) {
_source.removeAll(objects);
}
void retainAll(Iterable<Object> objects) {
_source.retainAll(objects);
}
void removeWhere(bool test(T element)) {
_source.removeWhere((S element) => test(element as T));
}
void retainWhere(bool test(T element)) {
_source.retainWhere((S element) => test(element as T));
}
bool containsAll(Iterable<Object> objects) => _source.containsAll(objects);
Set<T> intersection(Set<Object> other) {
if (_emptySet != null) return _conditionalAdd(other, true);
return new CastSet<S, T>(_source.intersection(other), null);
}
Set<T> difference(Set<Object> other) {
if (_emptySet != null) return _conditionalAdd(other, false);
return new CastSet<S, T>(_source.difference(other), null);
}
Set<T> _conditionalAdd(Set<Object> other, bool otherContains) {
Set<T> result = (_emptySet == null) ? new Set<T>() : _emptySet<T>();
for (var element in _source) {
T castElement = element as T;
if (otherContains == other.contains(castElement)) result.add(castElement);
}
return result;
}
Set<T> union(Set<T> other) => _clone()..addAll(other);
void clear() {
_source.clear();
}
Set<T> _clone() {
Set<T> result = (_emptySet == null) ? new Set<T>() : _emptySet<T>();
result.addAll(this);
return result;
}
Set<T> toSet() => _clone();
T lookup(Object key) => _source.lookup(key) as T;
}
abstract class _CastQueueMixin<S, T> implements Queue<T> {
Queue<S> get _source;
T removeFirst() => _source.removeFirst() as T;
T removeLast() => _source.removeLast() as T;
@deprecated
void add(T value) {
_source.add(value as S);
}
void addFirst(T value) {
_source.addFirst(value as S);
}
void addLast(T value) {
_source.addLast(value as S);
}
bool remove(Object other) => _source.remove(other);
void addAll(Iterable<T> elements) {
_source.addAll(new CastIterable<T, S>(elements));
}
void removeWhere(bool test(T element)) {
_source.removeWhere((S element) => test(element as T));
}
void retainWhere(bool test(T element)) {
_source.retainWhere((S element) => test(element as T));
}
void clear() {
_source.clear();
}
}
class CastMap<SK, SV, K, V> implements Map<K, V> {
final Map<SK, SV> _source;
CastMap(this._source);
Map<RK, RV> cast<RK, RV>() => new CastMap<SK, SV, RK, RV>(_source);
bool containsValue(Object value) => _source.containsValue(value);
bool containsKey(Object key) => _source.containsKey(key);
V operator [](Object key) => _source[key] as V;
void operator []=(K key, V value) {
_source[key as SK] = value as SV;
}
V putIfAbsent(K key, V ifAbsent()) => _source.putIfAbsent(
key as SK, (ifAbsent == null) ? null : () => ifAbsent() as SV) as V;
void addAll(Map<K, V> other) {
_source.addAll(new CastMap<K, V, SK, SV>(other));
}
V remove(Object key) => _source.remove(key) as V;
void clear() {
_source.clear();
}
void forEach(void f(K key, V value)) {
_source.forEach((SK key, SV value) {
f(key as K, value as V);
});
}
Iterable<K> get keys => new CastIterable<SK, K>(_source.keys);
Iterable<V> get values => new CastIterable<SV, V>(_source.values);
int get length => _source.length;
bool get isEmpty => _source.isEmpty;
bool get isNotEmpty => _source.isNotEmpty;
}
class CastQueue<S, T> extends _CastIterableBase<S, T>
with _CastQueueMixin<S, T> {
final Queue<S> _source;
CastQueue(this._source);
Queue<R> cast<R>() => new CastQueue<S, R>(_source);
}
// TODO(lrn): Use when ListQueue implements List.
// class CastListQueue<S, T>
// extends _CastListBase<S, T> with _CastQueueMixin<S, T>
// implements ListQueue<T> {
// final ListQueue<S> _source;
// CastListQueue(this._source);
// ListQueue<R> cast<R>() => new CastListQueue<S, R>(_source);
// }

View file

@ -10,6 +10,7 @@ import 'dart:core' hide Symbol;
import 'dart:core' as core;
import 'dart:math' show Random;
part 'cast.dart';
part 'iterable.dart';
part 'list.dart';
part 'print.dart';

View file

@ -7,6 +7,7 @@ internal_sdk_sources = [
"internal.dart",
# The above file needs to be first as it lists the parts below.
"cast.dart",
"iterable.dart",
"list.dart",
"linked_list.dart",

View file

@ -0,0 +1,180 @@
// Copyright (c) 2011, 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.
import "dart:collection";
import "dart:typed_data";
import "package:expect/expect.dart";
void main() {
testIterable();
testList();
}
final elements = <C>[c, d, e, f, null];
void testIterable() {
var iterable = new Iterable<C>.generate(elements.length, (n) => elements[n]);
// Down-cast
{
// An iterable that (likely) can do direct access.
var dIterable = Iterable.castTo<C, D>(iterable);
Expect.throws(() => dIterable.first, null, "1.first");
Expect.equals(d, dIterable.elementAt(1));
Expect.throws(() => dIterable.elementAt(2), null, "1.2"); // E is not D.
Expect.equals(f, dIterable.skip(3).first); // Skip does not access element.
Expect.equals(null, dIterable.skip(3).elementAt(1));
Expect.throws(() => dIterable.toList(), null, "1.toList");
}
{
// An iterable that cannot do direct access.
var dIterable2 = Iterable.castTo<C, D>(iterable.where((_) => true));
Expect.throws(() => dIterable2.first, null, "2.first");
Expect.equals(d, dIterable2.elementAt(1));
Expect.throws(() => dIterable2.elementAt(2), null, "2.2"); // E is not D.
Expect.equals(f, dIterable2.skip(3).first); // Skip does not access element.
Expect.equals(null, dIterable2.skip(3).elementAt(1));
Expect.throws(() => dIterable2.toList(), null, "2.toList");
}
{
// Iterable that definitely won't survive accessing element 2.
var iterable3 = new Iterable<C>.generate(
elements.length, (n) => n == 3 ? throw "untouchable" : elements[n]);
var dIterable3 = Iterable.castTo<C, D>(iterable3);
Expect.throws(() => dIterable3.first, null, "3.first");
Expect.equals(d, dIterable3.elementAt(1));
Expect.throws(() => dIterable3.elementAt(3), null, "3.3");
// Skip does not access element.
Expect.equals(null, dIterable3.skip(4).first);
Expect.equals(null, dIterable3.skip(3).elementAt(1));
Expect.throws(() => dIterable3.toList(), null, "3.toList");
}
// Up-cast.
{
var oIterable4 = Iterable.castTo<C, Object>(iterable);
Expect.listEquals(elements, oIterable4.toList());
}
}
void testList() {
// Down-cast.
var list = new List<C>.from(elements);
var dList = List.castTo<C, D>(list);
Expect.throws(() => dList.first); // C is not D.
Expect.equals(d, dList[1]);
Expect.throws(() => dList[2]); // E is not D.
Expect.equals(f, dList[3]);
Expect.equals(null, dList.last);
Expect.throws(() => dList.toList());
dList[2] = d;
Expect.equals(d, dList[2]); // Setting works.
// Up-cast.
var list2 = new List<C>.from(elements);
var dList2 = List.castTo<C, Object>(list2);
Expect.listEquals(elements, dList2);
Expect.throws(() => dList2[2] = new Object()); // Cannot set non-C.
Expect.listEquals(elements, dList2);
}
void testSet() {
var set = new Set<C>.from(elements); // Linked HashSet.
Expect.listEquals(elements, set.toList()); // Preserves order.
var dSet = Set.castTo<C, D>(set);
// Preserves order.
Expect.throws(() => dSet.first); // C is not D.
Expect.equals(d, dSet.elementAt(1));
Expect.throws(() => dSet.elementAt(2)); // E is not D.
Expect.throws(() => dSet.toList());
// Contains is not typed.
var newC = new C();
Expect.isFalse(dSet.contains(newC));
dSet.add(newC);
Expect.isTrue(dSet.contains(newC));
Expect.equals(5, dSet.length);
dSet.remove(newC);
Expect.equals(5, dSet.length);
dSet.remove(c); // Success, no type checks.
Expect.equals(4, dSet.length);
// Up-cast
var set2 = new Set<C>.from(elements);
var dSet2 = Set.castTo<C, Object>(set2);
var newObject = new Object();
Expect.throws(() => dSet2.add(newObject));
Expect.isFalse(dSet.contains(newObject));
var toSet2 = dSet2.toSet();
Expect.isTrue(toSet2 is LinkedHashSet<Object>);
Expect.isTrue(toSet2 is! LinkedHashSet<C>);
// Custom emptySet.
var set3 = new Set<C>.from(elements);
var dSet3 = Set.castTo<C, Object>(set3, newSet: <T>() => new HashSet<T>());
var toSet3 = dSet3.toSet();
Expect.isTrue(toSet3 is HashSet<Object>);
Expect.isTrue(toSet3 is HashSet<C>);
Expect.isTrue(toSet3 is! LinkedHashSet<Object>);
}
void testMap() {
var map = new Map.fromIterables(elements, elements);
var dMap = Map.castTo<C, C, D, D>(map);
Expect.isTrue(dMap is Map<D, D>);
Expect.equals(null, dMap[new C()]);
Expect.throws(() => dMap[c]);
Expect.isTrue(dMap.containsKey(c));
Expect.equals(d, dMap[d]);
Expect.throws(() => dMap[e]);
Expect.equals(null, dMap[null]);
Expect.equals(5, dMap.length);
dMap.remove(c); // Success, no type checks along the way.
Expect.equals(4, dMap.length);
Expect.equals(null, dMap[c]);
Expect.throws(() => dMap[c] = d);
Expect.throws(() => dMap[d] = c);
Expect.equals(4, dMap.length);
Expect.isTrue(dMap.keys is Iterable<D>);
Expect.isTrue(dMap.values is Iterable<D>);
Expect.throws(() => dMap.keys.toList());
Expect.throws(() => dMap.values.toList());
}
class C {}
class D extends C {}
class E extends C {}
class F implements D, E {}
final c = new C();
final d = new D();
final e = new E();
final f = new F();

View file

@ -106,6 +106,9 @@ unicode_test: Fail
[ $runtime == vm ]
string_case_test/01: RuntimeError
[ !$strong ]
cast_test: SkipByDesign # Uses generic method parameters.
[ $arch == simarmv5te && ($runtime == dart_precompiled || $runtime == vm) ]
int_parse_radix_test/*: Pass, Slow
integer_parsed_mul_div_vm_test: Pass, Slow