Last active
May 19, 2016 23:51
-
-
Save acdenhartog/2944c2161801b7b24b3b2c79de4332d0 to your computer and use it in GitHub Desktop.
Partial includeProjects macro implementation for SBT
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
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))) | |
}") | |
} | |
} |
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
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) | |
} |
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
//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] | |
} |
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
//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") | |
} |
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
//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