I am trying to generate some implicits via a macro -the condensed version of the macro looks like this:
object Implicits {
def generate(c:Context):c.Expr[Unit]={
import c.universe._
c.Expr[Unit] {
q"""
object Dud{
implicit val p:java.io.File = new java.io.File("/tmp")
def toString():String ={ "Dud here" }
}
import Dud._
"""
}
}
}
I am using the macro:
object ImplicitTest extends App {
def genImplicits = macro Implicits.generate
genImplicits
val f: File = implicitly[File]
println(f)
}
The test bails out complaining that
ImplicitTest.scala could not find implicit value for parameter e: java.io.File
[error] val f: File = implicitly[File]
[error] ^
What am I doing wrong with this macro?
Based on Travis's answer (Thank you) I wrote the macro using macro annotation: Here is the code if it helps someone else -it's proof of concept
#compileTimeOnly("enable macro paradise to expand macro annotations")
class defaultFileMacro extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro DefaultMacro.impl
}
object DefaultMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val inputs:List[Tree] = annottees.map(_.tree).toList
val tree= inputs(0)
val q"val $list:List[$t]= $files" = tree
print(show(q"""implicit val fl1:$t = $files(0)"""))
c.Expr[Any] {
q"""
implicit val fl1:$t = $files(0)
"""
}
}
}
Usage:
object ImplicitTest extends App {
def findDefaultFile() = {
#defaultFileMacro val list: List[File] = List(new File("/tmp"))
val f: File = implicitly[File]
println(f)
}
findDefaultFile()
}
run
> run-main ImplicitTest
[info] Running ImplicitTest
/tmp
The call to the macro method will be expanded to something like this (I generated this text by printing the expression before returning it, and after adding an override to the toString definition):
{
object Dud extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
implicit val p: java.io.File = new java.io.File("/tmp");
override def toString(): String = "Dud here"
};
import Dud._;
()
}
Note that this doesn't bring anything into scope in the body of ImplicitTest. Both the Dud object and the import are inside a block, and neither are available by the time you get to the val f: File = implicitly[File] line.
You can write an implicit macro method, or you can write a macro annotation that you could apply to ImplicitTest that would add both Dud and the import, but you can't introduce new objects or imports into scope with a def macro.
Related
I have a Play Framework project using Scala 2.13 and the latest Play SBT plugin v2.8.16. I want to use a wrapper class to represent an ID type. I had been using a value class (AnyVal) but I want to migrate to newtype for performance reasons.
/model/package.scala:
import io.estatico.newtype.macros.newtype
import play.api.libs.json.{Json, Reads, Writes}
package object model {
#newtype case class FileId(raw: String)
object FileId {
implicit val reads: Reads[FileId] = json => json.validate[String].map(FileId(_))
implicit val writes: Writes[FileId] = fileId => Json.toJson(fileId.raw)
}
}
I want to be able to pass in a file ID as a path segment in a URL. Here is the relevant route:
GET /files/:id controllers.HomeController.getFileContents(id: model.FileId)
/controllers/HomeController.scala:
package controllers
import model.{FileContents, FileId}
import javax.inject._
import play.api.libs.json.Json
import play.api.mvc._
#Singleton
class HomeController #Inject()(val controllerComponents: ControllerComponents) extends BaseController {
def getFileContents(id: FileId) = Action { implicit request: Request[AnyContent] =>
val fileContents = FileContents(id, "file.txt", "this is the file contents")
Ok(Json.toJson(fileContents))
}
}
I wrote a custom binder for this.
/binders/CustomBinders.scala
package binders
import model.FileId
import play.api.mvc.PathBindable
object CustomBinders {
implicit def fileIdPathBindable(implicit stringBinder: PathBindable[String]): PathBindable[FileId] =
new PathBindable[FileId] {
override def bind(key: String, value: String): Either[String, FileId] =
stringBinder.bind(key, value).map(FileId(_))
override def unbind(key: String, value: FileId): String =
stringBinder.unbind(key, value.raw)
}
}
I configured build.sbt to enable macros and to use the custom binder. When I try to compile, I get an error:
[error] /play-scala-seed/conf/routes:8:1: class type required but model.FileId.Type found
[error] GET /files/:id controllers.HomeController.getFileContents(id: model.FileId)
Enabling debug for the newtype shows this but I can't quite figure out what's needed to make this work:
Expanded #newtype FileId:
{
type FileId = FileId.Type;
object FileId extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
implicit val reads: Reads[FileId] = ((json) => json.validate[String].map(((x$1) => FileId(x$1))));
implicit val writes: Writes[FileId] = ((fileId) => Json.toJson(fileId.raw));
def apply(raw: String): FileId = raw.asInstanceOf[FileId];
final implicit class Ops$newtype extends AnyVal {
<paramaccessor> val $this$: Type = _;
def <init>($this$: Type) = {
super.<init>();
()
};
def raw: String = $this$.asInstanceOf[String]
};
implicit def opsThis(x: Ops$newtype): Type = x.$this$;
#new _root_.scala.inline() implicit def unsafeWrap: Coercible[Repr, Type] = Coercible.instance;
#new _root_.scala.inline() implicit def unsafeUnwrap: Coercible[Type, Repr] = Coercible.instance;
#new _root_.scala.inline() implicit def unsafeWrapM[M[_]]: Coercible[M[Repr], M[Type]] = Coercible.instance;
#new _root_.scala.inline() implicit def unsafeUnwrapM[M[_]]: Coercible[M[Type], M[Repr]] = Coercible.instance;
#new _root_.scala.inline() implicit def cannotWrapArrayAmbiguous1: Coercible[_root_.scala.Array[Repr], _root_.scala.Array[Type]] = Coercible.instance;
#new _root_.scala.inline() implicit def cannotWrapArrayAmbiguous2: Coercible[_root_.scala.Array[Repr], _root_.scala.Array[Type]] = Coercible.instance;
#new _root_.scala.inline() implicit def cannotUnwrapArrayAmbiguous1: Coercible[_root_.scala.Array[Type], _root_.scala.Array[Repr]] = Coercible.instance;
#new _root_.scala.inline() implicit def cannotUnwrapArrayAmbiguous2: Coercible[_root_.scala.Array[Type], _root_.scala.Array[Repr]] = Coercible.instance;
def deriving[TC[_]](implicit ev: TC[Repr]): TC[Type] = ev.asInstanceOf[TC[Type]];
type Repr = String;
type Base = _root_.scala.Any {
type __FileId__newtype
};
abstract trait Tag extends _root_.scala.Any;
type Type <: Base with Tag
};
()
}
I would like to use the newtype FileId as path segment parameter in my URL. I have tried looking around for guides on this but have not found anything that works. What changes do I need to make?
I'm trying to extract ClassSymbols for all type parameters' bounds of a method.
The "solution" I came up with:
Macro annotation implementation:
#compileTimeOnly("Compile-time only annotation")
class classSyms extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro impl
}
object classSyms {
def impl(c: whitebox.Context)(annottees: c.Tree*) = {
import c.universe._
annottees.toList foreach {
case q"$mods def $templatename[..$typeparams](...$paramss): $tpt = $body" =>
typeparams foreach {
case q"$mods type $name[..$tparams] >: $low <: $high" =>
if (!high.isEmpty) {
//requires FQCN, does not work with imported names
val classSymbol = c.mirror.staticClass(high.toString)
println(classSymbol)
}
}
}
q"..$annottees"
}
}
Example:
package pack.age
trait Test
package another.pack.age
import pack.age._
trait Bar{
#classSyms
def foo[M <: pack.age.Test, T](): Unit //works ok
#classSyms
def baz[M <: Test, T](): Unit //throws scala.ScalaReflectionException: class Test not found.
}
The problem is such requirements of specifying fully-qualified class name as a parameter bound does not make this macro implementation very useful (no one wants to write this long fqcn stuff, especially if the name is imported).
Is it possible to extract ClassSymbol by an imported name?
Try to use c.typecheck.
Replace
val classSymbol = c.mirror.staticClass(high.toString)
with
val classSymbol = c.typecheck(tq"$high", mode = c.TYPEmode).symbol.asClass
Consider the following two classes, both of them have a parseFrom function.
class X {}
object X {
def parseFrom(b: Array[Byte]): String = "Hello"
}
class Y {}
object Y {
def parseFrom(b: Array[Byte]): String = "HelloY"
}
I want to write a macro:
getParseFrom[X] that will return the X.parseFrom function.
This is what I have so far:
import scala.reflect.macros.whitebox.Context
def getParseFromImpl[T: c.WeakTypeTag](c: Context): c.Tree = {
import c.universe._
val tpe = weakTypeOf[T]
q"""
$tpe.parseFrom(_)
"""
}
def getParseFrom[T]: Array[Byte] => String = macro getParseFromImpl[T]
Is this possible with scala macros in scala 2.12?
Try
q"""
${tpe.typeSymbol.companion}.parseFrom(_)
"""
The use cases for implicit macros is supposed to be the so-called "materialisation" of type class instances.
Unfortunately, the example in the documentation is a bit vague on how that is achieved.
Upon being invoked, the materializer can acquire a representation of T and generate the appropriate instance of the Showable type class.
Let's say I have the following trait ...
trait PrettyPrinter[T]{
def printed(x:T) : String
}
object PrettyPrinter{
def pretty[T](x:T)(implicit pretty:PrettyPrinter[T]) = pretty printed x
implicit def prettyList[T](implicit pretty :PrettyPrinter[T]) = new PrettyPrinter[List[T]] {
def printed(x:List[T]) = x.map(pretty.printed).mkString("List(",", ",")")
}
}
and three test classes
class A(val x:Int)
class B(val x:Int)
class C(val x:Int)
Now I understand that instead of writing the following boilerplate
implicit def aPrinter = new PrettyPrinter[A] {def printed(a:A) = s"A(${a.x})"}
implicit def bPrinter = new PrettyPrinter[B] {def printed(b:B) = s"B(${b.x})"}
implicit def cPrinter = new PrettyPrinter[C] {def printed(c:C) = s"C(${c.x})"}
we should be able to add
implicit def materialise[T] : PrettyPrinter[T] = macro implMaterialise[T]
def implMaterialise[T](c:blackbox.Context):c.Expr[PrettyPrinter[T]] = {
import c.universe._
???
}
to the object PrettyPrinter{...} which then generates the corresponding PrettyPrinters on demand ... how? How do I actually get that "representation of T"?
If I try c.typeOf[T], for example, "No TypeTag available for T".
UPDATE
Trying to use class tags doesn't seem to work either.
implicit def materialise[T:ClassTag] : PrettyPrinter[T] = macro implMaterialise[T]
def implMaterialise[T:ClassTag](c:blackbox.Context):c.Expr[PrettyPrinter[T]] = {
import c.universe._
???
}
results in
Error:(17, 69) macro implementations cannot have implicit parameters other than WeakTypeTag evidences
implicit def materialise[T:ClassTag] : PrettyPrinter[T] = macro implMaterialise[T]
^
update2
Interestingly, using WeakTypeTags doesn't really change anything as
implicit def materialise[T:WeakTypeTag]: PrettyPrinter[T] = macro implMaterialise[T]
def implMaterialise[T](c:blackbox.Context)(implicit evidence : WeakTypeTag[T]):c.Expr[PrettyPrinter[T]]
= {
import c.universe._
???
}
will result in
Error:(18, 71) macro implementations cannot have implicit parameters other than WeakTypeTag evidences
implicit def materialise[T:WeakTypeTag]: PrettyPrinter[T] = macro implMaterialise[T]
^
How do I actually get that "representation of T"?
You need to use c.WeakTypeTag, as hinted at by the compiler message you found in your "UPDATE" section.
This project has a working example that you can adapt: https://github.com/underscoreio/essential-macros/blob/master/printtype/lib/src/main/scala/PrintType.scala
object PrintTypeApp extends App {
import PrintType._
printSymbol[List[Int]]
}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
import scala.util.{ Try => ScalaTry }
object PrintType {
// Macro that generates a `println` statement to print
// declaration information of type `A`.
//
// This only prints meaningful output if we can inspect
// `A` to get at its definition:
def printSymbol[A]: Unit =
macro PrintTypeMacros.printTypeSymbolMacro[A]
}
class PrintTypeMacros(val c: Context) {
import c.universe._
def printTypeSymbolMacro[A: c.WeakTypeTag]: c.Tree =
printSymbol(weakTypeOf[A].typeSymbol, "")
}
The following macro is pasted from http://docs.scala-lang.org/overviews/quasiquotes/usecases.html:
import reflect.macros.Context
import language.experimental.macros
val universe = reflect.runtime.universe; import universe._
import reflect.runtime.currentMirror
import tools.reflect.ToolBox
val toolbox = currentMirror.mkToolBox()
object debug {
def apply[T](x: =>T): T = macro impl
def impl(c: Context)(x: c.Tree) = { import c.universe._
val q"..$stats" = x
val loggedStats = stats.flatMap { stat =>
val msg = "executing " + showCode(stat)
List(q"println($msg)", stat)
}
q"..$loggedStats"
}
}
It produces this error message in Scala 2.11.1:
Q.scala:9: error: macro implementation reference has wrong shape. required:
macro [<static object>].<method name>[[<type args>]] or
macro [<macro bundle>].<method name>[[<type args>]]
def apply[T](x: =>T): T = macro impl
^
I've tried varying the code in numerous ways (import reflect.macros.whitebox.Context, import reflect.macros.blackbox.Context, putting scala. at the start of each import, making the arguments consistently by-name or consistently by-value, macro impl[T], getting rid of the type parameter, macro debug.impl, putting the apply after the impl, and more) with no success. What am I doing wrong? Is it something in the imports? Those come (mostly) from a different web page, http://docs.scala-lang.org/overviews/quasiquotes/setup.html.
The same error occurs on both of the other example macros from that page:
object Macro {
def apply(x: Int): Int = macro impl
def impl(c: Context)(x: c.Expr[Int]): c.Expr[Int] = { import c.universe._
c.Expr(q"$x + 1")
}
}
object Macro {
def apply(x: Int): Int = macro impl
def impl(c: Context)(x: c.Tree) = { import c.universe._
q"$x + 1"
}
}
scala Foo.scala wraps the code that you feed to it in an anonymous class. As a result, something like:
import scala.reflect.macros.Context
import scala.language.experimental.macros
object debug {
def apply[T](x: T): T = macro impl
def impl(c: Context)(x: c.Tree): c.Tree = ???
}
Gets transformed into:
[[syntax trees at end of parser]] // Test.scala
package <empty> {
object Main extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
def main(args: Array[String]): scala.Unit = {
final class $anon extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
import scala.reflect.macros.Context;
import scala.language.experimental.macros;
object debug extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
<macro> def apply[T](x: T): T = impl;
def impl(c: Context)(x: c.Tree): c.Tree = $qmark$qmark$qmark
}
};
new $anon()
}
}
}
However, macro implementations must be defined in static object or bundles, which is what the error messages tries to say:
/Users/xeno_by/Projects/211x/sandbox/Test.scala:5: error: macro implementation reference has wrong shape. required:
macro [<static object>].<method name>[[<type args>]] or
macro [<macro bundle>].<method name>[[<type args>]]
def apply[T](x: T): T = macro impl
^
one error found
Unfortunately, if something is put into inside an anonymous class, it's no longer static, which means that scala Foo.scala-style development is incompatible with macros. Consider using alternatives, e.g. scalac or sbt.