Skip to content

Instantly share code, notes, and snippets.

@smarter
Created September 13, 2024 15:53
Show Gist options
  • Save smarter/7211132efd78b7191fe393800e366835 to your computer and use it in GitHub Desktop.
Save smarter/7211132efd78b7191fe393800e366835 to your computer and use it in GitHub Desktop.
Macro to transform a type application `F[A, B, ...]` into `App[F, (A, B, ...)]` to work around https://github.com/scala/scala3/issues/16782
import scala.compiletime.erasedValue
import scala.quoted.*
/** Represent a type application F[Args*]
*
* @see [[App.erasedApp]]
*/
sealed trait App[F <: AnyKind, Args <: Tuple]
object App:
/** Transform a type application `F[A, B, ...]` into `App[F, (A, B, ...)]`
*
* The transformation is applied on T' := T after widening and dealiasing non-opaque types
* If T' is not a type application, return T unchanged.
* If T' is a type application with at least one higher-kinded argument,
* emit a compile-time error.
*
* The result value is erased and is meant to be used in the same way as `erasedValue`:
*
* type Abs[T <: Int]
* inline def reifyTest[T]: Any =
* inline erasedApp[T] match
* case _: App1[Abs, t] => valueOf[t]
* def test =
* reifyTest[Abs[1]]
*
* This is useful to work around https://github.com/scala/scala3/issues/16782
*
* @see [[App1]], [[App2]]
*/
inline def erasedApp[T]: Any = ${erasedAppImpl[T]}
def erasedAppImpl[T: Type](using Quotes): Expr[Any] =
import quotes.reflect.*
widenDealiasKeepOpaques(TypeRepr.of[T]) match
case AppliedType(parent, args) =>
def toTuple(xs: List[TypeRepr], acc: Type[? <: Tuple]): Type[? <: Tuple] = xs match
case Nil => acc
case x :: rest => (x.asType, acc) match
case ('[t], '[type accTuple <: Tuple; accTuple]) =>
toTuple(rest, Type.of[t *: accTuple])
case (xTpe, _) =>
report.error(
s"Higher-kinded type arguments are not supported but found `${Type.show(using xTpe)}` in `${Type.show[T]}`")
Type.of[Nothing]
(parent.asType, toTuple(args, Type.of[EmptyTuple])) match
case ('[type parentTpe <: AnyKind; parentTpe], '[type argsTuple <: Tuple; argsTuple]) =>
'{ erasedValue[App[parentTpe, argsTuple]] }
case _ =>
'{ erasedValue[T] }
/** Perform successive widenings and dealiasings of non-opaque types until none can be applied anymore. */
def widenDealiasKeepOpaques(using Quotes)(tp: quotes.reflect.TypeRepr): quotes.reflect.TypeRepr =
val res = tp.widen.dealiasKeepOpaques
if res == tp then res else widenDealiasKeepOpaques(res)
end App
/** Represent a type application `F[Arg]` */
type App1[F <: AnyKind, Arg] = App[F, Tuple1[Arg]]
/** Represent a type application `F[Arg1, Arg2]` */
type App2[F <: AnyKind, Arg1, Arg2] = App[F, (Arg1, Arg2)]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment