question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Inline summon generated by macro doesn't consider expanded macro scope

See original GitHub issue

Compiler version

3.0.0-RC3

Attempt 1: summonInline

Minimized code

a:scala:

import scala.quoted.*

case class F[A]()

object F:
  implicit def option[A: F]: F[Option[A]] = F()

  inline def test[A]: F[Option[A]] =
    ${ _test[A] }

  def _test[A](using Quotes, Type[A]): Expr[F[Option[A]]] =
    import quotes.reflect.*

    // Dynamically created `implicit val`
    val faSym  = Symbol.newVal(Symbol.spliceOwner, "fa", TypeRepr.of[F[A]], Flags.Implicit, Symbol.noSymbol)
    val faBody = '{ F[Int]() }.asTerm
    val faDef  = ValDef(faSym, Some(faBody))

    val summonInline = '{ scala.compiletime.summonInline[F[Option[A]]] }.asTerm
    val result = Block(List(faDef), summonInline).asExprOf[F[Option[A]]]

    println(s"\n${result.show}\n")
    result

b:scala:

object Test:

  val ko = F.test[Int]

  // Copy-paste of the .show output of the generated Expr
  val ok =
    implicit val fa: F[scala.Int] = F.apply[scala.Int]()
    scala.compiletime.package$package.summonInline[F[scala.Option[scala.Int]]]

Output

{
  implicit val fa: F[scala.Int] = F.apply[scala.Int]()
  scala.compiletime.package$package.summonInline[F[scala.Option[scala.Int]]]
}

[error] -- Error: b.scala:3:17 ----------------
[error] 3 |  val ko = F.test[Int]
[error]   |           ^^^^^^^^^^^
[error]   |           cannot reduce summonFrom with
[error]   |            patterns :  case given t @ _:F[Option[Int]]
[error]   | This location contains code that was inlined from package.scala:140
[error]   | This location contains code that was inlined from a.scala:19
[error]   | This location contains code that was inlined from a.scala:19
[error] one error found

Attempt 2: inline call to Implicits.search

Minimized code

a:scala:

import scala.quoted.*

object Util:
  def summonLater[A: Type](using Quotes): Expr[A] =
    '{ inlineSummon[A] }

  inline def inlineSummon[A]: A =
    ${ _inlineSummon[A] }

  def _inlineSummon[A: Type](using Quotes): Expr[A] =
    summonOrError[A]

  def summonOrError[A](using Type[A])(using Quotes): Expr[A] =
    import quotes.reflect.*
    Implicits.search(TypeRepr.of[A]) match
      case iss: ImplicitSearchSuccess => iss.tree.asExpr.asInstanceOf[Expr[A]]
      case isf: ImplicitSearchFailure => report.throwError(isf.explanation)

case class F[A]()

object F:
  implicit def option[A: F]: F[Option[A]] = F()

  inline def test[A]: F[Option[A]] =
    ${ _test[A] }

  def _test[A](using Quotes, Type[A]): Expr[F[Option[A]]] =
    import quotes.reflect.*

    // Dynamically created `implicit val`
    val faSym  = Symbol.newVal(Symbol.spliceOwner, "fa", TypeRepr.of[F[A]], Flags.Implicit, Symbol.noSymbol)
    val faBody = '{ F[Int]() }.asTerm
    val faDef  = ValDef(faSym, Some(faBody))

    val summonInline = Util.summonLater[F[Option[A]]].asTerm
    val result = Block(List(faDef), summonInline).asExprOf[F[Option[A]]]

    println(s"\n${result.show}\n")
    result

b:scala:

object Test:

  val ko = F.test[Int]

  // Copy-paste of the .show output of the generated Expr
  val ok =
    implicit val fa: F[scala.Int] = F.apply[scala.Int]()
    Util.inlineSummon[F[scala.Option[scala.Int]]]

Output

{
  implicit val fa: F[scala.Int] = F.apply[scala.Int]()
  Util.inlineSummon[F[scala.Option[scala.Int]]]
}

[error] -- Error: b.scala:3:17 ----------------
[error] 3 |  val ko = F.test[Int]
[error]   |           ^^^^^^^^^^^
[error]   |           no implicit values were found that match type F[Int]
[error]   | This location contains code that was inlined from b.scala:3
[error]   | This location contains code that was inlined from a.scala:5
[error]   | This location contains code that was inlined from a.scala:5
[error] one error found

Expectation

Neither macro compiles, however in both cases if I copy-paste the output of .show, the code compiles ok.

Implicit search should consider sibling implicits in local scope, just like normal code.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:15 (13 by maintainers)

github_iconTop GitHub Comments

1reaction
som-snyttcommented, May 7, 2021

In attempt 1, if it is inline def summonInline:

exception occurred while compiling a.scala
java.lang.AssertionError: assertion failed: unresolved symbols: type A (line 12) #9326 when pickling a.scala while compiling a.scala
Exception in thread "main" java.lang.AssertionError: assertion failed: unresolved symbols: type A (line 12) #9326 when pickling a.scala
        at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
        at dotty.tools.dotc.core.tasty.TreePickler.pickle(TreePickler.scala:788)
        at dotty.tools.dotc.quoted.PickledQuotes$.pickle(PickledQuotes.scala:168)

If it is transparent inline def summonInline then it just works.

Then I quit while I was ahead.

0reactions
japgollycommented, May 10, 2021

But this is a quite bad pattern as it is just forcing a new implicit search for something that we already know how to build. If we created fa then we should just use it where we need to find fa.

If you know exactly what you need, then yes 100% this is a bad pattern, I agree with you. The problem is that there are cases where you not only do you know what what’s required, but the effort to work out if something is required and then to try to recursively populate it with it’s required implicits, especially when you’re generating implicits that can (according to user-space rules and not the rules of the macro) have mutual/recursive implicit dependencies, trying to calculate all of that effort 100% statically is equivalent to just implementing the implicit search algorithm. Using this pattern I can just generate all of the expected implicits and if the user has done everything right on their side, then everything should work out at expansion time. If they haven’t, then they’ll get a nice implicit-not-found error after my macro expands which is perfect for both users, and me as the macro author.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Macros - Scala 3
Here, we need a tweak of the typing rules. An inline function such as assert that contains a splice operation outside an enclosing...
Read more >
Within a function passed to a scala macro, I cannot reference ...
Isn't the class recompiled with the macro expanded? If recompiled with inline expansion, no compilation error should occur. object Generation { ...
Read more >
Macros: Essential Information - World of Warcraft Forums
Most often, macros are created to use items or abilities. However, macros can also be used to send chat messages, equip gear, change...
Read more >
Scala Compile-time Operations | Macros in Scala 3
It is possible to emit error messages when inlining code. ... If error is called outside an inline method, the error will be...
Read more >
Pathfinder Community Sheet - Roll20 Wiki
The Pathfinder's Community Sheet Guide was created for the Pathfinder ... Q: I edited a macro with [[1d6]] and it doesn't work, why?...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found