Skip to content

CyclicReference exception during macro expansion with transparent inline #16582

Closed
@hmf

Description

@hmf

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions