Skip to content

Commit fd413fd

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. Old definitions are made tasty compatible and just delegate to the new representation. Fixes scala#12721 Fixes scala#15992 Fixes scala#16207
1 parent d96e9e4 commit fd413fd

File tree

6 files changed

+177
-27
lines changed

6 files changed

+177
-27
lines changed

library/src/scala/Tuple.scala

Lines changed: 122 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,44 @@ sealed trait Tuple extends Product {
99
import Tuple.*
1010

1111
/** Create a copy of this tuple as an Array */
12-
inline def toArray: Array[Object] =
13-
runtime.Tuples.toArray(this)
12+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
13+
private[Tuple] inline def toArray: Array[Object] =
14+
Tuple.toArray(this)
1415

1516
/** Create a copy of this tuple as a List */
16-
inline def toList: List[Union[this.type]] =
17-
this.productIterator.toList
18-
.asInstanceOf[List[Union[this.type]]]
17+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
18+
private[Tuple] inline def toList: List[Union[this.type]] =
19+
Tuple.toList(this)
1920

2021
/** Create a copy of this tuple as an IArray */
21-
inline def toIArray: IArray[Object] =
22-
runtime.Tuples.toIArray(this)
22+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
23+
private[Tuple] inline def toIArray: IArray[Object] =
24+
Tuple.toIArray(this)
2325

2426
/** Return a copy of `this` tuple with an element appended */
25-
inline def :* [This >: this.type <: Tuple, L] (x: L): Append[This, L] =
26-
runtime.Tuples.append(x, this).asInstanceOf[Append[This, L]]
27+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
28+
private[Tuple] inline def :* [This >: this.type <: Tuple, L] (x: L): Append[This, L] =
29+
Tuple.:*(this)(x)
2730

2831
/** Return a new tuple by prepending the element to `this` tuple.
2932
* This operation is O(this.size)
3033
*/
34+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
3135
inline def *: [H, This >: this.type <: Tuple] (x: H): H *: This =
36+
// Tuple.*:(this)(x) // FIXME
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
*/
37-
inline def ++ [This >: this.type <: Tuple](that: Tuple): Concat[This, that.type] =
38-
runtime.Tuples.concat(this, that).asInstanceOf[Concat[This, that.type]]
42+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
43+
private[Tuple] inline def ++ [This >: this.type <: Tuple](that: Tuple): Concat[This, that.type] =
44+
Tuple.++(this)(that)
3945

4046
/** Return the size (or arity) of the tuple */
41-
inline def size[This >: this.type <: Tuple]: Size[This] =
42-
runtime.Tuples.size(this).asInstanceOf[Size[This]]
47+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
48+
private[Tuple] inline def size[This >: this.type <: Tuple]: Size[This] =
49+
Tuple.size(this).asInstanceOf[Size[This]]
4350

4451
/** Given two tuples, `(a1, ..., an)` and `(a1, ..., an)`, returns a tuple
4552
* `((a1, b1), ..., (an, bn))`. If the two tuples have different sizes,
@@ -48,47 +55,136 @@ sealed trait Tuple extends Product {
4855
* tuple types has a `EmptyTuple` tail. Otherwise the result type is
4956
* `(A1, B1) *: ... *: (Ai, Bi) *: Tuple`
5057
*/
51-
inline def zip[This >: this.type <: Tuple, T2 <: Tuple](t2: T2): Zip[This, T2] =
52-
runtime.Tuples.zip(this, t2).asInstanceOf[Zip[This, T2]]
58+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
59+
private[Tuple] inline def zip[This >: this.type <: Tuple, T2 <: Tuple](t2: T2): Zip[This, T2] =
60+
Tuple.zip(this)(t2)
5361

5462
/** Called on a tuple `(a1, ..., an)`, returns a new tuple `(f(a1), ..., f(an))`.
5563
* The result is typed as `(F[A1], ..., F[An])` if the tuple type is fully known.
5664
* If the tuple is of the form `a1 *: ... *: Tuple` (that is, the tail is not known
5765
* to be the cons type.
5866
*/
59-
inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] =
60-
runtime.Tuples.map(this, f).asInstanceOf[Map[this.type, F]]
67+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
68+
private[Tuple] inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] =
69+
Tuple.map(this)(f)
6170

6271
/** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting
6372
* of its first n elements.
6473
*/
65-
inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] =
66-
runtime.Tuples.take(this, n).asInstanceOf[Take[This, n.type]]
74+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
75+
private[Tuple] inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] =
76+
Tuple.take(this)(n)
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
*/
72-
inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] =
73-
runtime.Tuples.drop(this, n).asInstanceOf[Drop[This, n.type]]
82+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
83+
private[Tuple] inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] =
84+
Tuple.drop(this)(n)
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
*/
79-
inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] =
80-
runtime.Tuples.splitAt(this, n).asInstanceOf[Split[This, n.type]]
90+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
91+
private[Tuple] inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] =
92+
Tuple.splitAt(this)(n)
8193

8294
/** Given a tuple `(a1, ..., am)`, returns the reversed tuple `(am, ..., a1)`
8395
* consisting all its elements.
8496
*/
85-
@experimental
86-
inline def reverse[This >: this.type <: Tuple]: Reverse[This] =
87-
runtime.Tuples.reverse(this).asInstanceOf[Reverse[This]]
97+
@experimental // We may remove this one May not need to add this method if it is not yet stable
98+
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
99+
private[Tuple] inline def reverse[This >: this.type <: Tuple]: Reverse[This] =
100+
Tuple.reverse(this)
88101
}
89102

90103
object Tuple {
91104

105+
// FIXME
106+
// fails tests/run-deep-subtype/Tuple-size.scala:40:19
107+
// 40 | assert(1 == (1 *: Tuple()).size)
108+
// | ^^
109+
// | illegal repeated type application
110+
// | You might have meant something like:
111+
// | Tuple.*:[Int, EmptyTuple.type]
112+
// extension [H](x: H)
113+
// /** Return a new tuple by prepending the element to `tail` tuple.
114+
// * This operation is O(tail.size)
115+
// */
116+
// def *:[T <: Tuple](tail: T): H *: T = runtime.Tuples.cons(x, tail).asInstanceOf[H *: T]
117+
118+
extension [This <: Tuple](tuple: This)
119+
120+
/** Return the size (or arity) of the tuple */
121+
def size: Size[This] = runtime.Tuples.size(tuple).asInstanceOf[Size[This]]
122+
123+
/** Return a copy of `tuple` with an element appended */
124+
def :*[X] (x: X): Append[This, X] = runtime.Tuples.append(x, tuple).asInstanceOf[Append[This, X]]
125+
126+
/** Return a new tuple by concatenating `this` tuple with `that` tuple.
127+
* This operation is O(this.size + that.size)
128+
*/
129+
def ++(that: Tuple): Concat[This, that.type] = // TODO change signature? def ++[That <: Tuple](that: That): Concat[This, That]
130+
runtime.Tuples.concat(tuple, that).asInstanceOf[Concat[This, that.type]]
131+
132+
/** Given a tuple `(a1, ..., am)`, returns the reversed tuple `(am, ..., a1)`
133+
* consisting all its elements.
134+
*/
135+
@experimental
136+
def reverse: Reverse[This] =
137+
runtime.Tuples.reverse(tuple).asInstanceOf[Reverse[This]]
138+
139+
/** Given two tuples, `(a1, ..., an)` and `(a1, ..., an)`, returns a tuple
140+
* `((a1, b1), ..., (an, bn))`. If the two tuples have different sizes,
141+
* the extra elements of the larger tuple will be disregarded.
142+
* The result is typed as `((A1, B1), ..., (An, Bn))` if at least one of the
143+
* tuple types has a `EmptyTuple` tail. Otherwise the result type is
144+
* `(A1, B1) *: ... *: (Ai, Bi) *: Tuple`
145+
*/
146+
def zip[That <: Tuple](that: That): Zip[This, That] = // TODO change signature? def zip(that: Tuple): Zip[This, that.type]
147+
runtime.Tuples.zip(tuple, that).asInstanceOf[Zip[This, That]]
148+
149+
/** Called on a tuple `(a1, ..., an)`, returns a new tuple `(f(a1), ..., f(an))`.
150+
* The result is typed as `(F[A1], ..., F[An])` if the tuple type is fully known.
151+
* If the tuple is of the form `a1 *: ... *: Tuple` (that is, the tail is not known
152+
* to be the cons type.
153+
*/
154+
def map[F[_]](f: [t] => t => F[t]): Map[This, F] =
155+
runtime.Tuples.map(tuple, f).asInstanceOf[Map[This, F]]
156+
157+
/** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting
158+
* of its first n elements.
159+
*/
160+
def take(n: Int): Take[This, n.type] =
161+
runtime.Tuples.take(tuple, n).asInstanceOf[Take[This, n.type]]
162+
163+
164+
/** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting
165+
* all its elements except the first n ones.
166+
*/
167+
def drop(n: Int): Drop[This, n.type] =
168+
runtime.Tuples.drop(tuple, n).asInstanceOf[Drop[This, n.type]]
169+
170+
/** Given a tuple `(a1, ..., am)`, returns a pair of the tuple `(a1, ..., an)`
171+
* consisting of the first n elements, and the tuple `(an+1, ..., am)` consisting
172+
* of the remaining elements.
173+
*/
174+
def splitAt(n: Int): Split[This, n.type] =
175+
runtime.Tuples.splitAt(tuple, n).asInstanceOf[Split[This, n.type]]
176+
177+
/** Create a copy of this tuple as a List */
178+
def toList: List[Union[This]] =
179+
tuple.productIterator.toList.asInstanceOf[List[Union[This]]]
180+
181+
extension (tuple: Tuple)
182+
/** Create a copy of this tuple as an Array */
183+
def toArray: Array[Object] = runtime.Tuples.toArray(tuple)
184+
185+
/** Create a copy of this tuple as an IArray */
186+
def toIArray: IArray[Object] = runtime.Tuples.toIArray(tuple)
187+
92188
/** Type of a tuple with an element appended */
93189
type Append[X <: Tuple, Y] <: NonEmptyTuple = X match {
94190
case EmptyTuple => Y *: EmptyTuple

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/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)

tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ val experimentalDefinitionInLibrary = Set(
9393

9494
// New feature: reverse method on Tuple
9595
"scala.Tuple.reverse",
96+
"scala.Tuple$.reverse",
9697
"scala.Tuple$.Helpers",
9798
"scala.Tuple$.Helpers$",
9899
"scala.Tuple$.Helpers$.ReverseImpl",

tests/run/i15992.scala

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import language.implicitConversions
2+
3+
class Inverse[F[_], G[_]]
4+
type MatchSome[X] = X match { case Some[t] => t }
5+
def matchSome[X](x: X): MatchSome[X] = x match { case k: Some[t] => k.get }
6+
given Inverse[MatchSome, Some] = new Inverse
7+
8+
extension [T](x: T) inline def widen[S >: T]: S = x
9+
10+
given [X, Y](using ev: X =:= Y): Conversion[X, Y] = ev(_)
11+
inline def rel[A] = <:<.refl[Any].asInstanceOf[A]
12+
given simplify_map_map[T <: Tuple, F[_], G[_]]: (Tuple.Map[Tuple.Map[T, G], F] =:= Tuple.Map[T, [X] =>> F[G[X]]]) = rel
13+
given simplify_map_id[T <: Tuple]: (Tuple.Map[T, [X] =>> X] =:= T) = rel
14+
given mapinverse_inversemap[T <: Tuple, F[_], G[_]](using Inverse[F, G]): (Tuple.Map[T, F] =:= Tuple.InverseMap[T, G]) = rel
15+
given simplify_map_inversemap[T <: Tuple, F[_]]: (Tuple.InverseMap[Tuple.Map[T, F], F] =:= T) = rel
16+
given map_ismappedby[O <: Tuple, F[_], M <: Tuple.Map[O, F]]: Tuple.IsMappedBy[F][M] = rel
17+
18+
19+
def f[T <: Tuple](t: T): Tuple.Map[T, [X] =>> Option[List[X]]] =
20+
val tl: Tuple.Map[T, List] = t.widen.map([X] => (x: X) => List(x))
21+
val tol: Tuple.Map[Tuple.Map[T, List], Option] = tl.widen.map([X] => (x: X) => Option(x))
22+
tol
23+
24+
def g[T <: Tuple](t: T): T =
25+
val nt: Tuple.Map[T, [X] =>> X] = t.widen.map[[X] =>> X]([X] => (x: X) => {println(x); x})
26+
nt
27+
28+
def h[T <: Tuple](to: T)(using Tuple.IsMappedBy[Some][T]): Tuple.InverseMap[T, Some] =
29+
val t: Tuple.Map[T, MatchSome] = to.widen.map([X] => (x: X) => matchSome(x))
30+
t
31+
32+
def back_forth[T <: Tuple](t: T): T =
33+
val ts: Tuple.Map[T, Some] = t.widen.map([X] => (x: X) => Some(x))
34+
val nt = h(ts)
35+
nt
36+
37+
38+
@main def Test =
39+
println(f(4, 2))
40+
println(g(4, 2))
41+
println(back_forth(4, 2))
42+
println(h(Some(1), Some(2)))

tests/run/tuples1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ object Test extends App {
9090
val conc6: (String, Int, String, Int) = concat0(tl1, tl1)
9191

9292
def size[X <: Tuple](x: X): Tuple.Size[X] = x.size
93-
def size0(x: Tuple): Tuple.Size[x.type] = x.size
93+
def size0(x: Tuple): Tuple.Size[x.type] = Tuple.size[x.type](x)
9494
val x3s0: 3 = size(x3)
9595
val us0: 0 = size(Tuple())
9696
val x3s1: 3 = size0(x3)

0 commit comments

Comments
 (0)