Def macro - scala 2.13 - not found: value cond - scala

I am trying to make working an example from the scala docs:
https://docs.scala-lang.org/overviews/macros/overview.html
I have two files:
object Main extends App {
println("Testing assert macro...")
val result = Asserts.assert(true, "abc")
}
and:
import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
object Asserts {
val assertionsEnabled: Boolean = true
def assert(cond: Boolean, msg: Any): Unit = macro Asserts.assertImpl
private def raise(msg: Any) = throw new AssertionError(msg)
def assertImpl(c: Context)(cond: c.Expr[Boolean], msg: c.Expr[Any]) : c.Expr[Unit] = {
import c.universe._
if (assertionsEnabled)
c.Expr(q"if (!cond) raise(msg)")
else
c.Expr(q"()")
}
}
but I am getting an error:
Error:(8, 30) not found: value cond val result =
Asserts.assert(true, "abc")
any idea how to make it working ?
thanks!

Macros and main code must be in different subprojects
https://www.scala-sbt.org/1.x/docs/Macro-Projects.html
You missed dollar signs ($cond, $msg instead of cond, msg), otherwise while the macro is expanded inside Main you're trying to use absent local variables cond, msg rather than splice parameters cond, msg of the macro. Also it must be known inside Main that raise is Asserts.raise so either use import Asserts.raise in Main or fully qualified name in the quasiquote. Try
c.Expr(q"if (!$cond) Asserts.raise($msg)")
Also Asserts.raise must be accessible inside Main so remove private.

Related

scala 3 macro: get class properties

i want to writing a macro to get property names of a class.
but can not use Symbol module in quoted statement. i receive blow error...
inline def getProps(inline className: String): Iterable[String] = ${ getPropsImpl('className) }
private def getPropsImpl(className: Expr[String])(using Quotes): Expr[Iterable[String]] = {
import quotes.reflect.*
val props = '{
Symbol.classSymbol($className).fieldMembers.map(_.name) // error access to parameter x$2 from
} wrong staging level:
props - the definition is at level 0,
} - but the access is at level 1.
There are compile time and runtime of macros. And there are compile time and runtime of main code. The runtime of macros is the compile time of main code.
def getPropsImpl... =
'{ Symbol.classSymbol($className).fieldMembers.map(_.name) }
...
is incorrect because what Scala 3 macros do is transforming trees into trees (i.e. Exprs into Exprs, Expr is a wrapper over a tree) (*). The tree
Symbol.classSymbol($className).fieldMembers.map(_.name)
will make no sense inside the scope of application site. Symbol, Symbol.classSymbol etc. make sense here, inside the scope of macro.
def getPropsImpl... =
Symbol.classSymbol(className).fieldMembers.map(_.name)
...
would be also incorrect because className as a value doesn't exist yet, it's just a tree now.
I guess correct is with .valueOrAbort
import scala.quoted.*
inline def getProps(inline className: String): Iterable[String] = ${getPropsImpl('className)}
def getPropsImpl(className: Expr[String])(using Quotes): Expr[Iterable[String]] = {
import quotes.reflect.*
Expr.ofSeq(
Symbol.classSymbol(className.valueOrAbort).fieldMembers.map(s =>
Literal(StringConstant(s.name)).asExprOf[String]
)
)
}
Usage:
// in other file
getProps("mypackage.App.A") //ArraySeq(s, i)
// in other subproject
package mypackage
object App {
case class A(i: Int, s: String)
}
(*) Scala 2 macros can do more with c.eval. In Scala 3 there is similar thing staging.run but it's forbidden in macros.
Actually, c.eval (or forbidden staging.run) can be emulated in Scala 3 too
get annotations from class in scala 3 macros

How to use a `universe.Tree` created and type-checked in one Scala macro execution, in another macro execution?

In the JSON library jsfacile I was able to automatically derive type-classes for types with recursive type references thanx to a buffer where outer executions of a macro store instances of universe.Tree which are used later by inner executions of the same or other macro.
This works fine as long as the universe.Tree instance was not type-checked (with the Typers.typecheck method).
The problem is that this forces to type-check the same Tree instance more than one time: one time in the macro execution that created it (after having stored it in the buffer); and more times in each inner macro execution that needs it.
The intention of this question is to find out a way to share the Tree instance between macro executions after it was type-checked; in order to improve compilation speed.
I tried to wrap the type-checked Tree into a universe.Expr[Unit] and migrate it to the mirror of the macro execution that uses it by means of the Expr.in[U <: Universe](otherMirror: Mirror[U]): U # Expr[T] method.
But it fails with an internal error:
Internal error: unable to find the outer accessor symbol of class <Name of the class where the macro is expanded>
Any idea?
Generally, typechecking a tree manually and sharing the typed tree among different contexts is a bad idea. See the following example:
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
import scala.collection.mutable
object Macros {
val mtcCache = mutable.Map[whitebox.Context#Type, whitebox.Context#Tree]()
trait MyTypeclass[A]
object MyTypeclass {
implicit def materialize[A]: MyTypeclass[A] = macro materializeImpl[A]
def materializeImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
if (!mtcCache.contains(typA))
mtcCache += (typA -> c.typecheck(q"new MyTypeclass[$typA] {}"))
mtcCache(typA).asInstanceOf[Tree]
}
}
}
import Macros.MyTypeclass
object App { // Internal error: unable to find the outer accessor symbol of object App
class A { // Internal error: unable to find the outer accessor symbol of class A
class B { // Internal error: unable to find the outer accessor symbol of class A
class C {
implicitly[MyTypeclass[Int]] // new MyTypeclass[Int] {} is created and typechecked here
}
implicitly[MyTypeclass[Int]] // cached typed instance is inserted here, this is the reason of above error
}
implicitly[MyTypeclass[Int]] // cached typed instance is inserted here, this is the reason of above error
}
implicitly[MyTypeclass[Int]] // cached typed instance is inserted here, this is the reason of above error
}
Scala 2.13.3.
With implicitly we put in some places trees with incorrect symbol owner chain.
If you make A, B, C objects then errors disappear (so whether this prevents compilation depends on a luck).
Also if you remove c.typecheck then errors disappear.
Also if we return c.untypecheck(mtcCache(typA).asInstanceOf[Tree]) instead of mtcCache(typA).asInstanceOf[Tree] then errors disappear. But sometimes c.typecheck + c.untypecheck can damage a tree.
So you can try to put both untyped and typed versions of a tree to the cache if you need both but return the untyped one
type CTree = whitebox.Context#Tree
val mtcCache = mutable.Map[whitebox.Context#Type, (CTree, CTree)]()
trait MyTypeclass[A]
object MyTypeclass {
implicit def materialize[A]: MyTypeclass[A] = macro materializeImpl[A]
def materializeImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
val tree = q"new MyTypeclass[$typA] {}"
if (!mtcCache.contains(typA))
mtcCache += (typA -> (tree, c.typecheck(tree)))
mtcCache(typA)._1.asInstanceOf[Tree]
}
}
or if you need typechecking only to trigger the recursion then you can typecheck a tree, put the untyped tree to the cache and return the untyped one
val mtcCache = mutable.Map[whitebox.Context#Type, whitebox.Context#Tree]()
trait MyTypeclass[A]
object MyTypeclass {
implicit def materialize[A]: MyTypeclass[A] = macro materializeImpl[A]
def materializeImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
val tree = q"new MyTypeclass[$typA] {}"
c.typecheck(tree)
if (!mtcCache.contains(typA)) mtcCache += (typA -> tree)
mtcCache(typA).asInstanceOf[Tree]
}
}
Pull request: https://github.com/readren/json-facile/pull/1

Robustness of pattern matching Trees with quasiquotes

I have a macro and part of that macro consists of replacing every call to a certain method with something else. To accomplish this I use a Transformer and try to match every Tree that enters the transform method against a quasiquote. When I write it like below, it seems to work.
package mypackage
object myobject {
implicit def mymethod[T](t: Option[T]): T = ???
}
object Macros {
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
def myMacro(c: Context)(expr: c.Tree): c.Tree = {
import c.universe._
val transformer = new Transformer {
private def doSomething(value: c.Tree): TermName = {
???
}
override def transform(tree: c.Tree) = tree match {
case q"mypackage.myobject.mymethod[..$_]($value)" =>
val result = doSomething(value)
q"$result"
case _ => super.transform(tree)
}
}
val transformed = transformer.transform(expr)
???
}
}
But I thought you should always use fully qualified names in macros or you could get into trouble. So I wrote it like q"_root_.mypackage.myobject.mymethod[..$_]($value)", but then it no longer matched and the calls to mymethod no longer got replaced.
I also looked at the suggestion in the scala docs to unquote symbols, but I couldn't get that to work either.
So my question is: will this code (with q"mypackage.myobject.mymethod[..$_]($value)") always replace all the calls to mymethod and never replace any other method calls? And if not, how can I make it more robust?
scala.reflect macros are non hygienic, so, theoretically, q"mypackage.myobject.mymethod[..$_]($value)" could be matched by someone else.
I'd suggest match that method with q"..$mods def $name[..$tparams](...$paramss): $tpeopt = $expr" (assuming that is definition, not declaration). You can add checks on name.
Another solution is to mark method with annotation, and remove it in macro phase.

macro does not find out enclosing vals

I tried collecting all vals in an enclosing scope.
This is the macro implementation:
import scala.language.experimental.macros
import scala.reflect.macro.blackbox.Context
def impl(c: Context) = {
import c.universe._
c.Expr[Seq[Any]](q"Seq(..${
c.internal.enclosingOwner.owner.typeSignature.decls.collect {
case s if s.isTerm && s.asTerm.isVal => Ident(s)
}
})")
}
def get(c: Context) = macro impl
This is how I applied it:
object Foo {
val foo = ""
def poo = get
}
Then it threw an error that doesn't make sense to me:
error: symbol value foo does not exist in Foo.poo
Why does this happen? How do you prevent it?
You don't want the Ident method that takes a symbol, you want Ident.apply, which takes a Name.
Replacing Ident(s) with Ident(s.name) (or maybe a little more clearly q"this.$s") will work.

Calling a macro from a macro

I am trying to call a macro from a macro, but I'm doing something wrong. It looks approximately like this:
import play.api.libs.json._
import scala.reflect.macros.Context
import language.experimental.macros
object Extension {
def apply[A]: Format[A] = macro applyImpl[A]
def applyImpl[A: c.WeakTypeTag](c: Context): c.Expr[Format[A]] = {
import c.universe._
val aTpeW = c.weakTypeOf[A]
val aClazz = aTpeW.typeSymbol.asClass
if (!aClazz.isSealed) { // fall back to Json.format
val t = reify { Json.format[A] } .tree
return c.Expr[Format[A]](t)
}
???
}
}
In other words, based on some condition of the type of A, instead of generating a tree in my macro, I want to return the body of another macro (Json.format). But somehow this gets expanded already before using the macro. When I compile this, I get
[error] .../Extension.scala:47: No unapply function found
[error] val t = reify { Json.format[A] } .tree
[error] ^
Which means that format is already executed (it should not be). The format method is defined as
def format[A] = macro JsMacroImpl.formatImpl[A]
One needs to jump right into the macro body it seems:
if (!aClazz.isSealed) { // fall back to Json.format
return JsMacroImpl.formatImpl[A](c)
}
(IntelliJ had this red, so I thought it was wrong, but it actually compiles)
Alternatively you should be able to call the macro from a macro, when you put both macros in different compilation units (i.e. different projects). Scala cannot compile a macro and apply it in the same compilation run.