-
-
Save arjanblokzijl/2651683 to your computer and use it in GitHub Desktop.
Mixing in a trait dynamically
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
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 <path to the result of the previous compilation> | |
Tested in 2.10.0-M3, will most likely not compile by the time 2.10.0 final is released, because we're actively rehashing the API. | |
However the principles will remain the same in the final release, so the concept itself is okay. | |
===Common_1.scala=== | |
trait Persisted { | |
def id: Long | |
} | |
===Macros_2.scala=== | |
import language.experimental.macros | |
import scala.reflect.makro.Context | |
import scala.reflect.api.Modifier._ | |
object Macros { | |
def toPersisted[T](instance: T, id: Long): T with Persisted = macro impl[T] | |
def impl[T: c.TypeTag](c: Context)(instance: c.Expr[T], id: c.Expr[Long]) = { | |
import c.mirror._ | |
val t = c.typeTag[T] | |
val u = t.tpe.typeSymbol | |
if (!(u.modifiers contains `case`)) | |
c.abort(c.enclosingPosition, "toPersisted only accepts case classes, you provided %s".format(t.tpe)) | |
// use reflection API to get the list of all declared fields | |
// more info here: http://scalamacros.org/documentation.html | |
val fields = u.typeSignature.members.filter(m => m.owner == u && !m.isMethod).toList.reverse | |
val fieldNames = fields map (field => nme.dropLocalSuffix(field.name)) | |
// how did I know what trees to generate? | |
// read up the docs at http://scalamacros.org/documentation.html | |
val instanceParam = ValDef(Modifiers(Set(parameter, paramAccessor)), newTermName("instance"), TypeTree(u.asType), EmptyTree) | |
val idParam = ValDef(Modifiers(Set(parameter, 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 ctor = DefDef(NoMods, nme.CONSTRUCTOR, Nil, List(List(instanceParam, idParam)), TypeTree(), Block(List(body), Literal(Constant(())))) | |
val idVal = idParam.copy(mods = Modifiers(Set(paramAccessor))) | |
val tmpl = Template(List(Ident(u), Ident(staticClass("Persisted"))), emptyValDef, List(idVal, ctor)) | |
val cdef = ClassDef(NoMods, newTypeName(c.fresh(u.name + "$Persisted")), Nil, tmpl) | |
val init = New(Ident(cdef.name), List(List(instance.tree, id.tree))) | |
Expr(Block(cdef, 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 { | |
<paramaccessor> 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