Search code examples
scalaimplicitscala-macros

Implicit macro. Default implicit value. How?


I don't even know how to ask the question.

I have a macro that creates an instance of IsEnum[T] for a type T.

I'm doing testing for it, and want to make sure that the implicit is not found for types that are not sealed, or that, in general, don't meet the requirements of an enum.

So I created this method for testing

    def enumOf[T](implicit isEnum:IsEnum[T] = null) = isEnum

And then I ensure that enumOf[NotAnEnum] == null

But instead, it fails at compile time.

One thing is the macro erroring. Another thing is the macro just not applying for a given case. How to make that distinction when creating macros?

Edit: I've used c.abort and c.error, both giving me the same results.


Solution

  • Sounds like you didn't make your macro materializing type class IsEnum whitebox. Normally implicit macros should be whitebox.

    import scala.language.experimental.macros
    import scala.reflect.macros.whitebox
    
    trait IsEnum[T]
    object IsEnum {
      implicit def mkIsEnum[T]: IsEnum[T] = macro mkIsEnumImpl[T]
      def mkIsEnumImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
        import c.universe._
        val typ = weakTypeOf[T]
        val classSymbol = typ.typeSymbol.asClass
        if (!classSymbol.isTrait || !classSymbol.isSealed) c.abort(c.enclosingPosition, s"$typ must be sealed trait")
        val symbols = classSymbol.knownDirectSubclasses
        symbols.collectFirst {
          case symbol if !symbol.isModuleClass || !symbol.asClass.isCaseClass =>
            c.abort(c.enclosingPosition, s"${symbol.asClass.toType} must be case object")
        }
        q"new IsEnum[$typ] {}"
      }
    }
    
    def enumOf[T](implicit isEnum: IsEnum[T] = null) = isEnum
    

    Usage:

    sealed trait A
    object A {
      case object B extends A
      case object C extends A
      case class D() extends A
    }
    enumOf[A] //null
    
    sealed trait A
    object A {
      case object B extends A
      case object C extends A
      class D extends A
    }
    enumOf[A] //null
    
    sealed trait A
    object A {
      case object B extends A
      object C extends A
    }
    enumOf[A] //null
    
    trait A
    object A {
      case object B extends A
      case object C extends A
    }
    enumOf[A] //null
    
    sealed trait A
    object A {
      case object B extends A
      case object C extends A
    }
    enumOf[A] //App$$anon$1@47f37ef1
    

    Runtime of macros is compile time of main code. If a blackbox macro (even implicit blackbox macro) throws an exception then it will be a compile error during compilation of main code. If a whitebox implicit macro throws an exception then during compilation of main code the implicit will be silently removed from candidates.

    https://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html