scalascala-macroscallbyname

Handling by-name parameters in Scala macro


I have a macro that does some analysis on nested function applications. It matches applications and retrieve the parameter types this way:

case q"$f[..$targs](..$args)(...$otherArgs)" =>

    // retrieve the list of all parameter types
    val paramTpes = f.tpe match {
      case pmt: PolyType if pmt.paramLists.size == 0 =>
        Seq()
      case pmt: PolyType =>
        pmt.paramLists(0) map {_.typeSignature
             .substituteTypes(pmt.typeParams, targs map (_.tpe))}
      case pmt: MethodType if pmt.paramLists.size == 0 =>
        Seq()
      case pmt: MethodType =>
        pmt.paramLists(0) map (_.typeSignature)
    }

Now, if there happen to be by-name parameters, what I get is some weird type that prints => T but cannot be matched with anything. There seem to be no facility in the reflection API to handle these properly. What I would like to do is retrieve T in case it' a => T, because => T causes further problems in the generated code.


Solution

  • Usually, use the extractors. Sometimes I have to see what the runtime class is, to remember what extractor to use. I don't use the API every day.

    scala> import reflect.runtime._, universe._
    import reflect.runtime._
    import universe._
    
    scala> class X { def x(i: => Int) = i * 2 }
    defined class X
    
    scala> typeOf[X].member(TermName("x"))
    res0: reflect.runtime.universe.Symbol = method x
    
    scala> .typeSignature
    res1: reflect.runtime.universe.Type = (i: => scala.Int)scala.Int
    
    scala> res1 match { case MethodType(ps, res) => ps }
    res2: List[reflect.runtime.universe.Symbol] = List(value i)
    
    scala> .head
    res3: reflect.runtime.universe.Symbol = value i
    
    scala> .typeSignature
    res4: reflect.runtime.universe.Type = => scala.Int
    
    scala> res4.getClass
    res5: Class[_ <: reflect.runtime.universe.Type] = class scala.reflect.internal.Types$ClassArgsTypeRef
    
    scala> res4 match { case TypeRef(pre, sym, args) => sym }
    res6: reflect.runtime.universe.Symbol = class <byname>
    
    scala> res4 match { case TypeRef(pre, sym, args) => args }
    res7: List[reflect.runtime.universe.Type] = List(scala.Int)
    
    scala> definitions
    res8: reflect.runtime.universe.DefinitionsApi = scala.reflect.internal.Definitions$definitions$@4e80a001
    
    scala> definitions.By
    ByNameParamClass   ByteClass   ByteTpe
    
    scala> definitions.ByNameParamClass
    res9: reflect.runtime.universe.ClassSymbol = class <byname>
    

    I vaguely remember the special name, but do I get a stable prefix for pattern matching? I guess not.

    scala> res4 match { case TypeRef(pre, definitions.ByNameParamClass, args) => args }
    <console>:20: error: stable identifier required, but scala.reflect.runtime.`package`.universe.definitions.ByNameParamClass found.
     Note that method ByNameParamClass is not stable because its type, => reflect.runtime.universe.ClassSymbol, is volatile.
                  res4 match { case TypeRef(pre, definitions.ByNameParamClass, args) => args }
                                                             ^
    
    scala> val k = definitions.ByNameParamClass
    k: reflect.runtime.universe.ClassSymbol = class <byname>
    
    scala> res4 match { case TypeRef(pre, k, args) => args }
    res11: List[reflect.runtime.universe.Type] = List(scala.Int)
    
    scala>