Skip to content

Reflection: no exported way to access TypeDef#rhs / TypeRef#underlying on a TypeRepr #15799

Open
@neko-kai

Description

@neko-kai

Compiler version

3.1.3

Minimized code

example.scala:

package izumi.reflect.dottyreflection

@main def main = {
  object X {
    type Alias = String
    opaque type Opaque = String
    type Bound <: String
    opaque type OpaqueBound <: String = String
  }
  import X.*

  println(s"Alias: ${exampleMacro.typeDefRhs[Alias]}")
  println(s"Opaque: ${exampleMacro.typeDefRhs[Opaque]}")
  println(s"Bound: ${exampleMacro.typeDefRhs[Bound]}")
  println(s"OpaqueBound: ${exampleMacro.typeDefRhs[OpaqueBound]}")
}

exampleMacro.scala:

package izumi.reflect.dottyreflection

import scala.quoted.*

object exampleMacro {

  inline def typeDefRhs[T <: AnyKind]: (String, String) = ${ typeDefRhsImpl[T] }

  def typeDefRhsImpl[T <: AnyKind: Type](using Quotes): Expr[(String, String)] = {
    import quotes.reflect.*
    val typeRepr = TypeRepr.of[T]
    val typeSymbol = typeRepr.typeSymbol
    val defDefRhsTpe: String = typeSymbol.tree match {
      case t: TypeDef => t.rhs.asInstanceOf[TypeTree].tpe.show
    }
    val underlyingTpe: String = {
      // TypeRef#underlying(using Context)
      val underlyingMethod = typeRepr.getClass.getMethods.collect { case m if m.getName == "underlying" => m }.head
      // QuotesImpl#ctx: Context
      val quotesImplCtxMethod = quotes.getClass.getMethods.collect { case m if m.getName == "ctx" => m }.head

      val underlying = underlyingMethod.invoke(typeRepr, quotesImplCtxMethod.invoke(quotes))
      underlying.asInstanceOf[TypeRepr].show
    }
    '{ (${ Expr(defDefRhsTpe) }, ${ Expr(underlyingTpe) }) }
  }

}

Output

Alias: (_ >: scala.Predef.String <: scala.Predef.String,_ >: scala.Predef.String <: scala.Predef.String)
Opaque: (_ >: scala.Nothing <: scala.Any,_ >: scala.Nothing <: scala.Any)
Bound: (_ >: scala.Nothing <: scala.Predef.String,_ >: scala.Nothing <: scala.Predef.String)
OpaqueBound: (_ >: scala.Nothing <: scala.Predef.String,_ >: scala.Nothing <: scala.Predef.String)

Expectation

We need to get the right hand side of type alias / opaque / abstract type to to construct a Type Tag from it, but the only way to do this right now is by using Symbol#tree in typeRepr.typeSymbol.tree match { case t: TypeDef => t.rhs.asInstanceOf[TypeTree].tpe }

Unfortunately, Symbol#tree sometimes requires -Yretain-trees to work, which creates problems for end-users.

However, the right hand side is contained in a TypeRepr's TypeRef#underlying[1] method. But since the method is not exported in Quotes.scala, the only way to access this right now is through java reflection.

Original issue: zio/izumi-reflect#307

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions