summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/typechecker/Macros.scala
blob: 48ec59234ee52e558a21fc291ea1f193948e7237 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11



                       

                                    




                                


                                                                       
                                                  

   













                                                              
     
                                                                 
                              




                                                             


                                                      
                                     

                                                                                                              

              
    



                                                                                                                   


                                                                                        
                                                       





                                                                                                          



                                                                                                                   
                                                    
                                                                  

                                                                                                                                                     
                                   
     

                                                                                    
 

                                                                 
                                              
                                              
                         
                                                           


                               



                                                                                  


                                                                                 


     








                                                                        
                                                                                    
                                                                                  






                                                                 
                                                                                     
                                                       
                                                
              
                                                                
                                                                                      



                                            











                                                                
                                                                









                                                                                 
                                                   
             






                                                                                      
                                                           





                                                                       

                                                                                    

                                                 


                                                                        




                                                                                          


                                                            

                                                                          
                         



                                                          



                                                                        
                                       



                                                                            




                                                          







                                                  


     
package scala.tools.nsc
package typechecker

import symtab.Flags._
import scala.tools.nsc.util._
import scala.reflect.ReflectionUtils

trait Macros { self: Analyzer =>
  import global._
  import definitions._

  def macroMeth(mac: Symbol): Symbol = {
    var owner = mac.owner
    if (!owner.isModuleClass) owner = owner.companionModule.moduleClass
    owner.info.decl(nme.macroMethodName(mac.name))
  }

  def macroArgs(tree: Tree): (List[List[Tree]]) = tree match {
    case Apply(fn, args) =>
      macroArgs(fn) :+ args
    case TypeApply(fn, args) =>
      macroArgs(fn) :+ args
    case Select(qual, name) if !isStaticMacro(tree.symbol) =>
      List(List(qual))
    case _ =>
      List(List())
  }

  private def isStaticMacro(mac: Symbol): Boolean =
    mac.owner.isModuleClass

  /**
   *  The definition of the method implementing a macro. Example:
   *  Say we have in a class C
   *
   *    def macro foo[T](xs: List[T]): T = expr
   *
   *  Then the following macro method is generated for `foo`:
   *
   *    def defmacro$foo
   *           (_context: scala.reflect.macro.Context)
   *           (_this: _context.Tree)
   *           (T: _context.TypeTree)
   *           (xs: _context.Tree): _context.Tree = {
   *      import _context._  // this means that all methods of Context can be used unqualified in macro's body
   *      expr
   *    }
   *
   *  If `foo` is declared in an object, the second parameter list is () instead of (_this: _context.Tree).
   *  If macro has no type arguments, the third parameter list is omitted (it's not empty, but omitted altogether).
   *
   *  To find out the desugared representation of your particular macro, compile it with -Ydebug.
   */
  def macroMethDef(mdef: DefDef): Tree = {
    def paramDef(name: Name, tpt: Tree) = ValDef(Modifiers(PARAM), name, tpt, EmptyTree)
    val contextType = TypeTree(ReflectMacroContext.tpe)
    val globParamSec = List(paramDef(nme.macroContext, contextType))
    def globSelect(name: Name) = Select(Ident(nme.macroContext), name)
    def globTree = globSelect(tpnme.Tree)
    def globTypeTree = globSelect(tpnme.TypeTree)
    val thisParamSec = if (isStaticMacro(mdef.symbol)) List() else List(paramDef(nme.macroThis, globTree))
    def tparamInMacro(tdef: TypeDef) = paramDef(tdef.name.toTermName, globTypeTree)
    def vparamInMacro(vdef: ValDef): ValDef = paramDef(vdef.name, vdef.tpt match {
      case tpt @ AppliedTypeTree(hk, _) if treeInfo.isRepeatedParamType(tpt) => AppliedTypeTree(hk, List(globTree))
      case _ => globTree
    })
    def wrapImplicit(tree: Tree) = atPos(tree.pos) {
      // implicit hasn't proven useful so far, so I'm disabling it
      //val implicitDecl = ValDef(Modifiers(IMPLICIT), nme.macroContextImplicit, SingletonTypeTree(Ident(nme.macroContext)), Ident(nme.macroContext))
      val importGlob = Import(Ident(nme.macroContext), List(ImportSelector(nme.WILDCARD, -1, null, -1)))
      Block(List(importGlob), tree)
    }
    var formals = (mdef.vparamss map (_ map vparamInMacro))
    if (mdef.tparams.nonEmpty) formals = (mdef.tparams map tparamInMacro) :: formals

    atPos(mdef.pos) {
      new DefDef( // can't call DefDef here; need to find out why
        mods = mdef.mods &~ MACRO &~ OVERRIDE,
        name = nme.macroMethodName(mdef.name),
        tparams = List(),
        vparamss = globParamSec :: thisParamSec :: formals,
        tpt = globTree,
        wrapImplicit(mdef.rhs))
    }
  }

  def addMacroMethods(templ: Template, namer: Namer): Unit = {
    for (ddef @ DefDef(mods, _, _, _, _, _) <- templ.body if mods hasFlag MACRO) {
      val trace = scala.tools.nsc.util.trace when settings.debug.value
      val sym = namer.enterSyntheticSym(trace("macro def: ")(macroMethDef(ddef)))
      trace("added to "+namer.context.owner.enclClass+": ")(sym)
    }
  }

  lazy val mirror = new scala.reflect.runtime.Mirror {
    lazy val libraryClassLoader = {
      val classpath = global.classPath.asURLs
      ScalaClassLoader.fromURLs(classpath, self.getClass.getClassLoader)
    }

    override def defaultReflectiveClassLoader() = libraryClassLoader
  }

  /** Return optionally address of companion object and implementation method symbol
   *  of given macro; or None if implementation classfile cannot be loaded or does
   *  not contain the macro implementation.
   */
  def macroImpl(mac: Symbol): Option[(AnyRef, mirror.Symbol)] = {
    try {
      val mmeth = macroMeth(mac)
      if (mmeth == NoSymbol) None
      else {
        val receiverClass: mirror.Symbol = mirror.symbolForName(mmeth.owner.fullName)
        val receiverObj = receiverClass.companionModule
        if (receiverObj == mirror.NoSymbol) None
        else {
          val receiver = mirror.companionInstance(receiverClass)
          val rmeth = receiverObj.info.member(mirror.newTermName(mmeth.name.toString))
          if (rmeth == mirror.NoSymbol) None
          else {
            Some((receiver, rmeth))
          }
        }
      }
    } catch {
      case ex: ClassNotFoundException =>
        None
    }
  }

  /** Return result of macro expansion.
   *  Or, if that fails, and the macro overrides a method return
   *  tree that calls this method instead of the macro.
   */
  def macroExpand(tree: Tree, context: Context): Option[Any] = {
    val macroDef = tree.symbol
    macroImpl(macroDef) match {
      case Some((receiver, rmeth)) =>
        val argss = List(global) :: macroArgs(tree)
        val paramss = macroMeth(macroDef).paramss
        val rawArgss = for ((as, ps) <- argss zip paramss) yield {
          if (isVarArgsList(ps)) as.take(ps.length - 1) :+ as.drop(ps.length - 1)
          else as
        }
        val rawArgs: Seq[Any] = rawArgss.flatten
        val savedInfolevel = nodePrinters.infolevel
        try {
          // @xeno.by: InfoLevel.Verbose examines and prints out infos of symbols
          // by the means of this'es these symbols can climb up the lexical scope
          // when these symbols will be examined by a node printer
          // they will enumerate and analyze their children (ask for infos and tpes)
          // if one of those children involves macro expansion, things might get nasty
          // that's why I'm temporarily turning this behavior off
          nodePrinters.infolevel = nodePrinters.InfoLevel.Quiet
          Some(mirror.invoke(receiver, rmeth)(rawArgs: _*))
        } catch {
          case ex =>
            val realex = ReflectionUtils.unwrapThrowable(ex)
            val stacktrace = new java.io.StringWriter()
            realex.printStackTrace(new java.io.PrintWriter(stacktrace))
            val msg = System.getProperty("line.separator") + stacktrace
            context.unit.error(tree.pos, "exception during macro expansion: " + msg)
            None
        } finally {
          nodePrinters.infolevel = savedInfolevel
        }
      case None =>
        val trace = scala.tools.nsc.util.trace when settings.debug.value
        def notFound() = {
          context.unit.error(tree.pos, "macro implementation not found: " + macroDef.name)
          None
        }
        def fallBackToOverridden(tree: Tree): Option[Tree] = {
          tree match {
            case Select(qual, name) if (macroDef.isMacro) =>
              macroDef.allOverriddenSymbols match {
                case first :: _ =>
                  Some(Select(qual, name) setPos tree.pos setSymbol first)
                case _ =>
                  trace("macro is not overridden: ")(tree)
                  notFound()
              }
            case Apply(fn, args) =>
              fallBackToOverridden(fn) match {
                case Some(fn1) => Some(Apply(fn1, args) setPos tree.pos)
                case _         => None
              }
            case TypeApply(fn, args) =>
              fallBackToOverridden(fn) match {
                case Some(fn1) => Some(TypeApply(fn1, args) setPos tree.pos)
                case _         => None
              }
            case _ =>
              trace("unexpected tree in fallback: ")(tree)
              notFound()
          }
        }
        fallBackToOverridden(tree) match {
          case Some(tree1) =>
            trace("falling back to ")(tree1)
            currentRun.macroExpansionFailed = true
            Some(tree1)
          case None =>
            None
        }
    }
  }
}