Skip to content

Instantly share code, notes, and snippets.

@xeno-by
Created July 10, 2013 16:38
Show Gist options
  • Save xeno-by/5967900 to your computer and use it in GitHub Desktop.
Save xeno-by/5967900 to your computer and use it in GitHub Desktop.
Macro-powered structural types
import scala.annotation.StaticAnnotation
import scala.reflect.macros.Macro
import language.experimental.macros
class body(tree: Any) extends StaticAnnotation
trait Macros extends Macro {
import c.universe._
def selFieldImpl = {
val field = c.macroApplication.symbol
val bodyAnn = field.annotations.filter(_.tpe <:< typeOf[body]).head
bodyAnn.scalaArgs.head
}
def mkObjectImpl(xs: c.Tree*) = {
val kvps = xs.toList map { case q"${_}(${Literal(Constant(name: String))}).->[${_}]($value)" => name -> value }
val fields = kvps map { case (k, v) => q"@body($v) def ${TermName(k)} = macro Macros.selFieldImpl" }
q"class Workaround { ..$fields }; new Workaround{}"
}
}
object mkObject {
def apply(xs: Any*) = macro Macros.mkObjectImpl
}
=================
object Test {
def main(args: Array[String]) = {
val foo = mkObject("x" -> "2", "y" -> 3)
println(foo.x)
println(foo.y)
// println(foo.z) => will result in a compilation error
}
}
@constantOut
Copy link

constantOut commented May 8, 2020

I've re-implemented it for a newer version of Scala, but when I'm trying to use these types - I see reflMethod$Method1 with Lscala/runtime/StructuralCallSite and other reflection related instructions. My original impression was that since we are generating proper types - we don't need reflection.
I assume the problem is related to the scope in which macros operates,
is there any way I can generate new type and properly return it from a macros ?
Maybe it would work better as compiler plugin ?


import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context


object Record {
    def apply(xs: => Any): Product = macro applyImpl

    def applyImpl(c: Context)(xs: c.Tree): c.Tree = {
        import c.universe._

        val fieldsInfo = xs match {
            case q"{..$fieldsExpr}" =>
                fieldsExpr.map(f => f match {
                    case q"val $name: $valType = $value" =>
                        if(valType.isEmpty) (name, value.tpe.erasure, value)
                        else (name, valType.tpe, value)
                    case q"var $name: $valType = $value" =>
                        c.abort(
                            c.enclosingPosition,
                            s"var fields are not supported")
                    case _ => c.abort(
                        c.enclosingPosition,
                        s"Error in ${f.toString()} in position. Only applicable to assignment expressions"
                    )
                })
            case _ => c.abort(
                c.enclosingPosition,
                "Only applicable to block expression"
            )
        }

            q"""{
               case class Workaround(..${fieldsInfo.map(x => q"${x._1}: ${x._2}")});
               new Workaround(..${fieldsInfo.map(_._3)});
               }"""
    }

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