Skip to content

Unresolved symbols when pickling quotes: by-name parameters, underlyingArgument and unintuitive error message #7030

Closed
@anatoliykmetyuk

Description

@anatoliykmetyuk

Macros:

import scala.quoted._

inline def inner(exprs: Any): Any = ${innerImpl('exprs)}
def innerImpl(exprs: Expr[Any]) given QuoteContext: Expr[Any] =
  '{$exprs ; ()}

inline def outer(expr: => Any): Any = ${outerImpl('expr)}
def outerImpl(body: Expr[Any]) given (ctx: QuoteContext): Expr[Any] = {
  import ctx.tasty._
  body.unseal.underlyingArgument.seal
}

Test:

val x = outer(inner(???))
Crash
exception occurred while compiling ../pg-dotc/Main.scala
java.lang.AssertionError: assertion failed: unresolved symbols: value exprs(line 0) when pickling ../pg-dotc/Main.scala while compiling ../pg-dotc/Main.scala
Exception in thread "main" java.lang.AssertionError: assertion failed: unresolved symbols: value exprs(line 0) when pickling ../pg-dotc/Main.scala
	at dotty.DottyPredef$.assertFail(DottyPredef.scala:16)
	at dotty.tools.dotc.core.tasty.TreePickler.pickle(TreePickler.scala:695)
	at dotty.tools.dotc.transform.Pickler.run$$anonfun$10$$anonfun$8(Pickler.scala:60)
	at dotty.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at dotty.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.immutable.List.foreach(List.scala:392)
	at dotty.tools.dotc.transform.Pickler.run$$anonfun$2(Pickler.scala:83)
	at dotty.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at dotty.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.immutable.List.foreach(List.scala:392)
	at dotty.tools.dotc.transform.Pickler.run(Pickler.scala:83)
	at dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:316)
	at scala.collection.immutable.List.map(List.scala:286)
	at dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:318)
	at dotty.tools.dotc.transform.Pickler.runOn(Pickler.scala:87)
	at dotty.tools.dotc.Run.runPhases$4$$anonfun$4(Run.scala:158)
	at dotty.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at dotty.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.IndexedSeqOptimized.foreach(IndexedSeqOptimized.scala:36)
	at scala.collection.IndexedSeqOptimized.foreach$(IndexedSeqOptimized.scala:33)
	at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:198)
	at dotty.tools.dotc.Run.runPhases$5(Run.scala:170)
	at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:178)
	at dotty.runtime.function.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
	at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:65)
	at dotty.tools.dotc.Run.compileUnits(Run.scala:185)
	at dotty.tools.dotc.Run.compileSources(Run.scala:120)
	at dotty.tools.dotc.Run.compile(Run.scala:104)
	at dotty.tools.dotc.Driver.doCompile(Driver.scala:35)
	at dotty.tools.dotc.Driver.process(Driver.scala:180)
	at dotty.tools.dotc.Driver.process(Driver.scala:149)
	at dotty.tools.dotc.Driver.process(Driver.scala:161)
	at dotty.tools.dotc.Driver.main(Driver.scala:188)
	at dotty.tools.dotc.Main.main(Main.scala)
[error] Nonzero exit code returned from runner: 1
[error] (dotty-compiler / Compile / runMain) Nonzero exit code returned from runner: 1
[error] Total time: 8 s, completed Aug 12, 2019 2:07:51 PM

To make it compile, make the parameter to inner by-name:

inline def inner(exprs: => Any): Any = ${innerImpl('exprs)}

In my original use case, it was a mistake for me not to make it by-name. However, due to the obscure crash, I was not able to see that mistake right away.

This is already the second time I encounter unintuitive behaviour of underlyingArgument (don't remember the first time, but it was likely the same thing). I expected it to just return an immediate child node of the callee. However, it seems that it performs a deep traversal and transformation of the callee tree. If you debug outerImpl as follows:

def outerImpl(body: Expr[Any]) given (ctx: QuoteContext): Expr[Any] = {
  import ctx.tasty._
  println(s"Original:\n${body.unseal}")
  println(s"\nUnderlying:\n${body.unseal.underlyingArgument}")
  body.unseal.underlyingArgument.seal
}
Original will be
Inlined(
  EmptyTree,List(

  ),Inlined(
    Apply(
      Ident(
        inner
      ),List(
        Ident(
          ???
        )
      )
    ),List(
      ValDef(
        exprs,TypeTree[TypeRef(
          ThisType(
            TypeRef(
              NoPrefix,module class scala
            )
          ),class Nothing
        )],Ident(
          ???
        )
      )
    ),Typed(
      Inlined(
        Ident(
          Macros$package$
        ),List(

        ),Block(
          List(
            Inlined(
              EmptyTree,List(

              ),Inlined(
                EmptyTree,List(

                ),Ident(
                  exprs
                )
              )
            )
          ),Literal(
            Constant(
              (

              )
            )
          )
        )
      ),Ident(
        Any
      )
    )
  )
)
Underlying will be
Typed(
  Block(
    List(
      Ident(
        exprs
      )
    ),Literal(
      Constant(
        (

        )
      )
    )
  ),Ident(
    Any
  )
)

Notice how the following code is not present in Underlying:

      ValDef(
        exprs,TypeTree[TypeRef(
          ThisType(
            TypeRef(
              NoPrefix,module class scala
            )
          ),class Nothing
        )],Ident(
          ???
        )
      )

Which is the likely cause of the crash.

I believe underlyingArgument should do just that – pill off the "outer layer" of the tree, returning the meaningful child. If the user really wants the current logic, we should make it into a separate method with a name reflecting its deep nature.

/cc @liufengyun since you looked into the issues with unresolved symbols while pickling recently

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions