Skip to content

Instantly share code, notes, and snippets.

@acdenhartog
Last active May 19, 2016 23:51
Show Gist options
  • Save acdenhartog/2944c2161801b7b24b3b2c79de4332d0 to your computer and use it in GitHub Desktop.
Save acdenhartog/2944c2161801b7b24b3b2c79de4332d0 to your computer and use it in GitHub Desktop.
Partial includeProjects macro implementation for SBT
import language.experimental.macros
object ProjectMacros {
import scala.reflect._
import reflect.macros._
def includeProjectsMacroImpl[T: c.WeakTypeTag](c: blackbox.Context)(instance: c.Expr[T]): c.Expr[SettingDef] = {
import c.universe._
def isProject(s:Symbol) = s.typeSignature <:< typeOf[Project] && !s.isMethod //methods could take parameters
def includeProject(project:Tree) = q"${typeOf[Project.type].termSymbol}.includeProject($project)"
def reflectiveCall(getter:String) =
q"${instance.tree}.getClass.getMethod($getter).invoke($instance).asInstanceOf[${typeOf[Project]}]"
def projectName(project:Symbol) = project.name.toString.dropRight(1)//makes overrides identical
c.Expr[SettingDef](q"..${
c.weakTypeOf[T].baseClasses.view
.flatMap(_.typeSignature.decls.filter(isProject)) //decls as members does not function properly on object
.map(projectName).distinct //avoid duplicating overrides
.map(projectName=>includeProject(reflectiveCall(projectName)))
}")
}
}
def includeProjectsMacroImplCommented[T: c.WeakTypeTag](c: whitebox.Context)(instance: c.Expr[T]): c.Expr[SettingDef] = {
//implicit def context = c
import c.universe._
val projectType = typeOf[Project]
def isProject(s:Symbol) = s.typeSignature <:< projectType && !s.isMethod //methods could take parameters
def includeProject(project:Tree) = q"${typeOf[Project.type].termSymbol}.includeProject($project)"
/* HOW NOT TO WRITE MACROS BY EXAMPLE: */
//Causes error: Unexpected tree in genLoad
def wrongIncludeProject(project:Tree) = q"${symbolOf[Project.type]}.includeProject($project)"
//Causes error: symbol value __ does not exist in
def wrongDirect(project:Symbol) = q"$project"
//Causes error: value __ in trait|object|class __ cannot be accessed in __
def wrongSelect(project:Symbol) = q"$instance.$project"
//Causes error: java.lang.AssertionError: assertion failed: qual = <refinement>, name = __
def wrongSymSelect(project:Symbol) = q"${symbolOf[T]}.$project"
//Causes error: value __ in trait|object|class __ cannot be accessed in __
def wrongTySymSelect(project:Symbol) = q"${weakTypeOf[T].termSymbol}.$project"
//Always works, i.e. something else is broken:
def sanityCheck(project:Symbol) = q"null.asInstanceOf[$projectType]"
// this is purely to bypass compiler checks, hopefully scala.meta can replace this eventually
def reflectiveCall(getter:String) = // utterly disgusting but it works.
q"${instance.tree}.getClass.getMethod($getter).invoke($instance).asInstanceOf[$projectType]"
//need to drop LOCAL_SUFFIX_STRING, and ensure that overrides become identical
def projectName(project:Symbol) = project.name.toString.dropRight(1)
//Note: typeSignature.members does not function properly on object
val allProjects = c.weakTypeOf[T].baseClasses.view.flatMap(_.typeSignature.decls.filter(isProject))
//Causes bug: overriding symbols distinct
def wrongAllIncluded = allProjects.distinct.map(project=>includeProject(reflectiveCall(projectName(project))))
//distinct ensures that overrides are not called multiple times
val allIncluded = allProjects.map(projectName).distinct.map(project=>includeProject(reflectiveCall(project)))
val outputTree = q"..$allIncluded"
//c.echo(null,outputTree.toString)
c.Expr[SettingDef](outputTree)
}
//these are dummies used so I do not have to work in the (dificult to experiment with) SBT code base directly:
case class Project(marker:String) {
def hello() = println(s"${super.toString} — $marker")
}
trait SettingDef
object Project {
def includeProject(project: Project):SettingDef = {
project.hello() //whatever magic needs to happen downstream
new SettingDef {}//this is a dummy, probably something like this is needed to deal with SBT syntax rules
}
def includeProjectsFrom[T](instance:T):SettingDef = macro ProjectMacros.includeProjectsMacroImpl[T]
}
//these examples all work as is required:
trait SubClass
object Example extends Stuff {
val HA = new Project("HA")
val H__B__N = new Project("H__B__N") with SubClass
lazy val HL = new Project("HL")
}
class Stuff extends DeepStuff {
val `SA` = new Project("`SA`")
val SB = new Project("SB") with SubClass
lazy val SL = new Project("SL")
override lazy val DL = new Project("DL")
}
trait DeepStuff {
val `D$$$nusoa#*A` = new Project("`D$$$nusoa#*A`")
val DB = new Project("DB") with SubClass
lazy val DL = new Project("DL")
}
//So far I have only found this corner case that does not work:
sbt.Project.includeProjectsFrom(
new Object
with DeepStuff {
val `#BeCAsUe_WE_caN` = //anonymous field not found by macro
new Project("`#BeCAsUe_WE_caN`")
})
//But this would be inside build.sbt so it should just have been defined at root level anyway.
// could be found by using reflection, but I do not think that is worth it here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment