Skip to content

Commit f03c313

Browse files
authored
Merge pull request #238 from martijnhoekstra/tosemantics
to(Seq) doesn't evaluate collection when called on a Seq
2 parents 6abe710 + 0933bbd commit f03c313

File tree

3 files changed

+63
-6
lines changed

3 files changed

+63
-6
lines changed

compat/src/main/scala-2.11_2.12/scala/collection/compat/CompatImpl.scala

+48-4
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,58 @@
1212

1313
package scala.collection.compat
1414

15+
import scala.reflect.ClassTag
1516
import scala.collection.generic.CanBuildFrom
16-
import scala.collection.mutable.Builder
1717
import scala.collection.{immutable => i, mutable => m}
1818

19+
/* builder optimized for a single ++= call, which returns identity on result if possible
20+
* and defers to the underlying builder if not.
21+
*/
22+
private final class IdentityPreservingBuilder[A, CC[X] <: TraversableOnce[X]](that: m.Builder[A, CC[A]])(implicit ct: ClassTag[CC[A]])
23+
extends m.Builder[A, CC[A]] {
24+
25+
//invariant: ruined => (collection == null)
26+
var collection: CC[A] = null.asInstanceOf[CC[A]]
27+
var ruined = false
28+
29+
private[this] def ruin(): Unit = {
30+
if(collection != null) that ++= collection
31+
collection = null.asInstanceOf[CC[A]]
32+
ruined = true
33+
}
34+
35+
override def ++=(elems: TraversableOnce[A]): this.type =
36+
elems match {
37+
case ct(ca) if collection == null && !ruined => {
38+
collection = ca
39+
this
40+
}
41+
case _ => {
42+
ruin()
43+
that ++= elems
44+
this
45+
}
46+
}
47+
48+
def +=(elem: A): this.type = {
49+
ruin()
50+
that += elem
51+
this
52+
}
53+
54+
def clear(): Unit = {
55+
collection = null.asInstanceOf[CC[A]]
56+
if (ruined) that.clear()
57+
ruined = false
58+
}
59+
60+
def result(): CC[A] = if(collection == null) that.result() else collection
61+
}
62+
1963
private[compat] object CompatImpl {
20-
def simpleCBF[A, C](f: => Builder[A, C]): CanBuildFrom[Any, A, C] = new CanBuildFrom[Any, A, C] {
21-
def apply(from: Any): Builder[A, C] = apply()
22-
def apply(): Builder[A, C] = f
64+
def simpleCBF[A, C](f: => m.Builder[A, C]): CanBuildFrom[Any, A, C] = new CanBuildFrom[Any, A, C] {
65+
def apply(from: Any): m.Builder[A, C] = apply()
66+
def apply(): m.Builder[A, C] = f
2367
}
2468

2569
type ImmutableBitSetCC[X] = ({ type L[_] = i.BitSet })#L[X]

compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala

+9-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,15 @@ private[compat] trait PackageShared {
4545
}
4646

4747
implicit def genericCompanionToCBF[A, CC[X] <: GenTraversable[X]](
48-
fact: GenericCompanion[CC]): CanBuildFrom[Any, A, CC[A]] =
49-
simpleCBF(fact.newBuilder[A])
48+
fact: GenericCompanion[CC]): CanBuildFrom[Any, A, CC[A]] = {
49+
val builder: m.Builder[A, CC[A]] = fact match {
50+
case c.Seq | i.Seq => new IdentityPreservingBuilder[A, i.Seq](i.Seq.newBuilder[A])
51+
case c.LinearSeq | i.LinearSeq =>
52+
new IdentityPreservingBuilder[A, i.LinearSeq](i.LinearSeq.newBuilder[A])
53+
case _ => fact.newBuilder[A]
54+
}
55+
simpleCBF(builder)
56+
}
5057

5158
implicit def sortedSetCompanionToCBF[A: Ordering,
5259
CC[X] <: c.SortedSet[X] with c.SortedSetLike[X, CC[X]]](

compat/src/test/scala/test/scala/collection/CollectionTest.scala

+6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.junit.Test
1717

1818
import scala.collection.compat._
1919
import scala.collection.immutable.BitSet
20+
import scala.collection.LinearSeq
2021

2122
class CollectionTest {
2223
@Test
@@ -40,6 +41,11 @@ class CollectionTest {
4041
//val mT: Map[Int, String] = m
4142
assertEquals(Map(1 -> "a", 2 -> "b"), m)
4243
assertTrue(m.isInstanceOf[Map[_, _]])
44+
45+
// Stream.to(Seq) doesn't evaluate the stream
46+
val strm = 1 #:: {throw new Exception("not lazy")} #:: Stream.empty[Int]
47+
val strmsq: Seq[Int] = strm.to(Seq)
48+
var strmln: LinearSeq[Int] = strm.to(LinearSeq)
4349
}
4450

4551
@Test

0 commit comments

Comments
 (0)