Skip to content

Quoted patterns: match fails on an expression that is created inside the macro #16521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
hmf opened this issue Dec 14, 2022 · 1 comment
Closed
Labels
area:metaprogramming:quotes Issues related to quotes and splices itype:invalid

Comments

@hmf
Copy link

hmf commented Dec 14, 2022

Compiler version

Scala version 3.2.1

Minimized code

I have the following macro to calculate a power of an integer (both base and exponent are integers).

  transparent inline def power3(a: Int, b:Int): Any = ${ power3Impl('a,'b) }

  def power3Impl(a: Expr[Int], b: Expr[Int])(using q1: Quotes): Expr[Int] =
    import quotes.reflect.*
    // Ifs don't work
    b match 
      case '{0:Int} => '{1}
      case '{1:Int} => a
      case '{ $expr: Int } => 
        println(s"b(a) : ${b.show} = ${b.value}")
        println(s"Recursing on ${expr.value}")
        val next_b: Expr[Int] = '{$expr - 1}
        val next = power3Impl(a, next_b)
        println(s"next = ${next.value}")
        '{$a * $next}

and I test it in another file with:

    val p3_0 = data.Macros3.power3(2,0)
    assert(p3_0 == 1)
    val p3_1 = data.Macros3.power3(2,1)
    assert(p3_1 == 2)
    val p3_2 = data.Macros3.power3(2,2)
    assert(p3_2 == 4)

Output

The line:

    val p3_2 = data.Macros3.power3(2,2)

produces the following error:

Exception occurred while executing macro expansion.
java.lang.StackOverflowError

The output is:

b(a) : 3 = Some(3)
Recursing on Some(3)
b(a) : 3.-(1) = Some(2)
Recursing on Some(2)
b(a) : 3.-(1).-(1) = Some(1)
Recursing on Some(1)
b(a) : 3.-(1).-(1).-(1) = Some(0)
Recursing on Some(0)
b(a) : 3.-(1).-(1).-(1).-(1) = Some(-1)
Recursing on Some(-1)
b(a) : 3.-(1).-(1).-(1).-(1).-(1) = Some(-2)
Recursing on Some(-2)
b(a) : 3.-(1).-(1).-(1).-(1).-(1).-(1) = Some(-3)
Recursing on Some(-3)

Expectation

I expect the expression from:

        val next_b: Expr[Int] = '{$expr - 1}

to match on the '{1} quote case on the next spliced call. Note that in the tests, these do match on 0 and 1 respectively when the values come from the original source code. I have the following code that works:

  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] =
    import quotes.reflect.*
    // Ifs don't work
    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
            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}

but extracting the value and repeating the same matches seems redundant and incorrect.

@hmf hmf added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 14, 2022
@anatoliykmetyuk anatoliykmetyuk added area:metaprogramming:quotes Issues related to quotes and splices and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 19, 2022
@nicolasstucki
Copy link
Contributor

It is the same issue as in #16533 (comment).

Using quote pattern matching is not meant to be used to recover values, rather matches the structure of the expression. A '{1 + 2} will not match '{3} because they do not have the same structure. To match on the value one needs to match on the runtime value of the evaluated code. This is where .value, Expr.apply and Expr.unapply are useful.

While the other comment shows how to do this correctly and optimally, we can also use the shorter (slower) version that uses Expr.apply and Expr.unapply.

  def power3Impl(a: Expr[Int], b: Expr[Int])(using q1: Quotes): Expr[Int] =
    b match
      case Expr(0) => '{1} // or Expr(1)
      case Expr(1) => a
      case Expr(n) =>
        println(s"b(a) : ${b.show} = ${n}")
        println(s"Recursing on ${n}")
        val next_b: Expr[Int] = Expr(n - 1)
        val next = power3Impl(a, next_b)
        println(s"next = ${next.value}")
        '{$a * $next}
      case _ =>
        quotes.reflect.report.errorAndAbort("b must be a constant")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metaprogramming:quotes Issues related to quotes and splices itype:invalid
Projects
None yet
Development

No branches or pull requests

3 participants