Skip to content

Instantly share code, notes, and snippets.

@bwmcadams
Last active February 16, 2016 18:19
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save bwmcadams/937ca257e093d00efadf to your computer and use it in GitHub Desktop.
ADT Root Type validator, makes sure it's either a trait or abstract class *AND* sealed.
package codes.bytes.macros_intro.macros
import scala.annotation.{ compileTimeOnly, StaticAnnotation }
import scala.language.postfixOps
import scala.reflect.macros.whitebox.Context
import scala.language.experimental.macros
object ADT {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
import Flag._
// check if it meets the requirements for what can be annotated
val inputs = annottees.map(_.tree).toList
val result: Tree = {
def validateClassDef(
cD: c.universe.ClassDef,
mods: c.universe.Modifiers,
name: c.universe.TypeName,
tparams: List[c.universe.TypeDef],
impl: c.universe.Template,
companion: Option[ModuleDef]): c.universe.Tree = {
if (mods.hasFlag(TRAIT)) {
if (!mods.hasFlag(SEALED)) {
c.error(c.enclosingPosition, s"ADT Root traits (trait $name) must be sealed.")
}
else {
c.info(c.enclosingPosition, s"ADT Root trait $name sanity checks OK.", force = true)
}
companion match {
/**
* According to the docs, if you annotate a *class* with a companion,
* the class and companion will be sent in (e.g., List(class, object) for
* entry Tree. If you annotate an *object* with a companion, only the object
* is passed in.
*
* Using ClassDef match, Scala will refuse to accept returned Tree unless it
* includes both companions sent in. For QuasiQuotes it seems to ignore that,
* yet the object still works fine after Macro transform.
*/
case Some(mD) q"$cD; $mD"
case None cD
}
} else if (!mods.hasFlag(ABSTRACT)) {
c.error(c.enclosingPosition, s"ADT Root classes (class $name) must be abstract.")
cD
} else if (!mods.hasFlag(SEALED)) {
// class that's abstract
c.error(c.enclosingPosition, s"ADT Root classes (abstract class $name) must be sealed.")
cD
} else {
c.info(c.enclosingPosition, s"ADT Root class $name sanity checks OK.", force = true)
companion match {
/**
* According to the docs, if you annotate a *class* with a companion,
* the class and companion will be sent in (e.g., List(class, object) for
* entry Tree. If you annotate an *object* with a companion, only the object
* is passed in.
*
* Using ClassDef match, Scala will refuse to accept returned Tree unless it
* includes both companions sent in. For QuasiQuotes it seems to ignore that,
* yet the object still works fine after Macro transform.
*/
case Some(mD) q"$cD; $mD"
case None cD
}
}
}
inputs match {
case (cD @ ClassDef(mods, name, tparams, impl)) :: Nil
validateClassDef(cD, mods, name, tparams, impl, companion = None)
// annotated class with companion object.
// In the case of an annotated class/object w/ a companion, the companion is passed to
// annottees. We *are* assuming the class is one annotated here.
case (cD @ ClassDef(mods, name, tparams, impl)) :: (mD: ModuleDef) :: Nil
validateClassDef(cD, mods, name, tparams, impl, companion = Some(mD))
case (o @ ModuleDef(_, name, _)) :: Nil
c.error(c.enclosingPosition, s"ADT Roots (object $name) may not be Objects.")
o
case (d @ DefDef(mods, name, _, _, _, _)) :: Nil
c.error(c.enclosingPosition, s"ADT Roots (def $name) may not be Methods.")
d
case (d @ ValDef(mods, name, _, _)) :: Nil
if (mods.hasFlag(Flag.MUTABLE))
c.error(c.enclosingPosition, s"ADT Roots (var $name) may not be Variables.")
else
c.error(c.enclosingPosition, s"ADT Roots (val $name) may not be Variables.")
d
// Not sure what would hit here, I checked and you cannot annotate a package object at all
case x :: Nil
c.error(c.enclosingPosition, s"Invalid ADT Root ($x) [${x.getClass}].")
x
case Nil
c.error(c.enclosingPosition, "Cannot validate ADT Root of empty Tree.")
// the errors should cause us to stop before this,
// but needed to match up our match type
reify {}.tree
}
}
c.Expr[Any](result)
}
}
/**
* From the Macro Paradise Docs...
*
* note the @compileTimeOnly annotation. It is not mandatory, but is recommended to avoid confusion.
* Macro annotations look like normal annotations to the vanilla Scala compiler, so if you forget
* to enable the macro paradise plugin in your build, your annotations will silently fail to expand.
* The @compileTimeOnly annotation makes sure that no reference to the underlying definition is
* present in the program code after typer, so it will prevent the aforementioned situation
* from happening.
*/
@compileTimeOnly("Enable Macro Paradise for Expansion of Annotations via Macros.")
final class ADT extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro ADT.impl
}
// vim: set ts=2 sw=2 sts=2 et:
scala> :paste
// Entering paste mode (ctrl-D to finish)
@ADT
sealed abstract class TestCompanions {
def check = "Test Companion Class"
}
object TestCompanions {
def check = "Test Companion Object"
}
// Exiting paste mode, now interpreting.
<console>:11: ADT Root class TestCompanions sanity checks OK.
@ADT
^
defined class TestCompanions
defined object TestCompanions
scala> TestCompanions.check
res2: String = Test Companion Object
scala> new TestCompanions {}.check
res3: String = Test Companion Class
scala> import codes.bytes.macros_intro.macros.ADT
import codes.bytes.macros_intro.macros.ADT
scala> :paste
// Entering paste mode (ctrl-D to finish)
@ADT
trait Foo
@ADT
object Bar
@ADT
abstract class Baz
@ADT
sealed trait Spam
@ADT
sealed abstract class Eggs
// Exiting paste mode, now interpreting.
<console>:11: error: ADT Root traits (trait Foo) must be sealed.
@ADT
^
<console>:14: error: ADT Roots (object Bar) may not be Objects.
@ADT
^
<console>:17: error: ADT Root classes (abstract class Baz) must be sealed.
@ADT
^
<console>:20: ADT Root trait Spam sanity checks OK.
@ADT
^
<console>:23: ADT Root class Eggs sanity checks OK.
@ADT
scala> @ADT
| class NonAbstractUnsealedClass
<console>:11: error: ADT Root classes (class NonAbstractUnsealedClass) must be abstract.
@ADT
@bwmcadams
Copy link
Author

Fixed to work with companion objects.

@bwmcadams
Copy link
Author

Thanks to @julianpeeters, who solved the quasiquotes version of combining companions for return.

@bwmcadams
Copy link
Author

annotation macros should be whitebox, not blackbox.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment