From 6ede644025c5a82f22ef69048a85d96b36825ebe Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 4 Dec 2023 13:25:03 +0100 Subject: [PATCH] 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 #12721 Fixes #15992 Fixes #16207 --- library/src/scala/Tuple.scala | 148 +++++++++++++++--- tests/pos/i12721.scala | 3 + tests/pos/i16207.scala | 8 + .../stdlibExperimentalDefinitions.scala | 1 + tests/run/i15992.scala | 42 +++++ tests/run/tuples1.scala | 2 +- 6 files changed, 177 insertions(+), 27 deletions(-) create mode 100644 tests/pos/i12721.scala create mode 100644 tests/pos/i16207.scala create mode 100644 tests/run/i15992.scala diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index ae46f7fbf347..4105d2e7fb2c 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -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, @@ -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 diff --git a/tests/pos/i12721.scala b/tests/pos/i12721.scala new file mode 100644 index 000000000000..29cac9da71ba --- /dev/null +++ b/tests/pos/i12721.scala @@ -0,0 +1,3 @@ + def bar(t: Any): Int = 1 + def foo(t: AnyRef): Unit = + t.asInstanceOf[NonEmptyTuple].toList.map(bar) diff --git a/tests/pos/i16207.scala b/tests/pos/i16207.scala new file mode 100644 index 000000000000..9d96d014a619 --- /dev/null +++ b/tests/pos/i16207.scala @@ -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) diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index a01c71724b0e..ad6aa94880e2 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -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", diff --git a/tests/run/i15992.scala b/tests/run/i15992.scala new file mode 100644 index 000000000000..7ec22f44f811 --- /dev/null +++ b/tests/run/i15992.scala @@ -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))) diff --git a/tests/run/tuples1.scala b/tests/run/tuples1.scala index 53b2c43852cd..61ec099989aa 100644 --- a/tests/run/tuples1.scala +++ b/tests/run/tuples1.scala @@ -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)