diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index c6db4d0b83bc..5f7387d60950 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -2,6 +2,7 @@ package scala import annotation.showAsInfix import compiletime._ import compiletime.ops.int._ +import scala.annotation.experimental /** Tuple of arbitrary arity */ sealed trait Tuple extends Product { @@ -54,6 +55,13 @@ sealed trait Tuple extends Product { inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] = runtime.Tuples.map(this, f).asInstanceOf[Map[this.type, F]] + /** Called on a tuple `(a1, ..., an)`, returns a new tuple `f(a1) ++ ... ++ f(an)`. + * The result is typed as `Concat[F[A1], ...Concat[F[An], EmptyTuple]...])` if the tuple type is fully known. + */ + @experimental + inline def flatMap[This >: this.type <: Tuple, F[_] <: Tuple](f: [t] => t => F[t]): FlatMap[This, F] = + runtime.Tuples.flatMap(this, f).asInstanceOf[FlatMap[This, F]] + /** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting * of its first n elements. */ diff --git a/library/src/scala/runtime/Tuples.scala b/library/src/scala/runtime/Tuples.scala index 267e04f65aed..2d4837d9f5de 100644 --- a/library/src/scala/runtime/Tuples.scala +++ b/library/src/scala/runtime/Tuples.scala @@ -388,6 +388,11 @@ object Tuples { case _ => fromIArray(self.productIterator.map(f(_).asInstanceOf[Object]).toArray.asInstanceOf[IArray[Object]]) // TODO use toIArray } + def flatMap[F[_] <: Tuple](self: Tuple, f: [t] => t => F[t]): Tuple = self match { + case EmptyTuple => self + case _ => fromIArray(self.toIArray.flatMap(f(_).toIArray)) + } + def take(self: Tuple, n: Int): Tuple = { if (n < 0) throw new IndexOutOfBoundsException(n.toString) val selfSize: Int = self.size diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index c4d52097eba8..9b511b814bfb 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -11,5 +11,8 @@ object MiMaFilters { exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SourceFileMethods.getJPath"), exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SourceFileMethods.name"), exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SourceFileMethods.path"), + + // New APIs marked @experimental in 3.1.0 + exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.flatMap"), ) } diff --git a/tests/run/TupleFlatMap.check b/tests/run/TupleFlatMap.check new file mode 100644 index 000000000000..0c895ceba72e --- /dev/null +++ b/tests/run/TupleFlatMap.check @@ -0,0 +1,4 @@ +() +(1,1,2,2,x,x,d,d) +() +(1,1!,x,x!) \ No newline at end of file diff --git a/tests/run/TupleFlatMap.scala b/tests/run/TupleFlatMap.scala new file mode 100644 index 000000000000..0afcf280706c --- /dev/null +++ b/tests/run/TupleFlatMap.scala @@ -0,0 +1,19 @@ +@main def Test = { + + println( + Tuple().flatMap[EmptyTuple, [t] =>> (t, t)]([t] => (x: t) => (x, x)) + ) + + println( + (1, 2, "x", "d").flatMap[(Int, Int, String, String), [t] =>> (t, t)]([t] => (x: t) => (x, x)) + ) + + println( + (1, "x").flatMap[(Int, String), [t] =>> EmptyTuple]([t] => (x: t) => Tuple()) + ) + + println( + (1, "x").flatMap[(Int, String), [t] =>> (t, String)]([t] => (x: t) => (x, x.toString + "!")) + ) + +}