Accessing class definition by name from scala macro - scala

I'm trying to create a Scala macro which defines a single a Class argument, and which modifies the class to which it is attached based on the implementation of the Class which is provided as an argument.
//Simple class with a few arguments
class A(a: String, b: String)
//Definition of this class should be modified based on class definition of A
#parameterized(classOf[A])
class B
I've managed to create a simple macro that is able to extract the argument from the annotation, resulting in a TypeName object that contains a string representation of the full class name.
The problem is now that I need to access the definition of A from the macro implementation (specifically, I want to see what the constructor arguments are).
Is there a way to access/create a TypeTag[A] in some way? Is there a way I can access the AST of class A?
To illustrate what I'm trying to achieve, this is what I currently have as a macro definition:
object parameterizedMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
import Flag._
//Extract the parameter type which was provided as an argument (rather hacky way of getting this info)
val parameterType = c.macroApplication match {
case Apply(Select(Apply(_, List(
TypeApply(Ident(TermName("classOf")), List(Ident(TypeName(parameterType))))
)) , _), _) => parameterType
case _ =>
sys.error("Could not match #parameterized arguments. Was a class provided?")
}
//Should generate method list based on the code of parameterType
val methods = ???
val result = {
annottees.map(_.tree).toList match {
case q"object $name extends ..$parents { ..$body }" :: Nil =>
q"""
object $name extends ..$parents {
..${methods}
..$body
}
"""
case q"class $name (..$args) extends ..$parents { ..$body }" :: Nil =>
q"""
class $name (..$args) extends ..$parents {
..${methods}
..$body
}
"""
}
}
c.Expr[Any](result)
}
}

It took a lot of trial and error, as I didn't manage to find a lot of documentation on the various classes, but I did end up managing to achieve what I wanted to do.
To acquire the parameter, I ended up having to put the fully qualified name in the annotation instead of the original TypeOf[Name].
#parameterized(the.fully.qualified.Name)
Which I could then get using
val parameterType = c.macroApplication match {
case Apply(Select(Apply(_, List(
parameterType
)) , _), _) => parameterType
case _ =>
error("Could not match #parameterized arguments. Was a class provided?")
}
val fullClassName = parameterType.toString()
Using TypeOf as mentioned in the original post didn't work for me as I didnt manage to get from the TypeApply to the correct fully qualified name. This doesn't really make the whole thing insecure as everything is checked at compile time, but it does look a bit less like standard Scala.
Once I had the fully qualified name, I could use the universe's mirror (The documentation on mirrors helped here) to get a ClassSymbol, from which it is possible to get the class constructor arguments:
val constructorArguments = {
val clazz = c.mirror.staticClass(fullClassName) //Get ClassSymbol
val clazzInfo = clazz.info //Turn ClassSymbol into Type
val constructor = clazzInfo.member(termNames.CONSTRUCTOR) //Get constructor member Symbol
val constructorMethod = constructor.asMethod //Turn into MethodSymbol
val parametersList = constructorMethod.paramLists //Finally extract list of parameters
if (parametersList.size != 1)
error("Expected only a single constructor in " + fullClassName)
val parameters = parametersList.head
for (parameter <- parameters) yield {
val term = parameter.asTerm //parameter is a term
(term.name.toString, term.typeSignature)
}
}

Related

Is there some way in scala that I can return a type?

I have a lot of classes such as DataFrameFlow, TextFlow, RDDFlow. They all derive from base class Flow.
Now I want to write a function judgeFlow which can read from a path: String and return something representing exact Flow type from which I can create corresponding instance. The whole code seems like the following
def judgeFlow(path:String) = /*1*/ {
Flow.getStoreType(path) match {
case StoreType.tdw =>
DataFrameFlow
case StoreType.hdfs =>
TextFlow
}
}
def createFlow(typeInfo:/*2*/) = /*3*/{
new typeInfo()
}
However, I don't know how to write in place 1, 2 and 3.
EDIT
Knowing how to construct them is not enough here, because I also want the following:
pattern matching through typeInfo
some ways to do asInstanceOf
EDIT 2
Definition of Flow
abstract class Flow(var outputName: String) extends Serializable{
def this() = this("")
...
}
Definition of DataFrameFlow
class DataFrameFlow(d: DataFrame, path: String) extends Flow {
var data: DataFrame = d
def this(data: DataFrame) = this(data, "")
def this(path: String) = this(null, path)
def this() = this(null, "")
...
}
Pattern matching can't return different types from different cases. The type returned by pattern matching is the least upper bound of types returned in cases.
When someone wants to return different types, most probably he/she wants a type class.
sealed abstract class Flow
class DataFrameFlow extends Flow
class TextFlow extends Flow
class RDDFlow extends Flow
trait JudgeFlow[In] {
type Out <: Flow
def judgeFlow(in: In): Out
}
object JudgeFlow {
implicit val `case1`: JudgeFlow[???] { type Out = DataFrameFlow } = ???
implicit val `case2`: JudgeFlow[???] { type Out = TextFlow } = ???
implicit val `case3`: JudgeFlow[???] { type Out = RDDFlow } = ???
}
def judgeFlow[In](in: In)(implicit jf: JudgeFlow[In]): jf.Out = jf.judgeFlow(in)
But the trouble is that types are resolved at compile time. You seem to want to choose a case based on a value of string i.e. at runtime. So you can't return more specific types than just Flow at compile time.
flatMap with Shapeless yield FlatMapper not found
It's hard to guess your use case completely.
But using Scala reflection you can try
import scala.reflect.runtime.universe._
import scala.reflect.runtime.currentMirror
def judgeFlow(path:String): Type = {
Flow.getStoreType(path) match {
case StoreType.tdw =>
typeOf[DataFrameFlow]
case StoreType.hdfs =>
typeOf[TextFlow]
}
}
def createFlow(typeInfo: Type): Flow = {
val constructorSymbol = typeInfo.decl(termNames.CONSTRUCTOR).asMethod
val classSymbol = typeInfo.typeSymbol.asClass
val classMirror = currentMirror.reflectClass(classSymbol)
val constructorMirror = classMirror.reflectConstructor(constructorSymbol)
constructorMirror().asInstanceOf[Flow]
}

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!

Use Scala macros to generate methods

I want to generate aliases of methods using annotation macros in Scala 2.11+. I am not even sure that is even possible. If yes, how?
Example - Given this below, I want the annotation macros to expand into
class Socket {
#alias(aliases = Seq("!", "ask", "read"))
def load(n: Int): Seq[Byte] = {/* impl */}
}
I want the above to generate the synonym method stubs as follows:
class Socket {
def load(n: Int): Seq[Byte] = // ....
def !(n: Int) = load(n)
def ask(n: Int) = load(n)
def read(n: Int) = load(n)
}
The above is of course a facetious example but I can see this technique being useful to auto generate sync/async versions of APIs or in DSLs with lots of synonyms. Is it possible to also expose these generated methods in the Scaladoc too? Is this something possible using Scala meta?
Note: What I am asking is quite different from: https://github.com/ktoso/scala-macro-method-alias
Also please don't mark this as a duplicate of this as the question is a bit different and a lot has changed in Scala macro land in past 3 years.
This doesn't seem possible exactly as stated. Using a macro annotation on a class member does not allow you to manipulate the tree of the class itself. That is, when you annotate a method within a class with a macro annotation, macroTransform(annottees: Any*) will be called, but the only annottee will be the method itself.
I was able to get a proof-of-concept working with two annotations. It's obviously not as nice as simply annotating the class, but I can't think of another way around it.
You'll need:
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
The idea is, you can annotate each method with this annotation, so that a macro annotation on the parent class is able to find which methods you want to expand.
class alias(aliases: String *) extends StaticAnnotation
Then the macro:
// Annotate the containing class to expand aliased methods within
#compileTimeOnly("You must enable the macro paradise plugin.")
class aliased extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AliasMacroImpl.impl
}
object AliasMacroImpl {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val result = annottees map (_.tree) match {
// Match a class, and expand.
case (classDef # q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }") :: _ =>
val aliasedDefs = for {
q"#alias(..$aliases) def $tname[..$tparams](...$paramss): $tpt = $expr" <- stats
Literal(Constant(alias)) <- aliases
ident = TermName(alias.toString)
} yield {
val args = paramss map { paramList =>
paramList.map { case q"$_ val $param: $_ = $_" => q"$param" }
}
q"def $ident[..$tparams](...$paramss): $tpt = $tname(...$args)"
}
if(aliasedDefs.nonEmpty) {
q"""
$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
..$stats
..$aliasedDefs
}
"""
} else classDef
// Not a class.
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a class")
}
c.Expr[Any](result)
}
}
Keep in mind this implementation will be brittle. It only inspects the annottees to check that the first is a ClassDef. Then, it looks for members of the class that are methods annotated with #alias, and creates multiple aliased trees to splice back into the class. If there are no annotated methods, it simply returns the original class tree. As is, this will not detect duplicate method names, and strips away modifiers (the compiler would not let me match annotations and modifiers at the same time).
This can easily be expanded to handle companion objects as well, but I left them out to keep the code smaller. See the quasiquotes syntax summary for the matchers I used. Handling companion objects would require modifying the result match to handle case classDef :: objDef :: Nil, and case objDef :: Nil.
In use:
#aliased
class Socket {
#alias("ask", "read")
def load(n: Int): Seq[Byte] = Seq(1, 2, 3).map(_.toByte)
}
scala> val socket = new Socket
socket: Socket = Socket#7407d2b8
scala> socket.load(5)
res0: Seq[Byte] = List(1, 2, 3)
scala> socket.ask(5)
res1: Seq[Byte] = List(1, 2, 3)
scala> socket.read(5)
res2: Seq[Byte] = List(1, 2, 3)
It can also handle multiple parameter lists:
#aliased
class Foo {
#alias("bar", "baz")
def test(a: Int, b: Int)(c: String) = a + b + c
}
scala> val foo = new Foo
foo: Foo = Foo#3857a375
scala> foo.baz(1, 2)("4")
res0: String = 34

Is it possible to replace the default apply method generated for case classes in a Scala macro?

It seems like this doesn't work ( Using 2.11.1 and macro paradise 2.0.1).
I was hoping that the case class generated methods would either be suppressed, or be in the tree so I could get rid of it. Is this a hard limitation?
class evis extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro EvisMacro.impl
}
object EvisMacro {
def impl(c: blackbox.Context)(annottees: c.Expr[Any]*) : c.Expr[Any] = {
import c.universe._
def makeApply(tpName: TypeName, parents: List[Tree], params: List[List[ValDef]] ) : List[Tree]= {
List(q"""def apply(...$params): $tpName = null""")
}
val result = annottees map (_.tree) match {
case (classDef # q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }")
:: Nil if mods.hasFlag(Flag.CASE) =>
c.info(c.enclosingPosition, s"Eviscerating $tpname !($mods, $parents, $paramss)", true)
parents match {
case q"${pname: TypeName}" :: rest =>
c.info(c.enclosingPosition, s"${pname.decodedName}", true )
val sc = c.universe.rootMirror.staticClass( pname.decodedName.toString )
c.info(c.enclosingPosition, s"${sc}", true )
}
val name = tpname.toTermName
q"""
$classDef
object $name {
..${makeApply(tpname, parents, paramss)}
}
"""
case (classDef # q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }")
:: q"object $objName {..$objDefs}"
:: Nil if mods.hasFlag(Flag.CASE) =>
q"""
$classDef
object $objName {
..${makeApply(tpname, parents, paramss)}
..$objDefs
}
"""
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: must be a case class")
}
c.Expr[Any](result)
}
}
Using it:
trait Thing
#evis
case class Trade(id: Long, notional: Long, comment: String) extends Thing
#evis
case class Pop(name: String) extends Thing
object Pop{
}
object TestTrade extends App{
val t = Trade (1, 1, "")
val p : Pop = Pop("")
println(t)
}
Results in:
Error:(2, 2) method apply is defined twice
conflicting symbols both originated in file 'core/src/main/scala/Test.scala'
#evis
^
The problem is caused by the fact that, to the compiler, code generated by macro annotations isn't any different from code written by hand. If you manually write the code produced by the macro provided in the example, you'll get exactly the same double definition error, so it's not a bug - it's a limitation of case class synthesis. Unfortunately, case class synthesis is not extensible, so this needs to be worked around.
One workaround that I'd propose is erasing the CASE flag from the mods of the class, and then the macro can be completely free in choosing which members to generate. This, however, means that the macro will have to generate all the code that case classes typically generate, which is not going to be very pleasant. Another caveat here is that the compiler treats pattern matching over CASE classes specially by emitting somewhat more efficient code, so such an emulation would also lose some performance (I'd reckon that the loss will be miniscule, and probably even non-existent with the new name-based pattern matching mechanism from Scala 2.11 - but that would need to be tested).

How can I create an instance of a Case Class with constructor arguments with no Parameters in Scala?

I'm making a Scala app that sets by reflection field values. This works OK.
However, in order to set field values I need a created instance. If I have a class with an empty constructor, I can do this easily with classOf[Person].getConstructors....
However, when I try doing this with a Case class with a non empty constructor It doesn't work. I have all of the field names and its values, as well as the Object type I need to create. Can I instance the Case Class somehow with what I've got?
The only thing I don't have is the parameter names from the Case Class constructor or a way to create this without parameter and then setting the values via reflection.
Let's go to the example.
I have the following
case class Person(name : String, age : Int)
class Dog(name : String) {
def this() = {
name = "Tony"
}
}
class Reflector[O](obj : O) {
def setValue[F](propName : String, value : F) = ...
def getValue(propName : String) = ...
}
//This works
val dog = classOf[Dog].newInstance()
new Reflector(dog).setValue("name", "Doggy")
//This doesn't
val person = classOf[Person].newInstance //Doesn't work
val ctor = classOf[Person].getConstructors()(0)
val ctor.newInstance(parameters) //I have the property names and values, but I don't know
// which of them is for each parameter, nor I name the name of the constructor parameters
If you are looking for a way to instantiate the object with no arguments, you could do the same as you did in your example, just so long as your reflection setter can handle setting the immutable vals.
You would provide an alternate constructor, as below:
case class Person(name : String, age : Int) {
def this() = this("", 0)
}
Note that the case class will not generate a zero-arg companion object, so you will need to instantiate it as: new Person() or classOf[Person].newInstance(). However, that should be what you are looking to do.
Should give you output like:
scala> case class Person(name : String, age : Int) {
| def this() = this("", 0)
| }
defined class Person
scala> classOf[Person].newInstance()
res3: Person = Person(,0)
The case class should have default args, so that you can just Person(); in the absence of a default arg, supplying a null for name might (or ought to) hit a require(name != null).
Alternatively, use reflection to figure out which params have defaults and then supply nulls or zeros for the rest.
import reflect._
import scala.reflect.runtime.{ currentMirror => cm }
import scala.reflect.runtime.universe._
// case class instance with default args
// Persons entering this site must be 18 or older, so assume that
case class Person(name: String, age: Int = 18) {
require(age >= 18)
}
object Test extends App {
// Person may have some default args, or not.
// normally, must Person(name = "Guy")
// we will Person(null, 18)
def newCase[A]()(implicit t: ClassTag[A]): A = {
val claas = cm classSymbol t.runtimeClass
val modul = claas.companionSymbol.asModule
val im = cm reflect (cm reflectModule modul).instance
defaut[A](im, "apply")
}
def defaut[A](im: InstanceMirror, name: String): A = {
val at = newTermName(name)
val ts = im.symbol.typeSignature
val method = (ts member at).asMethod
// either defarg or default val for type of p
def valueFor(p: Symbol, i: Int): Any = {
val defarg = ts member newTermName(s"$name$$default$$${i+1}")
if (defarg != NoSymbol) {
println(s"default $defarg")
(im reflectMethod defarg.asMethod)()
} else {
println(s"def val for $p")
p.typeSignature match {
case t if t =:= typeOf[String] => null
case t if t =:= typeOf[Int] => 0
case x => throw new IllegalArgumentException(x.toString)
}
}
}
val args = (for (ps <- method.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
(im reflectMethod method)(args: _*).asInstanceOf[A]
}
assert(Person(name = null) == newCase[Person]())
}
The approach below works for any Scala class that has either a no-arg ctor or has an all defaulted primary ctor.
It makes fewer assumptions than some others about how much information is available at the point of call as all it needs is a Class[_] instance and not implicits etc. Also the approach does not rely on the class having to be a case class or having a companion at all.
FYI During construction precedence is given to the no-arg ctor if present.
object ClassUtil {
def newInstance(cz: Class[_ <: AnyRef]): AnyRef = {
val bestCtor = findNoArgOrPrimaryCtor(cz)
val defaultValues = getCtorDefaultArgs(cz, bestCtor)
bestCtor.newInstance(defaultValues: _*).asInstanceOf[A]
}
private def defaultValueInitFieldName(i: Int): String = s"$$lessinit$$greater$$default$$${i + 1}"
private def findNoArgOrPrimaryCtor(cz: Class[_]): Constructor[_] = {
val ctors = cz.getConstructors.sortBy(_.getParameterTypes.size)
if (ctors.head.getParameterTypes.size == 0) {
// use no arg ctor
ctors.head
} else {
// use primary ctor
ctors.reverse.head
}
}
private def getCtorDefaultArgs(cz: Class[_], ctor: Constructor[_]): Array[AnyRef] = {
val defaultValueMethodNames = ctor.getParameterTypes.zipWithIndex.map {
valIndex => defaultValueInitFieldName(valIndex._2)
}
try {
defaultValueMethodNames.map(cz.getMethod(_).invoke(null))
} catch {
case ex: NoSuchMethodException =>
throw new InstantiationException(s"$cz must have a no arg constructor or all args must be defaulted")
}
}
}
I ran into a similar problem. Given the ease of using Macro Paradise, Macro Annotations are a solution (for scala 2.10.X and 2.11 so far).
Check out this question and the example project linked in the comments below.