Skip to content

Commit ce1fa55

Browse files
committed
Redefine Tuple operations
This provides a way forward to fixing the signatures of some tuple methods, and removes the inlining from the tuple methods. Optimization will be implemented later directly on these method calls, which avoids unnecessary complications due to inlining artifacts. Fixes scala#12721 Fixes scala#16207
1 parent 21929a8 commit ce1fa55

File tree

15 files changed

+173
-37
lines changed

15 files changed

+173
-37
lines changed

compiler/test/dotc/pos-test-pickling.blacklist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ i17149.scala
6363
tuple-fold.scala
6464
mt-redux-norm.perspective.scala
6565
i18211.scala
66+
i15743.scala
6667

6768
# Opaque type
6869
i5720.scala

library/src-bootstrapped/scala/Tuple.scala

Lines changed: 127 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,40 @@ sealed trait Tuple extends Product {
99
import Tuple.*
1010

1111
/** Create a copy of this tuple as an Array */
12+
private[Tuple]
1213
inline def toArray: Array[Object] =
1314
runtime.Tuples.toArray(this)
1415

1516
/** Create a copy of this tuple as a List */
17+
private[Tuple]
1618
inline def toList: List[Union[this.type]] =
1719
this.productIterator.toList
1820
.asInstanceOf[List[Union[this.type]]]
1921

2022
/** Create a copy of this tuple as an IArray */
23+
private[Tuple]
2124
inline def toIArray: IArray[Object] =
2225
runtime.Tuples.toIArray(this)
2326

2427
/** Return a copy of `this` tuple with an element appended */
25-
inline def :* [This >: this.type <: Tuple, L] (x: L): Append[This, L] =
28+
private[Tuple]
2629
runtime.Tuples.append(x, this).asInstanceOf[Append[This, L]]
27-
2830
/** Return a new tuple by prepending the element to `this` tuple.
2931
* This operation is O(this.size)
3032
*/
33+
private[Tuple]
3134
inline def *: [H, This >: this.type <: Tuple] (x: H): H *: This =
3235
runtime.Tuples.cons(x, this).asInstanceOf[H *: This]
3336

3437
/** Return a new tuple by concatenating `this` tuple with `that` tuple.
3538
* This operation is O(this.size + that.size)
3639
*/
40+
private[Tuple]
3741
inline def ++ [This >: this.type <: Tuple](that: Tuple): Concat[This, that.type] =
3842
runtime.Tuples.concat(this, that).asInstanceOf[Concat[This, that.type]]
3943

4044
/** Return the size (or arity) of the tuple */
45+
private[Tuple]
4146
inline def size[This >: this.type <: Tuple]: Size[This] =
4247
runtime.Tuples.size(this).asInstanceOf[Size[This]]
4348

@@ -48,6 +53,7 @@ sealed trait Tuple extends Product {
4853
* tuple types has a `EmptyTuple` tail. Otherwise the result type is
4954
* `(A1, B1) *: ... *: (Ai, Bi) *: Tuple`
5055
*/
56+
private[Tuple]
5157
inline def zip[This >: this.type <: Tuple, T2 <: Tuple](t2: T2): Zip[This, T2] =
5258
runtime.Tuples.zip(this, t2).asInstanceOf[Zip[This, T2]]
5359

@@ -56,39 +62,140 @@ sealed trait Tuple extends Product {
5662
* If the tuple is of the form `a1 *: ... *: Tuple` (that is, the tail is not known
5763
* to be the cons type.
5864
*/
65+
private[Tuple]
5966
inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] =
6067
runtime.Tuples.map(this, f).asInstanceOf[Map[this.type, F]]
6168

6269
/** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting
6370
* of its first n elements.
6471
*/
72+
private[Tuple]
6573
inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] =
6674
runtime.Tuples.take(this, n).asInstanceOf[Take[This, n.type]]
6775

6876

6977
/** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting
7078
* all its elements except the first n ones.
7179
*/
80+
private[Tuple]
7281
inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] =
7382
runtime.Tuples.drop(this, n).asInstanceOf[Drop[This, n.type]]
7483

7584
/** Given a tuple `(a1, ..., am)`, returns a pair of the tuple `(a1, ..., an)`
7685
* consisting of the first n elements, and the tuple `(an+1, ..., am)` consisting
7786
* of the remaining elements.
7887
*/
88+
private[Tuple]
7989
inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] =
8090
runtime.Tuples.splitAt(this, n).asInstanceOf[Split[This, n.type]]
81-
82-
/** Given a tuple `(a1, ..., am)`, returns the reversed tuple `(am, ..., a1)`
83-
* consisting all its elements.
84-
*/
85-
@experimental
86-
inline def reverse[This >: this.type <: Tuple]: Reverse[This] =
87-
runtime.Tuples.reverse(this).asInstanceOf[Reverse[This]]
8891
}
8992

9093
object Tuple {
9194

95+
// TODO should it be `extension [H](x: H) def *:(tail: Tuple): H *: tuple.type` ?
96+
extension [H, Tail <: Tuple](x: H)
97+
/** Return a new tuple by prepending the element to `tail` tuple.
98+
* This operation is O(tail.size)
99+
*/
100+
def *:(tail: Tail): H *: Tail = runtime.Tuples.cons(x, tail).asInstanceOf[H *: Tail]
101+
102+
extension [This <: Tuple](tuple: This)
103+
/** Get the head of this tuple */
104+
def head: Head[This] & Head[tuple.type] =
105+
runtime.Tuples.apply(tuple, 0).asInstanceOf[Head[This] & Head[tuple.type]]
106+
107+
/** Get the tail of this tuple.
108+
* This operation is O(tuple.size)
109+
*/
110+
def tail: Tail[This] & Tail[tuple.type] =
111+
runtime.Tuples.tail(tuple).asInstanceOf[Tail[This] & Tail[tuple.type]]
112+
113+
/** Return the size (or arity) of the tuple */
114+
def size: Size[This] & Size[tuple.type] =
115+
runtime.Tuples.size(tuple).asInstanceOf[Size[This] & Size[tuple.type]]
116+
117+
/** Get the i-th element of this tuple.
118+
* Equivalent to productElement but with a precise return type.
119+
*/
120+
def apply(n: Int): Elem[This, n.type] & Elem[tuple.type, n.type] =
121+
runtime.Tuples.apply(tuple, n).asInstanceOf[Elem[This, n.type] & Elem[tuple.type, n.type]]
122+
123+
/** Get the initial part of the tuple without its last element */
124+
def init: Init[This] & Init[tuple.type] =
125+
runtime.Tuples.init(tuple).asInstanceOf[Init[This] & Init[tuple.type]]
126+
127+
/** Get the last of this tuple */
128+
def last: Last[This] & Last[tuple.type] =
129+
runtime.Tuples.last(tuple).asInstanceOf[Last[This] & Last[tuple.type]]
130+
131+
/** Return a copy of `tuple` with an element appended */
132+
def :*[X] (x: X): Append[This, X] & Append[tuple.type, X] =
133+
runtime.Tuples.append(x, tuple).asInstanceOf[Append[This, X] & Append[tuple.type, X]]
134+
135+
/** Return a new tuple by concatenating `this` tuple with `that` tuple.
136+
* This operation is O(this.size + that.size)
137+
*/
138+
def ++(that: Tuple): Concat[This, that.type] & Concat[tuple.type, that.type] =
139+
runtime.Tuples.concat(tuple, that).asInstanceOf[Concat[This, that.type] & Concat[tuple.type, that.type]]
140+
141+
/** Given a tuple `(a1, ..., am)`, returns the reversed tuple `(am, ..., a1)`
142+
* consisting all its elements.
143+
*/
144+
@experimental
145+
def reverse: Reverse[This] & Reverse[tuple.type] =
146+
runtime.Tuples.reverse(tuple).asInstanceOf[Reverse[This] & Reverse[tuple.type]]
147+
148+
/** Given two tuples, `(a1, ..., an)` and `(a1, ..., an)`, returns a tuple
149+
* `((a1, b1), ..., (an, bn))`. If the two tuples have different sizes,
150+
* the extra elements of the larger tuple will be disregarded.
151+
* The result is typed as `((A1, B1), ..., (An, Bn))` if at least one of the
152+
* tuple types has a `EmptyTuple` tail. Otherwise the result type is
153+
* `(A1, B1) *: ... *: (Ai, Bi) *: Tuple`
154+
*/
155+
// TODO change signature? def zip[That <: Tuple](that: That): Zip[This, tuple.type] & Zip[tuple.type, tuple.type] =
156+
def zip[That <: Tuple](that: That): Zip[This, That] & Zip[tuple.type, That] =
157+
runtime.Tuples.zip(tuple, that).asInstanceOf[Zip[This, That] & Zip[tuple.type, That]]
158+
159+
/** Called on a tuple `(a1, ..., an)`, returns a new tuple `(f(a1), ..., f(an))`.
160+
* The result is typed as `(F[A1], ..., F[An])` if the tuple type is fully known.
161+
* If the tuple is of the form `a1 *: ... *: Tuple` (that is, the tail is not known
162+
* to be the cons type.
163+
*/
164+
def map[F[_]](f: [t] => t => F[t]): Map[This, F] & Map[tuple.type, F] =
165+
runtime.Tuples.map(tuple, f).asInstanceOf[Map[This, F] & Map[tuple.type, F]]
166+
167+
/** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting
168+
* of its first n elements.
169+
*/
170+
def take(n: Int): Take[This, n.type] & Take[tuple.type, n.type] =
171+
runtime.Tuples.take(tuple, n).asInstanceOf[Take[This, n.type] & Take[tuple.type, n.type]]
172+
173+
/** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting
174+
* all its elements except the first n ones.
175+
*/
176+
def drop(n: Int): Drop[This, n.type] & Take[tuple.type, n.type] =
177+
runtime.Tuples.drop(tuple, n).asInstanceOf[Drop[This, n.type] & Take[tuple.type, n.type]]
178+
179+
/** Given a tuple `(a1, ..., am)`, returns a pair of the tuple `(a1, ..., an)`
180+
* consisting of the first n elements, and the tuple `(an+1, ..., am)` consisting
181+
* of the remaining elements.
182+
*/
183+
def splitAt(n: Int): Split[This, n.type] & Split[tuple.type, n.type] =
184+
runtime.Tuples.splitAt(tuple, n).asInstanceOf[Split[This, n.type] & Split[tuple.type, n.type]]
185+
186+
/** Create a copy of this tuple as a List */
187+
def toList: List[Union[This]] & List[Union[tuple.type]] =
188+
tuple.productIterator.toList.asInstanceOf[List[Union[This]] & List[Union[tuple.type]]]
189+
end extension
190+
191+
extension (tuple: Tuple)
192+
/** Create a copy of this tuple as an Array */
193+
def toArray: Array[AnyRef] = runtime.Tuples.toArray(tuple)
194+
195+
/** Create a copy of this tuple as an IArray */
196+
def toIArray: IArray[AnyRef] = runtime.Tuples.toIArray(tuple)
197+
end extension
198+
92199
/** Type of a tuple with an element appended */
93200
type Append[X <: Tuple, Y] <: NonEmptyTuple = X match {
94201
case EmptyTuple => Y *: EmptyTuple
@@ -98,24 +205,27 @@ object Tuple {
98205
/** Type of the head of a tuple */
99206
type Head[X <: Tuple] = X match {
100207
case x *: _ => x
208+
case EmptyTuple => Nothing
101209
}
102210

103211
/** Type of the initial part of the tuple without its last element */
104212
type Init[X <: Tuple] <: Tuple = X match {
105213
case _ *: EmptyTuple => EmptyTuple
106-
case x *: xs =>
107-
x *: Init[xs]
214+
case x *: xs => x *: Init[xs]
215+
case EmptyTuple => Nothing
108216
}
109217

110218
/** Type of the tail of a tuple */
111219
type Tail[X <: Tuple] <: Tuple = X match {
112220
case _ *: xs => xs
221+
case EmptyTuple => Nothing
113222
}
114223

115224
/** Type of the last element of a tuple */
116225
type Last[X <: Tuple] = X match {
117226
case x *: EmptyTuple => x
118227
case _ *: xs => Last[xs]
228+
case EmptyTuple => Nothing
119229
}
120230

121231
/** Type of the concatenation of two tuples */
@@ -180,6 +290,7 @@ object Tuple {
180290
* returns the tuple type `(A1, B1) *: ... *: (An, Bn) *: Ct`
181291
* where `Ct` is `EmptyTuple` if `At` or `Bt` is `EmptyTuple`, otherwise `Ct` is `Tuple`.
182292
*/
293+
// TODO should zip be covariant? type Zip[T1 <: Tuple, +T2 <: Tuple] <: Tuple = ...
183294
type Zip[T1 <: Tuple, T2 <: Tuple] <: Tuple = (T1, T2) match {
184295
case (h1 *: t1, h2 *: t2) => (h1, h2) *: Zip[t1, t2]
185296
case (EmptyTuple, _) => EmptyTuple
@@ -294,24 +405,29 @@ sealed trait NonEmptyTuple extends Tuple {
294405
/** Get the i-th element of this tuple.
295406
* Equivalent to productElement but with a precise return type.
296407
*/
408+
private[NonEmptyTuple]
297409
inline def apply[This >: this.type <: NonEmptyTuple](n: Int): Elem[This, n.type] =
298410
runtime.Tuples.apply(this, n).asInstanceOf[Elem[This, n.type]]
299411

300412
/** Get the head of this tuple */
413+
private[NonEmptyTuple]
301414
inline def head[This >: this.type <: NonEmptyTuple]: Head[This] =
302415
runtime.Tuples.apply(this, 0).asInstanceOf[Head[This]]
303416

304417
/** Get the initial part of the tuple without its last element */
418+
private[NonEmptyTuple]
305419
inline def init[This >: this.type <: NonEmptyTuple]: Init[This] =
306420
runtime.Tuples.init(this).asInstanceOf[Init[This]]
307421

308422
/** Get the last of this tuple */
423+
private[NonEmptyTuple]
309424
inline def last[This >: this.type <: NonEmptyTuple]: Last[This] =
310425
runtime.Tuples.last(this).asInstanceOf[Last[This]]
311426

312427
/** Get the tail of this tuple.
313428
* This operation is O(this.size)
314429
*/
430+
private[NonEmptyTuple]
315431
inline def tail[This >: this.type <: NonEmptyTuple]: Tail[This] =
316432
runtime.Tuples.tail(this).asInstanceOf[Tail[This]]
317433
}

library/src/scala/runtime/Tuples.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ object Tuples {
288288
// Tail for Tuple1 to Tuple22
289289
private def specialCaseTail(self: Tuple): Tuple = {
290290
(self: Any) match {
291+
case self: EmptyTuple =>
292+
throw new NoSuchElementException("tail of empty tuple")
291293
case self: Tuple1[?] =>
292294
EmptyTuple
293295
case self: Tuple2[?, ?] =>
@@ -352,7 +354,7 @@ object Tuples {
352354
}
353355
}
354356

355-
def tail(self: NonEmptyTuple): Tuple = (self: Any) match {
357+
def tail(self: Tuple): Tuple = (self: Any) match {
356358
case xxl: TupleXXL => xxlTail(xxl)
357359
case _ => specialCaseTail(self)
358360
}
@@ -514,6 +516,8 @@ object Tuples {
514516
// Init for Tuple1 to Tuple22
515517
private def specialCaseInit(self: Tuple): Tuple = {
516518
(self: Any) match {
519+
case self: EmptyTuple =>
520+
throw new NoSuchElementException("init of empty tuple")
517521
case _: Tuple1[?] =>
518522
EmptyTuple
519523
case self: Tuple2[?, ?] =>
@@ -561,16 +565,16 @@ object Tuples {
561565
}
562566
}
563567

564-
def init(self: NonEmptyTuple): Tuple = (self: Any) match {
568+
def init(self: Tuple): Tuple = (self: Any) match {
565569
case xxl: TupleXXL => xxlInit(xxl)
566570
case _ => specialCaseInit(self)
567571
}
568572

569-
def last(self: NonEmptyTuple): Any = (self: Any) match {
573+
def last(self: Tuple): Any = (self: Any) match {
570574
case self: Product => self.productElement(self.productArity - 1)
571575
}
572576

573-
def apply(self: NonEmptyTuple, n: Int): Any =
577+
def apply(self: Tuple, n: Int): Any =
574578
self.productElement(n)
575579

576580
// Benchmarks showed that this is faster than doing (it1 zip it2).copyToArray(...)

scaladoc/src/dotty/tools/scaladoc/transformers/InheritanceInformationTransformer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package transformers
33

44
class InheritanceInformationTransformer(using DocContext) extends (Module => Module):
55
override def apply(original: Module): Module =
6-
val subtypes = getSupertypes(original.rootPackage).groupMap(_(0))(_(1)).view.mapValues(_.distinct).toMap
6+
val subtypes = getSupertypes(original.rootPackage).groupMap(_._1)(_._2).view.mapValues(_.distinct).toMap
77
original.updateMembers { m =>
88
val edges = getEdges(m.asLink.copy(kind = bareClasslikeKind(m.kind)), subtypes)
99
val st: Seq[LinkToType] = edges.map(_._1).distinct

tests/neg/i13780-1.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
-- [E007] Type Mismatch Error: tests/neg/i13780-1.scala:38:24 ----------------------------------------------------------
1+
-- [E007] Type Mismatch Error: tests/neg/i13780-1.scala:38:26 ----------------------------------------------------------
22
38 | case x: (h *: t) => x.head // error
33
| ^^^^^^
4-
| Found: Tuple.Head[VS & h *: t]
4+
| Found: Tuple.Head[VS & h *: t] & Tuple.Head[(x : VS & h *: t)]
55
| Required: h
66
|
77
| where: VS is a type in method foo with bounds <: Tuple

tests/neg/i13780-1.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* Note that the code can be fixed with an explicit type argument to `.head`:
2020
*
2121
* def foo[VS <: Tuple](x: VS): SelectH[VS] = x match
22-
* case x: (h *: t) => x.head[h *: t]
22+
* case x: (h *: t) => Tuple.head[h *: t](x)
2323
*
2424
* So it *seems* like it would be fine to relax the rule, based on the insight
2525
* that `VS` in `Tuple.Head[VS & (h *: t)]` does not contribute anything to the
@@ -38,7 +38,7 @@ object ExampleFromSpata:
3838
case x: (h *: t) => x.head // error
3939

4040
def bar[VS <: Tuple](x: VS): SelectH[VS] = x match
41-
case x: (h *: t) => x.head[h *: t] // ok
41+
case x: (h *: t) => Tuple.head[h *: t](x) // ok
4242
end ExampleFromSpata
4343

4444
trait Z {

tests/pos/Tuple_apply.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def testTuple(tup: Tuple) = tup(0)
2+
def testNonEmptyTuple(tup: NonEmptyTuple) = tup(0)
3+
def testConsUnbound(tup: Any *: Tuple) = tup(0)
4+
def testCons(tup: Any *: EmptyTuple) = tup(0)

tests/pos/i12721.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def bar(t: Any): Int = 1
2+
def foo(t: AnyRef): Unit =
3+
t.asInstanceOf[NonEmptyTuple].toList.map(bar)

tests/pos/i15743.gadt.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ class Alt:
99
case c1 @ C1() => // GADT constr: T := Tuple
1010
val t1: T = c1.getZ
1111
val t2: Int *: T = (1: Int) *: t1
12-
val i1: Int = (t2: Int *: T).head[Int *: T]
12+
val i1: Int = (t2: Int *: T).head

tests/pos/i15743.pass.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
// Like pos/i15743 but already passed, because the bounds are never lost so reduction never fails
22
class Pass:
33
def pass[T >: Tuple <: Tuple](t2: Int *: T) =
4-
val i1: Int = (t2: Int *: T).head[Int *: T]
4+
val i1: Int = (t2: Int *: T).head

tests/pos/i15743.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ case class Bar[T <: Tuple](val x: Int *: T)
22

33
class Test:
44
def fail(e: Any): Int =
5-
e match { case b: Bar[t] => b.x.head[Int *: t] }
5+
e match { case b: Bar[t] => b.x.head }

tests/pos/i16207.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.compiletime.constValueTuple
2+
import scala.deriving.Mirror.ProductOf
3+
4+
case class C(date: Int, time: Int)
5+
6+
inline def labelsOf[A](using p: ProductOf[A]): Tuple = constValueTuple[p.MirroredElemLabels]
7+
8+
val headers: List[String] = labelsOf[C].toList.map(_.toString)

0 commit comments

Comments
 (0)