Robustness of pattern matching Trees with quasiquotes - scala

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.

Related

Scala 3 Macro - Retain a generic type in runtime

I'm looking for an option to retain a generic type in runtime in Scala3. In Scala2 there was a TypeTag for this, however, now it is removed and the suggested option is to use macros (https://contributors.scala-lang.org/t/scala-3-and-reflection/3627).
The documentation, however, is somewhat cryptic...
This is what I'm trying to do:
Here's a macro implementation:
object TestMacroImpl {
def getClassImpl[T](using Quotes)(using t: Type[T]): Expr[Class[T]] = '{
classOf[${t}]
}
}
Here's a macro:
import macros.TestMacro.getClassMacro
class TypedBox[T] {
val staticClass: Class[T] = TypedBox.getStaticClass[T]
}
object TypedBox {
inline def getStaticClass[T] = ${ getClassMacro[T] }
}
Test:
object Test {
def main(args: Array[String]): Unit = {
val stringBox = TypedBox[String]()
println(stringBox.staticClass)
}
}
I would envision this to be resolved as val staticClass = classOf[String]
But this does not compile, I'm getting:
/workspace/macros-test/src/main/scala/macros/TestMacro.scala:7:13
t.Underlying is not a class type
classOf[${t}]
What am I missing?
Not really sure why but I don't think you can reliably get an Expr[Class[T]] out of macros (from what I understood, it could be that the Class does not yet exist at the time of macro execution).
Plus, a Class[T] does not retain the parameterized types: classOf[Map [String, String]] = classOf[Map[Int, Int]] for instance.
If you don't care about them, I'd use a ClassTag instead of TypeTag which is still available in Scala 3. And no need for macros.
By the way, in macros, you can write something like the following to get a Expr[ClassTag[T]]:
private def getClassTag[T](using Type[T], Quotes): Expr[ClassTag[T]] = {
import quotes.reflect._
Expr.summon[ClassTag[T]] match {
case Some(ct) =>
ct
case None =>
report.error(
s"Unable to find a ClassTag for type ${Type.show[T]}",
Position.ofMacroExpansion
)
throw new Exception("Error when applying macro")
}
}
Finally, you might find some useful things at https://github.com/gaeljw/typetrees/blob/main/src/main/scala/io/github/gaeljw/typetrees/TypeTreeTagMacros.scala#L8 (disclaimer: I wrote it for personal projects).

How to save a Type or TypeTag to a val for later use?

I would like to save a Type or TypeTag in a val for later use. At this time, I am having to specify a type in several locations in a block of code. I do not need to parameterize the code because only one type will be used. This is more of a curiosity than a necessity.
I tried using typeOf, classOf, getClass, and several other forms of accessing the class and type. The solution is likely simple but my knowledge of Scala typing or type references is missing this concept.
object Example extends App {
import scala.reflect.runtime.universe._
object TestClass { val str = "..." }
case class TestClass() { val word = ",,," }
def printType[A: TypeTag](): Unit = println(typeOf[A])
printType[List[Int]]() //prints 'List[Int]'
printType[TestClass]() //prints 'Example.TestClass'
val typeOfCompanion: ??? = ??? //TODO what goes here?
val typeOfCaseClass: ??? = ??? //TODO what goes here?
printType[typeOfCompanion]() //TODO should print something like 'Example.TestClass'
printType[typeOfCaseClass]() //TODO should print something like 'Example.TestClass'
}
The solution should be able to save a Type or TypeTag or what the solution is. Then, pass typeOfCompanion or typeOfCaseClass like printTypetypeOfCompanion for printing. Changing the printing portion of the code may be required; I am not certain.
You have to be more explicit here
import scala.reflect.runtime.universe._
def printType(a: TypeTag[_]): Unit = println(a)
val typeOfCompanion = typeTag[List[Int]]
printType(typeOfCompanion)
def printType[A: TypeTag](): Unit = println(typeOf[A])
is exactly the same as
def printType[A]()(implicit a: TypeTag[A]): Unit = println(typeOf[A])
(except for the parameter name). So it can be called as
val listTypeTag /* : TypeTag[List[Int]] */ = typeTag[List[Int]]
printType()(listTypeTag)
(you can remove the empty parameter list from printType if you want).
For the companion, you need to use a singleton type:
val companionTag = typeTag[TestClass.type]
val caseClassTag = typeTag[TestClass]

What is the type of a field of a Scala class?

I would like to write a function like the following:
def printFieldOfClass(field: ???) =
println(field)
Suppose there is a case class definition such as case class A(id: String). It would then be possible to call printFieldOfClass(A.id) which would print the string "A.id". If we, however, try to call printFieldOfClass(A.idd), I would want the code not to compile. Is this even possible? And if so, what is the type of the parameter field?
Help is much appreciated!
EDIT: As there seems to be some confusion about what I am trying to do, let me clarify: I do not want to pass an instance of the case class, I much rather want to pass a reference to some field in a class definition. Also I do not want my function to be hard wired to any such class, it should work with all Scala classes, like so: printFieldOfClass(SomeClassTheFunctionDoesNotKnowAbout.someField) should either print "SomeClassTheFunctionDoesNotKnowAbout.someField" in case SomeClassTheFunctionDoesNotKnowAbout's definition specifies a field someField or, if the class has no such field, the call should not compile.
This isn't possible if printFieldOfClass is a normal method, as the comments explain. But you can make it a macro instead. It's basically a function which runs at compile-time, receives the syntax tree of its argument and generates a new tree to replace it. There are quite a few introductions to Scala macros, and writing another one in the answer doesn't make sense. But I don't advise trying it until you are very comfortable with Scala in general.
An example which does something close to what you want:
import scala.annotation.tailrec
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
object Macros {
def nameOfImpl(c: blackbox.Context)(x: c.Tree): c.Tree = {
import c.universe._
#tailrec def extract(x: c.Tree): String = x match {
case Ident(TermName(s)) => s
case Select(_, TermName(s)) => s
case Function(_, body) => extract(body)
case Block(_, expr) => extract(expr)
case Apply(func, _) => extract(func)
}
val name = extract(x)
q"$name"
}
def nameOfMemberImpl(c: blackbox.Context)(f: c.Tree): c.Tree = nameOfImpl(c)(f)
def nameOf(x: Any): String = macro nameOfImpl
def nameOf[T](f: T => Any): String = macro nameOfMemberImpl
}
//
// Sample usage:
//
val someVariable = ""
Macros.nameOf(someVariable) // "someVariable"
def someFunction(x: Int): String = ???
Macros.nameOf(someFunction _) // "someFunction"
case class SomeClass(someParam: String)
val myClass = SomeClass("")
Macros.nameOf(myClass.someParam) // "someParam"
// without having an instance of the class:
Macros.nameOf[SomeClass](_.someParam) // "someParam"

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.

Is it possible to define companion classes/modules in the Scala interpreter?

It's often convenient to test things out in the Scala interpreter. However, one issue I run into is that I have to restructure code that uses implicit conversions because defining an object with the same name as an existing class does not make it a companion module in the REPL. As a result, I can't be confident my code will still work when I translate back to "real source".
Is there a way to define companions in the REPL? Maybe something along the lines of
bigblock {
class A
object A {
implicit def strToA(s: String): A = // ...
}
}
such that
val v: A = "apple"
will compile.
That's close:
object ABlock {
class A
object A {
implicit def strToA(s: String): A = // ...
}
}
import ABlock._
Or, the following, if you put everything on one line:
class A; object A { implicit def strToA(s: String): A = // ... } }
...though either way you'll still need to import the implicit conversion to make the following work as you requested:
import ABlock.A.strToA // for the form with the enclosing object
import A.strToA // for the one-line form without an enclosing object
val v: A = "apple"
The reason you need to do this is that every line you enter at the REPL is enclosed in an object and each subsequent one is nested within the immediately preceding one. This is done so you can do things like the following without getting redefinition errors:
val a = 5
val a = "five"
(Effectively, the second definition of a here shadows the first.)
With more recent versions use can use the :paste command.