Skip to content

reflect runtime class name using Quotes #11161

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

Closed
deusaquilus opened this issue Jan 19, 2021 · 19 comments · Fixed by #11519
Closed

reflect runtime class name using Quotes #11161

deusaquilus opened this issue Jan 19, 2021 · 19 comments · Fixed by #11519

Comments

@deusaquilus
Copy link
Contributor

deusaquilus commented Jan 19, 2021

Minimized code

Let's say I create a simple enum type:

enum Shape:
  case Square(width: Int, height: Int) extends Shape
  case Circle(radius: Int) extends Shape

Then I create a macro to print it's name:

import scala.quoted._
inline def showType[T]: String = ${ showTypeImpl[T] }
def showTypeImpl[T: Type](using Quotes): Expr[String] = {
  import quotes.reflect._
  Expr(TypeRepr.of[T].typeSymbol.fullName)
}

Output

Then I call it:

showType[Shape.Circle]

// It prints:
// ".Shape$.Circle"

Expectation

This is very unexpected because if I use a ClassTag/runtime-class to print the name I get the following:

classTag[Shape.Circle].toString
// It prints:
// "Shape$Circle"

This is the same as the print-out from the runtimeClass:

classTag[Shape.Circle].runtimeClass.getName
// It prints:
// "Shape$Circle"

I would expect the result from typeSymbol.fullName to be the same as runtimeClass.getName.

@deusaquilus deusaquilus changed the title Enum Type Name is wrong Enum Type fullName value is wrong Jan 19, 2021
@bishabosha
Copy link
Member

this seems like a weird interaction with the empty package being special cased to print as the empty string

@bishabosha
Copy link
Member

bishabosha commented Jan 19, 2021

I would disagree that fullName is meant to be the same as the runtime class name, because this is meant to be the view before erasure, so inner classes are not yet flattened, and type members still exist

@deusaquilus
Copy link
Contributor Author

deusaquilus commented Jan 19, 2021

I would disagree that fullName is meant to be the same as the runtime class name, because this is meant to be the view before erasure, so inner classes are not yet flattened, and type members still exist

@bishabosha
What about TypeRepr.of[T].classSymbol.get.fullName? Should that be the same as runtimeClass.getName?
I don't have a strong opinion of what the thing should be, but it should be something that reliably matches runtimeClass.getName because there are some important edge cases where I need to compare these things by name.

@deusaquilus
Copy link
Contributor Author

The only alternative I can think of would be to have some kind of function that goes from Type[T] to ClassTag[T].

@bishabosha
Copy link
Member

bishabosha commented Jan 19, 2021

can you use Expr.summon to get a classtag from a type? (Or quotes.reflect.Implicits.search)

@bishabosha bishabosha changed the title Enum Type fullName value is wrong Quotes reflect runtime class name? Jan 19, 2021
@bishabosha bishabosha changed the title Quotes reflect runtime class name? reflect runtime class name using Quotes Jan 19, 2021
@deusaquilus
Copy link
Contributor Author

That would give me an Type[T] => Expr[ClassTag[T]]. What I would like in that case is Type[T] => ClassTag[T].

@deusaquilus
Copy link
Contributor Author

@bishabosha Looks like I found a workaround for this particular issue. I think I'll keep this open though because the naming seems incorrect.

@bishabosha
Copy link
Member

That would give me an Type[T] => Expr[ClassTag[T]]. What I would like in that case is Type[T] => ClassTag[T].

thats a great point 😂, what is the workaround?

@nicolasstucki
Copy link
Contributor

It is impossible to do Type[T] => ClassTag[T] as the type we are referring to is being compiled and hence no java.lang.Class[_] can exist for it yet.

@nicolasstucki
Copy link
Contributor

What we might have is a

object TypeRepr {
  ...
  def classNameOf[T: Type](using Quotes): Option[String] = ...
}

TypeRepr.classNameOf[T]

Not sure what it would return anonymous classes or any other class with a generated name. Those names might not be stable.

@sjrd
Copy link
Member

sjrd commented Jan 19, 2021

Have you tried:

def showTypeImpl[T: Type](using Quotes): Expr[String] = {
  import quotes.reflect._
  '{classOf[T].getName()}
}

?

This seems to me like the most straightforward way to get that information in a reliable way.

@deusaquilus
Copy link
Contributor Author

deusaquilus commented Jan 20, 2021

@sjrd No, I haven't tried that actually 🤦‍♂️. Thanks for suggesting!

@bishabosha
Copy link
Member

@deusaquilus but does your use case require inspecting the class name at compile time?

@gaeljw
Copy link

gaeljw commented Feb 7, 2021

def showTypeImpl[T: Type](using Quotes): Expr[String] = {
  import quotes.reflect._
  '{classOf[T].getName()}
}

This seems to me like the most straightforward way to get that information in a reliable way.

@sjrd I was trying your suggestion in a different context but I get

[error] -- Error: /tmp/jodah/src/main/scala/Macros.scala:32:46 -------------------------
[error] 32 |    val classExpr : Expr[String] = '{ classOf[T].getName() }
[error]    |                                              ^
[error]    |                                              T is not a class type

Am I missing something? 🤔

@gaeljw
Copy link

gaeljw commented Feb 14, 2021

For anyone interested, following the suggestion of @bishabosha I've workaround the initial issue by getting a ClassTag rather than a string representation.
You can see the code on the following project: https://github.com/gaeljw/typetrees/blob/main/src/main/scala/io/github/gaeljw/typetrees/TypeTreeTagMacros.scala#L8

@deusaquilus
Copy link
Contributor Author

deusaquilus commented Feb 15, 2021

@gaeljw That's really cool, I didn't know about Implicits.search or that you could use it to find a ClassTag for T.
Could a Expr[ClassTag[T]] also be summoned with Expr.summon?

@gaeljw
Copy link

gaeljw commented Feb 15, 2021

Could a Expr[ClassTag[T]] also be summoned with Expr.summon?

@deusaquilus absolutely, I just tried Expr.summon instead of the manual implicit search and it seems to work.

nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Feb 24, 2021
@nicolasstucki
Copy link
Contributor

The canonical solution is probably something like

import scala.quoted._
import scala.reflect.ClassTag

inline def showType[T]: String = ${ showTypeImpl[T] }

private def showTypeImpl[T: Type](using Quotes): Expr[String] =
  Expr.summon[ClassTag[T]] match
      case Some(ct) => '{ $ct.runtimeClass.getName }
      case None =>
        import quotes.reflect._
        report.throwError(s"Unable to find a ClassTag for type ${Type.show[T]}", Position.ofMacroExpansion)

@bishabosha
Copy link
Member

Is it still useful to inspect the future class name at compiletime, rather than waiting until runtime?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants