Skip to content
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

CyclicReference exception during macro expansion with transparent inline #16582

Closed
hmf opened this issue Dec 23, 2022 · 7 comments · Fixed by #16749
Closed

CyclicReference exception during macro expansion with transparent inline #16582

hmf opened this issue Dec 23, 2022 · 7 comments · Fixed by #16749
Assignees
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug
Milestone

Comments

@hmf
Copy link

hmf commented Dec 23, 2022

Compiler version

Scala version 3.2.1

Minimized code

  inline def ownerWorks(in: Int): Any = 
      ${ownerWorksImpl('in)}

  transparent inline def ownerDoesNotWork(in: Int): Any = 
      ${ownerWorksImpl('in)}
      
  def ownerWorksImpl(in: Expr[Int])(using Quotes): Expr[String] =
    import quotes.reflect.*
    val position = Position.ofMacroExpansion
    val file = position.sourceFile
    val owner0 = Symbol.spliceOwner.maybeOwner
    println("owner0 = " + owner0)
    val ownerName = owner0.tree match {
      case ValDef(name, _, _) => 
        name
      case DefDef(name, _, _, _) => 
        name
      case t => report.errorAndAbort(s"unexpected tree shape: ${t.show}")
    }
    val path = file.path
    val line = position.startLine
    val column = position.startColumn
    val v = in.valueOrAbort
    val out = Expr(s"val $ownerName $v: $file @ ${position.startLine}")
    out

Used as follows:

    val o1 = ownerWorks(1)
    println(o1)

    val o2 = ownerDoesNotWork(2)
    println(o2)

    val o3 = alternateDoesNotWork(3)
    println(o3)

Output

At the owner0.tree line, when ownerDoesNotWork is used, I get the following exception:

[error] -- Error: /home/hmf/VSCodeProjects/sploty/meta/src/data/Data8.scala:124:29 -----
[error] 124 |    val o2 = ownerDoesNotWork(2)
[error]     |             ^^^^^^^^^^^^^^^^^^^
[error]     |Exception occurred while executing macro expansion.
[error]     |dotty.tools.dotc.core.CyclicReference: 
[error]     |	at dotty.tools.dotc.core.CyclicReference$.apply(TypeErrors.scala:157)
[error]     |	at dotty.tools.dotc.core.SymDenotations$SymDenotation.completeFrom(SymDenotations.scala:171)
[error]     |	at dotty.tools.dotc.core.Denotations$Denotation.completeInfo$1(Denotations.scala:187)
[error]     |	at dotty.tools.dotc.core.Denotations$Denotation.info(Denotations.scala:189)
[error]     |	at dotty.tools.dotc.ast.tpd$.ValDef(tpd.scala:207)
[error]     |	at dotty.tools.dotc.quoted.reflect.FromSymbol$.valDefFromSym(FromSymbol.scala:50)
[error]     |	at dotty.tools.dotc.quoted.reflect.FromSymbol$.definitionFromSym(FromSymbol.scala:22)
[error]     |	at scala.quoted.runtime.impl.QuotesImpl$reflect$SymbolMethods$.tree(QuotesImpl.scala:2528)
[error]     |	at scala.quoted.runtime.impl.QuotesImpl$reflect$SymbolMethods$.tree(QuotesImpl.scala:2528)
[error]     |	at data.Macros5$.ownerWorksImpl(Macros5.scala:154)
[error]     |
[error]     |---------------------------------------------------------------------------

This exception is also generated during other calls such as Symbol.children, Symbol.flags and Symbol.declaredFields.

Expectation

The documentation found here states that " the tree for a symbol might not be defined". As per the linked best practices section, I have used the -Yretain-trees thus:

  override def scalacOptions = T{ Seq("-deprecation", "-feature", "-explain", "-Yretain-trees") }

So I assume that the tree should be available. Here the tree does seem to be available, but causes a loop. Seems like a possible bug.

However, the best practice says that we should "avoid Symbol.Tree". This may mean that in some conditions, possibly such as this one, no tree may be available. In other words, its not a bug. In such a case, if I follow, the above guidelines. how can I check for and deconstruct a ValDef (or any other element, for that matter)?

I have made many attempts and failed. In the example below I seem to only have access to a TermRef. I can deconstruct that but am unable to cast it to a ValDef, even when the reference states it is (or points to) one. I assume I have to de-reference it, but cannot see how.

  transparent inline def alternateDoesNotWork(in: Int): Any = 
      ${alternateWorksImpl('in)}
      
  def alternateWorksImpl(in: Expr[Int])(using Quotes): Expr[String] =
    import quotes.reflect.*
    val position = Position.ofMacroExpansion
    val file = position.sourceFile
    val owner0 = Symbol.spliceOwner.maybeOwner
    println("alternate owner0 = " + owner0)

    val term = owner0.termRef
    println(s"term = $term")
    val symbol = term.termSymbol
    println(s"term.termSymbol = ${symbol}")
    println(s"symbol.isValDef = ${symbol.isValDef}")
    println(s"symbol.isLocalDummy = ${symbol.isLocalDummy}")
    println(s"symbol.name = ${symbol.name}")

    // How should use the reflection API
    // val tree: Term = x.asTerm
    val ownerName = symbol.name
    // val ownerName = owner0.tree match {
    //   case ValDef(name, _, _) => 
    //     name
    //   case DefDef(name, _, _, _) => 
    //     name
    //   case t => report.errorAndAbort(s"unexpected tree shape: ${t.show}")
    // }

    // Alternatives?
    val TermRef(a,b) = term
    println("TermRef.TypeRepr = " + a)
    println("TermRef.name = " + b)
    // val valDef = term.asInstanceOf[ValDef]
    // println("valDef = " + valDef)
    // println("valDef.name = " + valDef.name)

    val path = file.path
    val line = position.startLine
    val column = position.startColumn
    val v = in.valueOrAbort
    val out = Expr(s"val $ownerName $v: $file @ ${position.startLine}")
    out
@hmf hmf added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 23, 2022
@prolativ prolativ added area:metaprogramming:reflection Issues related to the quotes reflection API and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 29, 2022
@prolativ
Copy link
Contributor

The snippets lack import scala.quoted.* at the beginning for completeness.

I'm afraid the difference in behaviour between transparent inline def and inline def might be as designed because transparent macros are expanded earlier than non-transparent ones and at that point the enclosing tree might not be ready for use from a macro but @nicolasstucki would have to confirm that

@nicolasstucki
Copy link
Contributor

Indeed, there is a difference in behaviour between transparent inline def and inline def. Accessing anything outside of the scope of the macro expansion that is defined in the current file such as Symbol.spliceOwner.maybeOwner can cause cycles while completing the typing of definitions.

In the context of a macro, we could try to catch this error and give a hint on why this could have happened.

@nicolasstucki
Copy link
Contributor

The cycle in this case it that in

  val o2 = ownerDoesNotWork(2)
  • To expand the macro we need to know the type of o2
  • We need to expand the macro to know the type of o2

@nicolasstucki
Copy link
Contributor

nicolasstucki commented Jan 23, 2023

The current error is

-- Error: t/Test_2.scala:5:27 --------------------------------------------------
5 |  val o2 = ownerDoesNotWork(2)
  |           ^^^^^^^^^^^^^^^^^^^
  |      Exception occurred while executing macro expansion.
  |      dotty.tools.dotc.core.CyclicReference: Recursive value o2 needs type
  |
  |-----------------------------------------------------------------------------
  |Inline stack trace
  |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  |This location contains code that was inlined from Macro_1.scala:7
7 |    ${ownerWorksImpl('in)}
  |    ^^^^^^^^^^^^^^^^^^^^^^
   -----------------------------------------------------------------------------

This error seems to be enough.

  • A user of the macro would know to try adding an explicit type to o2. This would fix the problem.
  • The implementor of the macro would know that they have a CyclicReference and therefore are accessing something that they cannot in this context.

@nicolasstucki
Copy link
Contributor

The only thing missing from the message is the stack trace

@hmf
Copy link
Author

hmf commented Jan 23, 2023

Thank you both for the explanation. I admit I am having trouble wrapping my mind around these details.

@nicolasstucki
The changes you reference above says it closes this issue. Forgot to close this one or is it meant to stay open?

@nicolasstucki
Copy link
Contributor

Meant to be automatically closed when #16749 is merged.

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

Successfully merging a pull request may close this issue.

4 participants