Skip to content

Instantly share code, notes, and snippets.

@nikita-volkov
Forked from xeno-by/gist:2559714
Last active June 15, 2016 00:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nikita-volkov/5000556 to your computer and use it in GitHub Desktop.
Save nikita-volkov/5000556 to your computer and use it in GitHub Desktop.
Mixing in a trait dynamically with Scala 2.10.0. Formatted.

Answers http://stackoverflow.com/questions/10373318/mixing-in-a-trait-dynamically.

Compile as follows: scalac Common_1.scala Macros_2.scala scalac Common_1.scala Test_3.scala -cp

Tested in 2.10.0

===Common_1.scala===

trait Persisted {
  def id: Long
}

===Macros_2.scala===

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

object Macros {

  def toPersisted
    [ T ]
    ( instance : T, id : Long )
    : T with Persisted
    = macro toPersistedMacro[T]

  def toPersistedMacro
    [ T : c.WeakTypeTag ]
    ( c : Context )
    ( instance : c.Expr[T],
      id : c.Expr[Long] )
    : c.Expr[T with Persisted]
    = {
      import c.universe._

      val t = weakTypeOf[T]
      val s = t.typeSymbol.asClass

      if (!s.isCaseClass)
        c.abort(c.enclosingPosition, "toPersisted only accepts case classes, you provided %s".format(t))

      val accessors = t.members.sorted.collect{ case x : TermSymbol if x.isCaseAccessor && x.isMethod => x }
      val fieldNames = accessors.map(_.name)

      // some of the missing parts of the reflection API
      // we should be able to come up with something better in future point releases
      val PARAMACCESSOR = (1L << 29).asInstanceOf[FlagSet]

      val instanceParam = ValDef(NoMods, newTermName("instance"), TypeTree(s.toType), EmptyTree)
      val idParam = ValDef(Modifiers(PARAMACCESSOR), newTermName("id"), Ident(definitions.LongClass), EmptyTree)
      val superArgs = fieldNames.map{ fieldName => Select(Ident(instanceParam.name), fieldName) }
      val body = Apply(Select(Super(This(newTypeName("")), newTypeName("")), nme.CONSTRUCTOR), superArgs)
      val constructor = DefDef(NoMods, nme.CONSTRUCTOR, Nil, List(List(instanceParam, idParam)), TypeTree(), Block(List(body), Literal(Constant(()))))
      val idVal = idParam.duplicate

      val tmpl = Template(List(Ident(s), Ident(c.mirror.staticClass("Persisted"))), emptyValDef, List(idVal, constructor))
      val classDef = ClassDef(NoMods, newTypeName(c.fresh(s.name + "$Persisted")), Nil, tmpl)
      val init = New(Ident(classDef.name), List(List(instance.tree, id.tree)))

      c.Expr[T with Persisted](Block(classDef, init))
    }

}

===Test_3.scala===

object Test extends App {
  import Macros._
  case class Person(first: String, last: String)
  val p = toPersisted(Person("hello", "world"), 42)
  println(p.first)
  println(p.last)
  println(p.id)
}

===Output===

C:\Projects\Kepler\sandbox>scalac Common_1.scala Macros_2.scala
<scalac has exited with code 0>

C:\Projects\Kepler\sandbox>scalac -Ymacro-debug-lite Common_1.scala Test_3.scala -cp .
typechecking macro expansion Macros.toPersisted[Test.Person](Test.this.Person.apply("hello", "world"), 42L) 
at source-C:\Projects\Kepler\sandbox\Test_3.scala,line-4,offset=114
{
  class Person$Persisted1 extends Person with Persisted {
    val id: Long = _;
    def <init>(instance: Test.Person, id: Long) = {
      super.<init>(instance.first, instance.last);
      ()
    }
  };
  new Person$Persisted1(Test.this.Person.apply("hello", "world"), 42L)
}
*snip*
<scalac has exited with code 0>

C:\Projects\Kepler\sandbox>scala Test
hello
world
42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment