Skip to content

Instantly share code, notes, and snippets.

@gregghz
Last active May 26, 2022 17:30
Show Gist options
  • Save gregghz/6b74e50c1600c30322403449ce978a8c to your computer and use it in GitHub Desktop.
Save gregghz/6b74e50c1600c30322403449ce978a8c to your computer and use it in GitHub Desktop.
Polymorphic Method Macro
object PolymorphicMethod {
inline def make[T]: Unit = ${ impl[T] }
def impl[T: Type](using Quotes): Expr[Unit] = {
import quotes.reflect.*
def decls(owner: Symbol): List[Symbol] = {
val targetSymbol = TypeRepr.of[T].typeSymbol
val abstractDef: DefDef = targetSymbol.declaredMethod("poly").head.tree.asInstanceOf[DefDef]
val methodType = PolyType(List("A"))(
_ => List(TypeBounds(TypeRepr.of[Nothing], TypeRepr.of[Any])),
pt => MethodType(List("x"))(
_ => abstractDef.termParamss.head.params.map(_.tpt.tpe),
_ => abstractDef.returnTpt.tpe
)
)
List(
Symbol.newMethod(owner, "poly", methodType)
)
}
val parents = List(TypeTree.of[Object], TypeTree.of[T])
val classSymbol = Symbol.newClass(
Symbol.spliceOwner,
"ImplT",
parents.map(_.tpe),
decls,
None
)
val impls = {
val polySymbol = classSymbol.declaredMethod("poly").head
List(
DefDef(polySymbol, argss => {
val paramTerm = argss.drop(1).head.head match {
case i: Ident => i.underlying
}
Some(Select.unique(paramTerm, "head"))
})
)
}
val classDef = ClassDef(classSymbol, parents, impls)
val unit = '{ () }
val result = Block(List(classDef), unit.asTerm).asExprOf[Unit]
println(result.show)
result
}
}
@gregghz
Copy link
Author

gregghz commented May 26, 2022

I am calling this code from a test:

trait TestTrait {
  def poly[A](x: List[A]): A
}

object Thing {
  PolymorphicMethod.make[TestTrait]
}

The println produces this:

{
  class ImplT extends java.lang.Object with com.gregghz.macros.TestTrait {
    override def poly[A](x: scala.collection.immutable.List[A]): A = x.head
  }
  ()
}

But compilation fails with this:

[error] 8 |  PolymorphicMethod.make[TestTrait]
[error]   |  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]   |class ImplT needs to be abstract, since def poly[A](x: List[A]): A in trait TestTrait in package com.gregghz.macros is not defined 
[error]   |(Note that
[error]   | parameter List[A] in def poly[A](x: List[A]): A in trait TestTrait in package com.gregghz.macros does not match
[error]   | parameter List[A] in def poly[A](x: List[A]): A in class ImplT
[error]   | their type parameters differ)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment