Skip to content

Commit

Permalink
Redefine Tuple operations
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nicolasstucki committed Dec 7, 2023
1 parent 5fdbba4 commit 6ede644
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 27 deletions.
148 changes: 122 additions & 26 deletions library/src/scala/Tuple.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,44 @@ sealed trait Tuple extends Product {
import Tuple.*

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

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

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

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

/** Return a new tuple by prepending the element to `this` tuple.
* This operation is O(this.size)
*/
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
inline def *: [H, This >: this.type <: Tuple] (x: H): H *: This =
// Tuple.*:(this)(x) // FIXME
runtime.Tuples.cons(x, this).asInstanceOf[H *: This]

/** Return a new tuple by concatenating `this` tuple with `that` tuple.
* This operation is O(this.size + that.size)
*/
inline def ++ [This >: this.type <: Tuple](that: Tuple): Concat[This, that.type] =
runtime.Tuples.concat(this, that).asInstanceOf[Concat[This, that.type]]
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
private[Tuple] inline def ++ [This >: this.type <: Tuple](that: Tuple): Concat[This, that.type] =
Tuple.++(this)(that)

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

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

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

/** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting
* of its first n elements.
*/
inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] =
runtime.Tuples.take(this, n).asInstanceOf[Take[This, n.type]]
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
private[Tuple] inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] =
Tuple.take(this)(n)


/** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting
* all its elements except the first n ones.
*/
inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] =
runtime.Tuples.drop(this, n).asInstanceOf[Drop[This, n.type]]
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
private[Tuple] inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] =
Tuple.drop(this)(n)

/** Given a tuple `(a1, ..., am)`, returns a pair of the tuple `(a1, ..., an)`
* consisting of the first n elements, and the tuple `(an+1, ..., am)` consisting
* of the remaining elements.
*/
inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] =
runtime.Tuples.splitAt(this, n).asInstanceOf[Split[This, n.type]]
// @publicInBinary // For TASTy compatibility, but not actually available in the the binary
private[Tuple] inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] =
Tuple.splitAt(this)(n)

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

object Tuple {

// FIXME
// fails tests/run-deep-subtype/Tuple-size.scala:40:19
// 40 | assert(1 == (1 *: Tuple()).size)
// | ^^
// | illegal repeated type application
// | You might have meant something like:
// | Tuple.*:[Int, EmptyTuple.type]
// extension [H](x: H)
// /** Return a new tuple by prepending the element to `tail` tuple.
// * This operation is O(tail.size)
// */
// def *:[T <: Tuple](tail: T): H *: T = runtime.Tuples.cons(x, tail).asInstanceOf[H *: T]

extension [This <: Tuple](tuple: This)

/** Return the size (or arity) of the tuple */
def size: Size[This] = runtime.Tuples.size(tuple).asInstanceOf[Size[This]]

/** Return a copy of `tuple` with an element appended */
def :*[X] (x: X): Append[This, X] = runtime.Tuples.append(x, tuple).asInstanceOf[Append[This, X]]

/** Return a new tuple by concatenating `this` tuple with `that` tuple.
* This operation is O(this.size + that.size)
*/
def ++(that: Tuple): Concat[This, that.type] = // TODO change signature? def ++[That <: Tuple](that: That): Concat[This, That]
runtime.Tuples.concat(tuple, that).asInstanceOf[Concat[This, that.type]]

/** Given a tuple `(a1, ..., am)`, returns the reversed tuple `(am, ..., a1)`
* consisting all its elements.
*/
@experimental
def reverse: Reverse[This] =
runtime.Tuples.reverse(tuple).asInstanceOf[Reverse[This]]

/** Given two tuples, `(a1, ..., an)` and `(a1, ..., an)`, returns a tuple
* `((a1, b1), ..., (an, bn))`. If the two tuples have different sizes,
* the extra elements of the larger tuple will be disregarded.
* The result is typed as `((A1, B1), ..., (An, Bn))` if at least one of the
* tuple types has a `EmptyTuple` tail. Otherwise the result type is
* `(A1, B1) *: ... *: (Ai, Bi) *: Tuple`
*/
def zip[That <: Tuple](that: That): Zip[This, That] = // TODO change signature? def zip(that: Tuple): Zip[This, that.type]
runtime.Tuples.zip(tuple, that).asInstanceOf[Zip[This, That]]

/** Called on a tuple `(a1, ..., an)`, returns a new tuple `(f(a1), ..., f(an))`.
* The result is typed as `(F[A1], ..., F[An])` if the tuple type is fully known.
* If the tuple is of the form `a1 *: ... *: Tuple` (that is, the tail is not known
* to be the cons type.
*/
def map[F[_]](f: [t] => t => F[t]): Map[This, F] =
runtime.Tuples.map(tuple, f).asInstanceOf[Map[This, F]]

/** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting
* of its first n elements.
*/
def take(n: Int): Take[This, n.type] =
runtime.Tuples.take(tuple, n).asInstanceOf[Take[This, n.type]]


/** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting
* all its elements except the first n ones.
*/
def drop(n: Int): Drop[This, n.type] =
runtime.Tuples.drop(tuple, n).asInstanceOf[Drop[This, n.type]]

/** Given a tuple `(a1, ..., am)`, returns a pair of the tuple `(a1, ..., an)`
* consisting of the first n elements, and the tuple `(an+1, ..., am)` consisting
* of the remaining elements.
*/
def splitAt(n: Int): Split[This, n.type] =
runtime.Tuples.splitAt(tuple, n).asInstanceOf[Split[This, n.type]]

/** Create a copy of this tuple as a List */
def toList: List[Union[This]] =
tuple.productIterator.toList.asInstanceOf[List[Union[This]]]

extension (tuple: Tuple)
/** Create a copy of this tuple as an Array */
def toArray: Array[Object] = runtime.Tuples.toArray(tuple)

/** Create a copy of this tuple as an IArray */
def toIArray: IArray[Object] = runtime.Tuples.toIArray(tuple)

/** Type of a tuple with an element appended */
type Append[X <: Tuple, Y] <: NonEmptyTuple = X match {
case EmptyTuple => Y *: EmptyTuple
Expand Down
3 changes: 3 additions & 0 deletions tests/pos/i12721.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def bar(t: Any): Int = 1
def foo(t: AnyRef): Unit =
t.asInstanceOf[NonEmptyTuple].toList.map(bar)
8 changes: 8 additions & 0 deletions tests/pos/i16207.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import scala.compiletime.constValueTuple
import scala.deriving.Mirror.ProductOf

case class C(date: Int, time: Int)

inline def labelsOf[A](using p: ProductOf[A]): Tuple = constValueTuple[p.MirroredElemLabels]

val headers: List[String] = labelsOf[C].toList.map(_.toString)
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ val experimentalDefinitionInLibrary = Set(

// New feature: reverse method on Tuple
"scala.Tuple.reverse",
"scala.Tuple$.reverse",
"scala.Tuple$.Helpers",
"scala.Tuple$.Helpers$",
"scala.Tuple$.Helpers$.ReverseImpl",
Expand Down
42 changes: 42 additions & 0 deletions tests/run/i15992.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import language.implicitConversions

class Inverse[F[_], G[_]]
type MatchSome[X] = X match { case Some[t] => t }
def matchSome[X](x: X): MatchSome[X] = x match { case k: Some[t] => k.get }
given Inverse[MatchSome, Some] = new Inverse

extension [T](x: T) inline def widen[S >: T]: S = x

given [X, Y](using ev: X =:= Y): Conversion[X, Y] = ev(_)
inline def rel[A] = <:<.refl[Any].asInstanceOf[A]
given simplify_map_map[T <: Tuple, F[_], G[_]]: (Tuple.Map[Tuple.Map[T, G], F] =:= Tuple.Map[T, [X] =>> F[G[X]]]) = rel
given simplify_map_id[T <: Tuple]: (Tuple.Map[T, [X] =>> X] =:= T) = rel
given mapinverse_inversemap[T <: Tuple, F[_], G[_]](using Inverse[F, G]): (Tuple.Map[T, F] =:= Tuple.InverseMap[T, G]) = rel
given simplify_map_inversemap[T <: Tuple, F[_]]: (Tuple.InverseMap[Tuple.Map[T, F], F] =:= T) = rel
given map_ismappedby[O <: Tuple, F[_], M <: Tuple.Map[O, F]]: Tuple.IsMappedBy[F][M] = rel


def f[T <: Tuple](t: T): Tuple.Map[T, [X] =>> Option[List[X]]] =
val tl: Tuple.Map[T, List] = t.widen.map([X] => (x: X) => List(x))
val tol: Tuple.Map[Tuple.Map[T, List], Option] = tl.widen.map([X] => (x: X) => Option(x))
tol

def g[T <: Tuple](t: T): T =
val nt: Tuple.Map[T, [X] =>> X] = t.widen.map[[X] =>> X]([X] => (x: X) => {println(x); x})
nt

def h[T <: Tuple](to: T)(using Tuple.IsMappedBy[Some][T]): Tuple.InverseMap[T, Some] =
val t: Tuple.Map[T, MatchSome] = to.widen.map([X] => (x: X) => matchSome(x))
t

def back_forth[T <: Tuple](t: T): T =
val ts: Tuple.Map[T, Some] = t.widen.map([X] => (x: X) => Some(x))
val nt = h(ts)
nt


@main def Test =
println(f(4, 2))
println(g(4, 2))
println(back_forth(4, 2))
println(h(Some(1), Some(2)))
2 changes: 1 addition & 1 deletion tests/run/tuples1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ object Test extends App {
val conc6: (String, Int, String, Int) = concat0(tl1, tl1)

def size[X <: Tuple](x: X): Tuple.Size[X] = x.size
def size0(x: Tuple): Tuple.Size[x.type] = x.size
def size0(x: Tuple): Tuple.Size[x.type] = Tuple.size[x.type](x)
val x3s0: 3 = size(x3)
val us0: 0 = size(Tuple())
val x3s1: 3 = size0(x3)
Expand Down

0 comments on commit 6ede644

Please sign in to comment.