I am trying to unit test the following, simplified, macro (by comparing the emitted expressions):
trait MyOutput {
def speak(): Unit
}
inline def myMacro(): MyOutput = ${ myMacroImpl() }
def myMacroImpl()(using Quotes): Expr[MyOutput] = {
'{
new MyOutput {
def speak(): Unit = {
println("Hello World")
}
}
}
}
I tried the following
inline private def testMacro[A](inline generated: A, expected: A): A = {
${ testMacroExpr('generated, 'expected) }
}
def testMacroExpr[A](generated: Expr[A], expected: Expr[A])(using Quotes): Expr[A] = {
import quotes.reflect._
if (generated != expected) {
report.throwError("BOOM")
}
}
// main.scala
#main def hello() = {
testMacro(
myMacro(),
new MyOutput {
def speak(): Unit = {
println("Hello World")
}
}
)
}
But the comparison always returns false. Also, I remarked expected.asTerm.toString() returns Inlined(EmptyTree,List(),Ident(expected$proxy1)).
I also tried:
using functions defined on Expr and Quotes, such as matches
comparing generated.show and expected.asTerm.underlying.show
Can someone point me to solution please?
Related
I have the following code:
def disableRules(someId: String) = Action.async { implicit req =>
Metrics.measureTime("disableRules") {
someFutureOpr(someId).map(_ => Ok)
.recover {
case e: Exception => handlerError(s"Failure occurred on disableRules request ${e.getMessage}", "disableRules")
}
}
}
def activeRules(someId: String) = Action.async { implicit req =>
Metrics.measureTime("activeRules") {
someFutureOpr2(someId).map(_ => Ok)
.recover {
case e: Exception => handlerError(s"Failure occurred on activeRules request ${e.getMessage}", "activeRules")
}
}
}
...
As you can see, I have mesureTime and handleError functions that I pass to them the name of the function as String, is there way to make it implicitly, I mean its will take the function Name, if not - there way to extract the function Name and print it, also regarding params.
You can solve this with a macro (and no runtime reflection):
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
class CaptureImpl(val c: blackbox.Context) {
import c.universe._
def describe[T: c.WeakTypeTag](
expr: c.Expr[T]
): c.Expr[String] = c.Expr[String](q"(${expr.tree.toString()})")
}
object CaptureMethod {
def apply[T](expr: T): String = macro CaptureImpl.describe[T]
}
Example:
object Test {
def foo(): String = "hello"
def bar(a: Int): Int = a
def baz(s: String): String = s
def main(args: Array[String]): Unit = {
println(CaptureMethod(foo()))
println(CaptureMethod(bar(1)))
println(CaptureMethod(baz("yes")))
}
}
Yields:
Test.this.foo()
Test.this.bar(1)
Test.this.baz("yes")
Calculate it inside Metrics:
object Metrics {
def currentMethodName() : String = Thread.currentThread.getStackTrace()(3).getMethodName
def measureTime(): Unit = {
println(currentMethodName)
}
}
Then for example:
def a1() = {
Metrics.measureTime()
}
def a2() = {
Metrics.measureTime()
}
will output:
a1
a2
Is this a safe operation?
If we had:
def currentMethodName() : String = Thread.currentThread.getStackTrace.toList.mkString("\n")
we get:
java.lang.Thread.getStackTrace(Thread.java:1559)
HelloWorld1$Metrics$.currentMethodName(HelloWorld1.scala:69)
HelloWorld1$Metrics$.measureTime(HelloWorld1.scala:72)
HelloWorld1$.a1(HelloWorld1.scala:77)
HelloWorld1$.main(HelloWorld1.scala:103)
HelloWorld1.main(HelloWorld1.scala)
So we see that:
In index 0 we get getStackTrace.
In index 1 we have currentMethodName.
In index 2 we have measureTime.
Since measureTime is not the first method of the stack trace, fir sure we have another element in the stack trace. Therefore in your case yes, it is safe.
While creating a syntax tree, I want to synthesize variables in the syntax tree as a tree.
However, error in cross-stage evaluation.
Is it possible to create a single tree by compositing trees with different stages?
This is a sample to see errors.
object Sample {
case class Input(a: String, b: Int)
trait Scriber[T] {
def scribe(i: Input): T
}
trait Hoge[T] {
def exec(t: Input): Either[Throwable, T]
}
}
object Macro {
def exec[T](scribers: Seq[Scriber[_]]): Hoge[T] = macro SampleMacro.test[T]
}
class SampleMacro(val c: blackbox.Context) {
import c.universe._
def test[T: c.WeakTypeTag](scribers: c.Expr[Seq[Scriber[_]]]): c.Expr[Hoge[T]] = {
reify {
new Hoge[T] {
override def exec(t: Input): Either[Throwable, T] = {
val x = reify {
scribers.splice.map(_.scribe(t)) // <- cross stage evaluation.
}
Right(
c.Expr[T](
q"${weakTypeOf[T].typeSymbol.companion}.apply(..$x)"
).splice
)
}
}
}
}
}
In this case, t is cross stage.
This is a sample where cross stage evaluation errors will occur, but will not work even if resolved.
With quasiquotes try
def test[T: c.WeakTypeTag](scribers: c.Expr[Seq[Scriber[_]]]): c.Expr[Hoge[T]] = {
c.Expr[Hoge[T]](q"""
new Hoge[T] {
override def exec(t: Input): Either[Throwable, T] = {
val x = $scribers.map(_.scribe(t))
Right(
${weakTypeOf[T].typeSymbol.companion}.apply(x)
)
}
}""")
With reify/splice try
def test[T: c.WeakTypeTag](scribers: c.Expr[Seq[Scriber[_]]]): c.Expr[Hoge[T]] =
reify {
new Hoge[T] {
override def exec(t: Input): Either[Throwable, T] = {
val x = scribers.splice.map(_.scribe(t))
Right(
c.Expr[Seq[_] => T](
q"${weakTypeOf[T].typeSymbol.companion}"
).splice.apply(x)
)
}
}
}
I'm experimenting with Scala FastParsers library and I'm studying macro expanding of the following code:
val trueValue = "true".toCharArray
val falseValue = "false".toCharArray
object KParser1 {
import fastparsers.framework.implementations.FastParsersCharArray._
val kparser = FastParsersCharArray {
def func1: Parser[Any] = func1 | falseValue
}
}
Whole expanding is there but a piece of code from there really bothers me
while$2() {
if (inputpos$macro$2.$less(inputsize$macro$3).$amp$amp(input$macro$1(inputpos$macro$2).$eq$eq(' ').$bar$bar(input$macro$1(inputpos$macro$2).$eq$eq('\t')).$bar$bar(input$macro$1(inputpos$macro$2).$eq$eq('\n')).$bar$bar(input$macro$1(inputpos$macro$2).$eq$eq('\r'))))
{
inputpos$macro$2 = inputpos$macro$2.$plus(1);
while$2()
}
else
()
};
It looks like the code which skips whitespace from input stream but I can't infer what exactly is while$2: is it declared there as Unit => Unit and called automatically or is it some predefined function with type Unit => Any => Any?
It's just a loop. Compare:
scala> while (!done) println("working")
[[syntax trees at end of typer]] // <console>
package $line5 {
object $read extends scala.AnyRef {
def <init>(): $line5.$read.type = {
$read.super.<init>();
()
};
object $iw extends scala.AnyRef {
def <init>(): type = {
$iw.super.<init>();
()
};
import $line4.$read.$iw.$iw.done;
object $iw extends scala.AnyRef {
def <init>(): type = {
$iw.super.<init>();
()
};
private[this] val res0: Unit = while$1(){
if ($line4.$read.$iw.$iw.done.unary_!)
{
scala.this.Predef.println("working");
while$1()
}
else
()
};
<stable> <accessor> def res0: Unit = $iw.this.res0
}
}
}
}
I would like to print Scala source code of IF condition while being in THEN section.
Example: IF{ 2 + 2 < 5 } THEN { println("I am in THEN because: " + sourceCodeOfCondition) }
Let's skip THEN section right now, the question is: how to get source code of block after IF?
I assume that IF should be a macro...
Note: this question is redefined version of Macro to access source code of function at runtime where I described that { val i = 5; List(1, 2, 3); true }.logValueImpl works for me (according to other question Macro to access source code text at runtime).
Off-the-cuff implementation since I only have a minute:
import scala.reflect.macros.Context
import scala.language.experimental.macros
case class Conditional(conditionCode: String, value: Boolean) {
def THEN(doIt: Unit) = macro Conditional.THEN_impl
}
object Conditional {
def sourceCodeOfCondition: String = ???
def IF(condition: Boolean) = macro IF_impl
def IF_impl(c: Context)(condition: c.Expr[Boolean]): c.Expr[Conditional] = {
import c.universe._
c.Expr(q"Conditional(${ show(condition.tree) }, $condition)")
}
def THEN_impl(c: Context)(doIt: c.Expr[Unit]): c.Expr[Unit] = {
import c.universe._
val rewriter = new Transformer {
override def transform(tree: Tree) = tree match {
case Select(_, TermName("sourceCodeOfCondition")) =>
c.typeCheck(q"${ c.prefix.tree }.conditionCode")
case other => super.transform(other)
}
}
c.Expr(q"if (${ c.prefix.tree }.value) ${ rewriter.transform(doIt.tree) }")
}
}
And then:
object Demo {
import Conditional._
val x = 1
def demo = IF { x + 5 < 10 } THEN { println(sourceCodeOfCondition) }
}
And finally:
scala> Demo.demo
Demo.this.x.+(5).<(10)
It's a desugared representation of the source, but off the top of my head I think that's the best you're going to get.
See my blog post here for some discussion of the technique.
As of 2.13, you can also do this by wrapping the expression, which means you don't have to define a custom if function:
implicit def debugIf[A]: DebugIf => Unit = { cond: DebugIf =>
logger.info(s"condition = {}, result = ${cond.result}", cond.code)
}
decorateIfs {
if (System.currentTimeMillis() % 2 == 0) {
println("decorateIfs: if block")
} else {
println("decorateIfs: else block")
}
}
with the macro implementation:
def decorateIfs[A: c.WeakTypeTag](a: c.Expr[A])(output: c.Expr[DebugIf => Unit]): c.Expr[A] = {
def isEmpty(tree: Trees#Tree): Boolean = {
tree match {
case Literal(Constant(())) =>
true
case other =>
false
}
}
c.Expr[A] {
a.tree match {
// https://docs.scala-lang.org/overviews/quasiquotes/expression-details.html#if
case q"if ($cond) $thenp else $elsep" =>
val condSource = extractRange(cond) getOrElse ""
val printThen = q"$output(DebugIf($condSource, true))"
val elseThen = q"$output(DebugIf($condSource, false))"
val thenTree = q"""{ $printThen; $thenp }"""
val elseTree = if (isEmpty(elsep)) elsep else q"""{ $elseThen; $elsep }"""
q"if ($cond) $thenTree else $elseTree"
case other =>
other
}
}
}
private def extractRange(t: Trees#Tree): Option[String] = {
val pos = t.pos
val source = pos.source.content
if (pos.isRange) Option(new String(source.drop(pos.start).take(pos.end - pos.start))) else None
}
case class DebugIf(code: String, result: Boolean)
I'm creating a combinator parser in scala. The parse tree consists of Actions I need to visit when evaluating the parsed expression. One of these actions (the Function) will call a method on another object by reflection, but this fails when that is a vararg-method.
This is the code demonstrating the problem:
import scala.reflect.runtime.universe._
class M {
def e: Double = { Math.E }
def add(x: Double, y: Double): Double = { x + y }
def sin(x: Double): Double = { Math.sin(x * Math.PI / 180) }
def max(args: Double*): Double = { args.max }
}
sealed trait Action { def visit: Double }
case class Number(value: Double) extends Action { def visit: Double = value }
case class Function(Name: String, Args: Action*) extends Action {
def visit: Double = {
typeOf[M].member(Name: TermName) match {
case NoSymbol => throw new Error(s"Unknown function '$Name'")
case func: Symbol => runtimeMirror(getClass.getClassLoader).reflect(new M).reflectMethod(func.asMethod)(
(for { arg <- Args } yield arg.visit).toList: _*).asInstanceOf[Double]
}
}
}
object Parser {
def main(args: Array[String]) {
// Prints 2.718281828459045
println(Function("e").visit)
// Prints 7.0
println(Function("add", Number(3), Number(4)).visit)
// Prints 1.2246467991473532E-16
println(Function("sin", Number(180)).visit)
// Throws IllegalArgumentException: wrong number of arguments
println(Function("max", Number(1), Number(2.5), Number(50), Number(-3)).visit)
}
}
Anyone an idea how to fix this?
This is just a copy&paste solution, but I am sure you get the idea:
case class VarArgsFunction(Name: String, Args: Action*) extends Action {
def visit: Double = {
typeOf[M].member(Name: TermName) match {
case NoSymbol => throw new Error(s"Unknown function '$Name'")
case func: Symbol => {
runtimeMirror(getClass.getClassLoader).reflect(new M).reflectMethod(func.asMethod)(
(for { arg <- Args } yield arg.visit).toList).asInstanceOf[Double]
}
}
}
}
object Parser {
def main(args: Array[String]) {
// Prints 2.718281828459045
println(Function("e").visit)
// Prints 7.0
println(Function("add", Number(3), Number(4)).visit)
// Prints 1.2246467991473532E-16
println(Function("sin", Number(180)).visit)
// Throws IllegalArgumentException: wrong number of arguments
println(VarArgsFunction("max", Number(1), Number(2.5), Number(50), Number(-3)).visit)
}
}