Last active
September 23, 2024 22:36
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.reverse, 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