Skip to content

Instantly share code, notes, and snippets.

@xeno-by
Created July 10, 2013 16:38
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • 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
}
}
@xeno-by
Copy link
Author

xeno-by commented Jul 10, 2013

No reflection involved!

18:37 ~/Projects/Kepler_structural/sandbox (topic/structural)$ scalac Macros.scala && scalac Test.scala -Xprint:cleanup
[[syntax trees at end of                   cleanup]] // Test.scala
package <empty> {
  object Test extends Object {
    def main(args: Array[String]): Unit = {
      val foo: Object = {
        {
          new anonymous class anon$1()
        }
      };
      scala.this.Predef.println("2");
      scala.this.Predef.println(scala.Int.box(3))
    };
    def <init>(): Test.type = {
      Test.super.<init>();
      ()
    }
  };
  class Test$Workaround$1 extends Object {
    def <init>(): Test$Workaround$1 = {
      Test$Workaround$1.super.<init>();
      ()
    }
  };
  final class anon$1 extends Test$Workaround$1 {
    def <init>(): anonymous class anon$1 = {
      anon$1.super.<init>();
      ()
    }
  }
}

 // compileLateSynthetic-fbb25380ed1946c68a1246b286a54476.scala
package scala.reflect.macros.synthetic {
  class MacrosInvoker extends Object with Macros {
    def selFieldImpl(): reflect.macros.Universe$TreeContextApi = Macros$class.selFieldImpl(MacrosInvoker.this);
    def mkObjectImpl(xs: Seq): reflect.macros.Universe$TreeContextApi = Macros$class.mkObjectImpl(MacrosInvoker.this, xs);
    <paramaccessor> private[this] val c: reflect.macros.Context = _;
    <stable> <accessor> <paramaccessor> def c(): reflect.macros.Context = MacrosInvoker.this.c;
    def <init>(c: reflect.macros.Context): scala.reflect.macros.synthetic.MacrosInvoker = {
      MacrosInvoker.this.c = c;
      MacrosInvoker.super.<init>();
      Macros$class./*Macros$class*/$init$(MacrosInvoker.this);
      ()
    }
  }
}

warning: there were 2 feature warning(s); re-run with -feature for details
one warning found

@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