Skip to content

Scope issue expanding macro with combination of ValDef/Lambda #13929

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
cchantep opened this issue Nov 11, 2021 · 1 comment
Closed

Scope issue expanding macro with combination of ValDef/Lambda #13929

cchantep opened this issue Nov 11, 2021 · 1 comment
Assignees

Comments

@cchantep
Copy link
Contributor

cchantep commented Nov 11, 2021

See #13922

Compiler version

3.1.2-RC1-bin-20211022-f7abd32-NIGHTLY-git-f7abd32

Minimized code

import scala.quoted.*

inline def foo[T](f: T): Option[T] => T = ${ fooImpl[T]('f) }

private def fooImpl[T: Type](
  f: Expr[T]
)(using
  q: Quotes
): Expr[Option[T] => T] = {
  import q.reflect.*

  type Fn = () => T
  val tpe = TypeRepr.of[Fn]

  val forwardSym = Symbol.newVal(
    Symbol.spliceOwner,
    "forward",
    tpe,
    Flags.Lazy,
    Symbol.noSymbol
  )
  val forwardRef = Typed(Ref(forwardSym), Inferred(tpe))

  val lm = Lambda(
    Symbol.spliceOwner,
    MethodType(List("opt"))(
      _ => List(TypeRepr.of[Option[T]]),
      _ => TypeRepr.of[T]
    ),
    {
      case (m, List(arg: Term)) =>
        '{
        ${ arg.asExprOf[Option[T]] }.getOrElse(${
          forwardRef.asExprOf[Fn]
        }())
      }.asTerm.changeOwner(m)

      case (m, _) =>
        report.errorAndAbort(s"Fails compile: ${m}")
    }
  )

  ValDef
    .let(Symbol.spliceOwner, "x", lm) { x =>
    val forwardExpr: Expr[Fn] = '{ () => ${ f } }
    val forwardTerm = forwardExpr.asTerm

    Block(
      List(ValDef(forwardSym, Some(forwardTerm))),
      '{ (a: Option[T]) =>
        println("top=" + a)
        ${ x.asExprOf[Option[T] => T] }(a)
      }.asTerm
    )
  }.asExprOf[Option[T] => T]
}

foo("bar")(None)

Output

-- Error: ----------------------------------------------------------------------
1 |foo("bar")(None)
  |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |While expanding a macro, a reference to lazy value forward was used outside the scope where it was defined
  | This location contains code that was inlined from rs$line$1:1
1 error found

Expectation

top=None
val res0: String = bar

Details

In case there is no need to nest ValDef, the scope issue is not raised:

private def fooImpl[T: Type](
  f: Expr[T]
)(using
  q: Quotes
): Expr[Option[T] => T] = {
  import q.reflect.*

  type Fn = () => T
  val tpe = TypeRepr.of[Fn]

  val forwardSym = Symbol.newVal(
    Symbol.spliceOwner,
    "forward",
    tpe,
    Flags.Lazy,
    Symbol.noSymbol
  )
  val forwardRef = Typed(Ref(forwardSym), Inferred(tpe))

  val lm = Lambda(
    Symbol.spliceOwner,
    MethodType(List("opt"))(
      _ => List(TypeRepr.of[Option[T]]),
      _ => TypeRepr.of[T]
    ),
    {
      case (m, List(arg: Term)) =>
        '{
        ${ arg.asExprOf[Option[T]] }.getOrElse(${
          forwardRef.asExprOf[Fn]
        }())
      }.asTerm.changeOwner(m)

      case (m, _) =>
        report.errorAndAbort(s"Fails compile: ${m}")
    }
  )

  val forwardExpr: Expr[Fn] = '{ () => ${ f } }
  val forwardTerm = forwardExpr.asTerm

  //  __ HERE
  // V
  Block(
    List(ValDef(forwardSym, Some(forwardTerm))),
    '{ (a: Option[T]) =>
      println("top=" + a)
      ${ lm.asExprOf[Option[T] => T] }(a)
    }.asTerm
  ).asExprOf[Option[T] => T]
}
@cchantep
Copy link
Contributor Author

My bad, that's an issue in the tree definitions. It's quite easy to get lost with the current API when it's required to define separately Symbol & Term (there due to ValDef.let not allowing to give Flags).

Following is working:

inline def foo[T](f: T): Option[T] => T = ${ fooImpl[T]('f) }

private def fooImpl[T: Type](
  f: Expr[T]
)(using
  q: Quotes
): Expr[Option[T] => T] = {
  import q.reflect.*

  type Fn = () => T
  val tpe = TypeRepr.of[Fn]

  val forwardSym = Symbol.newVal(
    Symbol.spliceOwner,
    "forward",
    tpe,
    Flags.Lazy,
    Symbol.noSymbol
  )
  val forwardRef = Typed(Ref(forwardSym), Inferred(tpe))

  val lm = Lambda(
    Symbol.spliceOwner,
    MethodType(List("opt"))(
      _ => List(TypeRepr.of[Option[T]]),
      _ => TypeRepr.of[T]
    ),
    {
      case (m, List(arg: Term)) =>
        '{
        ${ arg.asExprOf[Option[T]] }.getOrElse(${
          forwardRef.asExprOf[Fn]
        }())
      }.asTerm //.changeOwner(m)

      case (m, _) =>
        report.errorAndAbort(s"Fails compile: ${m}")
    }
  )

  val forwardExpr: Expr[Fn] = '{ () => ${ f } }
  val forwardTerm = forwardExpr.asTerm

  val vd = ValDef(forwardSym, Some(forwardTerm))

  Block(
    List(vd),
    ValDef.let(Symbol.spliceOwner, "x", lm) { x =>
      '{ (a: Option[T]) =>
      println("top=" + a)
      ${ x.asExprOf[Option[T] => T] }(a)
    }.asTerm
    }
  ).asExprOf[Option[T] => T]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants