diff --git a/library/src/scala/quoted/Type.scala b/library/src/scala/quoted/Type.scala index dcef5eb82b5f..d2f560076f1a 100644 --- a/library/src/scala/quoted/Type.scala +++ b/library/src/scala/quoted/Type.scala @@ -1,6 +1,6 @@ package scala.quoted -import scala.annotation.compileTimeOnly +import scala.annotation.{compileTimeOnly, experimental} /** Type (or type constructor) `T` needed contextually when using `T` in a quoted expression `'{... T ...}` */ abstract class Type[T <: AnyKind] private[scala]: @@ -35,11 +35,50 @@ object Type: * @syntax markdown */ def valueOfConstant[T](using Type[T])(using Quotes): Option[T] = + ValueOf.unapply(quotes.reflect.TypeRepr.of[T]).asInstanceOf[Option[T]] + + /** Extracts the value of a tuple of singleton constant types. + * Returns Some of the tuple type if it is a tuple singleton constant types. + * Returns None if the type is not a tuple singleton constant types. + * + * Example usage: + * ```scala + * ... match + * case '{ $mirrorExpr : Mirror.Sum { type MirroredElemLabels = label } } => + * Type.valueOfTuple[label] // Option[Tuple] + * } + * ``` + * @syntax markdown + */ + @experimental + def valueOfTuple[T <: Tuple](using Type[T])(using Quotes): Option[T] = + valueOfTuple(quotes.reflect.TypeRepr.of[T]).asInstanceOf[Option[T]] + + private def valueOfTuple(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[Tuple] = import quotes.reflect.* - def valueOf(tpe: TypeRepr): Option[T] = - tpe.dealias.widenTermRefByName match - case ConstantType(const) => Some(const.value.asInstanceOf[T]) + val cons = Symbol.classSymbol("scala.*:") + def rec(tpe: TypeRepr): Option[Tuple] = + tpe.widenTermRefByName.dealias match + case AppliedType(fn, tpes) if defn.isTupleClass(fn.typeSymbol) => + tpes.foldRight(Option[Tuple](EmptyTuple)) { + case (_, None) => None + case (ValueOf(v), Some(acc)) => Some(v *: acc) + case _ => None + } + case AppliedType(tp, List(ValueOf(headValue), tail)) if tp.derivesFrom(cons) => + rec(tail) match + case Some(tailValue) => Some(headValue *: tailValue) + case None => None + case tpe => + if tpe.derivesFrom(Symbol.classSymbol("scala.EmptyTuple")) then Some(EmptyTuple) + else None + rec(tpe) + + private object ValueOf: + def unapply(using Quotes)(tpe: quotes.reflect.TypeRepr): Option[Any] = + import quotes.reflect.* + tpe.widenTermRefByName.dealias match + case ConstantType(const) => Some(const.value) case _ => None - valueOf(TypeRepr.of[T]) end Type diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 82e29ce30543..fd833d95e249 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -14,8 +14,7 @@ object MiMaFilters { exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeMember"), exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeMembers"), exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TermParamClauseMethods.isErased"), - exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TermParamClauseMethods.isErased"), - exclude[MissingClassProblem]("scala.annotation.experimental"), + exclude[DirectMissingMethodProblem]("scala.quoted.Type.valueOfTuple"), exclude[MissingClassProblem]("scala.annotation.experimental"), exclude[MissingClassProblem]("scala.annotation.internal.ErasedParam"), exclude[MissingClassProblem]("scala.annotation.internal.ErasedParam"), diff --git a/tests/run-macros/i12417/Macros_1.scala b/tests/run-macros/i12417/Macros_1.scala new file mode 100644 index 000000000000..e961ae6040a1 --- /dev/null +++ b/tests/run-macros/i12417/Macros_1.scala @@ -0,0 +1,13 @@ +import scala.deriving.Mirror +import scala.compiletime.{constValue, error} +import scala.quoted.* + +object TestMacro { + inline def test1[CASE_CLASS <: Product](using m: Mirror.ProductOf[CASE_CLASS]): String = + ${ code('m) } + + def code[CASE_CLASS <: Product: Type](m: Expr[Mirror.ProductOf[CASE_CLASS]])(using Quotes): Expr[String] = + m match + case '{ type t <: Tuple; $_ : Mirror { type MirroredElemLabels = `t` } } => + Expr(Type.valueOfTuple[t].toString) +} diff --git a/tests/run-macros/i12417/Test_2.scala b/tests/run-macros/i12417/Test_2.scala new file mode 100644 index 000000000000..0ebe6d377ba0 --- /dev/null +++ b/tests/run-macros/i12417/Test_2.scala @@ -0,0 +1,7 @@ +import scala.deriving.Mirror +import scala.compiletime.{constValue, error} + +object Test extends App { + case class A(x: String, y: Int) + assert(TestMacro.test1[A] == "Some((x,y))") +} diff --git a/tests/run-macros/i12417b.check b/tests/run-macros/i12417b.check new file mode 100644 index 000000000000..ac68840f16bf --- /dev/null +++ b/tests/run-macros/i12417b.check @@ -0,0 +1,6 @@ +Some(()) +Some((1)) +Some((1,2)) +Some((1,2,3)) +Some((1,2,3,4)) +Some((1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26)) diff --git a/tests/run-macros/i12417b/Macros_1.scala b/tests/run-macros/i12417b/Macros_1.scala new file mode 100644 index 000000000000..2b531e446bc1 --- /dev/null +++ b/tests/run-macros/i12417b/Macros_1.scala @@ -0,0 +1,18 @@ +import scala.deriving.Mirror +import scala.compiletime.{constValue, error} +import scala.quoted.* + +object TestMacro { + inline def test1: Unit = + ${ code() } + + def code()(using Quotes): Expr[Unit] = + '{ + println(${Expr(Type.valueOfTuple[EmptyTuple].toString)}) + println(${Expr(Type.valueOfTuple[1 *: EmptyTuple].toString)}) + println(${Expr(Type.valueOfTuple[(1, 2)].toString)}) + println(${Expr(Type.valueOfTuple[(1, 2, 3)].toString)}) + println(${Expr(Type.valueOfTuple[(1, 2, 3, 4)].toString)}) + println(${Expr(Type.valueOfTuple[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26)].toString)}) + } +} diff --git a/tests/run-macros/i12417b/Test_2.scala b/tests/run-macros/i12417b/Test_2.scala new file mode 100644 index 000000000000..182c2c0e2f8a --- /dev/null +++ b/tests/run-macros/i12417b/Test_2.scala @@ -0,0 +1 @@ +@main def Test = TestMacro.test1