Wrong level staging when quoting in macro

Hello,
I’m trying to get an optional inline value using this code:

package io.github.iltotore.scalalint

import scala.quoted._

object compileTime {

  given fromExpr[T]: FromExpr[T] with {
    def unapply(expr: Expr[T])(using Quotes) =
      import quotes.reflect._
      def rec(tree: Term): Option[T] = tree match {
        case Block(stats, e) => if stats.isEmpty then rec(e) else None
        case Inlined(_, bindings, e) => if bindings.isEmpty then rec(e) else None
        case Typed(e, _) => rec(e)
        case _ =>
          tree.tpe.widenTermRefByName match {
            case ConstantType(c) => Some(c.value.asInstanceOf[T])
            case _ => None
          }
      }
      rec(expr.asTerm)
  }


  inline def inlineValueOpt[T](expr: T): Option[T] = ${inlineValueOptImpl('{expr})}

  private def inlineValueOptImpl[T](expr: Expr[T])(using Quotes, Type[T]): Expr[Option[T]] = {

    def recursiveSearch[T](expr: Expr[T])(using Quotes, Type[T], FromExpr[T]): Expr[Option[T]] = {
      expr match {
        case '{val name: T = $next} => recursiveSearch(next)

        case _ =>
          val value = expr.value
          '{value}
      }
    }

    recursiveSearch(expr)
  }
}

But I don’t understand this error:

[error] -- Error: C:\Users\rapha\IdeaProjects\ScalaLint\main\src\io\github\iltotore\scalalint\compileTime.scala:34:12 
[error] 34 |          '{value}
[error]    |            ^^^^^
[error]    |            access to value value from wrong staging level:
[error]    |             - the definition is at level 0,
[error]    |             - but the access is at level 1.

What is this and how can I fix it ? I did some research and the only answer I found was for a very specific case.

1 Like

Can you try Expr(value) instead?

Seems to work but isn’t quoting supposed to work here ?

Also I get a new error. I have to use a ToExpr[T] but there is no such thing. All ToExpr[T] seem to be hardcoded. I thook it was possible in Scala 2 so why not in Dotty ?

You can’t use a quote there cause you’re quoting something that’s defined outside of the quotes. As for why you don’t have a toExpr available, I’m guessing it’s only defined for stuff knowable at compile time. Perhaps value.map(Expr(_)).map(exp => ‘{ Some($exp)}).getOrElse(‘{None}) would work for you here…

Still the same problem. A ToExpr is missing:

[error] -- Error: C:\Users\rapha\IdeaProjects\ScalaLint\main\src\io\github\iltotore\scalalint\compileTime.scala:31:40 
[error] 31 |        case _ => expr.value.map(Expr(_)).map(exp => '{ Some($exp)}).getOrElse('{None})
[error]    |                                        ^
[error]    |no implicit argument of type quoted.ToExpr[T] was found for parameter x$2 of method apply in object Expr.
[error]    |I found:
[error]    |
[error]    |    quoted.ToExpr.FloatToExpr[Nothing]
[error]    |
[error]    |But given instance FloatToExpr in object ToExpr does not match type quoted.ToExpr[T].

Maybe I can only use premade ToExpr. My original goal was to make a inlinedValueOpt that return the inline Some(value) if the argument is inlined or None

Does it help if you add a ToExpr context bound to your method, and perhaps also to inlineValueOpt?

You can create custom ToExpr instances, but the problem here is that T is completely generic so the compiler can’t know whether a ToExpr[T] exists or not.

It doesn’t because there is no given instance of ToExpr[T] so I think the best approach would be hardcoded type comparisons because ToExprs are already hardcoded.

Yes, that is what should be used. In this case we have a value of type T in the current stage and we want to lift (create a copy of it) in the next stage. For this to work we also need a given ToExpr[T].

Expr(expr.value)