From 7e6475a792596a83d4e2fbdc13240452d23d38bd 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. Fixes #12721 Fixes #16207 --- .../test/dotc/pos-test-pickling.blacklist | 1 + library/src-bootstrapped/scala/Tuple.scala | 444 ++++++++++++++++++ .../scala/Tuple.scala | 104 ++++ library/src/scala/runtime/Tuples.scala | 12 +- tests/pos/Tuple_apply.scala | 4 + tests/pos/i12721.scala | 3 + tests/pos/i15743.gadt.scala | 2 +- tests/pos/i15743.pass.scala | 2 +- tests/pos/i15743.scala | 2 +- tests/pos/i16207.scala | 8 + tests/run-staging/liftables.check | 18 +- .../stdlibExperimentalDefinitions.scala | 2 +- tests/run/tuples1.scala | 2 +- 13 files changed, 586 insertions(+), 18 deletions(-) create mode 100644 library/src-bootstrapped/scala/Tuple.scala rename library/{src => src-non-bootstrapped}/scala/Tuple.scala (69%) create mode 100644 tests/pos/Tuple_apply.scala create mode 100644 tests/pos/i12721.scala create mode 100644 tests/pos/i16207.scala diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index b0da78f0a1eb..5716e28a604a 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -63,6 +63,7 @@ i17149.scala tuple-fold.scala mt-redux-norm.perspective.scala i18211.scala +i15743.scala # Opaque type i5720.scala diff --git a/library/src-bootstrapped/scala/Tuple.scala b/library/src-bootstrapped/scala/Tuple.scala new file mode 100644 index 000000000000..57a3c3247efc --- /dev/null +++ b/library/src-bootstrapped/scala/Tuple.scala @@ -0,0 +1,444 @@ +package scala + +import annotation.{experimental, showAsInfix} +import compiletime.* +import compiletime.ops.int.* + +/** Tuple of arbitrary arity */ +sealed trait Tuple extends Product { + import Tuple.* + + /** Create a copy of this tuple as an Array */ + private[Tuple] + inline def toArray: Array[Object] = + runtime.Tuples.toArray(this) + + /** Create a copy of this tuple as a List */ + private[Tuple] + inline def toList: List[Union[this.type]] = + this.productIterator.toList + .asInstanceOf[List[Union[this.type]]] + + /** Create a copy of this tuple as an IArray */ + private[Tuple] + inline def toIArray: IArray[Object] = + runtime.Tuples.toIArray(this) + + /** Return a copy of `this` tuple with an element appended */ + private[Tuple] + inline def :* [This >: this.type <: Tuple, L] (x: L): Append[This, L] = + runtime.Tuples.append(x, this).asInstanceOf[Append[This, L]] + + /** Return a new tuple by prepending the element to `this` tuple. + * This operation is O(this.size) + */ + private[Tuple] + inline def *: [H, This >: this.type <: Tuple] (x: H): H *: This = + 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) + */ + private[Tuple] + inline def ++ [This >: this.type <: Tuple](that: Tuple): Concat[This, that.type] = + runtime.Tuples.concat(this, that).asInstanceOf[Concat[This, that.type]] + + /** Return the size (or arity) of the tuple */ + private[Tuple] + inline def size[This >: this.type <: Tuple]: Size[This] = + runtime.Tuples.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, + * 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` + */ + private[Tuple] + inline def zip[This >: this.type <: Tuple, T2 <: Tuple](t2: T2): Zip[This, T2] = + runtime.Tuples.zip(this, t2).asInstanceOf[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. + */ + private[Tuple] + inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] = + runtime.Tuples.map(this, f).asInstanceOf[Map[this.type, F]] + + /** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting + * of its first n elements. + */ + private[Tuple] + inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] = + runtime.Tuples.take(this, 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. + */ + private[Tuple] + inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] = + runtime.Tuples.drop(this, 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. + */ + private[Tuple] + inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] = + runtime.Tuples.splitAt(this, n).asInstanceOf[Split[This, n.type]] +} + +object Tuple { + + // TODO should it be `extension [H](x: H) def *:(tail: Tuple): H *: tuple.type` ? + extension [H, Tail <: Tuple](x: H) + /** Return a new tuple by prepending the element to `tail` tuple. + * This operation is O(tail.size) + */ + def *:(tail: Tail): H *: Tail = runtime.Tuples.cons(x, tail).asInstanceOf[H *: Tail] + + extension [This <: Tuple](tuple: This) + /** Get the head of this tuple */ + def head: Head[This] & Head[tuple.type] = + runtime.Tuples.apply(tuple, 0).asInstanceOf[Head[This] & Head[tuple.type]] + + /** Get the tail of this tuple. + * This operation is O(tuple.size) + */ + def tail: Tail[This] & Tail[tuple.type] = + runtime.Tuples.tail(tuple).asInstanceOf[Tail[This] & Tail[tuple.type]] + + /** Return the size (or arity) of the tuple */ + def size: Size[This] & Size[tuple.type] = + runtime.Tuples.size(tuple).asInstanceOf[Size[This] & Size[tuple.type]] + + /** Get the i-th element of this tuple. + * Equivalent to productElement but with a precise return type. + */ + def apply(n: Int): Elem[This, n.type] & Elem[tuple.type, n.type] = + runtime.Tuples.apply(tuple, n).asInstanceOf[Elem[This, n.type] & Elem[tuple.type, n.type]] + + /** Get the initial part of the tuple without its last element */ + def init: Init[This] & Init[tuple.type] = + runtime.Tuples.init(tuple).asInstanceOf[Init[This] & Init[tuple.type]] + + /** Get the last of this tuple */ + def last: Last[This] & Last[tuple.type] = + runtime.Tuples.last(tuple).asInstanceOf[Last[This] & Last[tuple.type]] + + /** Return a copy of `tuple` with an element appended */ + def :*[X] (x: X): Append[This, X] & Append[tuple.type, X] = + runtime.Tuples.append(x, tuple).asInstanceOf[Append[This, X] & Append[tuple.type, 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] & Concat[tuple.type, that.type] = + runtime.Tuples.concat(tuple, that).asInstanceOf[Concat[This, that.type] & Concat[tuple.type, that.type]] + + /** Given a tuple `(a1, ..., am)`, returns the reversed tuple `(am, ..., a1)` + * consisting all its elements. + */ + @experimental + def reverse: Reverse[This] & Reverse[tuple.type] = + runtime.Tuples.reverse(tuple).asInstanceOf[Reverse[This] & Reverse[tuple.type]] + + /** 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` + */ + // TODO change signature? def zip[That <: Tuple](that: That): Zip[This, tuple.type] & Zip[tuple.type, tuple.type] = + def zip[That <: Tuple](that: That): Zip[This, That] & Zip[tuple.type, That] = + runtime.Tuples.zip(tuple, that).asInstanceOf[Zip[This, That] & Zip[tuple.type, 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] & Map[tuple.type, F] = + runtime.Tuples.map(tuple, f).asInstanceOf[Map[This, F] & Map[tuple.type, 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] & Take[tuple.type, n.type] = + runtime.Tuples.take(tuple, n).asInstanceOf[Take[This, n.type] & Take[tuple.type, 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] & Take[tuple.type, n.type] = + runtime.Tuples.drop(tuple, n).asInstanceOf[Drop[This, n.type] & Take[tuple.type, 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] & Split[tuple.type, n.type] = + runtime.Tuples.splitAt(tuple, n).asInstanceOf[Split[This, n.type] & Split[tuple.type, n.type]] + + /** Create a copy of this tuple as a List */ + def toList: List[Union[This]] & List[Union[tuple.type]] = + tuple.productIterator.toList.asInstanceOf[List[Union[This]] & List[Union[tuple.type]]] + end extension + + extension (tuple: Tuple) + /** Create a copy of this tuple as an Array */ + def toArray: Array[AnyRef] = runtime.Tuples.toArray(tuple) + + /** Create a copy of this tuple as an IArray */ + def toIArray: IArray[AnyRef] = runtime.Tuples.toIArray(tuple) + end extension + + /** Type of a tuple with an element appended */ + type Append[X <: Tuple, Y] <: NonEmptyTuple = X match { + case EmptyTuple => Y *: EmptyTuple + case x *: xs => x *: Append[xs, Y] + } + + /** Type of the head of a tuple */ + type Head[X <: Tuple] = X match { + case x *: _ => x + case EmptyTuple => Nothing + } + + /** Type of the initial part of the tuple without its last element */ + type Init[X <: Tuple] <: Tuple = X match { + case _ *: EmptyTuple => EmptyTuple + case x *: xs => x *: Init[xs] + case EmptyTuple => Nothing + } + + /** Type of the tail of a tuple */ + type Tail[X <: Tuple] <: Tuple = X match { + case _ *: xs => xs + case EmptyTuple => Nothing + } + + /** Type of the last element of a tuple */ + type Last[X <: Tuple] = X match { + case x *: EmptyTuple => x + case _ *: xs => Last[xs] + case EmptyTuple => Nothing + } + + /** Type of the concatenation of two tuples */ + type Concat[X <: Tuple, +Y <: Tuple] <: Tuple = X match { + case EmptyTuple => Y + case x1 *: xs1 => x1 *: Concat[xs1, Y] + } + + /** Type of the element at position N in the tuple X */ + type Elem[X <: Tuple, N <: Int] = X match { + case x *: xs => + N match { + case 0 => x + case S[n1] => Elem[xs, n1] + } + } + + /** Literal constant Int size of a tuple */ + type Size[X <: Tuple] <: Int = X match { + case EmptyTuple => 0 + case x *: xs => S[Size[xs]] + } + + /** Fold a tuple `(T1, ..., Tn)` into `F[T1, F[... F[Tn, Z]...]]]` */ + type Fold[Tup <: Tuple, Z, F[_, _]] = Tup match + case EmptyTuple => Z + case h *: t => F[h, Fold[t, Z, F]] + + /** Converts a tuple `(T1, ..., Tn)` to `(F[T1], ..., F[Tn])` */ + type Map[Tup <: Tuple, F[_ <: Union[Tup]]] <: Tuple = Tup match { + case EmptyTuple => EmptyTuple + case h *: t => F[h] *: Map[t, F] + } + + /** Converts a tuple `(T1, ..., Tn)` to a flattened `(..F[T1], ..., ..F[Tn])` */ + type FlatMap[Tup <: Tuple, F[_ <: Union[Tup]] <: Tuple] <: Tuple = Tup match { + case EmptyTuple => EmptyTuple + case h *: t => Concat[F[h], FlatMap[t, F]] + } + + /** Filters out those members of the tuple for which the predicate `P` returns `false`. + * A predicate `P[X]` is a type that can be either `true` or `false`. For example: + * ```scala + * type IsString[x] <: Boolean = x match { + * case String => true + * case _ => false + * } + * summon[Tuple.Filter[(1, "foo", 2, "bar"), IsString] =:= ("foo", "bar")] + * ``` + * @syntax markdown + */ + type Filter[Tup <: Tuple, P[_] <: Boolean] <: Tuple = Tup match { + case EmptyTuple => EmptyTuple + case h *: t => P[h] match { + case true => h *: Filter[t, P] + case false => Filter[t, P] + } + } + + /** Given two tuples, `A1 *: ... *: An * At` and `B1 *: ... *: Bn *: Bt` + * where at least one of `At` or `Bt` is `EmptyTuple` or `Tuple`, + * returns the tuple type `(A1, B1) *: ... *: (An, Bn) *: Ct` + * where `Ct` is `EmptyTuple` if `At` or `Bt` is `EmptyTuple`, otherwise `Ct` is `Tuple`. + */ + // TODO should zip be covariant? type Zip[T1 <: Tuple, +T2 <: Tuple] <: Tuple = ... + type Zip[T1 <: Tuple, T2 <: Tuple] <: Tuple = (T1, T2) match { + case (h1 *: t1, h2 *: t2) => (h1, h2) *: Zip[t1, t2] + case (EmptyTuple, _) => EmptyTuple + case (_, EmptyTuple) => EmptyTuple + case _ => Tuple + } + + /** Converts a tuple `(F[T1], ..., F[Tn])` to `(T1, ... Tn)` */ + type InverseMap[X <: Tuple, F[_]] <: Tuple = X match { + case F[x] *: t => x *: InverseMap[t, F] + case EmptyTuple => EmptyTuple + } + + /** Implicit evidence. IsMappedBy[F][X] is present in the implicit scope iff + * X is a tuple for which each element's type is constructed via `F`. E.g. + * (F[A1], ..., F[An]), but not `(F[A1], B2, ..., F[An])` where B2 does not + * have the shape of `F[A]`. + */ + type IsMappedBy[F[_]] = [X <: Tuple] =>> X =:= Map[InverseMap[X, F], F] + + /** Type of the reversed tuple */ + @experimental + type Reverse[X <: Tuple] = Helpers.ReverseImpl[EmptyTuple, X] + + @experimental + object Helpers: + + /** Type of the reversed tuple */ + @experimental + type ReverseImpl[Acc <: Tuple, X <: Tuple] <: Tuple = X match + case x *: xs => ReverseImpl[x *: Acc, xs] + case EmptyTuple => Acc + + /** Transforms a tuple `(T1, ..., Tn)` into `(T1, ..., Ti)`. */ + type Take[T <: Tuple, N <: Int] <: Tuple = N match { + case 0 => EmptyTuple + case S[n1] => T match { + case EmptyTuple => EmptyTuple + case x *: xs => x *: Take[xs, n1] + } + } + + /** Transforms a tuple `(T1, ..., Tn)` into `(Ti+1, ..., Tn)`. */ + type Drop[T <: Tuple, N <: Int] <: Tuple = N match { + case 0 => T + case S[n1] => T match { + case EmptyTuple => EmptyTuple + case x *: xs => Drop[xs, n1] + } + } + + /** Splits a tuple (T1, ..., Tn) into a pair of two tuples `(T1, ..., Ti)` and + * `(Ti+1, ..., Tn)`. + */ + type Split[T <: Tuple, N <: Int] = (Take[T, N], Drop[T, N]) + + /** Given a tuple `(T1, ..., Tn)`, returns a union of its + * member types: `T1 | ... | Tn`. Returns `Nothing` if the tuple is empty. + */ + type Union[T <: Tuple] = Fold[T, Nothing, [x, y] =>> x | y] + + /** Empty tuple */ + def apply(): EmptyTuple = EmptyTuple + + /** Tuple with one element */ + def apply[T](x: T): T *: EmptyTuple = Tuple1(x) + + /** Matches an empty tuple. */ + def unapply(x: EmptyTuple): true = true + + /** Convert an array into a tuple of unknown arity and types */ + def fromArray[T](xs: Array[T]): Tuple = { + val xs2 = xs match { + case xs: Array[Object] => xs + case xs => xs.map(_.asInstanceOf[Object]) + } + runtime.Tuples.fromArray(xs2) + } + + /** Convert an immutable array into a tuple of unknown arity and types */ + def fromIArray[T](xs: IArray[T]): Tuple = { + val xs2: IArray[Object] = xs match { + case xs: IArray[Object] @unchecked => xs + case _ => + xs.map(_.asInstanceOf[Object]) + } + runtime.Tuples.fromIArray(xs2) + } + + /** Convert a Product into a tuple of unknown arity and types */ + def fromProduct(product: Product): Tuple = + runtime.Tuples.fromProduct(product) + + def fromProductTyped[P <: Product](p: P)(using m: scala.deriving.Mirror.ProductOf[P]): m.MirroredElemTypes = + runtime.Tuples.fromProduct(p).asInstanceOf[m.MirroredElemTypes] + + given canEqualEmptyTuple: CanEqual[EmptyTuple, EmptyTuple] = CanEqual.derived + given canEqualTuple[H1, T1 <: Tuple, H2, T2 <: Tuple]( + using eqHead: CanEqual[H1, H2], eqTail: CanEqual[T1, T2] + ): CanEqual[H1 *: T1, H2 *: T2] = CanEqual.derived +} + +/** A tuple of 0 elements */ +type EmptyTuple = EmptyTuple.type + +/** A tuple of 0 elements. */ +case object EmptyTuple extends Tuple { + override def toString(): String = "()" +} + +/** Tuple of arbitrary non-zero arity */ +sealed trait NonEmptyTuple extends Tuple { + import Tuple.* + + /** Get the i-th element of this tuple. + * Equivalent to productElement but with a precise return type. + */ + private[NonEmptyTuple] + inline def apply[This >: this.type <: NonEmptyTuple](n: Int): Elem[This, n.type] = + runtime.Tuples.apply(this, n).asInstanceOf[Elem[This, n.type]] + + private[NonEmptyTuple] + inline def head[This >: this.type <: NonEmptyTuple]: Head[This] = + runtime.Tuples.apply(this, 0).asInstanceOf[Head[This]] + + /** Get the initial part of the tuple without its last element */ + private[NonEmptyTuple] + inline def init[This >: this.type <: NonEmptyTuple]: Init[This] = + runtime.Tuples.init(this).asInstanceOf[Init[This]] + + /** Get the last of this tuple */ + private[NonEmptyTuple] + inline def last[This >: this.type <: NonEmptyTuple]: Last[This] = + runtime.Tuples.last(this).asInstanceOf[Last[This]] + + /** Get the tail of this tuple. + * This operation is O(this.size) + */ + private[NonEmptyTuple] + inline def tail[This >: this.type <: NonEmptyTuple]: Tail[This] = + runtime.Tuples.tail(this).asInstanceOf[Tail[This]] +} + +@showAsInfix +sealed abstract class *:[+H, +T <: Tuple] extends NonEmptyTuple + +object *: { + def unapply[H, T <: Tuple](x: H *: T): (H, T) = (x.head, x.tail) +} diff --git a/library/src/scala/Tuple.scala b/library/src-non-bootstrapped/scala/Tuple.scala similarity index 69% rename from library/src/scala/Tuple.scala rename to library/src-non-bootstrapped/scala/Tuple.scala index ae46f7fbf347..1afd2b0ef05c 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src-non-bootstrapped/scala/Tuple.scala @@ -89,6 +89,108 @@ sealed trait Tuple extends Product { object Tuple { + // TODO should it be `extension [H](x: H) def *:(tail: Tuple): H *: tuple.type` ? + extension [H, Tail <: Tuple](x: H) + /** Return a new tuple by prepending the element to `tail` tuple. + * This operation is O(tail.size) + */ + def *:(tail: Tail): H *: Tail = runtime.Tuples.cons(x, tail).asInstanceOf[H *: Tail] + + extension [This <: Tuple](tuple: This) + /** Get the head of this tuple */ + def head: Head[This] & Head[tuple.type] = + runtime.Tuples.apply(tuple, 0).asInstanceOf[Head[This] & Head[tuple.type]] + + /** Get the tail of this tuple. + * This operation is O(tuple.size) + */ + def tail: Tail[This] & Tail[tuple.type] = + runtime.Tuples.tail(tuple).asInstanceOf[Tail[This] & Tail[tuple.type]] + + /** Return the size (or arity) of the tuple */ + def size: Size[This] & Size[tuple.type] = + runtime.Tuples.size(tuple).asInstanceOf[Size[This] & Size[tuple.type]] + + /** Get the i-th element of this tuple. + * Equivalent to productElement but with a precise return type. + */ + def apply(n: Int): Elem[This, n.type] & Elem[tuple.type, n.type] = + runtime.Tuples.apply(tuple, n).asInstanceOf[Elem[This, n.type] & Elem[tuple.type, n.type]] + + /** Get the initial part of the tuple without its last element */ + def init: Init[This] & Init[tuple.type] = + runtime.Tuples.init(tuple).asInstanceOf[Init[This] & Init[tuple.type]] + + /** Get the last of this tuple */ + def last: Last[This] & Last[tuple.type] = + runtime.Tuples.last(tuple).asInstanceOf[Last[This] & Last[tuple.type]] + + /** Return a copy of `tuple` with an element appended */ + def :*[X] (x: X): Append[This, X] & Append[tuple.type, X] = + runtime.Tuples.append(x, tuple).asInstanceOf[Append[This, X] & Append[tuple.type, 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] & Concat[tuple.type, that.type] = + runtime.Tuples.concat(tuple, that).asInstanceOf[Concat[This, that.type] & Concat[tuple.type, that.type]] + + /** Given a tuple `(a1, ..., am)`, returns the reversed tuple `(am, ..., a1)` + * consisting all its elements. + */ + @experimental + def reverse: Reverse[This] & Reverse[tuple.type] = + runtime.Tuples.reverse(tuple).asInstanceOf[Reverse[This] & Reverse[tuple.type]] + + /** 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` + */ + // TODO change signature? def zip[That <: Tuple](that: That): Zip[This, tuple.type] & Zip[tuple.type, tuple.type] = + def zip[That <: Tuple](that: That): Zip[This, That] & Zip[tuple.type, That] = + runtime.Tuples.zip(tuple, that).asInstanceOf[Zip[This, That] & Zip[tuple.type, 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] & Map[tuple.type, F] = + runtime.Tuples.map(tuple, f).asInstanceOf[Map[This, F] & Map[tuple.type, 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] & Take[tuple.type, n.type] = + runtime.Tuples.take(tuple, n).asInstanceOf[Take[This, n.type] & Take[tuple.type, 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] & Take[tuple.type, n.type] = + runtime.Tuples.drop(tuple, n).asInstanceOf[Drop[This, n.type] & Take[tuple.type, 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] & Split[tuple.type, n.type] = + runtime.Tuples.splitAt(tuple, n).asInstanceOf[Split[This, n.type] & Split[tuple.type, n.type]] + + /** Create a copy of this tuple as a List */ + def toList: List[Union[This]] & List[Union[tuple.type]] = + tuple.productIterator.toList.asInstanceOf[List[Union[This]] & List[Union[tuple.type]]] + + extension (tuple: Tuple) + /** Create a copy of this tuple as an Array */ + def toArray: Array[AnyRef] = runtime.Tuples.toArray(tuple) + + /** Create a copy of this tuple as an IArray */ + def toIArray: IArray[AnyRef] = runtime.Tuples.toIArray(tuple) + /** Type of a tuple with an element appended */ type Append[X <: Tuple, Y] <: NonEmptyTuple = X match { case EmptyTuple => Y *: EmptyTuple @@ -98,6 +200,7 @@ object Tuple { /** Type of the head of a tuple */ type Head[X <: Tuple] = X match { case x *: _ => x + case EmptyTuple => Nothing } /** Type of the initial part of the tuple without its last element */ @@ -110,6 +213,7 @@ object Tuple { /** Type of the tail of a tuple */ type Tail[X <: Tuple] <: Tuple = X match { case _ *: xs => xs + case EmptyTuple => Nothing } /** Type of the last element of a tuple */ diff --git a/library/src/scala/runtime/Tuples.scala b/library/src/scala/runtime/Tuples.scala index 41425e8559ba..31afc3568f27 100644 --- a/library/src/scala/runtime/Tuples.scala +++ b/library/src/scala/runtime/Tuples.scala @@ -288,6 +288,8 @@ object Tuples { // Tail for Tuple1 to Tuple22 private def specialCaseTail(self: Tuple): Tuple = { (self: Any) match { + case self: EmptyTuple => + throw new NoSuchElementException("tail of empty tuple") case self: Tuple1[?] => EmptyTuple case self: Tuple2[?, ?] => @@ -352,7 +354,7 @@ object Tuples { } } - def tail(self: NonEmptyTuple): Tuple = (self: Any) match { + def tail(self: Tuple): Tuple = (self: Any) match { case xxl: TupleXXL => xxlTail(xxl) case _ => specialCaseTail(self) } @@ -514,6 +516,8 @@ object Tuples { // Init for Tuple1 to Tuple22 private def specialCaseInit(self: Tuple): Tuple = { (self: Any) match { + case self: EmptyTuple => + throw new NoSuchElementException("init of empty tuple") case _: Tuple1[?] => EmptyTuple case self: Tuple2[?, ?] => @@ -561,16 +565,16 @@ object Tuples { } } - def init(self: NonEmptyTuple): Tuple = (self: Any) match { + def init(self: Tuple): Tuple = (self: Any) match { case xxl: TupleXXL => xxlInit(xxl) case _ => specialCaseInit(self) } - def last(self: NonEmptyTuple): Any = (self: Any) match { + def last(self: Tuple): Any = (self: Any) match { case self: Product => self.productElement(self.productArity - 1) } - def apply(self: NonEmptyTuple, n: Int): Any = + def apply(self: Tuple, n: Int): Any = self.productElement(n) // Benchmarks showed that this is faster than doing (it1 zip it2).copyToArray(...) diff --git a/tests/pos/Tuple_apply.scala b/tests/pos/Tuple_apply.scala new file mode 100644 index 000000000000..643f274d66a2 --- /dev/null +++ b/tests/pos/Tuple_apply.scala @@ -0,0 +1,4 @@ +def testTuple(tup: Tuple) = tup(0) +def testNonEmptyTuple(tup: NonEmptyTuple) = tup(0) +def testConsUnbound(tup: Any *: Tuple) = tup(0) +def testCons(tup: Any *: EmptyTuple) = tup(0) 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/i15743.gadt.scala b/tests/pos/i15743.gadt.scala index 42a274ca26db..ec30348bc423 100644 --- a/tests/pos/i15743.gadt.scala +++ b/tests/pos/i15743.gadt.scala @@ -9,4 +9,4 @@ class Alt: case c1 @ C1() => // GADT constr: T := Tuple val t1: T = c1.getZ val t2: Int *: T = (1: Int) *: t1 - val i1: Int = (t2: Int *: T).head[Int *: T] + val i1: Int = (t2: Int *: T).head diff --git a/tests/pos/i15743.pass.scala b/tests/pos/i15743.pass.scala index 5b92b596b5f6..a3ea8790eef9 100644 --- a/tests/pos/i15743.pass.scala +++ b/tests/pos/i15743.pass.scala @@ -1,4 +1,4 @@ // Like pos/i15743 but already passed, because the bounds are never lost so reduction never fails class Pass: def pass[T >: Tuple <: Tuple](t2: Int *: T) = - val i1: Int = (t2: Int *: T).head[Int *: T] + val i1: Int = (t2: Int *: T).head diff --git a/tests/pos/i15743.scala b/tests/pos/i15743.scala index 9d9192baad92..da592772870c 100644 --- a/tests/pos/i15743.scala +++ b/tests/pos/i15743.scala @@ -2,4 +2,4 @@ case class Bar[T <: Tuple](val x: Int *: T) class Test: def fail(e: Any): Int = - e match { case b: Bar[t] => b.x.head[Int *: t] } + e match { case b: Bar[t] => b.x.head } 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-staging/liftables.check b/tests/run-staging/liftables.check index 9317ad29a8ec..4fc7fe8db570 100644 --- a/tests/run-staging/liftables.check +++ b/tests/run-staging/liftables.check @@ -69,22 +69,22 @@ scala.Tuple21.apply[scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala scala.Tuple22.apply[scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int](1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22) { val x$1: scala.Int = 1 - scala.Tuple22.apply[scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int](2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23).*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]](x$1) + scala.Tuple.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]](scala.Tuple22.apply[scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int](2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23))(x$1) } { val x$1: scala.Int = 1 - { + scala.Tuple.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]]]({ val `x$1₂`: scala.Int = 2 - scala.Tuple22.apply[scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int](3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24).*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]](`x$1₂`) - }.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]]](x$1) + scala.Tuple.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]](scala.Tuple22.apply[scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int](3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24))(`x$1₂`) + })(x$1) } { val x$1: scala.Int = 1 - { + scala.Tuple.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]]]]({ val `x$1₂`: scala.Int = 2 - { + scala.Tuple.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]]]({ val `x$1₃`: scala.Int = 3 - scala.Tuple22.apply[scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int](4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25).*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]](`x$1₃`) - }.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]]](`x$1₂`) - }.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]]]](x$1) + scala.Tuple.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.*:[scala.Int, scala.Tuple$package.EmptyTuple]]]]]]]]]]]]]]]]]]]]]]](scala.Tuple22.apply[scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int, scala.Int](4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25))(`x$1₃`) + })(`x$1₂`) + })(x$1) } diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index a01c71724b0e..755baf1ba278 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -92,7 +92,7 @@ val experimentalDefinitionInLibrary = Set( "scala.quoted.Quotes.reflectModule.TermParamClauseMethods.hasErasedArgs", // 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/tuples1.scala b/tests/run/tuples1.scala index 53b2c43852cd..e0edab2a6ce0 100644 --- a/tests/run/tuples1.scala +++ b/tests/run/tuples1.scala @@ -62,7 +62,7 @@ object Test extends App { def head2[X <: NonEmptyTuple](x: X): Tuple.Head[X] = x.head val hd1: Int = head1(x3) - // Without an explicit type parameter type inferance infers Nothing here. + // Without an explicit type parameter type inference infers Nothing here. val hd2: Int = head2[x3.type](x3) def tail1(x: NonEmptyTuple): Tuple.Tail[x.type] = x.tail