diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 88bd35eb12f4..740ff1108fad 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -25,3 +25,6 @@ i7087.scala # Opaque type i5720.scala + +# Tuples +toexproftuple.scala diff --git a/library/src-bootstrapped/scala/quoted/package.scala b/library/src-bootstrapped/scala/quoted/package.scala index 5122a18cee31..10d4aefd5bcb 100644 --- a/library/src-bootstrapped/scala/quoted/package.scala +++ b/library/src-bootstrapped/scala/quoted/package.scala @@ -95,6 +95,12 @@ package object quoted { '{ Tuple.fromIArray(IArray(${seq.toExprOfSeq}: _*)) } } } - } + /** Given a tuple of the form `(Expr[A1], ..., Expr[An])`, outputs a tuple `Expr[(A1, ..., An)]`. */ + def (tup: T) toExprOfTuple[T <: Tuple: Tuple.IsMappedBy[Expr]: Type] given (ctx: QuoteContext): Expr[Tuple.InverseMap[T, Expr]] = { + import ctx.tasty._ + tup.asInstanceOf[Product].productIterator.toSeq.asInstanceOf[Seq[Expr[_]]].toExprOfTuple + .cast[Tuple.InverseMap[T, Expr]] + } + } } diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 3ba1f121c347..dea576cadb64 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -103,6 +103,19 @@ object Tuple { 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 Unit => Unit + } + + /** 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] + /** Convert an array into a tuple of unknown arity and types */ def fromArray[T](xs: Array[T]): Tuple = { val xs2 = xs match { diff --git a/tests/neg/toexproftuple.scala b/tests/neg/toexproftuple.scala new file mode 100644 index 000000000000..42e52bddea54 --- /dev/null +++ b/tests/neg/toexproftuple.scala @@ -0,0 +1,11 @@ +import scala.quoted._, scala.deriving._ +import given scala.quoted._ + +inline def mcr: Any = ${mcrImpl} +def mcrImpl given (ctx: QuoteContext): Expr[Any] = { + val tpl: (Expr[1], Expr[2], Expr[3]) = ('{1}, '{2}, '{3}) + '{val res: (1, 3, 3) = ${tpl.toExprOfTuple}; res} // error + + val tpl2: (Expr[1], 2, Expr[3]) = ('{1}, 2, '{3}) + '{val res = ${tpl2.toExprOfTuple}; res} // error +} diff --git a/tests/neg/tuple-isMappedBy-2.scala b/tests/neg/tuple-isMappedBy-2.scala new file mode 100644 index 000000000000..35c7aaffba20 --- /dev/null +++ b/tests/neg/tuple-isMappedBy-2.scala @@ -0,0 +1,5 @@ +import scala.Tuple.IsMappedBy + +object Test2 { + def test0[F[_], T: IsMappedBy[F]]: Unit = () // error: Type argument T does not conform to upper bound Tuple +} diff --git a/tests/neg/tuple-isMappedBy.scala b/tests/neg/tuple-isMappedBy.scala new file mode 100644 index 000000000000..913ce7714ed4 --- /dev/null +++ b/tests/neg/tuple-isMappedBy.scala @@ -0,0 +1,13 @@ +import scala.Tuple.IsMappedBy + +object Test1 { + + def test[F[_], T <: Tuple: IsMappedBy[F]]: Unit = () + + test[List, (List[Int], Char)] // error + test[List, (List[Int], Seq[Char])] // error + test[Seq, (List[Int], Seq[Char])] // error + test[List, Tuple] // error + test[List, Nothing] // error + +} diff --git a/tests/pos/toexproftuple.scala b/tests/pos/toexproftuple.scala new file mode 100644 index 000000000000..637c1298549f --- /dev/null +++ b/tests/pos/toexproftuple.scala @@ -0,0 +1,8 @@ +import scala.quoted._, scala.deriving._ +import given scala.quoted._ + +inline def mcr: Any = ${mcrImpl} +def mcrImpl given (ctx: QuoteContext): Expr[Any] = { + val tpl: (Expr[1], Expr[2], Expr[3]) = ('{1}, '{2}, '{3}) + '{val res: (1, 2, 3) = ${tpl.toExprOfTuple}; res} +} diff --git a/tests/pos/tuple-isMappedBy.scala b/tests/pos/tuple-isMappedBy.scala new file mode 100644 index 000000000000..c7e1f2ae3843 --- /dev/null +++ b/tests/pos/tuple-isMappedBy.scala @@ -0,0 +1,27 @@ + +import scala.Tuple.IsMappedBy + +object Test { + + def test[F[_], T <: Tuple: IsMappedBy[F]]: Unit = () + + test[[X] =>> X, Unit] + test[[X] =>> X, (Int, Long)] + + test[List, Unit] + test[List, (List[Int], List[Long])] + + trait A[+X] + trait B[-X] + trait C[X] + + test[A, Unit] + test[A, (A[Int], A[Long])] + + test[B, Unit] + test[B, (B[Int], B[Long])] + + test[C, Unit] + test[C, (C[Int], C[Long])] + +}