Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

bwmcadams commented Feb 13, 2016

Fixed to work with companion objects.

@bwmcadams
Copy link
Author

bwmcadams commented Feb 13, 2016

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

@bwmcadams
Copy link
Author

bwmcadams commented Feb 16, 2016

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