I implemented a recursive power version using macro quotes as a simpler example to show an issue I have. Here is the macro’s code:
transparent inline def power2(a: Int, b:Int): Any = ${ power2Impl('a,'b) }
def power2Impl(a: Expr[Int], b: Expr[Int])(using q1: Quotes): Expr[Int] =
b match
case '{0} => '{1}
case '{ $expr: tpe } =>
println(s"b : ${b.show} = ${b.value}")
println(s"b : ${Type.show[tpe]} = ${b.value}")
'{
val nb = $b-1
val next = ${ power2Impl(a, 'nb)}
$a * next
}
and I invoke it so:
val p2 = data.Macros3.power2(2,2)
and get this compile-time error:
error] |Exception occurred while executing macro expansion.
[error] |java.lang.StackOverflowError
[error] | at scala.quoted.runtime.impl.Scope.root(SpliceScope.scala:23)
[error] | at scala.quoted.runtime.impl.Scope.root$(SpliceScope.scala:13)
[error] | at scala.quoted.runtime.impl.SpliceScope.root(SpliceScope.scala:36)
[error] | at scala.quoted.runtime.impl.Scope.root(SpliceScope.scala:23)
[error] | at scala.quoted.runtime.impl.Scope.root$(SpliceScope.scala:13)
[error] | at scala.quoted.runtime.impl.SpliceScope.root(SpliceScope.scala:36)
[error] | at scala.quoted.runtime.impl.Scope.root(SpliceScope.scala:23)
[error] | at scala.quoted.runtime.impl.Scope.root$(SpliceScope.scala:13)
[error] | at scala.quoted.runtime.impl.SpliceScope.root(SpliceScope.scala:36)
[error] | at scala.quoted.runtime.impl.Scope.root(SpliceScope.scala:23)
So why is it not stopping when it should? The type checker (via IDE) reports that nb and next are both Int. So I added the print lines to see what is happening and get:
b : 2 = Some(2)
b : 2 = Some(2)
b : nb = None
b : nb.type = None
b : nb = None
b : nb.type = None
b : nb = None
b : nb.type = None
b : nb = None
The lines are duplicated just to check the type. Note that at the start I have Some(2), but then after the 1st subtraction, the compiler seems to generate a None. I am expecting a Some(1). I assume the Some(0) would match the first case.
Can anyone tell me what is happening and how to solve this?
def power2Impl(a: Expr[Int], b: Expr[Int])(using q1: Quotes): Expr[Int] =
import quotes.reflect.*
b match
case '{0} => '{1}
case '{1} => a
case '{ $expr: Int } =>
println(s"b(a) : ${b.show} = ${b.value}")
val next_b: Expr[Int] = '{$expr - 1}
next_b.value match
case None =>
report.errorAndAbort("power2Impl: Unexpected value")
case Some(0) =>
// a^1 = a * a⁰
a
case Some(1) =>
// a^2 = a * a¹
'{$a*$a}
case Some(x) =>
// a^b = a * a^x
println(s"Recursing on Some($x)")
val next = power2Impl(a, next_b)
println(s"next = ${next.value}")
'{$a * $next}
The problem is that in the previous version, the line:
val nb = $b-1
is code that will be generated, and the value is therefore not available when we splice :