0

I need Scala macros (reify, quasiquote, macro impl) for my Scala assertions library.


I want to be able to do this:

object1.assertEquals(object2) // success: object1 = object2

Or like this:

3.assertEquals(1 + 1) // failure: 1 + 1 /= 3 

Can Scala macros be defined inside an implicit class?

Michael Lafayette
  • 2,972
  • 3
  • 20
  • 54
  • Yes, a def macro is just a method invocation. The prefix you see in the application will look different. – som-snytt Mar 24 '16 at 00:47
  • @som-snytt - what do you mean by "The prefix you see in the application will look different"? – Michael Lafayette Mar 24 '16 at 03:06
  • @som-snytt - if I want the object itself, "this", to be passed into the macro, do I just do q"$this" and use this as a Tree just like the other Trees? – Michael Lafayette Mar 24 '16 at 03:07
  • `Context.prefix` tree isn't `object1` but `myimplicit(object1)`. There are better examples no doubt but https://github.com/som-snytt/regextractor/blob/master/util/src/main/scala/regex/Mex.scala#L80 – som-snytt Mar 24 '16 at 15:59

2 Answers2

1

//ed : write package

package so

trait AssertEquals[T, V] {
  def assertEquals(t: T, v: V): Boolean
}

object AssertEquals {
  implicit def assertEquals[T, V]: AssertEquals[T, V] = macro impl[T, V]


  implicit class WithAssertEquals[T](t: T) {
    def assertEquals[V](v: V)(implicit assertEquals: AssertEquals[T, V]): Boolean = assertEquals.assertEquals(t, v)
  }

  def impl[T: c.WeakTypeTag, V: c.WeakTypeTag](c: Context) = {
    import c.universe._
    val _t = c.weakTypeOf[T]
    val _v = c.weakTypeOf[V]

    //edit 2 : use symbolOf instead typeOf
    q"""
      {
      new ${symbolOf[so.AssertEquals[_, _]]}[${_t},${_v}]{
        def assertEquals(t: ${_t}, v: ${_v}): Boolean = t == v
      }
      }
      """
  }
}

//test

import AssertEquals.WithAssertEquals

assert(1.assertEquals(2) == false)
assert(2.assertEquals(2) == true)
assert("a".assertEquals("a") == true)
assert("a".assertEquals("b") == false)
assert("a".assertEquals(1) == false)
余杰水
  • 1,404
  • 1
  • 11
  • 14
1

The implementation has to be in an object or a macro bundle, but the method that is implemented by the macro can be in an implicit class. Notice val self = q"${c.prefix}.self" that is needed to get the reference to the object that is wrapped by the implicit class.

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context


object assertions {
  implicit class AssertEquals[T](val self: T) extends AnyVal {
    def assertEquals(other: T): Unit = macro AssertionMacros.assertEquals[T]
  }

  object AssertionMacros {
    def assertEquals[T](c: Context)(other: c.Tree): c.Tree = {
      import c.universe._
      val self = q"${c.prefix}.self"
      q"_root_.scala.Predef.assert($self == $other)"
    }
  }
}

Usage:

scala> import assertions._
import assertions._

scala> "foo" assertEquals "foo"

scala> "foo" assertEquals "bar"
java.lang.AssertionError: assertion failed
  at scala.Predef$.assert(Predef.scala:151)
  ... 43 elided
Jasper-M
  • 14,966
  • 2
  • 26
  • 37
  • This is a much cleaner implementation. I especially like how you referred to "self" explicitly so the macro feels like it belongs in the class. Best answer. – Michael Lafayette Apr 07 '16 at 15:48