scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

In a macro `c.eval` for `java.lang.Class[_]` produces `c.Type`/`Class[_]` depending on whether to define a variable #12680

Closed DmytroMitin closed 1 year ago

DmytroMitin commented 1 year ago

Reproduction steps

Scala version: 2.13.10

def foo(clazz: Class[_]): Unit = macro fooImpl

def fooImpl(c: blackbox.Context)(clazz: c.Tree): c.Tree = {
  import c.universe._
  val classValue = c.eval(c.Expr[Class[_]](c.untypecheck(clazz))) // HERE!!!
  q"()"
}
foo(classOf[String])
java.lang.ClassCastException: scala.reflect.internal.Types$ClassNoArgsTypeRef cannot be cast to java.lang.Class

https://scastie.scala-lang.org/DmytroMitin/RINjJfNsRNqprn9nR1ZpDQ/9 (toolbox is not used for actual reproduction, it's just for Scastie because it doesn't support multiple files/subprojects).

def foo(clazz: Class[_]): Unit = macro fooImpl

def fooImpl(c: blackbox.Context)(clazz: c.Tree): c.Tree = {
  import c.universe._
  println(c.eval(c.Expr[Class[_]](c.untypecheck(clazz)))) // HERE!!!
  q"()"
}
foo(classOf[String]) // ()
// prints: String

https://scastie.scala-lang.org/DmytroMitin/RINjJfNsRNqprn9nR1ZpDQ/18

Problem

The macro should expand successfully both times and c.eval should produce Class[_] both times.

A workaround is casting (when possible)

val classValue = c.mirror.asInstanceOf[c.universe.RuntimeMirror]
  .runtimeClass(
    c.eval(c.Expr[Type](c.untypecheck(clazz)))
  ).asInstanceOf[Class[_]

https://scastie.scala-lang.org/DmytroMitin/RINjJfNsRNqprn9nR1ZpDQ/33

or runtime reflection in macros

val rm = scala.reflect.runtime.currentMirror
val classValue =
  rm.runtimeClass(
    c.eval(c.Expr[rm.universe.Type](c.untypecheck(clazz)))
  ).asInstanceOf[Class[_]]

https://scastie.scala-lang.org/DmytroMitin/RINjJfNsRNqprn9nR1ZpDQ/32

If we remove c.untypecheck or replace it with c.resetAllAttrs (from https://github.com/scalamacros/resetallattrs) nothing changes.

If we replace existential with Class[T] nothing changes.

Discovered here: https://stackoverflow.com/questions/71390113/get-annotations-from-class-in-scala-3-macros

som-snytt commented 1 year ago

The example in the reflection scaladoc covers special handling of constants classOf[T] and Java enum.

But they are naively unwrapped at eval.

The comment:

People are very frequently using c.eval in order to obtain underlying values of literals. Spinning up a new compiler for that modest purpose is a gross waste of fossil fuels.

DmytroMitin commented 1 year ago

@som-snytt Many thanks. So evaluating a tree of Class[_] (enum value) into Type (Symbol) was intended behavior. I don't know whether the behavior should change. With new implementation this stops to work for classes not existing yet during macro expansion (although previous implementation was slightly inconsistent for classOf[String] / Class.forName("java.lang.String")). My point was that it's weird that the behavior depends on whether we define a local variable inside a macro.