package scala.tools.nsc package typechecker import symtab.Flags._ import scala.tools.nsc.util._ import scala.tools.nsc.util.ClassPath._ import scala.reflect.runtime.ReflectionUtils import scala.collection.mutable.ListBuffer import scala.compat.Platform.EOL import scala.reflect.internal.util.Statistics import scala.reflect.macros.util._ import java.lang.{Class => jClass} import java.lang.reflect.{Array => jArray, Method => jMethod} import scala.reflect.internal.util.Collections._ import scala.util.control.ControlThrowable import scala.reflect.macros.runtime.AbortMacroException /** * Code to deal with macros, namely with: * * Compilation of macro definitions * * Expansion of macro applications * * Say we have in a class C: * * def foo[T](xs: List[T]): T = macro fooBar * * Then fooBar needs to point to a static method of the following form: * * def fooBar[T: c.WeakTypeTag] // type tag annotation is optional * (c: scala.reflect.macros.Context) * (xs: c.Expr[List[T]]) * : c.Expr[T] = { * ... * } * * Then, if foo is called in qual.foo[Int](elems), where qual: D, * the macro application is expanded to a reflective invocation of fooBar with parameters: * * (simpleMacroContext{ type PrefixType = D; val prefix = qual }) * (Expr(elems)) * (TypeTag(Int)) */ trait Macros extends scala.tools.reflect.FastTrack with Traces { self: Analyzer => import global._ import definitions._ import treeInfo.{isRepeatedParamType => _, _} import MacrosStats._ def globalSettings = global.settings /** `MacroImplBinding` and its companion module are responsible for * serialization/deserialization of macro def -> impl bindings. * * The first officially released version of macros persisted these bindings across compilation runs * using a neat trick. The right-hand side of a macro definition (which contains a reference to a macro impl) * was typechecked and then put verbatim into an annotation on the macro definition. * * This solution is very simple, but unfortunately it's also lacking. If we use it, then * signatures of macro defs become transitively dependent on scala-reflect.jar * (because they refer to macro impls, and macro impls refer to scala.reflect.macros.Context defined in scala-reflect.jar). * More details can be found in comments to https://issues.scala-lang.org/browse/SI-5940. * * Therefore we have to avoid putting macro impls into binding pickles and come up with our own serialization format. * Situation is further complicated by the fact that it's not enough to just pickle macro impl's class name and method name, * because macro expansion needs some knowledge about the shape of macro impl's signature (which we can't pickle). * Hence we precompute necessary stuff (e.g. the layout of type parameters) when compiling macro defs. */ /** Represents all the information that a macro definition needs to know about its implementation. * Includes a path to load the implementation via Java reflection, * and various accounting information necessary when composing an argument list for the reflective invocation. */ private case class MacroImplBinding( // Java class name of the class that contains the macro implementation // is used to load the corresponding object with Java reflection val className: String, // method name of the macro implementation // `className` and `methName` are all we need to reflectively invoke a macro implementation // because macro implementations cannot be overloaded val methName: String, // flattens the macro impl's parameter lists having symbols replaced with metadata // currently metadata is an index of the type parameter corresponding to that type tag (if applicable) // f.ex. for: def impl[T: WeakTypeTag, U: WeakTypeTag, V](c: Context)(x: c.Expr[T]): (U, V) = ??? // `signature` will be equal to List(-1, -1, 0, 1) val signature: List[Int], // type arguments part of a macro impl ref (the right-hand side of a macro definition) // these trees don't refer to a macro impl, so we can pickle them as is val targs: List[Tree]) /** Macro def -> macro impl bindings are serialized into a `macroImpl` annotation * with synthetic content that carries the payload described in `MacroImplBinding`. * * For example, for a pair of macro definition and macro implementation: * def impl(c: scala.reflect.macros.Context): c.Expr[Unit] = c.literalUnit; * def foo: Unit = macro impl * * We will have the following annotation added on the macro definition `foo`: * * @scala.reflect.macros.internal.macroImpl( * `macro`( * "signature" = List(-1), * "methodName" = "impl", * "versionFormat" = 1, * "className" = "Macros$")) */ private object MacroImplBinding { val versionFormat = 1 def pickleAtom(obj: Any): Tree = obj match { case list: List[_] => Apply(Ident(ListModule), list map pickleAtom) case s: String => Literal(Constant(s)) case i: Int => Literal(Constant(i)) } def unpickleAtom(tree: Tree): Any = tree match { case Apply(list @ Ident(_), args) if list.symbol == ListModule => args map unpickleAtom case Literal(Constant(s: String)) => s case Literal(Constant(i: Int)) => i } def pickle(macroImplRef: Tree): Tree = { val MacroImplReference(owner, macroImpl, targs) = macroImplRef val paramss = macroImpl.paramss // todo. refactor when fixing SI-5498 def className: String = { def loop(sym: Symbol): String = sym match { case sym if sym.owner.isPackageClass => val suffix = if (sym.isModuleClass) "$" else "" sym.fullName + suffix case sym => val separator = if (sym.owner.isModuleClass) "" else "$" loop(sym.owner) + separator + sym.javaSimpleName.toString } loop(owner) } def signature: List[Int] = { val transformed = transformTypeTagEvidenceParams(paramss, (param, tparam) => tparam) transformed.flatten map (p => if (p.isTerm) -1 else p.paramPos) } val payload = List[(String, Any)]( "versionFormat" -> versionFormat, "className" -> className, "methodName" -> macroImpl.name.toString, "signature" -> signature ) // the shape of the nucleus is chosen arbitrarily. it doesn't carry any payload. // it's only necessary as a stub `fun` for an Apply node that carries metadata in its `args` // so don't try to find a program element named "macro" that corresponds to the nucleus // I just named it "macro", because it's macro-related, but I could as well name it "foobar" val nucleus = Ident(newTermName("macro")) val wrapped = Apply(nucleus, payload map { case (k, v) => Assign(pickleAtom(k), pickleAtom(v)) }) val pickle = gen.mkTypeApply(wrapped, targs map (_.duplicate)) // assign NoType to all freshly created AST nodes // otherwise pickler will choke on tree.tpe being null // there's another gotcha // if you don't assign a ConstantType to a constant // then pickling will crash new Transformer { override def transform(tree: Tree) = { tree match { case Literal(const @ Constant(x)) if tree.tpe == null => tree setType ConstantType(const) case _ if tree.tpe == null => tree setType NoType case _ => ; } super.transform(tree) } }.transform(pickle) } def unpickle(pickle: Tree): MacroImplBinding = { val (wrapped, targs) = pickle match { case TypeApply(wrapped, targs) => (wrapped, targs) case wrapped => (wrapped, Nil) } val Apply(_, pickledPayload) = wrapped val payload = pickledPayload.map{ case Assign(k, v) => (unpickleAtom(k), unpickleAtom(v)) }.toMap val pickleVersionFormat = payload("versionFormat").asInstanceOf[Int] if (versionFormat != pickleVersionFormat) throw new Error("macro impl binding format mismatch: expected $versionFormat, actual $pickleVersionFormat") val className = payload("className").asInstanceOf[String] val methodName = payload("methodName").asInstanceOf[String] val signature = payload("signature").asInstanceOf[List[Int]] MacroImplBinding(className, methodName, signature, targs) } } private def bindMacroImpl(macroDef: Symbol, macroImplRef: Tree): Unit = { val pickle = MacroImplBinding.pickle(macroImplRef) macroDef withAnnotation AnnotationInfo(MacroImplAnnotation.tpe, List(pickle), Nil) } private def loadMacroImplBinding(macroDef: Symbol): MacroImplBinding = { val Some(AnnotationInfo(_, List(pickle), _)) = macroDef.getAnnotation(MacroImplAnnotation) MacroImplBinding.unpickle(pickle) } /** Transforms parameters lists of a macro impl. * The `transform` function is invoked only for WeakTypeTag evidence parameters. * * The transformer takes two arguments: a value parameter from the parameter list * and a type parameter that is witnesses by the value parameter. * * If the transformer returns a NoSymbol, the value parameter is not included from the result. * If the transformer returns something else, this something else is included in the result instead of the value parameter. * * Despite of being highly esoteric, this function significantly simplifies signature analysis. * For example, it can be used to strip macroImpl.paramss from the evidences (necessary when checking def <-> impl correspondence) * or to streamline creation of the list of macro arguments. */ private def transformTypeTagEvidenceParams(paramss: List[List[Symbol]], transform: (Symbol, Symbol) => Symbol): List[List[Symbol]] = { if (paramss.isEmpty || paramss.last.isEmpty) return paramss // no implicit parameters in the signature => nothing to do if (paramss.head.isEmpty || !(paramss.head.head.tpe <:< MacroContextClass.tpe)) return paramss // no context parameter in the signature => nothing to do def transformTag(param: Symbol): Symbol = param.tpe.dealias match { case TypeRef(SingleType(SingleType(NoPrefix, c), universe), WeakTypeTagClass, targ :: Nil) if c == paramss.head.head && universe == MacroContextUniverse => transform(param, targ.typeSymbol) case _ => param } val transformed = paramss.last map transformTag filter (_ ne NoSymbol) if (transformed.isEmpty) paramss.init else paramss.init :+ transformed } def computeMacroDefTypeFromMacroImpl(macroDdef: DefDef, macroImpl: Symbol): Type = { // Step I. Transform c.Expr[T] to T var runtimeType = macroImpl.tpe.finalResultType.dealias match { case TypeRef(_, ExprClass, runtimeType :: Nil) => runtimeType case _ => AnyTpe // so that macro impls with rhs = ??? don't screw up our inference } // Step II. Transform type parameters of a macro implementation into type arguments in a macro definition's body runtimeType = runtimeType.substituteTypes(macroImpl.typeParams, loadMacroImplBinding(macroDdef.symbol).targs.map(_.tpe)) // Step III. Transform c.prefix.value.XXX to this.XXX and implParam.value.YYY to defParam.YYY def unsigma(tpe: Type): Type = transformTypeTagEvidenceParams(macroImpl.paramss, (param, tparam) => NoSymbol) match { case (implCtxParam :: Nil) :: implParamss => val implToDef = flatMap2(implParamss, macroDdef.vparamss)(map2(_, _)((_, _))).toMap object UnsigmaTypeMap extends TypeMap { def apply(tp: Type): Type = tp match { case TypeRef(pre, sym, args) => val pre1 = pre match { case SingleType(SingleType(SingleType(NoPrefix, c), prefix), value) if c == implCtxParam && prefix == MacroContextPrefix && value == ExprValue => ThisType(macroDdef.symbol.owner) case SingleType(SingleType(NoPrefix, implParam), value) if value == ExprValue => implToDef get implParam map (defParam => SingleType(NoPrefix, defParam.symbol)) getOrElse pre case _ => pre } val args1 = args map mapOver TypeRef(pre1, sym, args1) case _ => mapOver(tp) } } UnsigmaTypeMap(tpe) case _ => tpe } unsigma(runtimeType) } /** A reference macro implementation signature compatible with a given macro definition. * * In the example above for the following macro def: * def foo[T](xs: List[T]): T = macro fooBar * * This function will return: * (c: scala.reflect.macros.Context)(xs: c.Expr[List[T]]): c.Expr[T] * * Note that type tag evidence parameters are not included into the result. * Type tag context bounds for macro impl tparams are optional. * Therefore compatibility checks ignore such parameters, and we don't need to bother about them here. * * @param macroDef The macro definition symbol * @param tparams The type parameters of the macro definition * @param vparamss The value parameters of the macro definition * @param retTpe The return type of the macro definition */ private def macroImplSig(macroDef: Symbol, tparams: List[TypeDef], vparamss: List[List[ValDef]], retTpe: Type): (List[List[Symbol]], Type) = { // had to move method's body to an object because of the recursive dependencies between sigma and param object SigGenerator { def sigma(tpe: Type): Type = { class SigmaTypeMap extends TypeMap { def apply(tp: Type): Type = tp match { case TypeRef(pre, sym, args) => val pre1 = pre match { case ThisType(sym) if sym == macroDef.owner => SingleType(SingleType(SingleType(NoPrefix, ctxParam), MacroContextPrefix), ExprValue) case SingleType(NoPrefix, sym) => mfind(vparamss)(_.symbol == sym) match { case Some(macroDefParam) => SingleType(SingleType(NoPrefix, param(macroDefParam)), ExprValue) case _ => pre } case _ => pre } TypeRef(pre1, sym, args map mapOver) case _ => mapOver(tp) } } new SigmaTypeMap() apply tpe } def makeParam(name: Name, pos: Position, tpe: Type, flags: Long = 0L) = macroDef.newValueParameter(name, pos, flags) setInfo tpe val ctxParam = makeParam(nme.macroContext, macroDef.pos, MacroContextClass.tpe, SYNTHETIC) def implType(isType: Boolean, origTpe: Type): Type = if (isRepeatedParamType(origTpe)) appliedType( RepeatedParamClass.typeConstructor, List(implType(isType, sigma(origTpe.typeArgs.head)))) else { val tsym = getMember(MacroContextClass, if (isType) tpnme.WeakTypeTag else tpnme.Expr) typeRef(singleType(NoPrefix, ctxParam), tsym, List(sigma(origTpe))) } val paramCache = scala.collection.mutable.Map[Symbol, Symbol]() def param(tree: Tree): Symbol = paramCache.getOrElseUpdate(tree.symbol, { val sym = tree.symbol val sigParam = makeParam(sym.name, sym.pos, implType(sym.isType, sym.tpe)) if (sym.isSynthetic) sigParam.flags |= SYNTHETIC sigParam }) val paramss = List(ctxParam) :: mmap(vparamss)(param) val implRetTpe = typeRef(singleType(NoPrefix, ctxParam), getMember(MacroContextClass, tpnme.Expr), List(sigma(retTpe))) } import SigGenerator._ macroTraceVerbose("generating macroImplSigs for: ")(macroDef) macroTraceVerbose("tparams are: ")(tparams) macroTraceVerbose("vparamss are: ")(vparamss) macroTraceVerbose("retTpe is: ")(retTpe) macroTraceVerbose("macroImplSig is: ")((paramss, implRetTpe)) } /** Verifies that the body of a macro def typechecks to a reference to a static public non-overloaded method, * and that that method is signature-wise compatible with the given macro definition. * * @return Typechecked rhs of the given macro definition if everything is okay. * EmptyTree if an error occurs. */ def typedMacroBody(typer: Typer, macroDdef: DefDef): Tree = try new MacroTyper(typer, macroDdef).typed catch { case MacroBodyTypecheckException => EmptyTree } class MacroTyper(val typer: Typer, val macroDdef: DefDef) extends MacroErrors { // Phase I: sanity checks val macroDef = macroDdef.symbol macroLogVerbose("typechecking macro def %s at %s".format(macroDef, macroDdef.pos)) assert(macroDef.isTermMacro, macroDdef) if (fastTrack contains macroDef) MacroDefIsFastTrack() if (!typer.checkFeature(macroDdef.pos, MacrosFeature, immediate = true)) MacroFeatureNotEnabled() // we use typed1 instead of typed, because otherwise adapt is going to mess us up // if adapt sees ., it will want to perform eta-expansion and will fail // unfortunately, this means that we have to manually trigger macro expansion // because it's adapt which is responsible for automatic expansion during typechecking def typecheckRhs(rhs: Tree): Tree = { try { // interestingly enough, just checking isErroneous doesn't cut it // e.g. a "type arguments [U] do not conform to method foo's type parameter bounds" error // doesn't manifest itself as an error in the resulting tree val prevNumErrors = reporter.ERROR.count var rhs1 = typer.typed1(rhs, EXPRmode, WildcardType) def rhsNeedsMacroExpansion = rhs1.symbol != null && rhs1.symbol.isTermMacro && !rhs1.symbol.isErroneous while (rhsNeedsMacroExpansion) { rhs1 = macroExpand1(typer, rhs1) match { case Success(expanded) => try { val typechecked = typer.typed1(expanded, EXPRmode, WildcardType) macroLogVerbose("typechecked1:%n%s%n%s".format(typechecked, showRaw(typechecked))) typechecked } finally { popMacroContext() } case Fallback(fallback) => typer.typed1(fallback, EXPRmode, WildcardType) case Other(result) => result } } val typecheckedWithErrors = (rhs1 exists (_.isErroneous)) || reporter.ERROR.count != prevNumErrors if (typecheckedWithErrors) MacroDefUntypeableBodyError() rhs1 } catch { case ex: TypeError => typer.reportTypeError(context, rhs.pos, ex) MacroDefUntypeableBodyError() } } // Phase II: typecheck the right-hand side of the macro def val typed = typecheckRhs(macroDdef.rhs) typed match { case MacroImplReference(owner, meth, targs) => if (!meth.isMethod) MacroDefInvalidBodyError() if (!meth.isPublic) MacroImplNotPublicError() if (meth.isOverloaded) MacroImplOverloadedError() if (!owner.isStaticOwner && !owner.moduleClass.isStaticOwner) MacroImplNotStaticError() if (meth.typeParams.length != targs.length) MacroImplWrongNumberOfTypeArgumentsError(typed) bindMacroImpl(macroDef, typed) case _ => MacroDefInvalidBodyError() } // Phase III: check compatibility between the macro def and its macro impl // this check ignores type tag evidence parameters, because type tag context bounds are optional // aXXX (e.g. aparamss) => characteristics of the macro impl ("a" stands for "actual") // rXXX (e.g. rparamss) => characteristics of a reference macro impl signature synthesized from the macro def ("r" stands for "reference") val macroImpl = typed.symbol val aparamss = transformTypeTagEvidenceParams(macroImpl.paramss, (param, tparam) => NoSymbol) val aret = macroImpl.tpe.finalResultType val macroDefRet = if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe else computeMacroDefTypeFromMacroImpl(macroDdef, macroImpl) val (rparamss, rret) = macroImplSig(macroDef, macroDdef.tparams, macroDdef.vparamss, macroDefRet) val implicitParams = aparamss.flatten filter (_.isImplicit) if (implicitParams.nonEmpty) MacroImplNonTagImplicitParameters(implicitParams) if (aparamss.length != rparamss.length) MacroImplParamssMismatchError() val atparams = macroImpl.typeParams val atvars = atparams map freshVar def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars) try { map2(aparamss, rparamss)((aparams, rparams) => { if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams) if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams) }) // cannot fuse these loops because if aparamss.flatten != rparamss.flatten // then `atpeToRtpe` is going to fail with an unsound substitution map2(aparamss.flatten, rparamss.flatten)((aparam, rparam) => { if (aparam.name != rparam.name && !rparam.isSynthetic) MacroImplParamNameMismatchError(aparam, rparam) if (isRepeated(aparam) ^ isRepeated(rparam)) MacroImplVarargMismatchError(aparam, rparam) val aparamtpe = aparam.tpe.dealias match { case RefinedType(List(tpe), Scope(sym)) if tpe =:= MacroContextClass.tpe && sym.allOverriddenSymbols.contains(MacroContextPrefixType) => tpe case tpe => tpe } checkMacroImplParamTypeMismatch(atpeToRtpe(aparamtpe), rparam) }) checkMacroImplResultTypeMismatch(atpeToRtpe(aret), rret) val maxLubDepth = lubDepth(aparamss.flatten map (_.tpe)) max lubDepth(rparamss.flatten map (_.tpe)) val atargs = solvedTypes(atvars, atparams, atparams map varianceInType(aret), upper = false, depth = maxLubDepth) val boundsOk = typer.silent(_.infer.checkBounds(macroDdef, NoPrefix, NoSymbol, atparams, atargs, "")) boundsOk match { case SilentResultValue(true) => // do nothing, success case SilentResultValue(false) | SilentTypeError(_) => MacroImplTargMismatchError(atargs, atparams) } } catch { case ex: NoInstance => MacroImplTparamInstantiationError(atparams, ex) } } /** Macro classloader that is used to resolve and run macro implementations. * Loads classes from from -cp (aka the library classpath). * Is also capable of detecting REPL and reusing its classloader. */ lazy val macroClassloader: ClassLoader = { if (global.forMSIL) throw new UnsupportedOperationException("Scala reflection not available on this platform") val classpath = global.classPath.asURLs macroLogVerbose("macro classloader: initializing from -cp: %s".format(classpath)) val loader = ScalaClassLoader.fromURLs(classpath, self.getClass.getClassLoader) // a heuristic to detect the REPL if (global.settings.exposeEmptyPackage.value) { macroLogVerbose("macro classloader: initializing from a REPL classloader".format(global.classPath.asURLs)) import scala.tools.nsc.interpreter._ val virtualDirectory = global.settings.outputDirs.getSingleOutput.get new AbstractFileClassLoader(virtualDirectory, loader) {} } else { loader } } /** Produces a function that can be used to invoke macro implementation for a given macro definition: * 1) Looks up macro implementation symbol in this universe. * 2) Loads its enclosing class from the macro classloader. * 3) Loads the companion of that enclosing class from the macro classloader. * 4) Resolves macro implementation within the loaded companion. * * @return Requested runtime if macro implementation can be loaded successfully from either of the mirrors, * `null` otherwise. */ type MacroRuntime = MacroArgs => Any private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, MacroRuntime] private def macroRuntime(macroDef: Symbol): MacroRuntime = { macroTraceVerbose("looking for macro implementation: ")(macroDef) if (fastTrack contains macroDef) { macroLogVerbose("macro expansion is serviced by a fast track") fastTrack(macroDef) } else { macroRuntimesCache.getOrElseUpdate(macroDef, { val binding = loadMacroImplBinding(macroDef) val className = binding.className val methName = binding.methName macroLogVerbose(s"resolved implementation as $className.$methName") // I don't use Scala reflection here, because it seems to interfere with JIT magic // whenever you instantiate a mirror (and not do anything with in, just instantiate), performance drops by 15-20% // I'm not sure what's the reason - for me it's pure voodoo // upd. my latest experiments show that everything's okay // it seems that in 2.10.1 we can easily switch to Scala reflection try { macroTraceVerbose("loading implementation class: ")(className) macroTraceVerbose("classloader is: ")(ReflectionUtils.show(macroClassloader)) val implObj = ReflectionUtils.staticSingletonInstance(macroClassloader, className) // relies on the fact that macro impls cannot be overloaded // so every methName can resolve to at maximum one method val implMeths = implObj.getClass.getDeclaredMethods.find(_.getName == methName) val implMeth = implMeths getOrElse { throw new NoSuchMethodException(s"$className.$methName") } macroLogVerbose("successfully loaded macro impl as (%s, %s)".format(implObj, implMeth)) args => implMeth.invoke(implObj, ((args.c +: args.others) map (_.asInstanceOf[AnyRef])): _*) } catch { case ex: Exception => macroTraceVerbose(s"macro runtime failed to load: ")(ex.toString) macroDef setFlag IS_ERROR null } }) } } private def macroContext(typer: Typer, prefixTree: Tree, expandeeTree: Tree): MacroContext = new { val universe: self.global.type = self.global val callsiteTyper: universe.analyzer.Typer = typer.asInstanceOf[global.analyzer.Typer] val expandee = expandeeTree } with UnaffiliatedMacroContext { val prefix = Expr[Nothing](prefixTree)(TypeTag.Nothing) override def toString = "MacroContext(%s@%s +%d)".format(expandee.symbol.name, expandee.pos, enclosingMacros.length - 1 /* exclude myself */) } /** Calculate the arguments to pass to a macro implementation when expanding the provided tree. */ case class MacroArgs(c: MacroContext, others: List[Any]) private def macroArgs(typer: Typer, expandee: Tree): MacroArgs = { val macroDef = expandee.symbol val prefixTree = expandee.collect{ case Select(qual, name) => qual }.headOption.getOrElse(EmptyTree) val context = expandee.attachments.get[MacroRuntimeAttachment].flatMap(_.macroContext).getOrElse(macroContext(typer, prefixTree, expandee)) var typeArgs = List[Tree]() val exprArgs = ListBuffer[List[Expr[_]]]() def collectMacroArgs(tree: Tree): Unit = tree match { case Apply(fn, args) => // todo. infer precise typetag for this Expr, namely the declared type of the corresponding macro impl argument exprArgs.prepend(args map (arg => context.Expr[Nothing](arg)(TypeTag.Nothing))) collectMacroArgs(fn) case TypeApply(fn, args) => typeArgs = args collectMacroArgs(fn) case _ => } collectMacroArgs(expandee) val argcDoesntMatch = macroDef.paramss.length != exprArgs.length val nullaryArgsEmptyParams = exprArgs.isEmpty && macroDef.paramss == ListOfNil if (argcDoesntMatch && !nullaryArgsEmptyParams) { typer.TyperErrorGen.MacroPartialApplicationError(expandee) } val argss: List[List[Any]] = exprArgs.toList macroTraceVerbose("context: ")(context) macroTraceVerbose("argss: ")(argss) val preparedArgss: List[List[Any]] = if (fastTrack contains macroDef) { if (fastTrack(macroDef) validate context) argss else typer.TyperErrorGen.MacroPartialApplicationError(expandee) } else { // if paramss have typetag context bounds, add an arglist to argss if necessary and instantiate the corresponding evidences // consider the following example: // // class D[T] { // class C[U] { // def foo[V] = macro Impls.foo[T, U, V] // } // } // // val outer1 = new D[Int] // val outer2 = new outer1.C[String] // outer2.foo[Boolean] // // then T and U need to be inferred from the lexical scope of the call using `asSeenFrom` // whereas V won't be resolved by asSeenFrom and need to be loaded directly from `expandee` which needs to contain a TypeApply node // also, macro implementation reference may contain a regular type as a type argument, then we pass it verbatim val binding = loadMacroImplBinding(macroDef) macroTraceVerbose("binding: ")(binding) val tags = binding.signature filter (_ != -1) map (paramPos => { val targ = binding.targs(paramPos).tpe.typeSymbol val tpe = if (targ.isTypeParameterOrSkolem) { if (targ.owner == macroDef) { // doesn't work when macro def is compiled separately from its usages // then targ is not a skolem and isn't equal to any of macroDef.typeParams // val argPos = targ.deSkolemize.paramPos val argPos = macroDef.typeParams.indexWhere(_.name == targ.name) typeArgs(argPos).tpe } else targ.tpe.asSeenFrom( if (prefixTree == EmptyTree) macroDef.owner.tpe else prefixTree.tpe, macroDef.owner) } else targ.tpe context.WeakTypeTag(tpe) }) macroTraceVerbose("tags: ")(tags) // transforms argss taking into account varargness of paramss // note that typetag context bounds are only declared on macroImpls // so this optional arglist might not match macroDef's paramlist // nb! varargs can apply to any parameter section, not necessarily to the last one mapWithIndex(argss :+ tags)((as, i) => { val mapsToParamss = macroDef.paramss.indices contains i if (mapsToParamss) { val ps = macroDef.paramss(i) if (isVarArgsList(ps)) { val (normal, varargs) = as splitAt (ps.length - 1) normal :+ varargs // pack all varargs into a single List argument } else as } else as }) } macroTraceVerbose("preparedArgss: ")(preparedArgss) MacroArgs(context, preparedArgss.flatten) } /** Keeps track of macros in-flight. * See more informations in comments to `openMacros` in `scala.reflect.macros.Context`. */ private var _openMacros = List[MacroContext]() def openMacros = _openMacros private def pushMacroContext(c: MacroContext) = _openMacros ::= c private def popMacroContext() = _openMacros = _openMacros.tail def enclosingMacroPosition = openMacros map (_.macroApplication.pos) find (_ ne NoPosition) getOrElse NoPosition private sealed abstract class MacroExpansionResult private case class Success(expanded: Tree) extends MacroExpansionResult private case class Fallback(fallback: Tree) extends MacroExpansionResult { currentRun.seenMacroExpansionsFallingBack = true } private case class Other(result: Tree) extends MacroExpansionResult private def Delay(expanded: Tree) = Other(expanded) private def Skip(expanded: Tree) = Other(expanded) private def Cancel(expandee: Tree) = Other(expandee) private def Failure(expandee: Tree) = Other(expandee) /** Performs macro expansion: * 1) Checks whether the expansion needs to be delayed (see `mustDelayMacroExpansion`) * 2) Loads macro implementation using `macroMirror` * 3) Synthesizes invocation arguments for the macro implementation * 4) Checks that the result is a tree bound to this universe * 5) Typechecks the result against the return type of the macro definition * * If -Ymacro-debug-lite is enabled, you will get basic notifications about macro expansion * along with macro expansions logged in the form that can be copy/pasted verbatim into REPL. * * If -Ymacro-debug-verbose is enabled, you will get detailed log of how exactly this function * performs class loading and method resolution in order to load the macro implementation. * The log will also include other non-trivial steps of macro expansion. * * @return * the expansion result if the expansion has been successful, * the fallback method invocation if the expansion has been unsuccessful, but there is a fallback, * the expandee unchanged if the expansion has been delayed, * the expandee fully expanded if the expansion has been delayed before and has been expanded now, * the expandee with an error marker set if the expansion has been cancelled due malformed arguments or implementation * the expandee with an error marker set if there has been an error */ def macroExpand(typer: Typer, expandee: Tree, mode: Int = EXPRmode, pt: Type = WildcardType): Tree = { if (settings.Ymacronoexpand.value) return expandee // SI-6812 val start = if (Statistics.canEnable) Statistics.startTimer(macroExpandNanos) else null if (Statistics.canEnable) Statistics.incCounter(macroExpandCount) try { macroExpand1(typer, expandee) match { case Success(expanded) => try { def typecheck(phase: String, tree: Tree, pt: Type): Tree = { if (tree.isErroneous) return tree macroLogVerbose(s"typechecking against $phase $pt: $expanded") val numErrors = reporter.ERROR.count def hasNewErrors = reporter.ERROR.count > numErrors val result = typer.context.withImplicitsEnabled(typer.typed(tree, EXPRmode, pt)) macroTraceVerbose(s"""${if (hasNewErrors) "failed to typecheck" else "successfully typechecked"} against $phase $pt:\n$result\n""")(result) } var expectedTpe = expandee.tpe if (isNullaryInvocation(expandee)) expectedTpe = expectedTpe.finalResultType var typechecked = typecheck("macro def return type", expanded, expectedTpe) typechecked = typecheck("expected type", typechecked, pt) typechecked } finally { popMacroContext() } case Fallback(fallback) => typer.context.withImplicitsEnabled(typer.typed(fallback, EXPRmode, pt)) case Other(result) => result } } finally { if (Statistics.canEnable) Statistics.stopTimer(macroExpandNanos, start) } } /** Does the same as `macroExpand`, but without typechecking the expansion * Meant for internal use within the macro infrastructure, don't use it elsewhere. */ private def macroExpand1(typer: Typer, expandee: Tree): MacroExpansionResult = // verbose printing might cause recursive macro expansions, so I'm shutting it down here withInfoLevel(nodePrinters.InfoLevel.Quiet) { if (expandee.symbol.isErroneous || (expandee exists (_.isErroneous))) { val reason = if (expandee.symbol.isErroneous) "not found or incompatible macro implementation" else "erroneous arguments" macroTraceVerbose("cancelled macro expansion because of %s: ".format(reason))(expandee) return Cancel(typer.infer.setError(expandee)) } try { val runtime = macroRuntime(expandee.symbol) if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) else macroExpandWithoutRuntime(typer, expandee) } catch { case typer.TyperErrorGen.MacroExpansionException => Failure(expandee) } } /** Expands a macro when a runtime (i.e. the macro implementation) can be successfully loaded * Meant for internal use within the macro infrastructure, don't use it elsewhere. */ private def macroExpandWithRuntime(typer: Typer, expandee: Tree, runtime: MacroRuntime): MacroExpansionResult = { val wasDelayed = isDelayed(expandee) val undetparams = calculateUndetparams(expandee) val nowDelayed = !typer.context.macrosEnabled || undetparams.nonEmpty (wasDelayed, nowDelayed) match { case (true, true) => Delay(expandee) case (true, false) => Skip(macroExpandAll(typer, expandee)) case (false, true) => macroLogLite("macro expansion is delayed: %s".format(expandee)) delayed += expandee -> undetparams expandee updateAttachment MacroRuntimeAttachment(delayed = true, typerContext = typer.context, macroContext = Some(macroArgs(typer, expandee).c)) Delay(expandee) case (false, false) => import typer.TyperErrorGen._ macroLogLite("performing macro expansion %s at %s".format(expandee, expandee.pos)) val args = macroArgs(typer, expandee) try { val numErrors = reporter.ERROR.count def hasNewErrors = reporter.ERROR.count > numErrors val expanded = { pushMacroContext(args.c); runtime(args) } if (hasNewErrors) MacroGeneratedTypeError(expandee) expanded match { case expanded: Expr[_] => macroLogVerbose("original:") macroLogLite("" + expanded.tree + "\n" + showRaw(expanded.tree)) val freeSyms = expanded.tree.freeTerms ++ expanded.tree.freeTypes freeSyms foreach (sym => MacroFreeSymbolError(expandee, sym)) Success(atPos(enclosingMacroPosition.focus)(expanded.tree updateAttachment MacroExpansionAttachment(expandee))) case _ => MacroExpansionIsNotExprError(expandee, expanded) } } catch { case ex: Throwable => popMacroContext() val realex = ReflectionUtils.unwrapThrowable(ex) realex match { case ex: AbortMacroException => MacroGeneratedAbort(expandee, ex) case ex: ControlThrowable => throw ex case ex: TypeError => MacroGeneratedTypeError(expandee, ex) case _ => MacroGeneratedException(expandee, realex) } } finally { expandee.removeAttachment[MacroRuntimeAttachment] } } } /** Expands a macro when a runtime (i.e. the macro implementation) cannot be loaded * Meant for internal use within the macro infrastructure, don't use it elsewhere. */ private def macroExpandWithoutRuntime(typer: Typer, expandee: Tree): MacroExpansionResult = { import typer.TyperErrorGen._ val fallbackSym = expandee.symbol.nextOverriddenSymbol orElse MacroImplementationNotFoundError(expandee) macroTraceLite("falling back to: ")(fallbackSym) def mkFallbackTree(tree: Tree): Tree = { tree match { case Select(qual, name) => Select(qual, name) setPos tree.pos setSymbol fallbackSym case Apply(fn, args) => Apply(mkFallbackTree(fn), args) setPos tree.pos case TypeApply(fn, args) => TypeApply(mkFallbackTree(fn), args) setPos tree.pos } } Fallback(mkFallbackTree(expandee)) } /** Without any restrictions on macro expansion, macro applications will expand at will, * and when type inference is involved, expansions will end up using yet uninferred type params. * * For some macros this might be ok (thanks to TreeTypeSubstituter that replaces * the occurrences of undetparams with their inferred values), but in general case this won't work. * E.g. for reification simple substitution is not enough - we actually need to re-reify inferred types. * * Luckily, there exists a very simple way to fix the problem: delay macro expansion until everything is inferred. * Here are the exact rules. Macro application gets delayed if any of its subtrees contain: * 1) type vars (tpe.isInstanceOf[TypeVar]) // [Eugene] this check is disabled right now, because TypeVars seem to be created from undetparams anyways * 2) undetparams (sym.isTypeParameter && !sym.isSkolem) */ var hasPendingMacroExpansions = false private val delayed = perRunCaches.newWeakMap[Tree, scala.collection.mutable.Set[Int]] private def isDelayed(expandee: Tree) = delayed contains expandee private def calculateUndetparams(expandee: Tree): scala.collection.mutable.Set[Int] = delayed.get(expandee).getOrElse { val calculated = scala.collection.mutable.Set[Symbol]() expandee foreach (sub => { def traverse(sym: Symbol) = if (sym != null && (undetparams contains sym.id)) calculated += sym if (sub.symbol != null) traverse(sub.symbol) if (sub.tpe != null) sub.tpe foreach (sub => traverse(sub.typeSymbol)) }) macroLogVerbose("calculateUndetparams: %s".format(calculated)) calculated map (_.id) } private val undetparams = perRunCaches.newSet[Int] def notifyUndetparamsAdded(newUndets: List[Symbol]): Unit = { undetparams ++= newUndets map (_.id) if (macroDebugVerbose) newUndets foreach (sym => println("undetParam added: %s".format(sym))) } def notifyUndetparamsInferred(undetNoMore: List[Symbol], inferreds: List[Type]): Unit = { undetparams --= undetNoMore map (_.id) if (macroDebugVerbose) (undetNoMore zip inferreds) foreach { case (sym, tpe) => println("undetParam inferred: %s as %s".format(sym, tpe))} if (!delayed.isEmpty) delayed.toList foreach { case (expandee, undetparams) if !undetparams.isEmpty => undetparams --= undetNoMore map (_.id) if (undetparams.isEmpty) { hasPendingMacroExpansions = true macroTraceVerbose("macro expansion is pending: ")(expandee) } case _ => // do nothing } } /** Performs macro expansion on all subtrees of a given tree. * Innermost macros are expanded first, outermost macros are expanded last. * See the documentation for `macroExpand` for more information. */ def macroExpandAll(typer: Typer, expandee: Tree): Tree = new Transformer { override def transform(tree: Tree) = super.transform(tree match { // todo. expansion should work from the inside out case tree if (delayed contains tree) && calculateUndetparams(tree).isEmpty => val context = tree.attachments.get[MacroRuntimeAttachment].get.typerContext delayed -= tree context.implicitsEnabled = typer.context.implicitsEnabled context.enrichmentEnabled = typer.context.enrichmentEnabled context.macrosEnabled = typer.context.macrosEnabled macroExpand(newTyper(context), tree, EXPRmode, WildcardType) case _ => tree }) }.transform(expandee) } object MacrosStats { import scala.reflect.internal.TypesStats.typerNanos val macroExpandCount = Statistics.newCounter ("#macro expansions", "typer") val macroExpandNanos = Statistics.newSubTimer("time spent in macroExpand", typerNanos) }