From 74634c2a27539ade96d1e4dcab8f213a0bdbb465 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 11:20:57 +0200 Subject: [PATCH 01/14] New tests New CollectionStrawMan5, executed as runttest in two different ways: - built with scalac, test compiled by dotty in tests/run. - built with dotty, test compiled by dotty using separate compilation. --- .../collections/CollectionStrawMan4.scala | 2 +- .../collections/CollectionStrawMan5.scala | 509 ++++++++++++++++++ tests/run/CollectionTests.scala | 4 +- .../run/colltest5/CollectionStrawMan5_1.scala | 509 ++++++++++++++++++ tests/run/colltest5/CollectionTests_2.scala | 170 ++++++ 5 files changed, 1191 insertions(+), 3 deletions(-) create mode 100644 src/strawman/collections/CollectionStrawMan5.scala create mode 100644 tests/run/colltest5/CollectionStrawMan5_1.scala create mode 100644 tests/run/colltest5/CollectionTests_2.scala diff --git a/src/strawman/collections/CollectionStrawMan4.scala b/src/strawman/collections/CollectionStrawMan4.scala index 9159b1cfc157..8ec77e19bfa1 100644 --- a/src/strawman/collections/CollectionStrawMan4.scala +++ b/src/strawman/collections/CollectionStrawMan4.scala @@ -27,7 +27,7 @@ object CollectionStrawMan4 { def knownLength: Int = -1 } - /** Base trait for instances that can construct a collection from an iterator */ + /** Base trait for instances that can construct a collection from an iterable */ trait FromIterable[+C[X] <: Iterable[X]] { def fromIterable[B](v: Iterable[B]): C[B] } diff --git a/src/strawman/collections/CollectionStrawMan5.scala b/src/strawman/collections/CollectionStrawMan5.scala new file mode 100644 index 000000000000..aa127cb7e7eb --- /dev/null +++ b/src/strawman/collections/CollectionStrawMan5.scala @@ -0,0 +1,509 @@ +package strawman.collections + +import Predef.{augmentString => _, wrapString => _, _} +import scala.reflect.ClassTag +import annotation.unchecked.uncheckedVariance + +/** A strawman architecture for new collections. It contains some + * example collection classes and methods with the intent to expose + * some key issues. It would be good to compare this to other + * implementations of the same functionality, to get an idea of the + * strengths and weaknesses of different collection architectures. + * + * For a test file, see tests/run/CollectionTests.scala. + */ +object CollectionStrawMan5 { + + /* ------------ Base Traits -------------------------------- */ + + /** Iterator can be used only once */ + trait IterableOnce[+A] { + def iterator: Iterator[A] + } + + /** Base trait for instances that can construct a collection from an iterable */ + trait FromIterable[+C[X] <: Iterable[X]] { + def fromIterable[B](it: Iterable[B]): C[B] + } + + /** Base trait for companion objects of collections */ + trait IterableFactory[+C[X] <: Iterable[X]] extends FromIterable[C] { + def empty[X]: C[X] = fromIterable(View.Empty) + def apply[A](xs: A*): C[A] = fromIterable(View.Elems(xs: _*)) + } + + /** Base trait for Iterable operations */ + trait IterableLike[+A, +C[X] <: Iterable[X]] + extends FromIterable[C] + with IterableOps[A] + with IterableMonoTransforms[A @uncheckedVariance, C[A @uncheckedVariance]] + with IterablePolyTransforms[A @uncheckedVariance, C] { + protected def fromLikeIterable(coll: Iterable[A @uncheckedVariance]): C[A @uncheckedVariance] = fromIterable(coll) + } + + /** Base trait for Seq operations */ + trait SeqLike[+A, +C[X] <: Seq[X]] extends IterableLike[A, C] { + def reverse: C[A @uncheckedVariance] = { + var xs: List[A] = Nil + var it = iterator + while (it.hasNext) xs = new Cons(it.next, xs) + fromLikeIterable(xs) + } + } + + /** Base trait for generic collections */ + trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { + override def iterator: Iterator[A] + override def fromIterable[B](it: Iterable[B]): Iterable[B] + + protected def coll: Iterable[A] = this + def knownLength: Int = -1 + } + + /** Base trait for sequence collections */ + trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] { + def apply(i: Int): A + def length: Int + override def iterator: Iterator[A] + } + + trait Buildable[+A, +To <: Iterable[A]] extends Iterable[A] { + protected[this] def newBuilder: Builder[A, To] + override def partition(p: A => Boolean): (To, To) = { + val l, r = newBuilder + iterator.foreach(x => (if (p(x)) l else r) += x) + (l.result, r.result) + } + } + + trait Builder[-A, +To] { + def +=(x: A): this.type + def result: To + + def ++=(xs: IterableOnce[A]): this.type = { + xs.iterator.foreach(+=) + this + } + } + + /* ------------ Operations ----------------------------------- */ + + trait IterableOps[+A] extends Any { + def iterator: Iterator[A] + def foreach(f: A => Unit): Unit = iterator.foreach(f) + def foldLeft[B](z: B)(op: (B, A) => B): B = iterator.foldLeft(z)(op) + def foldRight[B](z: B)(op: (A, B) => B): B = iterator.foldRight(z)(op) + def indexWhere(p: A => Boolean): Int = iterator.indexWhere(p) + def isEmpty: Boolean = !iterator.hasNext + def head: A = iterator.next + def view: View[A] = View.fromIterator(iterator) + } + + trait IterableMonoTransforms[A, +Repr] extends Any { + protected def coll: Iterable[A] + protected def fromLikeIterable(coll: Iterable[A]): Repr + def filter(p: A => Boolean): Repr = fromLikeIterable(View.Filter(coll, p)) + def partition(p: A => Boolean): (Repr, Repr) = { + val pn = View.Partition(coll, p) + (fromLikeIterable(pn.left), fromLikeIterable(pn.right)) + } + def drop(n: Int): Repr = fromLikeIterable(View.Drop(coll, n)) + def to[C[X] <: Iterable[X]](fi: FromIterable[C]): C[A] = fi.fromIterable(coll) + } + + trait IterablePolyTransforms[+A, +C[A]] extends Any { + protected def coll: Iterable[A] + def fromIterable[B](coll: Iterable[B]): C[B] + def map[B](f: A => B): C[B] = fromIterable(View.Map(coll, f)) + def flatMap[B](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) + def ++[B >: A](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) + def zip[B](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) + } + + trait SeqMonoTransforms[A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { + def reverse: Repr = { + var xs: List[A] = Nil + var it = coll.iterator + while (it.hasNext) xs = new Cons(it.next, xs) + fromLikeIterable(xs) + } + } + + /* --------- Concrete collection types ------------------------------- */ + + /** Concrete collection type: List */ + sealed trait List[+A] extends Seq[A] with SeqLike[A, List] with Buildable[A, List[A]] { self => + def isEmpty: Boolean + def head: A + def tail: List[A] + def iterator = new Iterator[A] { + private[this] var current = self + def hasNext = !current.isEmpty + def next = { val r = current.head; current = current.tail; r } + } + def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) + def apply(i: Int): A = { + require(!isEmpty) + if (i == 0) head else tail.apply(i - 1) + } + def length: Int = + if (isEmpty) 0 else 1 + tail.length + protected[this] def newBuilder = new ListBuffer[A] + def ++:[B >: A](prefix: List[B]): List[B] = + if (prefix.isEmpty) this + else Cons(prefix.head, prefix.tail ++: this) + override def ++[B >: A](xs: IterableOnce[B]): List[B] = xs match { + case xs: List[B] => this ++: xs + case _ => super.++(xs) + } + override def reverse = super.reverse + } + + case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) extends List[A] { + override def isEmpty = false + override def head = x + def tail = next + } + + case object Nil extends List[Nothing] { + override def isEmpty = true + override def head = ??? + def tail = ??? + } + + object List extends IterableFactory[List] { + def fromIterable[B](coll: Iterable[B]): List[B] = coll match { + case coll: List[B] => coll + case _ => ListBuffer.fromIterable(coll).result + } + } + + /** Concrete collection type: ListBuffer */ + class ListBuffer[A] extends Seq[A] with SeqLike[A, ListBuffer] with Builder[A, List[A]] { + private var first, last: List[A] = Nil + private var aliased = false + def iterator = new Iterator[A] { + var current: List[A] = first + def hasNext = ??? + def next = ??? + } + def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) + def apply(i: Int) = first.apply(i) + def length = first.length + + private def copyElems(): Unit = { + val buf = ListBuffer.fromIterable(result) + first = buf.first + last = buf.last + aliased = false + } + def result = { + aliased = true + first + } + def +=(elem: A) = { + if (aliased) copyElems() + val last1 = Cons(elem, Nil) + last match { + case last: Cons[A] => last.next = last1 + case _ => first = last1 + } + last = last1 + this + } + } + + object ListBuffer extends IterableFactory[ListBuffer] { + def fromIterable[B](coll: Iterable[B]): ListBuffer[B] = new ListBuffer[B] ++= coll + } + + /** Concrete collection type: ArrayBuffer */ + class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) + extends Seq[A] with SeqLike[A, ArrayBuffer] with Builder[A, ArrayBuffer[A]] { + def this() = this(new Array[AnyRef](16), 0) + private var elems: Array[AnyRef] = initElems + private var start = 0 + private var end = initLength + def apply(n: Int) = elems(start + n).asInstanceOf[A] + def length = end - start + override def knownLength = length + override def view = new ArrayBufferView(elems, start, end) + def iterator = view.iterator + def fromIterable[B](it: Iterable[B]): ArrayBuffer[B] = + ArrayBuffer.fromIterable(it) + def +=(elem: A): this.type = { + if (end == elems.length) { + if (start > 0) { + Array.copy(elems, start, elems, 0, length) + end -= start + start = 0 + } + else { + val newelems = new Array[AnyRef](end * 2) + Array.copy(elems, 0, newelems, 0, end) + elems = newelems + } + } + elems(end) = elem.asInstanceOf[AnyRef] + end += 1 + this + } + def result = this + def trimStart(n: Int): Unit = start += (n max 0) + override def ++[B >: A](xs: IterableOnce[B]): ArrayBuffer[B] = xs match { + case xs: ArrayBuffer[B] => + val elems = new Array[AnyRef](length + xs.length) + Array.copy(this.elems, this.start, elems, 0, this.length) + Array.copy(xs.elems, xs.start, elems, this.length, xs.length) + new ArrayBuffer(elems, elems.length) + case _ => super.++(xs) + } + + override def toString = s"ArrayBuffer(${elems.slice(start, end).mkString(", ")})" + } + + object ArrayBuffer extends IterableFactory[ArrayBuffer] { + def fromIterable[B](coll: Iterable[B]): ArrayBuffer[B] = + if (coll.knownLength >= 0) { + val elems = new Array[AnyRef](coll.knownLength) + val it = coll.iterator + for (i <- 0 until elems.length) elems(i) = it.next().asInstanceOf[AnyRef] + new ArrayBuffer[B](elems, elems.length) + } + else { + val buf = new ArrayBuffer[B] + val it = coll.iterator + while (it.hasNext) buf += it.next() + buf + } + } + + class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends RandomAccessView[A] { + def apply(n: Int) = elems(start + n).asInstanceOf[A] + } + + /** Concrete collection type: String */ + implicit class StringOps(val s: String) + extends AnyVal with IterableOps[Char] + with SeqMonoTransforms[Char, String] + with IterablePolyTransforms[Char, List] { + protected def coll = new StringView(s) + def iterator = coll.iterator + protected def fromLikeIterable(coll: Iterable[Char]): String = { + val sb = new StringBuilder + for (ch <- coll) sb.append(ch) + sb.toString + } + def fromIterable[B](coll: Iterable[B]): List[B] = List.fromIterable(coll) + def map(f: Char => Char): String = { + val sb = new StringBuilder + for (ch <- StringOps(s)) sb.append(f(ch)) + sb.toString + } + def flatMap(f: Char => String): String = { + val sb = new StringBuilder + for (ch <- StringOps(s)) sb.append(f(ch)) + sb.toString + } + def ++(xs: IterableOnce[Char]): String = { + val sb = new StringBuilder(s) + for (ch <- xs.iterator) sb.append(ch) + sb.toString + } + def ++(xs: String): String = s + xs + } + + case class StringView(s: String) extends RandomAccessView[Char] { + val start = 0 + val end = s.length + def apply(n: Int) = s.charAt(n) + } + +/* ---------- Views -------------------------------------------------------*/ + + /** Concrete collection type: View */ + trait View[+A] extends Iterable[A] with IterableLike[A, View] { + override def view = this + override def fromIterable[B](c: Iterable[B]): View[B] = c match { + case c: View[B] => c + case _ => View.fromIterator(c.iterator) + } + } + + /** View defined in terms of indexing a range */ + trait RandomAccessView[+A] extends View[A] { + def start: Int + def end: Int + def apply(i: Int): A + def iterator: Iterator[A] = new Iterator[A] { + private var current = start + def hasNext = current < end + def next: A = { + val r = apply(current) + current += 1 + r + } + } + override def knownLength = end - start max 0 + } + + object View { + def fromIterator[A](it: => Iterator[A]): View[A] = new View[A] { + def iterator = it + } + case object Empty extends View[Nothing] { + def iterator = Iterator.empty + override def knownLength = 0 + } + case class Elems[A](xs: A*) extends View[A] { + def iterator = Iterator(xs: _*) + override def knownLength = xs.length + } + case class Filter[A](val underlying: Iterable[A], p: A => Boolean) extends View[A] { + def iterator = underlying.iterator.filter(p) + } + case class Partition[A](val underlying: Iterable[A], p: A => Boolean) { + val left = Partitioned(this, true) + val right = Partitioned(this, false) + } + case class Partitioned[A](partition: Partition[A], cond: Boolean) extends View[A] { + def iterator = partition.underlying.iterator.filter(x => partition.p(x) == cond) + } + case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { + def iterator = underlying.iterator.drop(n) + override def knownLength = + if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 + } + case class Map[A, B](underlying: Iterable[A], f: A => B) extends View[B] { + def iterator = underlying.iterator.map(f) + override def knownLength = underlying.knownLength + } + case class FlatMap[A, B](underlying: Iterable[A], f: A => IterableOnce[B]) extends View[B] { + def iterator = underlying.iterator.flatMap(f) + } + case class Concat[A](underlying: Iterable[A], other: IterableOnce[A]) extends View[A] { + def iterator = underlying.iterator ++ other + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength + other.knownLength + case _ => + -1 + } + } + case class Zip[A, B](underlying: Iterable[A], other: IterableOnce[B]) extends View[(A, B)] { + def iterator = underlying.iterator.zip(other) + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength min other.knownLength + case _ => + -1 + } + } + case class Reverse[A](underlying: Iterable[A]) extends View[A] { + def iterator = { + var xs: List[A] = Nil + val it = underlying.iterator + while (it.hasNext) xs = Cons(it.next(), xs) + xs.iterator + } + override def knownLength = underlying.knownLength + } + } + +/* ---------- Iterators ---------------------------------------------------*/ + + /** A core Iterator class */ + trait Iterator[+A] extends IterableOnce[A] { self => + def hasNext: Boolean + def next(): A + def iterator = this + def foldLeft[B](z: B)(op: (B, A) => B): B = + if (hasNext) foldLeft(op(z, next))(op) else z + def foldRight[B](z: B)(op: (A, B) => B): B = + if (hasNext) op(next(), foldRight(z)(op)) else z + def foreach(f: A => Unit): Unit = + while (hasNext) f(next()) + def indexWhere(p: A => Boolean): Int = { + var i = 0 + while (hasNext) { + if (p(next())) return i + i += 1 + } + -1 + } + def filter(p: A => Boolean): Iterator[A] = new Iterator[A] { + private var hd: A = _ + private var hdDefined: Boolean = false + + def hasNext: Boolean = hdDefined || { + do { + if (!self.hasNext) return false + hd = self.next() + } while (!p(hd)) + hdDefined = true + true + } + + def next() = + if (hasNext) { + hdDefined = false + hd + } + else Iterator.empty.next() + } + + def map[B](f: A => B): Iterator[B] = new Iterator[B] { + def hasNext = self.hasNext + def next() = f(self.next()) + } + + def flatMap[B](f: A => IterableOnce[B]): Iterator[B] = new Iterator[B] { + private var myCurrent: Iterator[B] = Iterator.empty + private def current = { + while (!myCurrent.hasNext && self.hasNext) + myCurrent = f(self.next()).iterator + myCurrent + } + def hasNext = current.hasNext + def next() = current.next() + } + def ++[B >: A](xs: IterableOnce[B]): Iterator[B] = new Iterator[B] { + private var myCurrent: Iterator[B] = self + private var first = true + private def current = { + if (!myCurrent.hasNext && first) { + myCurrent = xs.iterator + first = false + } + myCurrent + } + def hasNext = current.hasNext + def next() = current.next() + } + def drop(n: Int): Iterator[A] = { + var i = 0 + while (i < n && hasNext) { + next() + i += 1 + } + this + } + def zip[B](that: IterableOnce[B]): Iterator[(A, B)] = new Iterator[(A, B)] { + val thatIterator = that.iterator + def hasNext = self.hasNext && thatIterator.hasNext + def next() = (self.next(), thatIterator.next()) + } + } + + object Iterator { + val empty: Iterator[Nothing] = new Iterator[Nothing] { + def hasNext = false + def next = throw new NoSuchElementException("next on empty iterator") + } + def apply[A](xs: A*): Iterator[A] = new RandomAccessView[A] { + val start = 0 + val end = xs.length + def apply(n: Int) = xs(n) + }.iterator + } +} diff --git a/tests/run/CollectionTests.scala b/tests/run/CollectionTests.scala index 4696774d1988..e7b34d292be5 100644 --- a/tests/run/CollectionTests.scala +++ b/tests/run/CollectionTests.scala @@ -3,7 +3,7 @@ import scala.reflect.ClassTag object Test { import strawman.collections._ - import CollectionStrawMan4._ + import CollectionStrawMan5._ def seqOps(xs: Seq[Int]) = { val x1 = xs.foldLeft("")(_ + _) @@ -49,7 +49,7 @@ object Test { println(xs9) println(xs10) println(xs11) - println(xs12) + println(xs12) println(xs13) println(xs14) println(xs15) diff --git a/tests/run/colltest5/CollectionStrawMan5_1.scala b/tests/run/colltest5/CollectionStrawMan5_1.scala new file mode 100644 index 000000000000..aa127cb7e7eb --- /dev/null +++ b/tests/run/colltest5/CollectionStrawMan5_1.scala @@ -0,0 +1,509 @@ +package strawman.collections + +import Predef.{augmentString => _, wrapString => _, _} +import scala.reflect.ClassTag +import annotation.unchecked.uncheckedVariance + +/** A strawman architecture for new collections. It contains some + * example collection classes and methods with the intent to expose + * some key issues. It would be good to compare this to other + * implementations of the same functionality, to get an idea of the + * strengths and weaknesses of different collection architectures. + * + * For a test file, see tests/run/CollectionTests.scala. + */ +object CollectionStrawMan5 { + + /* ------------ Base Traits -------------------------------- */ + + /** Iterator can be used only once */ + trait IterableOnce[+A] { + def iterator: Iterator[A] + } + + /** Base trait for instances that can construct a collection from an iterable */ + trait FromIterable[+C[X] <: Iterable[X]] { + def fromIterable[B](it: Iterable[B]): C[B] + } + + /** Base trait for companion objects of collections */ + trait IterableFactory[+C[X] <: Iterable[X]] extends FromIterable[C] { + def empty[X]: C[X] = fromIterable(View.Empty) + def apply[A](xs: A*): C[A] = fromIterable(View.Elems(xs: _*)) + } + + /** Base trait for Iterable operations */ + trait IterableLike[+A, +C[X] <: Iterable[X]] + extends FromIterable[C] + with IterableOps[A] + with IterableMonoTransforms[A @uncheckedVariance, C[A @uncheckedVariance]] + with IterablePolyTransforms[A @uncheckedVariance, C] { + protected def fromLikeIterable(coll: Iterable[A @uncheckedVariance]): C[A @uncheckedVariance] = fromIterable(coll) + } + + /** Base trait for Seq operations */ + trait SeqLike[+A, +C[X] <: Seq[X]] extends IterableLike[A, C] { + def reverse: C[A @uncheckedVariance] = { + var xs: List[A] = Nil + var it = iterator + while (it.hasNext) xs = new Cons(it.next, xs) + fromLikeIterable(xs) + } + } + + /** Base trait for generic collections */ + trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { + override def iterator: Iterator[A] + override def fromIterable[B](it: Iterable[B]): Iterable[B] + + protected def coll: Iterable[A] = this + def knownLength: Int = -1 + } + + /** Base trait for sequence collections */ + trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] { + def apply(i: Int): A + def length: Int + override def iterator: Iterator[A] + } + + trait Buildable[+A, +To <: Iterable[A]] extends Iterable[A] { + protected[this] def newBuilder: Builder[A, To] + override def partition(p: A => Boolean): (To, To) = { + val l, r = newBuilder + iterator.foreach(x => (if (p(x)) l else r) += x) + (l.result, r.result) + } + } + + trait Builder[-A, +To] { + def +=(x: A): this.type + def result: To + + def ++=(xs: IterableOnce[A]): this.type = { + xs.iterator.foreach(+=) + this + } + } + + /* ------------ Operations ----------------------------------- */ + + trait IterableOps[+A] extends Any { + def iterator: Iterator[A] + def foreach(f: A => Unit): Unit = iterator.foreach(f) + def foldLeft[B](z: B)(op: (B, A) => B): B = iterator.foldLeft(z)(op) + def foldRight[B](z: B)(op: (A, B) => B): B = iterator.foldRight(z)(op) + def indexWhere(p: A => Boolean): Int = iterator.indexWhere(p) + def isEmpty: Boolean = !iterator.hasNext + def head: A = iterator.next + def view: View[A] = View.fromIterator(iterator) + } + + trait IterableMonoTransforms[A, +Repr] extends Any { + protected def coll: Iterable[A] + protected def fromLikeIterable(coll: Iterable[A]): Repr + def filter(p: A => Boolean): Repr = fromLikeIterable(View.Filter(coll, p)) + def partition(p: A => Boolean): (Repr, Repr) = { + val pn = View.Partition(coll, p) + (fromLikeIterable(pn.left), fromLikeIterable(pn.right)) + } + def drop(n: Int): Repr = fromLikeIterable(View.Drop(coll, n)) + def to[C[X] <: Iterable[X]](fi: FromIterable[C]): C[A] = fi.fromIterable(coll) + } + + trait IterablePolyTransforms[+A, +C[A]] extends Any { + protected def coll: Iterable[A] + def fromIterable[B](coll: Iterable[B]): C[B] + def map[B](f: A => B): C[B] = fromIterable(View.Map(coll, f)) + def flatMap[B](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) + def ++[B >: A](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) + def zip[B](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) + } + + trait SeqMonoTransforms[A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { + def reverse: Repr = { + var xs: List[A] = Nil + var it = coll.iterator + while (it.hasNext) xs = new Cons(it.next, xs) + fromLikeIterable(xs) + } + } + + /* --------- Concrete collection types ------------------------------- */ + + /** Concrete collection type: List */ + sealed trait List[+A] extends Seq[A] with SeqLike[A, List] with Buildable[A, List[A]] { self => + def isEmpty: Boolean + def head: A + def tail: List[A] + def iterator = new Iterator[A] { + private[this] var current = self + def hasNext = !current.isEmpty + def next = { val r = current.head; current = current.tail; r } + } + def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) + def apply(i: Int): A = { + require(!isEmpty) + if (i == 0) head else tail.apply(i - 1) + } + def length: Int = + if (isEmpty) 0 else 1 + tail.length + protected[this] def newBuilder = new ListBuffer[A] + def ++:[B >: A](prefix: List[B]): List[B] = + if (prefix.isEmpty) this + else Cons(prefix.head, prefix.tail ++: this) + override def ++[B >: A](xs: IterableOnce[B]): List[B] = xs match { + case xs: List[B] => this ++: xs + case _ => super.++(xs) + } + override def reverse = super.reverse + } + + case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) extends List[A] { + override def isEmpty = false + override def head = x + def tail = next + } + + case object Nil extends List[Nothing] { + override def isEmpty = true + override def head = ??? + def tail = ??? + } + + object List extends IterableFactory[List] { + def fromIterable[B](coll: Iterable[B]): List[B] = coll match { + case coll: List[B] => coll + case _ => ListBuffer.fromIterable(coll).result + } + } + + /** Concrete collection type: ListBuffer */ + class ListBuffer[A] extends Seq[A] with SeqLike[A, ListBuffer] with Builder[A, List[A]] { + private var first, last: List[A] = Nil + private var aliased = false + def iterator = new Iterator[A] { + var current: List[A] = first + def hasNext = ??? + def next = ??? + } + def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) + def apply(i: Int) = first.apply(i) + def length = first.length + + private def copyElems(): Unit = { + val buf = ListBuffer.fromIterable(result) + first = buf.first + last = buf.last + aliased = false + } + def result = { + aliased = true + first + } + def +=(elem: A) = { + if (aliased) copyElems() + val last1 = Cons(elem, Nil) + last match { + case last: Cons[A] => last.next = last1 + case _ => first = last1 + } + last = last1 + this + } + } + + object ListBuffer extends IterableFactory[ListBuffer] { + def fromIterable[B](coll: Iterable[B]): ListBuffer[B] = new ListBuffer[B] ++= coll + } + + /** Concrete collection type: ArrayBuffer */ + class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) + extends Seq[A] with SeqLike[A, ArrayBuffer] with Builder[A, ArrayBuffer[A]] { + def this() = this(new Array[AnyRef](16), 0) + private var elems: Array[AnyRef] = initElems + private var start = 0 + private var end = initLength + def apply(n: Int) = elems(start + n).asInstanceOf[A] + def length = end - start + override def knownLength = length + override def view = new ArrayBufferView(elems, start, end) + def iterator = view.iterator + def fromIterable[B](it: Iterable[B]): ArrayBuffer[B] = + ArrayBuffer.fromIterable(it) + def +=(elem: A): this.type = { + if (end == elems.length) { + if (start > 0) { + Array.copy(elems, start, elems, 0, length) + end -= start + start = 0 + } + else { + val newelems = new Array[AnyRef](end * 2) + Array.copy(elems, 0, newelems, 0, end) + elems = newelems + } + } + elems(end) = elem.asInstanceOf[AnyRef] + end += 1 + this + } + def result = this + def trimStart(n: Int): Unit = start += (n max 0) + override def ++[B >: A](xs: IterableOnce[B]): ArrayBuffer[B] = xs match { + case xs: ArrayBuffer[B] => + val elems = new Array[AnyRef](length + xs.length) + Array.copy(this.elems, this.start, elems, 0, this.length) + Array.copy(xs.elems, xs.start, elems, this.length, xs.length) + new ArrayBuffer(elems, elems.length) + case _ => super.++(xs) + } + + override def toString = s"ArrayBuffer(${elems.slice(start, end).mkString(", ")})" + } + + object ArrayBuffer extends IterableFactory[ArrayBuffer] { + def fromIterable[B](coll: Iterable[B]): ArrayBuffer[B] = + if (coll.knownLength >= 0) { + val elems = new Array[AnyRef](coll.knownLength) + val it = coll.iterator + for (i <- 0 until elems.length) elems(i) = it.next().asInstanceOf[AnyRef] + new ArrayBuffer[B](elems, elems.length) + } + else { + val buf = new ArrayBuffer[B] + val it = coll.iterator + while (it.hasNext) buf += it.next() + buf + } + } + + class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends RandomAccessView[A] { + def apply(n: Int) = elems(start + n).asInstanceOf[A] + } + + /** Concrete collection type: String */ + implicit class StringOps(val s: String) + extends AnyVal with IterableOps[Char] + with SeqMonoTransforms[Char, String] + with IterablePolyTransforms[Char, List] { + protected def coll = new StringView(s) + def iterator = coll.iterator + protected def fromLikeIterable(coll: Iterable[Char]): String = { + val sb = new StringBuilder + for (ch <- coll) sb.append(ch) + sb.toString + } + def fromIterable[B](coll: Iterable[B]): List[B] = List.fromIterable(coll) + def map(f: Char => Char): String = { + val sb = new StringBuilder + for (ch <- StringOps(s)) sb.append(f(ch)) + sb.toString + } + def flatMap(f: Char => String): String = { + val sb = new StringBuilder + for (ch <- StringOps(s)) sb.append(f(ch)) + sb.toString + } + def ++(xs: IterableOnce[Char]): String = { + val sb = new StringBuilder(s) + for (ch <- xs.iterator) sb.append(ch) + sb.toString + } + def ++(xs: String): String = s + xs + } + + case class StringView(s: String) extends RandomAccessView[Char] { + val start = 0 + val end = s.length + def apply(n: Int) = s.charAt(n) + } + +/* ---------- Views -------------------------------------------------------*/ + + /** Concrete collection type: View */ + trait View[+A] extends Iterable[A] with IterableLike[A, View] { + override def view = this + override def fromIterable[B](c: Iterable[B]): View[B] = c match { + case c: View[B] => c + case _ => View.fromIterator(c.iterator) + } + } + + /** View defined in terms of indexing a range */ + trait RandomAccessView[+A] extends View[A] { + def start: Int + def end: Int + def apply(i: Int): A + def iterator: Iterator[A] = new Iterator[A] { + private var current = start + def hasNext = current < end + def next: A = { + val r = apply(current) + current += 1 + r + } + } + override def knownLength = end - start max 0 + } + + object View { + def fromIterator[A](it: => Iterator[A]): View[A] = new View[A] { + def iterator = it + } + case object Empty extends View[Nothing] { + def iterator = Iterator.empty + override def knownLength = 0 + } + case class Elems[A](xs: A*) extends View[A] { + def iterator = Iterator(xs: _*) + override def knownLength = xs.length + } + case class Filter[A](val underlying: Iterable[A], p: A => Boolean) extends View[A] { + def iterator = underlying.iterator.filter(p) + } + case class Partition[A](val underlying: Iterable[A], p: A => Boolean) { + val left = Partitioned(this, true) + val right = Partitioned(this, false) + } + case class Partitioned[A](partition: Partition[A], cond: Boolean) extends View[A] { + def iterator = partition.underlying.iterator.filter(x => partition.p(x) == cond) + } + case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { + def iterator = underlying.iterator.drop(n) + override def knownLength = + if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 + } + case class Map[A, B](underlying: Iterable[A], f: A => B) extends View[B] { + def iterator = underlying.iterator.map(f) + override def knownLength = underlying.knownLength + } + case class FlatMap[A, B](underlying: Iterable[A], f: A => IterableOnce[B]) extends View[B] { + def iterator = underlying.iterator.flatMap(f) + } + case class Concat[A](underlying: Iterable[A], other: IterableOnce[A]) extends View[A] { + def iterator = underlying.iterator ++ other + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength + other.knownLength + case _ => + -1 + } + } + case class Zip[A, B](underlying: Iterable[A], other: IterableOnce[B]) extends View[(A, B)] { + def iterator = underlying.iterator.zip(other) + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength min other.knownLength + case _ => + -1 + } + } + case class Reverse[A](underlying: Iterable[A]) extends View[A] { + def iterator = { + var xs: List[A] = Nil + val it = underlying.iterator + while (it.hasNext) xs = Cons(it.next(), xs) + xs.iterator + } + override def knownLength = underlying.knownLength + } + } + +/* ---------- Iterators ---------------------------------------------------*/ + + /** A core Iterator class */ + trait Iterator[+A] extends IterableOnce[A] { self => + def hasNext: Boolean + def next(): A + def iterator = this + def foldLeft[B](z: B)(op: (B, A) => B): B = + if (hasNext) foldLeft(op(z, next))(op) else z + def foldRight[B](z: B)(op: (A, B) => B): B = + if (hasNext) op(next(), foldRight(z)(op)) else z + def foreach(f: A => Unit): Unit = + while (hasNext) f(next()) + def indexWhere(p: A => Boolean): Int = { + var i = 0 + while (hasNext) { + if (p(next())) return i + i += 1 + } + -1 + } + def filter(p: A => Boolean): Iterator[A] = new Iterator[A] { + private var hd: A = _ + private var hdDefined: Boolean = false + + def hasNext: Boolean = hdDefined || { + do { + if (!self.hasNext) return false + hd = self.next() + } while (!p(hd)) + hdDefined = true + true + } + + def next() = + if (hasNext) { + hdDefined = false + hd + } + else Iterator.empty.next() + } + + def map[B](f: A => B): Iterator[B] = new Iterator[B] { + def hasNext = self.hasNext + def next() = f(self.next()) + } + + def flatMap[B](f: A => IterableOnce[B]): Iterator[B] = new Iterator[B] { + private var myCurrent: Iterator[B] = Iterator.empty + private def current = { + while (!myCurrent.hasNext && self.hasNext) + myCurrent = f(self.next()).iterator + myCurrent + } + def hasNext = current.hasNext + def next() = current.next() + } + def ++[B >: A](xs: IterableOnce[B]): Iterator[B] = new Iterator[B] { + private var myCurrent: Iterator[B] = self + private var first = true + private def current = { + if (!myCurrent.hasNext && first) { + myCurrent = xs.iterator + first = false + } + myCurrent + } + def hasNext = current.hasNext + def next() = current.next() + } + def drop(n: Int): Iterator[A] = { + var i = 0 + while (i < n && hasNext) { + next() + i += 1 + } + this + } + def zip[B](that: IterableOnce[B]): Iterator[(A, B)] = new Iterator[(A, B)] { + val thatIterator = that.iterator + def hasNext = self.hasNext && thatIterator.hasNext + def next() = (self.next(), thatIterator.next()) + } + } + + object Iterator { + val empty: Iterator[Nothing] = new Iterator[Nothing] { + def hasNext = false + def next = throw new NoSuchElementException("next on empty iterator") + } + def apply[A](xs: A*): Iterator[A] = new RandomAccessView[A] { + val start = 0 + val end = xs.length + def apply(n: Int) = xs(n) + }.iterator + } +} diff --git a/tests/run/colltest5/CollectionTests_2.scala b/tests/run/colltest5/CollectionTests_2.scala new file mode 100644 index 000000000000..e7b34d292be5 --- /dev/null +++ b/tests/run/colltest5/CollectionTests_2.scala @@ -0,0 +1,170 @@ +import Predef.{augmentString => _, wrapString => _, _} +import scala.reflect.ClassTag + +object Test { + import strawman.collections._ + import CollectionStrawMan5._ + + def seqOps(xs: Seq[Int]) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + val y2: String = x2 + val x3 = xs.indexWhere(_ % 2 == 0) + val y3: Int = x3 + val x4 = xs.head + val y4: Int = x4 + val x5 = xs.to(List) + val y5: List[Int] = x5 + val (xs6, xs7) = xs.partition(_ % 2 == 0) + val ys6: Seq[Int] = xs6 + val ys7: Seq[Int] = xs7 + val xs8 = xs.drop(2) + val ys8: Seq[Int] = xs8 + val xs9 = xs.map(_ >= 0) + val ys9: Seq[Boolean] = xs9 + val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val ys10: Seq[Int] = xs10 + val xs11 = xs ++ xs + val ys11: Seq[Int] = xs11 + val xs12 = xs ++ Nil + val ys12: Seq[Int] = xs12 + val xs13 = Nil ++ xs + val ys13: Seq[Int] = xs13 + val xs14 = xs ++ Cons("a", Nil) + val ys14: Seq[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: Seq[(Int, Boolean)] = xs15 + val xs16 = xs.reverse + val ys16: Seq[Int] = xs16 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6) + println(xs7) + println(xs8) + println(xs9) + println(xs10) + println(xs11) + println(xs12) + println(xs13) + println(xs14) + println(xs15) + println(xs16) + } + + def viewOps(xs: View[Int]) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + val y2: String = x2 + val x3 = xs.indexWhere(_ % 2 == 0) + val y3: Int = x3 + val x4 = xs.head + val y4: Int = x4 + val x5 = xs.to(List) + val y5: List[Int] = x5 + val (xs6, xs7) = xs.partition(_ % 2 == 0) + val ys6: View[Int] = xs6 + val ys7: View[Int] = xs7 + val xs8 = xs.drop(2) + val ys8: View[Int] = xs8 + val xs9 = xs.map(_ >= 0) + val ys9: View[Boolean] = xs9 + val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val ys10: View[Int] = xs10 + val xs11 = xs ++ xs + val ys11: View[Int] = xs11 + val xs12 = xs ++ Nil + val ys12: View[Int] = xs12 + val xs13 = Nil ++ xs + val ys13: List[Int] = xs13 + val xs14 = xs ++ Cons("a", Nil) + val ys14: View[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: View[(Int, Boolean)] = xs15 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6.to(List)) + println(xs7.to(List)) + println(xs8.to(List)) + println(xs9.to(List)) + println(xs10.to(List)) + println(xs11.to(List)) + println(xs12.to(List)) + println(xs13.to(List)) + println(xs14.to(List)) + println(xs15.to(List)) + } + + def stringOps(xs: String) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + val y2: String = x2 + val x3 = xs.indexWhere(_ % 2 == 0) + val y3: Int = x3 + val x4 = xs.head + val y4: Int = x4 + val x5 = xs.to(List) + val y5: List[Char] = x5 + val (xs6, xs7) = xs.partition(_ % 2 == 0) + val ys6: String = xs6 + val ys7: String = xs7 + val xs8 = xs.drop(2) + val ys8: String = xs8 + val xs9 = xs.map(_ + 1) // !!! need a language change to make this work without the : Char + val ys9: Seq[Int] = xs9 + val xs9a = xs.map(_.toUpper) // !!! need a language change to make this work without the : Char + val ys9a: String = xs9a + val xs10 = xs.flatMap((x: Char) => s"$x,$x") + val ys10: String = xs10 + val xs11 = xs ++ xs + val ys11: String = xs11 + val xs11a = xs ++ List('x', 'y') // Cons('x', Cons('y', Nil)) + val ys11a: String = xs11a + val xs12 = xs ++ Nil + val ys12: String = xs12 + val xs13 = Nil ++ xs.iterator + val ys13: List[Char] = xs13 + val xs14 = xs ++ Cons("xyz", Nil) + val ys14: Seq[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: Seq[(Char, Int)] = xs15 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6) + println(xs7) + println(xs8) + println(xs9) + println(xs9a) + println(xs10) + println(xs11) + println(xs11a) + println(xs12) + println(xs13) + println(xs14) + println(xs15) + } + + def main(args: Array[String]) = { + val ints = Cons(1, Cons(2, Cons(3, Nil))) + val intsBuf = ints.to(ArrayBuffer) + val intsView = ints.view + seqOps(ints) + seqOps(intsBuf) + viewOps(intsView) + stringOps("abc") + } +} From a77eb1592b5981419c99074caee876665bbf4daa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 14:04:23 +0200 Subject: [PATCH 02/14] Small improvements in Types 1) Print RefinedTypes with their hashCode so that we can correlated with RefinedThis types 2) Fast abort of instantiate in case we have determined that it is not safe anyway --- src/dotty/tools/dotc/core/Types.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index fa049815ac7b..913339409fd8 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -903,7 +903,9 @@ object Types { case pre: RefinedType => object instantiate extends TypeMap { var isSafe = true - def apply(tp: Type): Type = tp match { + def apply(tp: Type): Type = + if (!isSafe) tp + else tp match { case TypeRef(RefinedThis(`pre`), name) if name.isHkArgName => member(name).info match { case TypeAlias(alias) => alias @@ -2061,7 +2063,7 @@ object Types { false } override def computeHash = doHash(refinedName, refinedInfo, parent) - override def toString = s"RefinedType($parent, $refinedName, $refinedInfo)" + override def toString = s"RefinedType($parent, $refinedName, $refinedInfo | $hashCode)" } class CachedRefinedType(parent: Type, refinedName: Name, infoFn: RefinedType => Type) extends RefinedType(parent, refinedName) { From 98d7183067f6a48957988ba99d234f60ab0246be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 14:07:44 +0200 Subject: [PATCH 03/14] Create LambdaTraits referred to from Unpickler LambdaTraits are created on demand; we need to make sure they exist when referred to from Tasty. --- src/dotty/tools/dotc/core/NameOps.scala | 7 ++++++- src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 81240a9fc603..b3b0982a43f7 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -116,7 +116,12 @@ object NameOps { name.drop(tpnme.hkArgPrefixLength).toString.toInt def isLambdaTraitName(implicit ctx: Context): Boolean = - name.startsWith(tpnme.hkLambdaPrefix) + name.isTypeName && name.startsWith(tpnme.hkLambdaPrefix) + + def lambdaTraitVariances(implicit ctx: Context): List[Int] = { + val vs = name.drop(tpnme.hkLambdaPrefix.length) + vs.map(c => tpnme.varianceSuffixes.indexOf(c) - 1).toList + } /** If the name ends with $nn where nn are * all digits, strip the $ and the digits. diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index b547862b4083..3a98033466c1 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -252,6 +252,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { readPackageRef().termRef case TYPEREF => val name = readName().toTypeName + if (name.isLambdaTraitName) // Make sure curresponding lambda trait exists + defn.LambdaTrait(name.lambdaTraitVariances) TypeRef(readType(), name) case TERMREF => readNameSplitSig() match { From 6545934f06f5c22ab4acacf28ca2206b898e021e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 14:08:34 +0200 Subject: [PATCH 04/14] Fix #765 for super accessors Partial fix of #765. Hack to make sure unexpandedName works for super accessor names. --- src/dotty/tools/dotc/core/NameOps.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index b3b0982a43f7..4d2335bd91a5 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -184,7 +184,13 @@ object NameOps { * an encoded name, e.g. super$$plus$eq. See #765. */ def unexpandedName: N = { - val idx = name.lastIndexOfSlice(nme.EXPAND_SEPARATOR) + var idx = name.lastIndexOfSlice(nme.EXPAND_SEPARATOR) + + // Hack to make super accessors from traits work. They would otherwise fail because of #765 + // TODO: drop this once we have more robust name handling + if (name.slice(idx - FalseSuperLength, idx) == FalseSuper) + idx -= FalseSuper.length + if (idx < 0) name else (name drop (idx + nme.EXPAND_SEPARATOR.length)).asInstanceOf[N] } @@ -436,4 +442,7 @@ object NameOps { name.dropRight(nme.LAZY_LOCAL.length) } } + + private final val FalseSuper = "$$super".toTermName + private val FalseSuperLength = FalseSuper.length } From eb2979d895e56be73fcc3bb14bd34198628fd8d9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 14:09:24 +0200 Subject: [PATCH 05/14] Fix toString and productPrefix of case objects Need to drop the final `$' in both cases. --- src/dotty/tools/dotc/transform/SyntheticMethods.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/src/dotty/tools/dotc/transform/SyntheticMethods.scala index a496f80ceef0..9dfd92fe9f2c 100644 --- a/src/dotty/tools/dotc/transform/SyntheticMethods.scala +++ b/src/dotty/tools/dotc/transform/SyntheticMethods.scala @@ -10,6 +10,7 @@ import DenotTransformers._ import ast.Trees._ import ast.untpd import Decorators._ +import NameOps._ import ValueClasses.isDerivedValueClass import scala.collection.mutable.ListBuffer import scala.language.postfixOps @@ -79,14 +80,17 @@ class SyntheticMethods(thisTransformer: DenotTransformer) { def forwardToRuntime(vrefss: List[List[Tree]]): Tree = ref(defn.runtimeMethodRef("_" + sym.name.toString)).appliedToArgs(This(clazz) :: vrefss.head) + def ownName(vrefss: List[List[Tree]]): Tree = + Literal(Constant(clazz.name.stripModuleClassSuffix.decode.toString)) + def syntheticRHS(implicit ctx: Context): List[List[Tree]] => Tree = synthetic.name match { case nme.hashCode_ if isDerivedValueClass(clazz) => vrefss => valueHashCodeBody case nme.hashCode_ => vrefss => caseHashCodeBody - case nme.toString_ => forwardToRuntime + case nme.toString_ => if (clazz.is(ModuleClass)) ownName else forwardToRuntime case nme.equals_ => vrefss => equalsBody(vrefss.head.head) case nme.canEqual_ => vrefss => canEqualBody(vrefss.head.head) case nme.productArity => vrefss => Literal(Constant(accessors.length)) - case nme.productPrefix => vrefss => Literal(Constant(clazz.name.decode.toString)) + case nme.productPrefix => ownName } ctx.log(s"adding $synthetic to $clazz at ${ctx.phase}") DefDef(synthetic, syntheticRHS(ctx.withOwner(synthetic))) From 9481fe9b7bd015437e56d33f7568887dc3ca8e5b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 14:11:52 +0200 Subject: [PATCH 06/14] Dealias applied type constructors Dealias TypeRefs that get applied to type arguments. Without that precaution we get Stackoverflows in lookupRefined/betaReduce for CollectionStrawMan5.scala. --- src/dotty/tools/dotc/core/TypeApplications.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index d5f44e63210b..3ed1798ed035 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -377,6 +377,14 @@ class TypeApplications(val self: Type) extends AnyVal { false } + /** Dealias type if it can be done without forcing anything */ + def safeDealias(implicit ctx: Context): Type = self match { + case self: TypeRef if self.denot.exists && self.symbol.isAliasType => + self.info.bounds.hi.stripTypeVar.safeDealias + case _ => + self + } + /** Replace references to type parameters with references to hk arguments `this.$hk_i` * Care is needed not to cause cyclic reference errors, hence `SafeSubstMap`. */ @@ -546,8 +554,8 @@ class TypeApplications(val self: Type) extends AnyVal { substHkArgs(body) case self: PolyType => self.instantiate(args) - case _ => - appliedTo(args, typeParams) + case self1 => + self1.safeDealias.appliedTo(args, typeParams) } } From c32a2acee179fecf1f9d5c53aab94547a1ee2d67 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 15:20:00 +0200 Subject: [PATCH 07/14] Tweak in NameOps The previous version seemed to fail non-deterministaically, but after a while I could not reproduce it anymore. Anyway, leaving the change in. --- src/dotty/tools/dotc/core/NameOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 4d2335bd91a5..72f89aec3b37 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -120,7 +120,7 @@ object NameOps { def lambdaTraitVariances(implicit ctx: Context): List[Int] = { val vs = name.drop(tpnme.hkLambdaPrefix.length) - vs.map(c => tpnme.varianceSuffixes.indexOf(c) - 1).toList + vs.toList.map(c => tpnme.varianceSuffixes.indexOf(c) - 1) } /** If the name ends with $nn where nn are From 1607c7b0df62b19b3aafeb9966deb8faffcce605 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 15:20:31 +0200 Subject: [PATCH 08/14] Make Names immutable Seqs --- src/dotty/tools/dotc/core/Names.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Names.scala b/src/dotty/tools/dotc/core/Names.scala index f1e6f7606921..10eef16c1ae4 100644 --- a/src/dotty/tools/dotc/core/Names.scala +++ b/src/dotty/tools/dotc/core/Names.scala @@ -37,7 +37,7 @@ object Names { */ abstract class Name extends DotClass with PreName - with Seq[Char] + with collection.immutable.Seq[Char] with IndexedSeqOptimized[Char, Name] { /** A type for names of the same kind as this name */ From cae12d368ae23204a5fd58156e1f6f214471172c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 16:06:19 +0200 Subject: [PATCH 09/14] Add companions to value classes during desugarings This means companions will be pickled and we can drop the special treatement in transformInfo of FirstTransform. That method is problematic, since it enters new symbols into a class scope. This is not allowed, since transformInfo needs to be purely functional, side effects are not permitted (`enteredAfter` does not work either). The problem manifested itself when compiling colltest5 with a requirement failure in the code of `entered` when called from FirstTransform (trying to enter in a frozen class). TODO: Once we use statics for LazyVals we can get rid of the "add companion object" logic in FirstTransform alltogether. --- src/dotty/tools/dotc/ast/Desugar.scala | 18 +++++++++++++++++- .../dotc/transform/ExtensionMethods.scala | 6 +----- .../tools/dotc/transform/FirstTransform.scala | 4 ++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index ac2f8ae73d5b..b6a4dc5e760d 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -256,7 +256,21 @@ object desugar { // prefixed by type or val). `tparams` and `vparamss` are the type parameters that // go in `constr`, the constructor after desugaring. + /** Does `tree' look like a reference to AnyVal? Temporary test before we have inline classes */ + def isAnyVal(tree: Tree): Boolean = tree match { + case Ident(tpnme.AnyVal) => true + case Select(qual, tpnme.AnyVal) => isScala(qual) + case _ => false + } + def isScala(tree: Tree): Boolean = tree match { + case Ident(nme.scala_) => true + case Select(Ident(nme.ROOTPKG), nme.scala_) => true + case _ => false + } + val isCaseClass = mods.is(Case) && !mods.is(Module) + val isValueClass = parents.nonEmpty && isAnyVal(parents.head) + // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. val constrTparams = constr1.tparams map toDefParam val constrVparamss = @@ -398,7 +412,9 @@ object desugar { companionDefs(parent, applyMeths ::: unapplyMeth :: defaultGetters) } else if (defaultGetters.nonEmpty) - companionDefs(anyRef, defaultGetters) + companionDefs(anyRef, defaultGetters) + else if (isValueClass) + companionDefs(anyRef, Nil) else Nil diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index a1d2e5c681b1..8d61cef42173 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -33,7 +33,7 @@ import SymUtils._ * This is different from the implementation of value classes in Scala 2 * (see SIP-15) which uses `asInstanceOf` which does not typecheck. */ -class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with FullParameterization with NeedsCompanions { thisTransformer => +class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with FullParameterization { thisTransformer => import tpd._ import ExtensionMethods._ @@ -45,10 +45,6 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful override def runsAfterGroupsOf = Set(classOf[FirstTransform]) // need companion objects to exist - def isCompanionNeeded(cls: ClassSymbol)(implicit ctx: Context): Boolean = { - isDerivedValueClass(cls) - } - override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { case moduleClassSym: ClassDenotation if moduleClassSym is ModuleClass => moduleClassSym.linkedClass match { diff --git a/src/dotty/tools/dotc/transform/FirstTransform.scala b/src/dotty/tools/dotc/transform/FirstTransform.scala index 37ae1d94e8bc..97dd9404fe40 100644 --- a/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -44,7 +44,7 @@ class FirstTransform extends MiniPhaseTransform with IdentityDenotTransformer wi this } - def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = { + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp/*{ tp match { //create companions for value classes that are not from currently compiled source file case tp@ClassInfo(_, cls, _, decls, _) @@ -59,7 +59,7 @@ class FirstTransform extends MiniPhaseTransform with IdentityDenotTransformer wi case _ => tp } } - +*/ override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { tree match { case Select(qual, _) if tree.symbol.exists => From b3f81fdcdca27a6fe647bce367ef221c917ea7b5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 16:35:20 +0200 Subject: [PATCH 10/14] Add check file --- tests/run/colltest5.check | 68 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/run/colltest5.check diff --git a/tests/run/colltest5.check b/tests/run/colltest5.check new file mode 100644 index 000000000000..6c61d8821444 --- /dev/null +++ b/tests/run/colltest5.check @@ -0,0 +1,68 @@ +------- +123 +123 +1 +1 +Cons(1,Cons(2,Cons(3,Nil))) +Cons(2,Nil) +Cons(1,Cons(3,Nil)) +Cons(3,Nil) +Cons(true,Cons(true,Cons(true,Nil))) +Cons(1,Cons(-1,Cons(2,Cons(-2,Cons(3,Cons(-3,Nil)))))) +Cons(1,Cons(2,Cons(3,Cons(1,Cons(2,Cons(3,Nil)))))) +Cons(1,Cons(2,Cons(3,Nil))) +Cons(1,Cons(2,Cons(3,Nil))) +Cons(1,Cons(2,Cons(3,Cons(a,Nil)))) +Cons((1,true),Cons((2,true),Cons((3,true),Nil))) +Cons(3,Cons(2,Cons(1,Nil))) +------- +123 +123 +1 +1 +Cons(1,Cons(2,Cons(3,Nil))) +ArrayBuffer(2) +ArrayBuffer(1, 3) +ArrayBuffer(3) +ArrayBuffer(true, true, true) +ArrayBuffer(1, -1, 2, -2, 3, -3) +ArrayBuffer(1, 2, 3, 1, 2, 3) +ArrayBuffer(1, 2, 3) +Cons(1,Cons(2,Cons(3,Nil))) +ArrayBuffer(1, 2, 3, a) +ArrayBuffer((1,true), (2,true), (3,true)) +ArrayBuffer(3, 2, 1) +------- +123 +123 +1 +1 +Cons(1,Cons(2,Cons(3,Nil))) +Cons(2,Nil) +Cons(1,Cons(3,Nil)) +Cons(3,Nil) +Cons(true,Cons(true,Cons(true,Nil))) +Cons(1,Cons(-1,Cons(2,Cons(-2,Cons(3,Cons(-3,Nil)))))) +Cons(1,Cons(2,Cons(3,Cons(1,Cons(2,Cons(3,Nil)))))) +Cons(1,Cons(2,Cons(3,Nil))) +Cons(1,Cons(2,Cons(3,Nil))) +Cons(1,Cons(2,Cons(3,Cons(a,Nil)))) +Cons((1,true),Cons((2,true),Cons((3,true),Nil))) +------- +abc +abc +1 +a +Cons(a,Cons(b,Cons(c,Nil))) +b +ac +c +Cons(98,Cons(99,Cons(100,Nil))) +ABC +a,ab,bc,c +abcabc +abcxy +abc +Cons(a,Cons(b,Cons(c,Nil))) +Cons(a,Cons(b,Cons(c,Cons(xyz,Nil)))) +Cons((a,98),Cons((b,99),Cons((c,100),Nil))) From 997afcbc9d8598496bcf6a4d1f0bed11c757243a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 16 Apr 2016 13:55:34 +0200 Subject: [PATCH 11/14] Strawman polishing --- src/strawman/collections/CollectionStrawMan5.scala | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/strawman/collections/CollectionStrawMan5.scala b/src/strawman/collections/CollectionStrawMan5.scala index aa127cb7e7eb..cfca6cd31e66 100644 --- a/src/strawman/collections/CollectionStrawMan5.scala +++ b/src/strawman/collections/CollectionStrawMan5.scala @@ -38,18 +38,12 @@ object CollectionStrawMan5 { with IterableOps[A] with IterableMonoTransforms[A @uncheckedVariance, C[A @uncheckedVariance]] with IterablePolyTransforms[A @uncheckedVariance, C] { - protected def fromLikeIterable(coll: Iterable[A @uncheckedVariance]): C[A @uncheckedVariance] = fromIterable(coll) + protected[this] def fromLikeIterable(coll: Iterable[A]): C[A] = fromIterable(coll) } /** Base trait for Seq operations */ - trait SeqLike[+A, +C[X] <: Seq[X]] extends IterableLike[A, C] { - def reverse: C[A @uncheckedVariance] = { - var xs: List[A] = Nil - var it = iterator - while (it.hasNext) xs = new Cons(it.next, xs) - fromLikeIterable(xs) - } - } + trait SeqLike[+A, +C[X] <: Seq[X]] + extends IterableLike[A, C] with SeqMonoTransforms[A @uncheckedVariance, C[A @uncheckedVariance]] /** Base trait for generic collections */ trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { From 8cb73c0f5ca8bd809d0b9d7549951678f2240a61 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 16 Apr 2016 13:55:49 +0200 Subject: [PATCH 12/14] Drop type annotation in collection test --- tests/run/colltest5/CollectionTests_2.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run/colltest5/CollectionTests_2.scala b/tests/run/colltest5/CollectionTests_2.scala index e7b34d292be5..cdff2e260c00 100644 --- a/tests/run/colltest5/CollectionTests_2.scala +++ b/tests/run/colltest5/CollectionTests_2.scala @@ -124,7 +124,7 @@ object Test { val ys9: Seq[Int] = xs9 val xs9a = xs.map(_.toUpper) // !!! need a language change to make this work without the : Char val ys9a: String = xs9a - val xs10 = xs.flatMap((x: Char) => s"$x,$x") + val xs10 = xs.flatMap(x => s"$x,$x") val ys10: String = xs10 val xs11 = xs ++ xs val ys11: String = xs11 From b8472d8c6e6c9a267088817eb8e05577e5eda60b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 17 Apr 2016 16:42:40 +0200 Subject: [PATCH 13/14] Updates of strawman Bring strawman-4 and strawman-5 to feature-parity. Test also strawman-4. --- .../collections/CollectionStrawMan4.scala | 109 +++- .../collections/CollectionStrawMan5.scala | 88 +-- tests/run/colltest4.check | 86 +++ .../run/colltest4/CollectionStrawMan4_1.scala | 539 ++++++++++++++++++ tests/run/colltest4/CollectionTests_2.scala | 172 ++++++ tests/run/colltest5.check | 18 + .../run/colltest5/CollectionStrawMan5_1.scala | 94 +-- tests/run/colltest5/CollectionTests_2.scala | 4 +- 8 files changed, 997 insertions(+), 113 deletions(-) create mode 100644 tests/run/colltest4.check create mode 100644 tests/run/colltest4/CollectionStrawMan4_1.scala create mode 100644 tests/run/colltest4/CollectionTests_2.scala diff --git a/src/strawman/collections/CollectionStrawMan4.scala b/src/strawman/collections/CollectionStrawMan4.scala index 8ec77e19bfa1..874f67a2d86f 100644 --- a/src/strawman/collections/CollectionStrawMan4.scala +++ b/src/strawman/collections/CollectionStrawMan4.scala @@ -2,6 +2,8 @@ package strawman.collections import Predef.{augmentString => _, wrapString => _, _} import scala.reflect.ClassTag +import annotation.unchecked.uncheckedVariance +import annotation.tailrec /** A strawman architecture for new collections. It contains some * example collection classes and methods with the intent to expose @@ -20,13 +22,6 @@ object CollectionStrawMan4 { def iterator: Iterator[A] } - /** Base trait for generic collections */ - trait Iterable[+A] extends IterableOnce[A] with FromIterable[Iterable] { - def iterator: Iterator[A] - def view: View[A] = View.fromIterator(iterator) - def knownLength: Int = -1 - } - /** Base trait for instances that can construct a collection from an iterable */ trait FromIterable[+C[X] <: Iterable[X]] { def fromIterable[B](v: Iterable[B]): C[B] @@ -38,16 +33,27 @@ object CollectionStrawMan4 { def apply[A](xs: A*): C[A] = fromIterable(View.Elems(xs: _*)) } + /** Base trait for generic collections */ + trait Iterable[+A] extends IterableOnce[A] with FromIterable[Iterable] { + def view: View[A] = View.fromIterator(iterator) // view is overridden, cannot be defined in ops + def knownLength: Int = -1 + } + /** Base trait for sequence collections */ trait Seq[+A] extends Iterable[A] with FromIterable[Seq] { def apply(i: Int): A def length: Int } + /** Base trait for collection builders */ trait Builder[-A, +To] { def +=(x: A): this.type - def ++=(xs: IterableOnce[A]): Unit = xs.iterator.foreach(+=) def result: To + + def ++=(xs: IterableOnce[A]): this.type = { + xs.iterator.foreach(+=) + this + } } /* ------------ Operations ----------------------------------- */ @@ -134,17 +140,18 @@ object CollectionStrawMan4 { require(!isEmpty) if (i == 0) head else tail.apply(i - 1) } - def :::[B >: A](prefix: List[B]): List[B] = - if (prefix.isEmpty) this - else Cons(prefix.head, prefix.tail ::: this) def length: Int = if (isEmpty) 0 else 1 + tail.length + def ++:[B >: A](prefix: List[B]): List[B] = + if (prefix.isEmpty) this + else Cons(prefix.head, prefix.tail ++: this) } - case class Cons[+A](x: A, xs: List[A]) extends List[A] { + case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally + extends List[A] { def isEmpty = false def head = x - def tail = xs + def tail = next } case object Nil extends List[Nothing] { @@ -157,20 +164,64 @@ object CollectionStrawMan4 { def fromIterator[B](it: Iterator[B]): List[B] = if (it.hasNext) Cons(it.next, fromIterator(it)) else Nil def fromIterable[B](c: Iterable[B]): List[B] = c match { - case View.Concat(xs, ys: Iterable[B]) => - fromIterable(xs) ::: fromIterable(ys) + case View.Concat(xs, ys: List[B]) => + fromIterable(xs) ++: ys case View.Drop(xs: List[B], n) => - var i = 0 - var ys = xs - while (i < n && !xs.isEmpty) { - ys = ys.tail - i += 1 - } - ys + @tailrec def loop(xs: List[B], n: Int): List[B] = + if (n > 0) loop(xs.tail, n - 1) else xs + loop(xs, n) + case c: List[B] => c case _ => fromIterator(c.iterator) } } + /** Concrete collection type: ListBuffer */ + class ListBuffer[A] extends Seq[A] with FromIterable[ListBuffer] with Builder[A, List[A]] { + private var first, last: List[A] = Nil + private var aliased = false + def iterator = first.iterator + def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) + def apply(i: Int) = first.apply(i) + def length = first.length + + private def copyElems(): Unit = { + val buf = ListBuffer.fromIterable(result) + first = buf.first + last = buf.last + aliased = false + } + def result = { + aliased = true + first + } + def +=(elem: A) = { + if (aliased) copyElems() + val last1 = Cons(elem, Nil) + last match { + case last: Cons[A] => last.next = last1 + case _ => first = last1 + } + last = last1 + this + } + override def toString: String = + if (first.isEmpty) "ListBuffer()" + else { + val b = new StringBuilder("ListBuffer(").append(first.head) + first.tail.foldLeft(b)(_.append(", ").append(_)).append(")").toString + } + } + + object ListBuffer extends IterableFactory[ListBuffer] { + def fromIterable[B](coll: Iterable[B]): ListBuffer[B] = coll match { + case pd @ View.Partitioned(partition: View.Partition[B]) => + partition.distribute(new ListBuffer[B]()) + pd.forced.get.asInstanceOf[ListBuffer[B]] + case _ => + new ListBuffer[B] ++= coll + } + } + /** Concrete collection type: ArrayBuffer */ class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) extends Seq[A] with FromIterable[ArrayBuffer] with Builder[A, ArrayBuffer[A]] { @@ -234,12 +285,6 @@ object CollectionStrawMan4 { def apply(n: Int) = elems(start + n).asInstanceOf[A] } - case class StringView(s: String) extends RandomAccessView[Char] { - val start = 0 - val end = s.length - def apply(n: Int) = s.charAt(n) - } - /** Concrete collection type: String */ implicit class StringOps(val s: String) extends AnyVal with Ops[Char] { def iterator: Iterator[Char] = new StringView(s).iterator @@ -277,6 +322,12 @@ object CollectionStrawMan4 { def ++(xs: String): String = s + xs } + case class StringView(s: String) extends RandomAccessView[Char] { + val start = 0 + val end = s.length + def apply(n: Int) = s.charAt(n) + } + /* ------------ Views --------------------------------------- */ /** A lazy iterable */ @@ -322,6 +373,8 @@ object CollectionStrawMan4 { } case class Partition[A](val underlying: Iterable[A], p: A => Boolean) { val left, right = Partitioned(this) + // `distribute` makes up for the lack of generic push-based functionality. + // It forces both halves of the partition with a given builder. def distribute(bf: => Builder[A, Iterable[A]]) = { val lb, rb = bf val it = underlying.iterator diff --git a/src/strawman/collections/CollectionStrawMan5.scala b/src/strawman/collections/CollectionStrawMan5.scala index cfca6cd31e66..1a89d9659c2b 100644 --- a/src/strawman/collections/CollectionStrawMan5.scala +++ b/src/strawman/collections/CollectionStrawMan5.scala @@ -3,6 +3,7 @@ package strawman.collections import Predef.{augmentString => _, wrapString => _, _} import scala.reflect.ClassTag import annotation.unchecked.uncheckedVariance +import annotation.tailrec /** A strawman architecture for new collections. It contains some * example collection classes and methods with the intent to expose @@ -32,24 +33,8 @@ object CollectionStrawMan5 { def apply[A](xs: A*): C[A] = fromIterable(View.Elems(xs: _*)) } - /** Base trait for Iterable operations */ - trait IterableLike[+A, +C[X] <: Iterable[X]] - extends FromIterable[C] - with IterableOps[A] - with IterableMonoTransforms[A @uncheckedVariance, C[A @uncheckedVariance]] - with IterablePolyTransforms[A @uncheckedVariance, C] { - protected[this] def fromLikeIterable(coll: Iterable[A]): C[A] = fromIterable(coll) - } - - /** Base trait for Seq operations */ - trait SeqLike[+A, +C[X] <: Seq[X]] - extends IterableLike[A, C] with SeqMonoTransforms[A @uncheckedVariance, C[A @uncheckedVariance]] - /** Base trait for generic collections */ trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { - override def iterator: Iterator[A] - override def fromIterable[B](it: Iterable[B]): Iterable[B] - protected def coll: Iterable[A] = this def knownLength: Int = -1 } @@ -58,9 +43,9 @@ object CollectionStrawMan5 { trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] { def apply(i: Int): A def length: Int - override def iterator: Iterator[A] } + /** Base trait for strict collections */ trait Buildable[+A, +To <: Iterable[A]] extends Iterable[A] { protected[this] def newBuilder: Builder[A, To] override def partition(p: A => Boolean): (To, To) = { @@ -68,8 +53,11 @@ object CollectionStrawMan5 { iterator.foreach(x => (if (p(x)) l else r) += x) (l.result, r.result) } + // one might also override other transforms here to avoid generating + // iterators if it helps efficiency. } + /** Base trait for collection builders */ trait Builder[-A, +To] { def +=(x: A): this.type def result: To @@ -82,6 +70,29 @@ object CollectionStrawMan5 { /* ------------ Operations ----------------------------------- */ + /** Base trait for Iterable operations + * + * VarianceNote + * ============ + * + * We require that for all child classes of Iterable the variance of + * the child class and the variance of the `C` parameter passed to `IterableLike` + * are the same. We cannot express this since we lack variance polymorphism. That's + * why we have to resort at some places to write `C[A @uncheckedVariance]`. + * + */ + trait IterableLike[+A, +C[X] <: Iterable[X]] + extends FromIterable[C] + with IterableOps[A] + with IterableMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote + with IterablePolyTransforms[A, C] { + protected[this] def fromLikeIterable(coll: Iterable[A]): C[A] = fromIterable(coll) + } + + /** Base trait for Seq operations */ + trait SeqLike[+A, +C[X] <: Seq[X]] + extends IterableLike[A, C] with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote + trait IterableOps[+A] extends Any { def iterator: Iterator[A] def foreach(f: A => Unit): Unit = iterator.foreach(f) @@ -93,16 +104,19 @@ object CollectionStrawMan5 { def view: View[A] = View.fromIterator(iterator) } - trait IterableMonoTransforms[A, +Repr] extends Any { + trait IterableMonoTransforms[+A, +Repr] extends Any { protected def coll: Iterable[A] - protected def fromLikeIterable(coll: Iterable[A]): Repr + protected[this] def fromLikeIterable(coll: Iterable[A]): Repr def filter(p: A => Boolean): Repr = fromLikeIterable(View.Filter(coll, p)) def partition(p: A => Boolean): (Repr, Repr) = { val pn = View.Partition(coll, p) (fromLikeIterable(pn.left), fromLikeIterable(pn.right)) } def drop(n: Int): Repr = fromLikeIterable(View.Drop(coll, n)) - def to[C[X] <: Iterable[X]](fi: FromIterable[C]): C[A] = fi.fromIterable(coll) + def to[C[X] <: Iterable[X]](fi: FromIterable[C]): C[A @uncheckedVariance] = + // variance seems sound because `to` could just as well have been added + // as a decorator. We should investigate this further to be sure. + fi.fromIterable(coll) } trait IterablePolyTransforms[+A, +C[A]] extends Any { @@ -112,9 +126,10 @@ object CollectionStrawMan5 { def flatMap[B](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) def ++[B >: A](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) def zip[B](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) + // sound bcs of VarianceNote } - trait SeqMonoTransforms[A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { + trait SeqMonoTransforms[+A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { def reverse: Repr = { var xs: List[A] = Nil var it = coll.iterator @@ -150,10 +165,12 @@ object CollectionStrawMan5 { case xs: List[B] => this ++: xs case _ => super.++(xs) } - override def reverse = super.reverse + @tailrec final override def drop(n: Int) = + if (n > 0) tail.drop(n - 1) else this } - case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) extends List[A] { + case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally + extends List[A] { override def isEmpty = false override def head = x def tail = next @@ -176,11 +193,7 @@ object CollectionStrawMan5 { class ListBuffer[A] extends Seq[A] with SeqLike[A, ListBuffer] with Builder[A, List[A]] { private var first, last: List[A] = Nil private var aliased = false - def iterator = new Iterator[A] { - var current: List[A] = first - def hasNext = ??? - def next = ??? - } + def iterator = first.iterator def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) def apply(i: Int) = first.apply(i) def length = first.length @@ -205,6 +218,12 @@ object CollectionStrawMan5 { last = last1 this } + override def toString: String = + if (first.isEmpty) "ListBuffer()" + else { + val b = new StringBuilder("ListBuffer(").append(first.head) + first.tail.foldLeft(b)(_.append(", ").append(_)).append(")").toString + } } object ListBuffer extends IterableFactory[ListBuffer] { @@ -291,12 +310,12 @@ object CollectionStrawMan5 { def fromIterable[B](coll: Iterable[B]): List[B] = List.fromIterable(coll) def map(f: Char => Char): String = { val sb = new StringBuilder - for (ch <- StringOps(s)) sb.append(f(ch)) + for (ch <- s) sb.append(f(ch)) sb.toString } def flatMap(f: Char => String): String = { val sb = new StringBuilder - for (ch <- StringOps(s)) sb.append(f(ch)) + for (ch <- s) sb.append(f(ch)) sb.toString } def ++(xs: IterableOnce[Char]): String = { @@ -393,15 +412,6 @@ object CollectionStrawMan5 { -1 } } - case class Reverse[A](underlying: Iterable[A]) extends View[A] { - def iterator = { - var xs: List[A] = Nil - val it = underlying.iterator - while (it.hasNext) xs = Cons(it.next(), xs) - xs.iterator - } - override def knownLength = underlying.knownLength - } } /* ---------- Iterators ---------------------------------------------------*/ diff --git a/tests/run/colltest4.check b/tests/run/colltest4.check new file mode 100644 index 000000000000..ad3ccb426b92 --- /dev/null +++ b/tests/run/colltest4.check @@ -0,0 +1,86 @@ +Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0 +------- +123 +123 +1 +1 +Cons(1,Cons(2,Cons(3,Nil))) +Cons(2,Nil) +Cons(1,Cons(3,Nil)) +Cons(3,Nil) +Cons(true,Cons(true,Cons(true,Nil))) +Cons(1,Cons(-1,Cons(2,Cons(-2,Cons(3,Cons(-3,Nil)))))) +Cons(1,Cons(2,Cons(3,Cons(1,Cons(2,Cons(3,Nil)))))) +Cons(1,Cons(2,Cons(3,Nil))) +Cons(1,Cons(2,Cons(3,Nil))) +Cons(1,Cons(2,Cons(3,Cons(a,Nil)))) +Cons((1,true),Cons((2,true),Cons((3,true),Nil))) +Cons(3,Cons(2,Cons(1,Nil))) +------- +123 +123 +1 +1 +Cons(1,Cons(2,Cons(3,Nil))) +ArrayBuffer(2) +ArrayBuffer(1, 3) +ArrayBuffer(3) +ArrayBuffer(true, true, true) +ArrayBuffer(1, -1, 2, -2, 3, -3) +ArrayBuffer(1, 2, 3, 1, 2, 3) +ArrayBuffer(1, 2, 3) +Cons(1,Cons(2,Cons(3,Nil))) +ArrayBuffer(1, 2, 3, a) +ArrayBuffer((1,true), (2,true), (3,true)) +ArrayBuffer(3, 2, 1) +------- +123 +123 +1 +1 +Cons(1,Cons(2,Cons(3,Nil))) +ListBuffer(2) +ListBuffer(1, 3) +ListBuffer(3) +ListBuffer(true, true, true) +ListBuffer(1, -1, 2, -2, 3, -3) +ListBuffer(1, 2, 3, 1, 2, 3) +ListBuffer(1, 2, 3) +Cons(1,Cons(2,Cons(3,Nil))) +ListBuffer(1, 2, 3, a) +ListBuffer((1,true), (2,true), (3,true)) +ListBuffer(3, 2, 1) +------- +123 +123 +1 +1 +Cons(1,Cons(2,Cons(3,Nil))) +Cons(2,Nil) +Cons(1,Cons(3,Nil)) +Cons(3,Nil) +Cons(true,Cons(true,Cons(true,Nil))) +Cons(1,Cons(-1,Cons(2,Cons(-2,Cons(3,Cons(-3,Nil)))))) +Cons(1,Cons(2,Cons(3,Cons(1,Cons(2,Cons(3,Nil)))))) +Cons(1,Cons(2,Cons(3,Nil))) +Cons(1,Cons(2,Cons(3,Nil))) +Cons(1,Cons(2,Cons(3,Cons(a,Nil)))) +Cons((1,true),Cons((2,true),Cons((3,true),Nil))) +------- +abc +abc +1 +a +Cons(a,Cons(b,Cons(c,Nil))) +b +ac +c +Cons(98,Cons(99,Cons(100,Nil))) +ABC +a,ab,bc,c +abcabc +abcxy +abc +Cons(a,Cons(b,Cons(c,Nil))) +Cons(a,Cons(b,Cons(c,Cons(xyz,Nil)))) +Cons((a,98),Cons((b,99),Cons((c,100),Nil))) diff --git a/tests/run/colltest4/CollectionStrawMan4_1.scala b/tests/run/colltest4/CollectionStrawMan4_1.scala new file mode 100644 index 000000000000..874f67a2d86f --- /dev/null +++ b/tests/run/colltest4/CollectionStrawMan4_1.scala @@ -0,0 +1,539 @@ +package strawman.collections + +import Predef.{augmentString => _, wrapString => _, _} +import scala.reflect.ClassTag +import annotation.unchecked.uncheckedVariance +import annotation.tailrec + +/** A strawman architecture for new collections. It contains some + * example collection classes and methods with the intent to expose + * some key issues. It would be good to compare this to other + * implementations of the same functionality, to get an idea of the + * strengths and weaknesses of different collection architectures. + * + * For a test file, see tests/run/CollectionTests.scala. + */ +object CollectionStrawMan4 { + + /* ------------ Base Traits -------------------------------- */ + + /** Iterator can be used only once */ + trait IterableOnce[+A] { + def iterator: Iterator[A] + } + + /** Base trait for instances that can construct a collection from an iterable */ + trait FromIterable[+C[X] <: Iterable[X]] { + def fromIterable[B](v: Iterable[B]): C[B] + } + + /** Base trait for companion objects of collections */ + trait IterableFactory[+C[X] <: Iterable[X]] extends FromIterable[C] { + def empty[X]: C[X] = fromIterable(View.Empty) + def apply[A](xs: A*): C[A] = fromIterable(View.Elems(xs: _*)) + } + + /** Base trait for generic collections */ + trait Iterable[+A] extends IterableOnce[A] with FromIterable[Iterable] { + def view: View[A] = View.fromIterator(iterator) // view is overridden, cannot be defined in ops + def knownLength: Int = -1 + } + + /** Base trait for sequence collections */ + trait Seq[+A] extends Iterable[A] with FromIterable[Seq] { + def apply(i: Int): A + def length: Int + } + + /** Base trait for collection builders */ + trait Builder[-A, +To] { + def +=(x: A): this.type + def result: To + + def ++=(xs: IterableOnce[A]): this.type = { + xs.iterator.foreach(+=) + this + } + } + + /* ------------ Operations ----------------------------------- */ + + /** Operations returning types unrelated to current collection */ + trait Ops[A] extends Any { + def iterator: Iterator[A] + def foreach(f: A => Unit): Unit = iterator.foreach(f) + def foldLeft[B](z: B)(op: (B, A) => B): B = iterator.foldLeft(z)(op) + def foldRight[B](z: B)(op: (A, B) => B): B = iterator.foldRight(z)(op) + def indexWhere(p: A => Boolean): Int = iterator.indexWhere(p) + def isEmpty: Boolean = !iterator.hasNext + def head: A = iterator.next + } + + /** Transforms returning same collection type */ + trait MonoTransforms[A, Repr] extends Any { + protected def coll: Iterable[A] + protected def fromIterable(it: Iterable[A]): Repr + def filter(p: A => Boolean): Repr = fromIterable(View.Filter(coll, p)) + def partition(p: A => Boolean): (Repr, Repr) = { + val pn = View.Partition(coll, p) + (fromIterable(pn.left), fromIterable(pn.right)) + } + def drop(n: Int): Repr = fromIterable(View.Drop(coll, n)) + def to[C[X] <: Iterable[X]](fv: FromIterable[C]): C[A] = fv.fromIterable(coll) + } + + trait PolyTransforms[A, C[X]] extends Any { + protected def coll: Iterable[A] + protected def fromIterable[B](it: Iterable[B]): C[B] + def map[B](f: A => B): C[B] = fromIterable(View.Map(coll, f)) + def flatMap[B](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) + def ++[B >: A](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) + def zip[B](xs: IterableOnce[B]): C[(A, B)] = fromIterable(View.Zip(coll, xs)) + } + + /** Transforms that only apply to Seq */ + trait MonoTransformsOfSeqs[A, Repr] extends Any with MonoTransforms[A, Repr] { + def reverse: Repr = fromIterable(View.Reverse(coll)) + } + + /** Implementation of Ops for all generic collections */ + implicit class IterableOps[A](val c: Iterable[A]) + extends AnyVal with Ops[A] { + def iterator = c.iterator + } + + /** Implementation of MonoTransforms for all generic collections */ + implicit class IterableMonoTransforms[A, C[X] <: Iterable[X]](val c: Iterable[A] with FromIterable[C]) + extends AnyVal with MonoTransforms[A, C[A]] { + protected def coll = c + protected def fromIterable(it: Iterable[A]): C[A] = c.fromIterable(it) + } + + /** Implementation of PolyTransforms for all generic collections */ + implicit class IterablePolyTransforms[A, C[X] <: Iterable[X]](val c: Iterable[A] with FromIterable[C]) + extends AnyVal with PolyTransforms[A, C] { + protected def coll = c + protected def fromIterable[B](it: Iterable[B]): C[B] = c.fromIterable(it) + } + + /** Implementation of MonoTransformsForSeqs for all generic collections */ + implicit class SeqMonoTransforms[A, C[X] <: Seq[X]](val c: Seq[A] with FromIterable[C]) + extends AnyVal with MonoTransformsOfSeqs[A, C[A]] { + protected def coll = c + protected def fromIterable(it: Iterable[A]): C[A] = c.fromIterable(it) + } + + /* --------- Concrete collection types ------------------------------- */ + + /** Concrete collection type: List */ + sealed trait List[+A] extends Seq[A] with FromIterable[List] { self => + def isEmpty: Boolean + def head: A + def tail: List[A] + def iterator = new Iterator[A] { + private[this] var current = self + def hasNext = !current.isEmpty + def next = { val r = current.head; current = current.tail; r } + } + def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) + def apply(i: Int): A = { + require(!isEmpty) + if (i == 0) head else tail.apply(i - 1) + } + def length: Int = + if (isEmpty) 0 else 1 + tail.length + def ++:[B >: A](prefix: List[B]): List[B] = + if (prefix.isEmpty) this + else Cons(prefix.head, prefix.tail ++: this) + } + + case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally + extends List[A] { + def isEmpty = false + def head = x + def tail = next + } + + case object Nil extends List[Nothing] { + def isEmpty = true + def head = ??? + def tail = ??? + } + + object List extends IterableFactory[List] { + def fromIterator[B](it: Iterator[B]): List[B] = + if (it.hasNext) Cons(it.next, fromIterator(it)) else Nil + def fromIterable[B](c: Iterable[B]): List[B] = c match { + case View.Concat(xs, ys: List[B]) => + fromIterable(xs) ++: ys + case View.Drop(xs: List[B], n) => + @tailrec def loop(xs: List[B], n: Int): List[B] = + if (n > 0) loop(xs.tail, n - 1) else xs + loop(xs, n) + case c: List[B] => c + case _ => fromIterator(c.iterator) + } + } + + /** Concrete collection type: ListBuffer */ + class ListBuffer[A] extends Seq[A] with FromIterable[ListBuffer] with Builder[A, List[A]] { + private var first, last: List[A] = Nil + private var aliased = false + def iterator = first.iterator + def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) + def apply(i: Int) = first.apply(i) + def length = first.length + + private def copyElems(): Unit = { + val buf = ListBuffer.fromIterable(result) + first = buf.first + last = buf.last + aliased = false + } + def result = { + aliased = true + first + } + def +=(elem: A) = { + if (aliased) copyElems() + val last1 = Cons(elem, Nil) + last match { + case last: Cons[A] => last.next = last1 + case _ => first = last1 + } + last = last1 + this + } + override def toString: String = + if (first.isEmpty) "ListBuffer()" + else { + val b = new StringBuilder("ListBuffer(").append(first.head) + first.tail.foldLeft(b)(_.append(", ").append(_)).append(")").toString + } + } + + object ListBuffer extends IterableFactory[ListBuffer] { + def fromIterable[B](coll: Iterable[B]): ListBuffer[B] = coll match { + case pd @ View.Partitioned(partition: View.Partition[B]) => + partition.distribute(new ListBuffer[B]()) + pd.forced.get.asInstanceOf[ListBuffer[B]] + case _ => + new ListBuffer[B] ++= coll + } + } + + /** Concrete collection type: ArrayBuffer */ + class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) + extends Seq[A] with FromIterable[ArrayBuffer] with Builder[A, ArrayBuffer[A]] { + def this() = this(new Array[AnyRef](16), 0) + private var elems: Array[AnyRef] = initElems + private var start = 0 + private var end = initLength + def apply(n: Int) = elems(start + n).asInstanceOf[A] + def length = end - start + override def knownLength = length + override def view = new ArrayBufferView(elems, start, end) + def iterator = view.iterator + def fromIterable[B](it: Iterable[B]): ArrayBuffer[B] = + ArrayBuffer.fromIterable(it) + def +=(elem: A): this.type = { + if (end == elems.length) { + if (start > 0) { + Array.copy(elems, start, elems, 0, length) + end -= start + start = 0 + } + else { + val newelems = new Array[AnyRef](end * 2) + Array.copy(elems, 0, newelems, 0, end) + elems = newelems + } + } + elems(end) = elem.asInstanceOf[AnyRef] + end += 1 + this + } + def result = this + def trimStart(n: Int): Unit = start += (n max 0) + override def toString = s"ArrayBuffer(${elems.slice(start, end).mkString(", ")})" + } + + object ArrayBuffer extends IterableFactory[ArrayBuffer] { + def fromIterable[B](c: Iterable[B]): ArrayBuffer[B] = c match { + case View.Concat(fst: ArrayBuffer[B], snd: ArrayBuffer[B]) => + val elems = new Array[AnyRef](fst.length + snd.length) + Array.copy(fst.elems, fst.start, elems, 0, fst.length) + Array.copy(snd.elems, snd.start, elems, fst.length, snd.length) + new ArrayBuffer(elems, elems.length) + case pd @ View.Partitioned(partition: View.Partition[B]) => + partition.distribute(new ArrayBuffer[B]()) + pd.forced.get.asInstanceOf[ArrayBuffer[B]] + case c if c.knownLength >= 0 => + val elems = new Array[AnyRef](c.knownLength) + val it = c.iterator + for (i <- 0 until elems.length) elems(i) = it.next().asInstanceOf[AnyRef] + new ArrayBuffer[B](elems, elems.length) + case _ => + val buf = new ArrayBuffer[B] + val it = c.iterator + while (it.hasNext) buf += it.next() + buf + } + } + + class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends RandomAccessView[A] { + def apply(n: Int) = elems(start + n).asInstanceOf[A] + } + + /** Concrete collection type: String */ + implicit class StringOps(val s: String) extends AnyVal with Ops[Char] { + def iterator: Iterator[Char] = new StringView(s).iterator + } + + implicit class StringMonoTransforms(val s: String) + extends AnyVal with MonoTransformsOfSeqs[Char, String] { + protected def coll: Iterable[Char] = StringView(s) + protected def fromIterable(it: Iterable[Char]): String = { + val sb = new StringBuilder + for (ch <- it) sb.append(ch) + sb.toString + } + } + + implicit class StringPolyTransforms(val s: String) + extends AnyVal with PolyTransforms[Char, Seq] { + protected def coll = StringView(s) + protected def fromIterable[B](it: Iterable[B]): Seq[B] = List.fromIterable(it) + def map(f: Char => Char): String = { + val sb = new StringBuilder + for (ch <- s) sb.append(f(ch)) + sb.toString + } + def flatMap(f: Char => String) = { + val sb = new StringBuilder + for (ch <- s) sb.append(f(ch)) + sb.toString + } + def ++(xs: IterableOnce[Char]): String = { + val sb = new StringBuilder(s) + for (ch <- xs.iterator) sb.append(ch) + sb.toString + } + def ++(xs: String): String = s + xs + } + + case class StringView(s: String) extends RandomAccessView[Char] { + val start = 0 + val end = s.length + def apply(n: Int) = s.charAt(n) + } + + /* ------------ Views --------------------------------------- */ + + /** A lazy iterable */ + trait View[+A] extends Iterable[A] with FromIterable[View] { + override def view = this + override def fromIterable[B](c: Iterable[B]) = c match { + case c: View[B] => c + case _ => View.fromIterator(c.iterator) + } + } + + /** Iterator defined in terms of indexing a range */ + trait RandomAccessView[+A] extends View[A] { + def start: Int + def end: Int + def apply(i: Int): A + def iterator: Iterator[A] = new Iterator[A] { + private var current = start + def hasNext = current < end + def next: A = { + val r = apply(current) + current += 1 + r + } + } + override def knownLength = end - start max 0 + } + + object View { + def fromIterator[A](it: => Iterator[A]): View[A] = new View[A] { + def iterator = it + } + case object Empty extends View[Nothing] { + def iterator = Iterator.empty + override def knownLength = 0 + } + case class Elems[A](xs: A*) extends View[A] { + def iterator = Iterator(xs: _*) + override def knownLength = xs.length + } + case class Filter[A](val underlying: Iterable[A], p: A => Boolean) extends View[A] { + def iterator = underlying.iterator.filter(p) + } + case class Partition[A](val underlying: Iterable[A], p: A => Boolean) { + val left, right = Partitioned(this) + // `distribute` makes up for the lack of generic push-based functionality. + // It forces both halves of the partition with a given builder. + def distribute(bf: => Builder[A, Iterable[A]]) = { + val lb, rb = bf + val it = underlying.iterator + while (it.hasNext) { + val x = it.next() + (if (p(x)) lb else rb) += x + } + left.forced = Some(lb.result) + right.forced = Some(rb.result) + } + } + case class Partitioned[A](partition: Partition[A]) extends View[A] { + private var myForced: Option[Iterable[A]] = None + def forced: Option[Iterable[A]] = myForced + private[View] def forced_=(x: Option[Iterable[A]]): Unit = myForced = x + def underlying = partition.underlying + def iterator = forced match { + case Some(c) => c.iterator + case None => + underlying.iterator.filter( + if (this eq partition.left) partition.p else !partition.p(_)) + } + } + case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { + def iterator = underlying.iterator.drop(n) + override def knownLength = + if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 + } + case class Map[A, B](underlying: Iterable[A], f: A => B) extends View[B] { + def iterator = underlying.iterator.map(f) + override def knownLength = underlying.knownLength + } + case class FlatMap[A, B](underlying: Iterable[A], f: A => IterableOnce[B]) extends View[B] { + def iterator = underlying.iterator.flatMap(f) + } + case class Concat[A](underlying: Iterable[A], other: IterableOnce[A]) extends View[A] { + def iterator = underlying.iterator ++ other + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength + other.knownLength + case _ => + -1 + } + } + case class Zip[A, B](underlying: Iterable[A], other: IterableOnce[B]) extends View[(A, B)] { + def iterator = underlying.iterator.zip(other) + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength min other.knownLength + case _ => + -1 + } + } + case class Reverse[A](underlying: Iterable[A]) extends View[A] { + def iterator = { + var xs: List[A] = Nil + val it = underlying.iterator + while (it.hasNext) xs = Cons(it.next(), xs) + xs.iterator + } + override def knownLength = underlying.knownLength + } + } + +/* ---------- Iterators ---------------------------------------------------*/ + + /** A core Iterator class */ + trait Iterator[+A] extends IterableOnce[A] { self => + def hasNext: Boolean + def next(): A + def iterator = this + def foldLeft[B](z: B)(op: (B, A) => B): B = + if (hasNext) foldLeft(op(z, next))(op) else z + def foldRight[B](z: B)(op: (A, B) => B): B = + if (hasNext) op(next(), foldRight(z)(op)) else z + def foreach(f: A => Unit): Unit = + while (hasNext) f(next()) + def indexWhere(p: A => Boolean): Int = { + var i = 0 + while (hasNext) { + if (p(next())) return i + i += 1 + } + -1 + } + def filter(p: A => Boolean): Iterator[A] = new Iterator[A] { + private var hd: A = _ + private var hdDefined: Boolean = false + + def hasNext: Boolean = hdDefined || { + do { + if (!self.hasNext) return false + hd = self.next() + } while (!p(hd)) + hdDefined = true + true + } + + def next() = + if (hasNext) { + hdDefined = false + hd + } + else Iterator.empty.next() + } + + def map[B](f: A => B): Iterator[B] = new Iterator[B] { + def hasNext = self.hasNext + def next() = f(self.next()) + } + + def flatMap[B](f: A => IterableOnce[B]): Iterator[B] = new Iterator[B] { + private var myCurrent: Iterator[B] = Iterator.empty + private def current = { + while (!myCurrent.hasNext && self.hasNext) + myCurrent = f(self.next()).iterator + myCurrent + } + def hasNext = current.hasNext + def next() = current.next() + } + def ++[B >: A](xs: IterableOnce[B]): Iterator[B] = new Iterator[B] { + private var myCurrent: Iterator[B] = self + private var first = true + private def current = { + if (!myCurrent.hasNext && first) { + myCurrent = xs.iterator + first = false + } + myCurrent + } + def hasNext = current.hasNext + def next() = current.next() + } + def drop(n: Int): Iterator[A] = { + var i = 0 + while (i < n && hasNext) { + next() + i += 1 + } + this + } + def zip[B](that: IterableOnce[B]): Iterator[(A, B)] = new Iterator[(A, B)] { + val thatIterator = that.iterator + def hasNext = self.hasNext && thatIterator.hasNext + def next() = (self.next(), thatIterator.next()) + } + } + + object Iterator { + val empty: Iterator[Nothing] = new Iterator[Nothing] { + def hasNext = false + def next = throw new NoSuchElementException("next on empty iterator") + } + def apply[A](xs: A*): Iterator[A] = new RandomAccessView[A] { + val start = 0 + val end = xs.length + def apply(n: Int) = xs(n) + }.iterator + } +} + diff --git a/tests/run/colltest4/CollectionTests_2.scala b/tests/run/colltest4/CollectionTests_2.scala new file mode 100644 index 000000000000..0961180ed4df --- /dev/null +++ b/tests/run/colltest4/CollectionTests_2.scala @@ -0,0 +1,172 @@ +import Predef.{augmentString => _, wrapString => _, _} +import scala.reflect.ClassTag + +object Test { + import strawman.collections._ + import CollectionStrawMan5._ + + def seqOps(xs: Seq[Int]) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + val y2: String = x2 + val x3 = xs.indexWhere(_ % 2 == 0) + val y3: Int = x3 + val x4 = xs.head + val y4: Int = x4 + val x5 = xs.to(List) + val y5: List[Int] = x5 + val (xs6, xs7) = xs.partition(_ % 2 == 0) + val ys6: Seq[Int] = xs6 + val ys7: Seq[Int] = xs7 + val xs8 = xs.drop(2) + val ys8: Seq[Int] = xs8 + val xs9 = xs.map(_ >= 0) + val ys9: Seq[Boolean] = xs9 + val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val ys10: Seq[Int] = xs10 + val xs11 = xs ++ xs + val ys11: Seq[Int] = xs11 + val xs12 = xs ++ Nil + val ys12: Seq[Int] = xs12 + val xs13 = Nil ++ xs + val ys13: Seq[Int] = xs13 + val xs14 = xs ++ Cons("a", Nil) + val ys14: Seq[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: Seq[(Int, Boolean)] = xs15 + val xs16 = xs.reverse + val ys16: Seq[Int] = xs16 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6) + println(xs7) + println(xs8) + println(xs9) + println(xs10) + println(xs11) + println(xs12) + println(xs13) + println(xs14) + println(xs15) + println(xs16) + } + + def viewOps(xs: View[Int]) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + val y2: String = x2 + val x3 = xs.indexWhere(_ % 2 == 0) + val y3: Int = x3 + val x4 = xs.head + val y4: Int = x4 + val x5 = xs.to(List) + val y5: List[Int] = x5 + val (xs6, xs7) = xs.partition(_ % 2 == 0) + val ys6: View[Int] = xs6 + val ys7: View[Int] = xs7 + val xs8 = xs.drop(2) + val ys8: View[Int] = xs8 + val xs9 = xs.map(_ >= 0) + val ys9: View[Boolean] = xs9 + val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val ys10: View[Int] = xs10 + val xs11 = xs ++ xs + val ys11: View[Int] = xs11 + val xs12 = xs ++ Nil + val ys12: View[Int] = xs12 + val xs13 = Nil ++ xs + val ys13: List[Int] = xs13 + val xs14 = xs ++ Cons("a", Nil) + val ys14: View[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: View[(Int, Boolean)] = xs15 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6.to(List)) + println(xs7.to(List)) + println(xs8.to(List)) + println(xs9.to(List)) + println(xs10.to(List)) + println(xs11.to(List)) + println(xs12.to(List)) + println(xs13.to(List)) + println(xs14.to(List)) + println(xs15.to(List)) + } + + def stringOps(xs: String) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + val y2: String = x2 + val x3 = xs.indexWhere(_ % 2 == 0) + val y3: Int = x3 + val x4 = xs.head + val y4: Int = x4 + val x5 = xs.to(List) + val y5: List[Char] = x5 + val (xs6, xs7) = xs.partition(_ % 2 == 0) + val ys6: String = xs6 + val ys7: String = xs7 + val xs8 = xs.drop(2) + val ys8: String = xs8 + val xs9 = xs.map(_ + 1) // !!! need a language change to make this work without the : Char + val ys9: Seq[Int] = xs9 + val xs9a = xs.map(_.toUpper) // !!! need a language change to make this work without the : Char + val ys9a: String = xs9a + val xs10 = xs.flatMap((x: Char) => s"$x,$x") + val ys10: String = xs10 + val xs11 = xs ++ xs + val ys11: String = xs11 + val xs11a = xs ++ List('x', 'y') // Cons('x', Cons('y', Nil)) + val ys11a: String = xs11a + val xs12 = xs ++ Nil + val ys12: String = xs12 + val xs13 = Nil ++ xs.iterator + val ys13: List[Char] = xs13 + val xs14 = xs ++ Cons("xyz", Nil) + val ys14: Seq[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: Seq[(Char, Int)] = xs15 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6) + println(xs7) + println(xs8) + println(xs9) + println(xs9a) + println(xs10) + println(xs11) + println(xs11a) + println(xs12) + println(xs13) + println(xs14) + println(xs15) + } + + def main(args: Array[String]) = { + val ints = Cons(1, Cons(2, Cons(3, Nil))) + val intsBuf = ints.to(ArrayBuffer) + val intsListBuf = ints.to(ListBuffer) + val intsView = ints.view + seqOps(ints) + seqOps(intsBuf) + seqOps(intsListBuf) + viewOps(intsView) + stringOps("abc") + } +} diff --git a/tests/run/colltest5.check b/tests/run/colltest5.check index 6c61d8821444..ad3ccb426b92 100644 --- a/tests/run/colltest5.check +++ b/tests/run/colltest5.check @@ -1,3 +1,4 @@ +Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0 ------- 123 123 @@ -38,6 +39,23 @@ ArrayBuffer(3, 2, 1) 1 1 Cons(1,Cons(2,Cons(3,Nil))) +ListBuffer(2) +ListBuffer(1, 3) +ListBuffer(3) +ListBuffer(true, true, true) +ListBuffer(1, -1, 2, -2, 3, -3) +ListBuffer(1, 2, 3, 1, 2, 3) +ListBuffer(1, 2, 3) +Cons(1,Cons(2,Cons(3,Nil))) +ListBuffer(1, 2, 3, a) +ListBuffer((1,true), (2,true), (3,true)) +ListBuffer(3, 2, 1) +------- +123 +123 +1 +1 +Cons(1,Cons(2,Cons(3,Nil))) Cons(2,Nil) Cons(1,Cons(3,Nil)) Cons(3,Nil) diff --git a/tests/run/colltest5/CollectionStrawMan5_1.scala b/tests/run/colltest5/CollectionStrawMan5_1.scala index aa127cb7e7eb..1a89d9659c2b 100644 --- a/tests/run/colltest5/CollectionStrawMan5_1.scala +++ b/tests/run/colltest5/CollectionStrawMan5_1.scala @@ -3,6 +3,7 @@ package strawman.collections import Predef.{augmentString => _, wrapString => _, _} import scala.reflect.ClassTag import annotation.unchecked.uncheckedVariance +import annotation.tailrec /** A strawman architecture for new collections. It contains some * example collection classes and methods with the intent to expose @@ -32,30 +33,8 @@ object CollectionStrawMan5 { def apply[A](xs: A*): C[A] = fromIterable(View.Elems(xs: _*)) } - /** Base trait for Iterable operations */ - trait IterableLike[+A, +C[X] <: Iterable[X]] - extends FromIterable[C] - with IterableOps[A] - with IterableMonoTransforms[A @uncheckedVariance, C[A @uncheckedVariance]] - with IterablePolyTransforms[A @uncheckedVariance, C] { - protected def fromLikeIterable(coll: Iterable[A @uncheckedVariance]): C[A @uncheckedVariance] = fromIterable(coll) - } - - /** Base trait for Seq operations */ - trait SeqLike[+A, +C[X] <: Seq[X]] extends IterableLike[A, C] { - def reverse: C[A @uncheckedVariance] = { - var xs: List[A] = Nil - var it = iterator - while (it.hasNext) xs = new Cons(it.next, xs) - fromLikeIterable(xs) - } - } - /** Base trait for generic collections */ trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { - override def iterator: Iterator[A] - override def fromIterable[B](it: Iterable[B]): Iterable[B] - protected def coll: Iterable[A] = this def knownLength: Int = -1 } @@ -64,9 +43,9 @@ object CollectionStrawMan5 { trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] { def apply(i: Int): A def length: Int - override def iterator: Iterator[A] } + /** Base trait for strict collections */ trait Buildable[+A, +To <: Iterable[A]] extends Iterable[A] { protected[this] def newBuilder: Builder[A, To] override def partition(p: A => Boolean): (To, To) = { @@ -74,8 +53,11 @@ object CollectionStrawMan5 { iterator.foreach(x => (if (p(x)) l else r) += x) (l.result, r.result) } + // one might also override other transforms here to avoid generating + // iterators if it helps efficiency. } + /** Base trait for collection builders */ trait Builder[-A, +To] { def +=(x: A): this.type def result: To @@ -88,6 +70,29 @@ object CollectionStrawMan5 { /* ------------ Operations ----------------------------------- */ + /** Base trait for Iterable operations + * + * VarianceNote + * ============ + * + * We require that for all child classes of Iterable the variance of + * the child class and the variance of the `C` parameter passed to `IterableLike` + * are the same. We cannot express this since we lack variance polymorphism. That's + * why we have to resort at some places to write `C[A @uncheckedVariance]`. + * + */ + trait IterableLike[+A, +C[X] <: Iterable[X]] + extends FromIterable[C] + with IterableOps[A] + with IterableMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote + with IterablePolyTransforms[A, C] { + protected[this] def fromLikeIterable(coll: Iterable[A]): C[A] = fromIterable(coll) + } + + /** Base trait for Seq operations */ + trait SeqLike[+A, +C[X] <: Seq[X]] + extends IterableLike[A, C] with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote + trait IterableOps[+A] extends Any { def iterator: Iterator[A] def foreach(f: A => Unit): Unit = iterator.foreach(f) @@ -99,16 +104,19 @@ object CollectionStrawMan5 { def view: View[A] = View.fromIterator(iterator) } - trait IterableMonoTransforms[A, +Repr] extends Any { + trait IterableMonoTransforms[+A, +Repr] extends Any { protected def coll: Iterable[A] - protected def fromLikeIterable(coll: Iterable[A]): Repr + protected[this] def fromLikeIterable(coll: Iterable[A]): Repr def filter(p: A => Boolean): Repr = fromLikeIterable(View.Filter(coll, p)) def partition(p: A => Boolean): (Repr, Repr) = { val pn = View.Partition(coll, p) (fromLikeIterable(pn.left), fromLikeIterable(pn.right)) } def drop(n: Int): Repr = fromLikeIterable(View.Drop(coll, n)) - def to[C[X] <: Iterable[X]](fi: FromIterable[C]): C[A] = fi.fromIterable(coll) + def to[C[X] <: Iterable[X]](fi: FromIterable[C]): C[A @uncheckedVariance] = + // variance seems sound because `to` could just as well have been added + // as a decorator. We should investigate this further to be sure. + fi.fromIterable(coll) } trait IterablePolyTransforms[+A, +C[A]] extends Any { @@ -118,9 +126,10 @@ object CollectionStrawMan5 { def flatMap[B](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) def ++[B >: A](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) def zip[B](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) + // sound bcs of VarianceNote } - trait SeqMonoTransforms[A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { + trait SeqMonoTransforms[+A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { def reverse: Repr = { var xs: List[A] = Nil var it = coll.iterator @@ -156,10 +165,12 @@ object CollectionStrawMan5 { case xs: List[B] => this ++: xs case _ => super.++(xs) } - override def reverse = super.reverse + @tailrec final override def drop(n: Int) = + if (n > 0) tail.drop(n - 1) else this } - case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) extends List[A] { + case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally + extends List[A] { override def isEmpty = false override def head = x def tail = next @@ -182,11 +193,7 @@ object CollectionStrawMan5 { class ListBuffer[A] extends Seq[A] with SeqLike[A, ListBuffer] with Builder[A, List[A]] { private var first, last: List[A] = Nil private var aliased = false - def iterator = new Iterator[A] { - var current: List[A] = first - def hasNext = ??? - def next = ??? - } + def iterator = first.iterator def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) def apply(i: Int) = first.apply(i) def length = first.length @@ -211,6 +218,12 @@ object CollectionStrawMan5 { last = last1 this } + override def toString: String = + if (first.isEmpty) "ListBuffer()" + else { + val b = new StringBuilder("ListBuffer(").append(first.head) + first.tail.foldLeft(b)(_.append(", ").append(_)).append(")").toString + } } object ListBuffer extends IterableFactory[ListBuffer] { @@ -297,12 +310,12 @@ object CollectionStrawMan5 { def fromIterable[B](coll: Iterable[B]): List[B] = List.fromIterable(coll) def map(f: Char => Char): String = { val sb = new StringBuilder - for (ch <- StringOps(s)) sb.append(f(ch)) + for (ch <- s) sb.append(f(ch)) sb.toString } def flatMap(f: Char => String): String = { val sb = new StringBuilder - for (ch <- StringOps(s)) sb.append(f(ch)) + for (ch <- s) sb.append(f(ch)) sb.toString } def ++(xs: IterableOnce[Char]): String = { @@ -399,15 +412,6 @@ object CollectionStrawMan5 { -1 } } - case class Reverse[A](underlying: Iterable[A]) extends View[A] { - def iterator = { - var xs: List[A] = Nil - val it = underlying.iterator - while (it.hasNext) xs = Cons(it.next(), xs) - xs.iterator - } - override def knownLength = underlying.knownLength - } } /* ---------- Iterators ---------------------------------------------------*/ diff --git a/tests/run/colltest5/CollectionTests_2.scala b/tests/run/colltest5/CollectionTests_2.scala index cdff2e260c00..0961180ed4df 100644 --- a/tests/run/colltest5/CollectionTests_2.scala +++ b/tests/run/colltest5/CollectionTests_2.scala @@ -124,7 +124,7 @@ object Test { val ys9: Seq[Int] = xs9 val xs9a = xs.map(_.toUpper) // !!! need a language change to make this work without the : Char val ys9a: String = xs9a - val xs10 = xs.flatMap(x => s"$x,$x") + val xs10 = xs.flatMap((x: Char) => s"$x,$x") val ys10: String = xs10 val xs11 = xs ++ xs val ys11: String = xs11 @@ -161,9 +161,11 @@ object Test { def main(args: Array[String]) = { val ints = Cons(1, Cons(2, Cons(3, Nil))) val intsBuf = ints.to(ArrayBuffer) + val intsListBuf = ints.to(ListBuffer) val intsView = ints.view seqOps(ints) seqOps(intsBuf) + seqOps(intsListBuf) viewOps(intsView) stringOps("abc") } From 79a284fe9229adecebffb29d48687fe96b292746 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 17 Apr 2016 17:42:16 +0200 Subject: [PATCH 14/14] Fix check files --- tests/run/colltest4.check | 1 - tests/run/colltest5.check | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/run/colltest4.check b/tests/run/colltest4.check index ad3ccb426b92..984a0803bcbe 100644 --- a/tests/run/colltest4.check +++ b/tests/run/colltest4.check @@ -1,4 +1,3 @@ -Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0 ------- 123 123 diff --git a/tests/run/colltest5.check b/tests/run/colltest5.check index ad3ccb426b92..984a0803bcbe 100644 --- a/tests/run/colltest5.check +++ b/tests/run/colltest5.check @@ -1,4 +1,3 @@ -Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0 ------- 123 123