Skip to content

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

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

Open
neko-kai opened this issue Aug 1, 2022 · 5 comments
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:enhancement

Comments

@neko-kai
Copy link
Contributor

neko-kai commented Aug 1, 2022

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

@neko-kai neko-kai added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Aug 1, 2022
@neko-kai
Copy link
Contributor Author

neko-kai commented Aug 1, 2022

Correction: typeRef.typeSymbol.owner.typeRef.memberType(typeRef.typeSymbol) seems to also produce the same output as TypeRef#underlying / TypeDef#rhs, but I'm not sure how reliable this is.

@WojciechMazur WojciechMazur added area:metaprogramming:reflection Issues related to the quotes reflection API itype:enhancement and removed itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Aug 2, 2022
@Kordyjan Kordyjan added this to the Future versions milestone Dec 12, 2022
@neko-kai
Copy link
Contributor Author

neko-kai commented Jan 20, 2023

typeRef.typeSymbol.owner.typeRef.memberType(typeRef.typeSymbol) seems to also produce the same output as TypeRef#underlying / TypeDef#rhs, but I'm not sure how reliable this is.

No, It's not a reliable substitution. When used on a TypeParamRef it returns Any instead of the underlying TypeBounds

btw access to underlying method on ParamRef would be great too.

@nicolasstucki
Copy link
Contributor

Indeed, it seems that we should add the underlying methods to TypeRef.

@nicolasstucki
Copy link
Contributor

Performance hint
-    '{ (${ Expr(defDefRhsTpe) }, ${ Expr(underlyingTpe) }) }
+    Expr((defDefRhsTpe, underlyingTpe))

nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Jan 23, 2023
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Jan 23, 2023
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Jan 23, 2023
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Jan 23, 2023
@neko-kai
Copy link
Contributor Author

@nicolasstucki
ParamRef.underlying is also needed (#16734)

nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Jan 24, 2023
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Feb 16, 2023
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 9, 2023
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:enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants