Long Term Macro Override Macro Support Question

96 views
Skip to first unread message

Stephen Twigg

unread,
Apr 20, 2015, 7:59:20 PM4/20/15
to scala-i...@googlegroups.com
I am currently designing a DSL using Scala macros (including macro paradise for macro annotations) to simplify the user-visible interface. I am using a few features of macros that I am unsure if they are considered 'acceptable.' (I apologize if this is the wrong venue. In my searches, I was finding more posts about macro features in scala-internals than scala-users.)

Is it acceptable to have a macro override another macro?
This is requisite because, in the subclass, I wish to refine the type. In each case, the macro is transforming a 'user-visible, external' function call with some fields to an internal one with more fields. This is particularly helpful for adding implicits without using a second parameter list (as this tends to break 'chained' applies). Further, the macro can trivially use enclosingPosition to see precisely in the user code where the function was invoked.

I currently do this (vastly simplified):
abstract class Parent {
  // external->internal API
  def do_modifySelf(myImplicit: SomeClass, debug_info: String): Parent

  // external API
  def modifySelf: Parent = macro MacroImpl.toModifySelf
}
class Child {
  // external->internal API
  def do_modifySelf(myImplicit: SomeClass, debug_info: String): Child = ??? // implementation here

  // external API
  override def modifySelf: Child = macro MacroImpl.toModifySelf
}

object MacroImpl {
  import scala.reflect.macro.blackbox.Context
  def toModifySelf(c: Context): c.Tree  = {
    import c.universe._
    def myThis = c.prefix.tree
    def emtype = tq"_root_.mypackage.SomeClass"
    val location = Literal(Constant(c.enclosingPosition.toString))
    q"$myThis.do_modifySelf(implicitly[$emType], $location)"
  }
}

Currently, it works amazingly. e.g. the following 'user code' typechecks and expands as expected:
val myParent: Parent = new Child
val myChild: Child = new Child
myParent.modifySelf: Parent
myChild.modifySelf: Child
myChild.modifySelf: Parent

//myParent.modifySelf: Child // fails to typecheck, as desired

Referencing https://groups.google.com/forum/#!topic/scala-internals/otRPlIRoEqs, it seems that everything is sound as long as a macro only ever overrides another macro and the overriding type is a subtype although I have not thoroughly considered the potential pathological cases. Actually, the override macro is seems only necessary to remind the type inferer that the type of the doModifySelf is changing. The actual macro that is applied does not change at all. (To some degree, I wonder if using a whitebox context and some other adjustments would allow me to avoid the macro overrides. Of course, if that works, I wonder if this is an appropriate use of whitebox macros.)

Stephen Twigg

unread,
Apr 20, 2015, 8:04:41 PM4/20/15
to scala-i...@googlegroups.com
As an immediate followup, the changing to whitebox allows the overrides to be omitted and typechecking to still all work appropriately:
e.g.

abstract class Parent {
  // external->internal API
  def do_modifySelf(myImplicit: SomeClass, debug_info: String): Parent

  // external API
  def modifySelf: Parent = macro MacroImpl.toModifySelf
}
class Child {
  // external->internal API
  def do_modifySelf(myImplicit: SomeClass, debug_info: String): Child = ??? // implementation here
}

object MacroImpl {
  import scala.reflect.macro.whitebox.Context
  def toModifySelf(c: Context): c.Tree  = {
    import c.universe._
    def myThis = c.prefix.tree
    def emtype = tq"_root_.mypackage.SomeClass"
    val location = Literal(Constant(c.enclosingPosition.toString))
    q"$myThis.do_modifySelf(implicitly[$emType], $location)"
  }
}

Then, the following 'user code' typechecks and expands as expected:

Roman Janusz

unread,
Apr 21, 2015, 2:01:23 AM4/21/15
to scala-i...@googlegroups.com
You could also stay with blackbox macros by introducing a type member into your base type and refine it in subtypes. Then you could declare your macro to return that type member.

abstract class Parent {
  type
Self <: Parent
 
 
def modifySelf: Self = macro ...
}
class Child extends Parent {
  type
Self <: Child
}

Stephen Twigg

unread,
Apr 21, 2015, 2:32:31 AM4/21/15
to scala-i...@googlegroups.com
Ah thank you! This works excellently and is substantially better as it keeps the use of Scala macros from being too exotic. It also properly replaces restating the abstract functions in every step of the hierarchy with just refining the type parameter a little more. I haven't had much experience with using type members before so I hadn't even considered using them. (Prior type arrangements architectures involved concrete non-leaf nodes; however, this proved to be problematic with other issues as well.) However, looking at them again this does seem to be a perfect use case.
Reply all
Reply to author
Forward
0 new messages