Skip to content

Instantly share code, notes, and snippets.

@gzoller
Created November 29, 2022 20:16
Show Gist options
  • Save gzoller/2ec240a4ad60d6c11e6e78d8325b6131 to your computer and use it in GitHub Desktop.
Save gzoller/2ec240a4ad60d6c11e6e78d8325b6131 to your computer and use it in GitHub Desktop.
Scala Compiler Plugin ClassDef Modificaiton
class ReflectionWorker extends StandardPlugin {
val name: String = "reflectionWorker"
override val description: String = "heavy-lift reflection worker"
def init(options: List[String]): List[PluginPhase] =
new ReflectionWorkerPhase :: Nil
}
class ReflectionWorkerPhase extends PluginPhase {
import tpd._
val phaseName = "reflectionWorker"
override val runsBefore = Set(Pickler.name)
override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): Tree =
if tree.isClassDef && !tree.rhs.symbol.isStatic then // only look at classes & traits, not objects
// 0. Get a FreshContext so we can set the tree to this tree. (for '{} later)
implicit val fresh = ctx.fresh
fresh.setTree(tree)
QuotesCache.init(fresh)
implicit val quotes:Quotes = QuotesImpl.apply() // picks up fresh
import quotes.reflect.*
val s3ReflectionClassSymbol = getClassIfDefined("co.blocke.scala_reflection.Skip_Reflection").asInstanceOf[ClassSymbol]
if tree.symbol.getAnnotation(s3ReflectionClassSymbol).isDefined then
// 1. Set up method symbol, define parameters and return type
val toJsonSymbol = Symbol.newMethod(
Symbol.spliceOwner,
"toJson",
MethodType(
List("sb","config"))( // parameter list
_ => List( // types of the parameters
TypeRepr.typeConstructorOf(classOf[StringBuilder]),
TypeRepr.typeConstructorOf(classOf[SJConfig])
),
_ => TypeRepr.typeConstructorOf(classOf[Unit]) // return type
)
)
// 2. Get our class' Symbol (for ownerhsip reassignment)
val classSymbol = tree.tpe.asInstanceOf[quotes.reflect.TypeRef].classSymbol.get
// 3. Define our method definition (DefDef) using our method symbol defined above
val toJsonMethodDef = DefDef(
toJsonSymbol,
{
case List(List(sb: Term, config: Term)) =>
Some('{ println("Greg Zoller") }.asTerm.changeOwner(toJsonSymbol))
}
).changeOwner(classSymbol)
// 4. Add toJsonMethodDef to tree and return
val classDef = tree.asInstanceOf[ClassDef]
val cd = ClassDef.copy(classDef)(
name = classDef.name,
constr = classDef.constructor,
parents = classDef.parents,
selfOpt = classDef.self,
body = classDef.body :+ toJsonMethodDef
)
println(cd.body.map(s => println(">> "+s))) // show body to prove toJson was added
cd.asInstanceOf[dotty.tools.dotc.ast.tpd.Tree]
else
tree
else
tree
}
/*
Shows 2 functions: toJsonOG (defined in the original Person class) and toJson (generated).
Note different treatment of parameter types for some reason...
>> DefDef(toJsonOG,List(List(ValDef(sb,Ident(StringBuilder),EmptyTree), ValDef(c,Ident(SJConfig),EmptyTree))),Ident(Unit),Block(List(Apply(Select(Ident(sb),append),List(Literal(Constant({))))),Block(List(Apply(Select(Ident(sb),append),List(Literal(Constant(}))))),Literal(Constant(())))))
>> DefDef(toJson,List(List(ValDef(sb,TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class mutable)),class StringBuilder)],EmptyTree), ValDef(config,TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class scala_reflection)),class SJConfig)],EmptyTree))),TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Unit)],Inlined(Ident(ReflectionWorkerPhase),List(),Apply(Ident(println),List(Literal(Constant(Greg Zoller))))))
Consuming class (using the plugin):
@Skip_Reflection
case class Person(name:String, age:Int) {
val greg: String = "hey"
def toJsonOG(sb: StringBuilder, c:SJConfig): Unit =
sb.append("{")
sb.append("}")
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment