Macro annotations transform untyped trees, so you should use c.typecheck
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
class GreaterThan(m: Double) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro GreaterThanMacro.impl
}
object GreaterThanMacro {
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
annottees match {
case q"$_ val $_: $tpt = $_" :: tail =>
if (!(c.typecheck(tq"$tpt", mode = c.TYPEmode).tpe <:< typeOf[Double]))
c.abort(c.enclosingPosition, s"$tpt is not Double")
q"..$tail"
}
}
}
case class Person(name: String, @GreaterThan(0) age: Double) // compiles
// doesn't compile: String is not Double
// case class Person(@GreaterThan(0) name: String, age: Double)
Besides c.abort
there are c.error
, c.warning
.
Please beware of limitations for type checking in macro annotations (when a type is defined in the same scope where annotation is expanded)
type Dbl = Double // outer scope
{
case class Person(name: String, @GreaterThan(0) age: Dbl) // compiles
}
type Dbl = Double // the same scope
// doesn't compile: scala.reflect.macros.TypecheckException: not found: type Dbl
// case class Person(name: String, @GreaterThan(0) age: Dbl)
Scala whitebox macro how to check if class fields are of type of a case class
Sometimes implicit macros (macro-generated type classes) can be an alternative to macro annotations.
Maybe you could also try the library https://github.com/fthomas/refined/
import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric.Positive
case class Person(name: String, age: Double Refined Positive)