Skip to content

Commit 619c2b2

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 #12721 Fixes #16207
1 parent e4495df commit 619c2b2

File tree

15 files changed

+173
-35
lines changed

15 files changed

+173
-35
lines changed

Diff for: compiler/test/dotc/pos-test-pickling.blacklist

+1
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

Diff for: library/src-bootstrapped/scala/Tuple.scala

+127-9
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,42 @@ 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 */
28+
private[Tuple]
2529
inline def :* [This >: this.type <: Tuple, L] (x: L): Append[This, L] =
2630
runtime.Tuples.append(x, this).asInstanceOf[Append[This, L]]
2731

2832
/** Return a new tuple by prepending the element to `this` tuple.
2933
* This operation is O(this.size)
3034
*/
35+
private[Tuple]
3136
inline def *: [H, This >: this.type <: Tuple] (x: H): H *: This =
3237
runtime.Tuples.cons(x, this).asInstanceOf[H *: This]
3338

3439
/** Return a new tuple by concatenating `this` tuple with `that` tuple.
3540
* This operation is O(this.size + that.size)
3641
*/
42+
private[Tuple]
3743
inline def ++ [This >: this.type <: Tuple](that: Tuple): Concat[This, that.type] =
3844
runtime.Tuples.concat(this, that).asInstanceOf[Concat[This, that.type]]
3945

4046
/** Return the size (or arity) of the tuple */
47+
private[Tuple]
4148
inline def size[This >: this.type <: Tuple]: Size[This] =
4249
runtime.Tuples.size(this).asInstanceOf[Size[This]]
4350

@@ -48,6 +55,7 @@ sealed trait Tuple extends Product {
4855
* tuple types has a `EmptyTuple` tail. Otherwise the result type is
4956
* `(A1, B1) *: ... *: (Ai, Bi) *: Tuple`
5057
*/
58+
private[Tuple]
5159
inline def zip[This >: this.type <: Tuple, T2 <: Tuple](t2: T2): Zip[This, T2] =
5260
runtime.Tuples.zip(this, t2).asInstanceOf[Zip[This, T2]]
5361

@@ -56,39 +64,140 @@ sealed trait Tuple extends Product {
5664
* If the tuple is of the form `a1 *: ... *: Tuple` (that is, the tail is not known
5765
* to be the cons type.
5866
*/
67+
private[Tuple]
5968
inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] =
6069
runtime.Tuples.map(this, f).asInstanceOf[Map[this.type, F]]
6170

6271
/** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting
6372
* of its first n elements.
6473
*/
74+
private[Tuple]
6575
inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] =
6676
runtime.Tuples.take(this, n).asInstanceOf[Take[This, n.type]]
6777

6878

6979
/** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting
7080
* all its elements except the first n ones.
7181
*/
82+
private[Tuple]
7283
inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] =
7384
runtime.Tuples.drop(this, n).asInstanceOf[Drop[This, n.type]]
7485

7586
/** Given a tuple `(a1, ..., am)`, returns a pair of the tuple `(a1, ..., an)`
7687
* consisting of the first n elements, and the tuple `(an+1, ..., am)` consisting
7788
* of the remaining elements.
7889
*/
90+
private[Tuple]
7991
inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] =
8092
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]]
8893
}
8994

9095
object Tuple {
9196

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

103213
/** Type of the initial part of the tuple without its last element */
104214
type Init[X <: Tuple] <: Tuple = X match {
105215
case _ *: EmptyTuple => EmptyTuple
106-
case x *: xs =>
107-
x *: Init[xs]
216+
case x *: xs => x *: Init[xs]
217+
case EmptyTuple => Nothing
108218
}
109219

110220
/** Type of the tail of a tuple */
111221
type Tail[X <: Tuple] <: Tuple = X match {
112222
case _ *: xs => xs
223+
case EmptyTuple => Nothing
113224
}
114225

115226
/** Type of the last element of a tuple */
116227
type Last[X <: Tuple] = X match {
117228
case x *: EmptyTuple => x
118229
case _ *: xs => Last[xs]
230+
case EmptyTuple => Nothing
119231
}
120232

121233
/** Type of the concatenation of two tuples */
@@ -180,6 +292,7 @@ object Tuple {
180292
* returns the tuple type `(A1, B1) *: ... *: (An, Bn) *: Ct`
181293
* where `Ct` is `EmptyTuple` if `At` or `Bt` is `EmptyTuple`, otherwise `Ct` is `Tuple`.
182294
*/
295+
// TODO should zip be covariant? type Zip[T1 <: Tuple, +T2 <: Tuple] <: Tuple = ...
183296
type Zip[T1 <: Tuple, T2 <: Tuple] <: Tuple = (T1, T2) match {
184297
case (h1 *: t1, h2 *: t2) => (h1, h2) *: Zip[t1, t2]
185298
case (EmptyTuple, _) => EmptyTuple
@@ -294,24 +407,29 @@ sealed trait NonEmptyTuple extends Tuple {
294407
/** Get the i-th element of this tuple.
295408
* Equivalent to productElement but with a precise return type.
296409
*/
410+
private[NonEmptyTuple]
297411
inline def apply[This >: this.type <: NonEmptyTuple](n: Int): Elem[This, n.type] =
298412
runtime.Tuples.apply(this, n).asInstanceOf[Elem[This, n.type]]
299413

300414
/** Get the head of this tuple */
415+
private[NonEmptyTuple]
301416
inline def head[This >: this.type <: NonEmptyTuple]: Head[This] =
302417
runtime.Tuples.apply(this, 0).asInstanceOf[Head[This]]
303418

304419
/** Get the initial part of the tuple without its last element */
420+
private[NonEmptyTuple]
305421
inline def init[This >: this.type <: NonEmptyTuple]: Init[This] =
306422
runtime.Tuples.init(this).asInstanceOf[Init[This]]
307423

308424
/** Get the last of this tuple */
425+
private[NonEmptyTuple]
309426
inline def last[This >: this.type <: NonEmptyTuple]: Last[This] =
310427
runtime.Tuples.last(this).asInstanceOf[Last[This]]
311428

312429
/** Get the tail of this tuple.
313430
* This operation is O(this.size)
314431
*/
432+
private[NonEmptyTuple]
315433
inline def tail[This >: this.type <: NonEmptyTuple]: Tail[This] =
316434
runtime.Tuples.tail(this).asInstanceOf[Tail[This]]
317435
}

Diff for: library/src/scala/runtime/Tuples.scala

+8-4
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(...)

Diff for: scaladoc/src/dotty/tools/scaladoc/transformers/InheritanceInformationTransformer.scala

+1-1
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

Diff for: tests/neg/i13780-1.check

+2-2
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

Diff for: tests/neg/i13780-1.scala

+2-2
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 {

Diff for: tests/pos/Tuple_apply.scala

+4
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)

Diff for: tests/pos/i12721.scala

+3
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)

Diff for: tests/pos/i15743.gadt.scala

+1-1
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

Diff for: tests/pos/i15743.pass.scala

+1-1
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

Diff for: tests/pos/i15743.scala

+1-1
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 }

Diff for: tests/pos/i16207.scala

+8
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)