From 74634c2a27539ade96d1e4dcab8f213a0bdbb465 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 11:20:57 +0200 Subject: 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. --- src/strawman/collections/CollectionStrawMan4.scala | 2 +- src/strawman/collections/CollectionStrawMan5.scala | 509 +++++++++++++++++++++ 2 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 src/strawman/collections/CollectionStrawMan5.scala (limited to 'src') diff --git a/src/strawman/collections/CollectionStrawMan4.scala b/src/strawman/collections/CollectionStrawMan4.scala index 9159b1cfc..8ec77e19b 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 000000000..aa127cb7e --- /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 + } +} -- cgit v1.2.3 From a77eb1592b5981419c99074caee876665bbf4daa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 14:04:23 +0200 Subject: 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(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index fa049815a..913339409 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) { -- cgit v1.2.3 From 98d7183067f6a48957988ba99d234f60ab0246be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 14:07:44 +0200 Subject: 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(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 81240a9fc..b3b0982a4 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 b547862b4..3a9803346 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 { -- cgit v1.2.3 From 6545934f06f5c22ab4acacf28ca2206b898e021e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 14:08:34 +0200 Subject: 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(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index b3b0982a4..4d2335bd9 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 } -- cgit v1.2.3 From eb2979d895e56be73fcc3bb14bd34198628fd8d9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 14:09:24 +0200 Subject: 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(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/src/dotty/tools/dotc/transform/SyntheticMethods.scala index a496f80ce..9dfd92fe9 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))) -- cgit v1.2.3 From 9481fe9b7bd015437e56d33f7568887dc3ca8e5b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 14:11:52 +0200 Subject: 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(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index d5f44e632..3ed1798ed 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) } } -- cgit v1.2.3 From c32a2acee179fecf1f9d5c53aab94547a1ee2d67 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 15:20:00 +0200 Subject: 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(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 4d2335bd9..72f89aec3 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 -- cgit v1.2.3 From 1607c7b0df62b19b3aafeb9966deb8faffcce605 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 15:20:31 +0200 Subject: Make Names immutable Seqs --- src/dotty/tools/dotc/core/Names.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/core/Names.scala b/src/dotty/tools/dotc/core/Names.scala index f1e6f7606..10eef16c1 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 */ -- cgit v1.2.3 From cae12d368ae23204a5fd58156e1f6f214471172c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 15 Apr 2016 16:06:19 +0200 Subject: 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 +++++++++++++++++- src/dotty/tools/dotc/transform/ExtensionMethods.scala | 6 +----- src/dotty/tools/dotc/transform/FirstTransform.scala | 4 ++-- 3 files changed, 20 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index ac2f8ae73..b6a4dc5e7 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 a1d2e5c68..8d61cef42 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 37ae1d94e..97dd9404f 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 => -- cgit v1.2.3 From 997afcbc9d8598496bcf6a4d1f0bed11c757243a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 16 Apr 2016 13:55:34 +0200 Subject: Strawman polishing --- src/strawman/collections/CollectionStrawMan5.scala | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/strawman/collections/CollectionStrawMan5.scala b/src/strawman/collections/CollectionStrawMan5.scala index aa127cb7e..cfca6cd31 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] { -- cgit v1.2.3 From b8472d8c6e6c9a267088817eb8e05577e5eda60b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 17 Apr 2016 16:42:40 +0200 Subject: Updates of strawman Bring strawman-4 and strawman-5 to feature-parity. Test also strawman-4. --- src/strawman/collections/CollectionStrawMan4.scala | 109 +++-- src/strawman/collections/CollectionStrawMan5.scala | 88 ++-- tests/run/colltest4.check | 86 ++++ tests/run/colltest4/CollectionStrawMan4_1.scala | 539 +++++++++++++++++++++ tests/run/colltest4/CollectionTests_2.scala | 172 +++++++ tests/run/colltest5.check | 18 + tests/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 (limited to 'src') diff --git a/src/strawman/collections/CollectionStrawMan4.scala b/src/strawman/collections/CollectionStrawMan4.scala index 8ec77e19b..874f67a2d 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 cfca6cd31..1a89d9659 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 000000000..ad3ccb426 --- /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 000000000..874f67a2d --- /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 000000000..0961180ed --- /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 6c61d8821..ad3ccb426 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 aa127cb7e..1a89d9659 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 cdff2e260..0961180ed 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") } -- cgit v1.2.3