How to access parent class params literals? - scala

Using Scala 2.x macros, how to access literals passed to parent classes when defining subclasses, typically with ADTs such as the following:
sealed abstract class Base(val s: String, val i: Int)
object Base {
case object A extends Base("a", 1)
case object B extends Base("b", 2)
case class C(override val s: String) extends Base(s, 3)
}
For object A, I would like to know parameter values are literals "a" and 1.
For object B, I would like to know parameter values are literals "b" and 2.
For case class C, I would like to know the second parameter is literal 3.
How can I accomplish this with Scala 2.x macros?

After much investigation and getting inspiration from bwmcadams /
supreme-macro-adventure, it looks like one solution is based on the combination of Macro Paradise and the creation of an #ADT annotation.
Macro paradise provides a mechanism to expand annotations on ADTs to rewrite them, thanks to the macroTransform function.
import scala.annotation.{compileTimeOnly, StaticAnnotation}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
#compileTimeOnly("enable macro paradise to expand macro annotations")
class ADT extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro ADT.impl
}
object ADT {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val trees = annottees.map(_.tree)
val rewritten = trees match {
case (cd: ClassDef) :: q"object $name { ..$body }" :: Nil =>
$body match {
case q"case object $name extends $_($literal, $_)" :: tail =>
[...]
}
case _ =>
trees
}
c.Expr[Any](q"{..$rewritten}")
}
}
With the help of quasiquotes, one can navigate $body and pattern match the different cases to access the arguments passed to the parent class (here $literal). Finally, one returns the rewritten ADT.

Related

Scala macro: get companion object from class type

I cannot manage to get the companion object / singleton from a class type in Scala macro / quasiquotes. Tried to follow https://docs.scala-lang.org/overviews/quasiquotes/type-details.html#singleton-type, the given example works but it is based on a literal string to quasiquote to get the companion object directly, which I cannot quite achieve the same thing if I start off from an extracted class type of a param after some quasiquote unlifting.
I have simplified and tried to highlight the intended usage, and current macro implementation below:
// Intended usage
package utils
sealed trait Base
object Base {
import macros._
#Component
final case class X(v: XInner) extends Base
}
final case class XInner(v: Int)
Base.X(123) // No need to do Base.X(XInner(123))
The current macro implementation
package macros
import scala.reflect.macros.whitebox
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly
class Component() extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro ComponentMacro.impl
}
private class ComponentMacro(val c: whitebox.Context) {
import c.universe._
// To map function result while allowing the use of params forwarding syntax like `apply _`
// e.g. `def y = (X.apply _).mapResult(Y(_))`
implicit class Func1Extra[I1, O1, O2](f: I1 => O1) {
def mapResult(g: O1 => O2): I1 => O2 = (i1: I1) => g(f(i1))
}
def impl(annottees: Tree*): Tree = annottees match {
case (clsDef: ClassDef) :: Nil =>
clsDef match {
case q"final case class $className(..$fields) extends ..$parents" if fields.length == 1 => {
val fieldType = fields(0).tpt
val singletonType = tq"$fieldType.type"
val tq"$singleton.type" = singletonType
q"""
$clsDef
object ${clsDef.name.toTermName} {
def apply = (${singleton}.apply _).mapResult(new ${clsDef.name}(_))
}
"""
}
case _ => c.abort(c.enclosingPosition, "Invalid annotation target")
}
case _ => c.abort(c.enclosingPosition, "Invalid annotation target")
}
}
The error while compiling is:
value apply is not a member of Utils.XInner
The error message seems to suggest that the apply method was done on the XInner class type, rather than the companion object of XInner.
Any idea as to how to get the component object of the same type name? Thanks in advance!

Scala macros, how to get full resolved symbol of identifier (term name and type name)?

I use a macro annotation to add a method to a class based on code introspection in another method of this class. I extract the method's syntax tree from the class definition, but within the method the identifiers of the terms and types are not expanded. These identifiers do not yet contain information about imports, other definitions within the class, and implicit conversions.
import geo.model.meta.src.state.IStateCoding
import java.util.{Collection ⇒ JCollection}
import scala.collection.JavaConverters._
#IntrospectionGenerator
class IntrospectionTest(
val a:Int,
val b:String,
map:ConcurrentHashMap[String, JCollection[String]]
){
def inrospectionTarget: IntrospectionTest = {
val map = new ConcurrentHashMap[String, JCollection[String]]()
map.put("some", Seq("bar").asJava)
new IntrospectionTest(a, b, map)
}
}
//Macros definition example
#compileTimeOnly("enable macro paradise to expand macro annotations")
class IntrospectionGenerator extends StaticAnnotation{
def macroTransform(annottees: Any*): Any = macro IntrospectionGenerator.impl
}
object IntrospectionGenerator {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
annottees.map(_.tree) match{
case (cls # ClassDef(modifiers, className, typeDefs, template)) :: Nil⇒
val nonInferedContent = template.body.collectFirst{
case DefDef(_, TermName("copyState"), Nil, Nil, _, content) ⇒
content
}.get
val idents = nonInferedContent.collect{
case iden # Ident(name) ⇒
idents //HOW TO GET SYMBOL FOR THIS IDENTIFIER?
}.flatten
val generatedMethod = q"override def copyStateSource = $someStructure"
val extTemplate = Template(
template.parents,
template.self,
template.body ::: generatedMethod :: Nil
)
c.Expr(ClassDef(modifiers, className, typeDefs, extTemplate))
case _ ⇒
c.abort(c.enclosingPosition, "incorrect use of StateCodingMacro")
}
}
}
1) How can I manually create a context (BlackBox) for a method in the context of a class so that this context knows all the definitions within the class?
2) How can I get the definition of some identifier (full qualified name of Class or method) inside the AST of method without a common typeCheck call? I want to preserve the original tree structure, but at the same time understand what the identifiers of terms and types in this tree refer to. Examples of identifiers from the above code: JCollection, asJava, a, b, map, IntrospectionTest.

Can I take a class as an argument to a macro annotation

I'm attempting to build a scala class based on some java classes generated by a third party annotation pre-processor.
I'd like to be able to "point" to a class from an annotated object, for example:
#MyAnnotation(classOf[GeneratedJavaClass]) object MyObject
or
#MyAnnotation object MyObject extends PlaceHolderTrait[GeneratedJavaClass]
Once I'm in the actual macro implementation, I would like to reflect on GeneratedJavaClass to find it's members which I'll use to build the implementation of MyObject
My starting point so far is based on https://github.com/travisbrown/type-provider-examples/blob/master/rdfs-public/src/main/scala/public/PrefixGenerator.scala.
I've tried to understand how I could take a Class[T] as the argument to the annotation and then match on c.macroApplication with Apply(Select(Apply(_, List(TypeApply(_, List(catalog)))), _), _) but the type I get is a TypeApply(_, List(Trees$Ident) and I don't see a way to get the class from there (I assume classOf[T] isn't a literal).
As an alternative, I thought I'd try to extract the type I want from a trait that I have the object extend. I tried matching the annotee against case List(q"object $name extends PlaceHolderTrait[$parent] { ..$body }") but again end up with a Trees$Ident and am not sure how to get the class that is being referenced.
I realize I could probably just pass a String of the fully qualified name and use reflection to get the class, but I was hoping for a nicer alternative. Note that I'm not tied to those 2 alternatives for specifying the class, they are just the 2 options I've been able to come up with.
Ok, finally got something working, based on https://github.com/scalamacros/paradise/issues/69 I realized that at the stage I'm running in the typer hasn't run and therefore expecting to be given a type is a bit silly. The macro api does provide the typeCheck method however, which lets you run the typer on a tree, as follows:
class AnnotationArgument[T] extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AnnotationArgumentImpl.impl
}
class AnnotationArgumentImpl(val c: blackbox.Context) {
import c.universe._
def impl(annottees: c.Expr[Any]*): c.Tree = {
val macroTypeWithArguments = c.typeCheck(q"${c.prefix.tree}").tpe // my.package.AnnotationArgument[my.package.MyClass]
val annotationClass: ClassSymbol = macroTypeWithArguments.typeSymbol.asClass // my.package.AnnotationArgument
val annotationTypePlaceholder: Type = annotationClass.typeParams.head.asType.toType // T
val argumentType: Type = annotationTypePlaceholder.asSeenFrom(args, annotationClass) // my.package.MyClass
println(s"the argument's type is $argumentType")
q"..${annottees}"
}
}
import my.package.MyClass
#AnnotationArgument[MyClass]
class AnnotationArgumentTestClass
another thought :
use another class annotation save class info
class AnnotationArgumentClass[T](clazz: Class[T]) extends StaticAnnotation
class AnnotationArgument extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AnnotationArgumentImpl.impl
}
class AnnotationArgumentImpl(val c: blackbox.Context) {
import c.universe._
def impl(annottees: c.Expr[Any]*): c.Tree = {
val classValue = annottees.head.tree match {
case x: MemberDefApi => x.mods.annotations collectFirst {
case q"new $annotation($classValue)" => classValue
}
}
/*edit :*/
val x = (c.eval(c.Expr[Type](c.typecheck(classValue.get))))
println(x.typeSymbol.fullName)
q"..${annottees}"
}
}
test :
package aaa
class AAA
//
import aaa.AAA
#AnnotationArgument
#AnnotationArgumentClass(classOf[AAA])
class AnnotationArgumentTestClass

Define a trait to be extended by case class in scala

I have some case classes which have a method tupled defined in its companion object. As it can be seen from the code below in companion objects, it is just code duplication.
case class Book(id: Int, isbn: String, name: String)
object Book {
def tupled = (Book.apply _).tupled // Duplication
}
case class Author(id: Int, name: String)
object Author {
def tupled = (Author.apply _).tupled // Duplication
}
From another question (can a scala self type enforce a case class type), it seems like we can not enforce the self-type of a trait to be a case class.
Is there a way to define a trait (say Tupled) that can be applied as following?
// What would be value of ???
trait Tupled {
self: ??? =>
def tupled = (self.apply _).tupled
}
// Such that I can replace tupled definition with Trait
object Book extends Tupled {
}
Because there's no relationship between FunctionN types in Scala, it's not possible to do this without arity-level boilerplate somewhere—there's just no way to abstract over the companion objects' apply methods without enumerating all the possible numbers of members.
You could do this by hand with a bunch of CompanionN[A, B, C, ...] traits, but that's pretty annoying. Shapeless provides a much better solution, which allows you to write something like the following:
import shapeless.{ Generic, HList }, shapeless.ops.product.ToHList
class CaseClassCompanion[C] {
def tupled[P <: Product, R <: HList](p: P)(implicit
gen: Generic.Aux[C, R],
toR: ToHList.Aux[P, R]
): C = gen.from(toR(p))
}
And then:
case class Book(id: Int, isbn: String, name: String)
object Book extends CaseClassCompanion[Book]
case class Author(id: Int, name: String)
object Author extends CaseClassCompanion[Author]
Which you can use like this:
scala> Book.tupled((0, "some ISBN", "some name"))
res0: Book = Book(0,some ISBN,some name)
scala> Author.tupled((0, "some name"))
res1: Author = Author(0,some name)
You might not even want the CaseClassCompanion part, since it's possible to construct a generic method that converts tuples to case classes (assuming the member types line up):
class PartiallyAppliedProductToCc[C] {
def apply[P <: Product, R <: HList](p: P)(implicit
gen: Generic.Aux[C, R],
toR: ToHList.Aux[P, R]
): C = gen.from(toR(p))
}
def productToCc[C]: PartiallyAppliedProductToCc[C] =
new PartiallyAppliedProductToCc[C]
And then:
scala> productToCc[Book]((0, "some ISBN", "some name"))
res2: Book = Book(0,some ISBN,some name)
scala> productToCc[Author]((0, "some name"))
res3: Author = Author(0,some name)
This will work for case classes with up to 22 members (since the apply method on the companion object can't be eta-expanded to a function if there are more than 22 arguments).
The problem is that the signature of apply differs from case to case, and that there is no common trait for these functions. Book.tupled and Author.tupled basically have the same code, but have very different signatures. Therefore, the solution may not be as nice as we'd like.
I can conceive of a way using an annotation macro to cut out the boilerplate. Since there isn't a nice way to do it with the standard library, I'll resort to code generation (which still has compile-time safety). The caveat here is that annotation macros require the use of the macro paradise compiler plugin. Macros must also be in a separate compilation unit (like another sbt sub-project). Code that uses the annotation would also require the use of the macro paradise plugin.
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
#compileTimeOnly("enable macro paradise to expand macro annotations")
class Tupled extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro tupledMacroImpl.impl
}
object tupledMacroImpl {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val result = annottees map (_.tree) match {
// A case class with companion object, we insert the `tupled` method into the object
// and leave the case class alone.
case (classDef # q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }")
:: (objDef # q"object $objName extends { ..$objEarlyDefs } with ..$objParents { $objSelf => ..$objDefs }")
:: Nil if mods.hasFlag(Flag.CASE) =>
q"""
$classDef
object $objName extends { ..$objEarlyDefs } with ..$objParents { $objSelf =>
..$objDefs
def tupled = ($objName.apply _).tupled
}
"""
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: must be a companion object of a case class.")
}
c.Expr[Any](result)
}
}
Usage:
#Tupled
case class Author(id: Int, name: String)
object Author
// Exiting paste mode, now interpreting.
defined class Author
defined object Author
scala> Author.tupled
res0: ((Int, String)) => Author = <function1>
Alternatively, something like this may be possible with shapeless. See #TravisBrown's better answer.

Detecting a macro annotated type within the body of a macro annotation

I want to use macro annotations (macro-paradise, Scala 2.11) to generate synthetic traits within an annotated trait's companion object. For example, given some STM abstraction:
trait Var[Tx, A] {
def apply() (implicit tx: Tx): A
def update(value: A)(implicit tx: Tx): Unit
}
I want to define a macro annotation txn such that:
#txn trait Cell[A] {
val value: A
var next: Option[Cell[A]]
}
Will be re-synthesised into:
object Cell {
trait Txn[-Tx, A] {
def value: A
def next: Var[Option[Cell.Txn[Tx, A]]] // !
}
}
trait Cell[A] {
val value: A
var next: Option[Cell[A]]
}
I got as far as producing the companion object, the inner trait, and the value member. But obviously, in order for the next member to have the augmented type (instead of Option[Cell[A]], I need Option[Cell.Txn[Tx, A]]), I need to pattern match the type tree and rewrite it.
For example, say I find the next value definition in the original Cell trait like this:
case v # ValDef(vMods, vName, tpt, rhs) =>
How can I analyse tpt recursively to rewrite any type X[...] being annotated with #txn to X.Txn[Tx, ...]? Is this even possible, given like in the above example that X is yet to be processed? Should I modify Cell to mix-in a marker trait to be detected?
So the pattern matching function could start like this:
val tpt1 = tpt match {
case tq"$ident" => $ident // obviously don't change this?
case ??? => ???
}
I would like to preface my answer with the disclaimer that this kind of stuff is not easy to do in current Scala. In an ideal macro system, we would like to typecheck tpt in its lexical context, then walk through the structure of the resulting type replacing X[A] with X.Txn[Tx, A] for those X that are subtypes of TxnMarker, and then use the resulting type in the macro expansion.
However, this kind of happy mixing of untyped trees (that come into macro annotations) and typed trees (that typechecker emits) is incompatible with how compiler internals work (some details about that can be found in Scala macros: What is the difference between typed (aka typechecked) an untyped Trees), so we'll have to approximate.
This will be an approximation, because both in 2.10 and 2.11 our macros are unhygienic, meaning that they are vulnerable to name clashes (e.g. in the final expansion tree Var in Var[...] could bind to something unrelated if e.g. that trait we're rewriting contains a type member called Var). Unfortunately at the moment there's just one way to address this problem robustly, and it is very-very hard to carry out without deep understanding of compiler internals, so I'm not going into those details here.
import scala.reflect.macros.whitebox._
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
trait Var[T]
trait TxnMarker
object txnMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
// NOTE: this pattern is only going to work with simple traits
// for a full pattern that captures all traits, refer to Denys's quasiquote guide:
// http://den.sh/quasiquotes.html#defns-summary
val q"$mods trait $name[..$targs] extends ..$parents { ..$stats }" = annottees.head.tree
def rewire(tpt: Tree): Tree = {
object RewireTransformer extends Transformer {
override def transform(tree: Tree): Tree = tree match {
case AppliedTypeTree(x # RefTree(xqual, xname), a :: Nil) =>
val dummyType = q"type SomeUniqueName[T] = $x[T]"
val dummyTrait = q"$mods trait $name[..$targs] extends ..$parents { ..${stats :+ dummyType} }"
val dummyTrait1 = c.typecheck(dummyTrait)
val q"$_ trait $_[..$_] extends ..$_ { ..${_ :+ dummyType1} }" = dummyTrait1
def refersToSelf = dummyTrait1.symbol == dummyType1.symbol.info.typeSymbol
def refersToSubtypeOfTxnMarker = dummyType1.symbol.info.baseClasses.contains(symbolOf[TxnMarker])
if (refersToSelf || refersToSubtypeOfTxnMarker) transform(tq"${RefTree(xqual, xname.toTermName)}.Txn[Tx, $a]")
else super.transform(tree)
case _ =>
super.transform(tree)
}
}
RewireTransformer.transform(tpt)
}
val stats1 = stats map {
// this is a simplification, probably you'll also want to do recursive rewiring and whatnot
// but I'm omitting that here to focus on the question at hand
case q"$mods val $name: $tpt = $_" => q"$mods def $name: $tpt"
case q"$mods var $name: $tpt = $_" => q"$mods def $name: Var[${rewire(tpt)}]"
case stat => stat
}
val annottee1 = q"$mods trait $name[..$targs] extends ..${parents :+ tq"TxnMarker"} { ..$stats }"
val companion = q"""
object ${name.toTermName} {
trait Txn[Tx, A] { ..$stats1 }
}
"""
c.Expr[Any](Block(List(annottee1, companion), Literal(Constant(()))))
}
}
class txn extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro txnMacro.impl
}
Actually, while I was writing this macro, I realized that it is ill-equipped to deal with cyclic dependencies of #txn-annotated definitions. The info.baseClasses.contains(symbolOf[TxnMarker]) check essentially forces expansion of the class/trait referred to in info, so the macro is going to loop. This won't lead to a SOE or a freeze though - scalac will just produce a cyclic reference error and bail out.
At the moment I've no idea how to address this problem purely with macros. Maybe you could leave the code generation part in an annotation macro and then move the type transformation part into a fundep materializer. Oh right, it seems that it might work! Instead of generating
object Cell {
trait Txn[-Tx, A] {
def value: A
def next: Var[Option[Cell.Txn[Tx, A]]]
}
}
trait Cell[A] {
val value: A
var next: Option[Cell[A]]
}
You could actually generate this:
object Cell {
trait Txn[-Tx, A] {
def value: A
def next[U](implicit ev: TxnTypeMapper[Option[Cell[A]], U]): U
}
}
trait Cell[A] {
val value: A
var next: Option[Cell[A]]
}
And TxnTypeMapper[T, U] could be summoned by a fundep materializer macro that would do the Type => Type transformation using Type.map, and that one won't lead to cyclic reference errors, because by the time a materializer is invoked (during typer), all macro annotations will have already expanded. Unfortunately, I don't have time to elaborate at the moment, but this looks doable!!