diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 6993b8202082..74dd434682d4 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)