diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/typechecker/Macros.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Macros.scala | 666 |
1 files changed, 243 insertions, 423 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 5ac37251ee..6c4d1e20aa 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -1,6 +1,7 @@ package scala.tools.nsc package typechecker +import java.lang.Math.min import symtab.Flags._ import scala.tools.nsc.util._ import scala.reflect.runtime.ReflectionUtils @@ -9,7 +10,12 @@ import scala.reflect.ClassTag import scala.reflect.internal.util.Statistics import scala.reflect.macros.util._ import scala.util.control.ControlThrowable -import scala.reflect.macros.runtime.AbortMacroException +import scala.reflect.macros.runtime.{AbortMacroException, MacroRuntimes} +import scala.reflect.runtime.{universe => ru} +import scala.reflect.macros.compiler.DefaultMacroCompiler +import scala.tools.reflect.FastTrack +import scala.runtime.ScalaRunTime +import Fingerprint._ /** * Code to deal with macros, namely with: @@ -36,7 +42,7 @@ import scala.reflect.macros.runtime.AbortMacroException * (Expr(elems)) * (TypeTag(Int)) */ -trait Macros extends scala.tools.reflect.FastTrack with Traces { +trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { self: Analyzer => import global._ @@ -74,7 +80,9 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * 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( + case class MacroImplBinding( + // Is this macro impl a bundle (a trait extending Macro) or a vanilla def? + val isBundle: Boolean, // Java class name of the class that contains the macro implementation // is used to load the corresponding object with Java reflection className: String, @@ -82,14 +90,21 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // `className` and `methName` are all we need to reflectively invoke a macro implementation // because macro implementations cannot be overloaded 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) - signature: List[Int], + // flattens the macro impl's parameter lists having symbols replaced with their fingerprints + // currently fingerprints are calculated solely from types of the symbols: + // * c.Expr[T] => IMPLPARAM_EXPR + // * c.WeakTypeTag[T] => index of the type parameter corresponding to that type tag + // * everything else (e.g. scala.reflect.macros.Context) => IMPLPARAM_OTHER + // f.ex. for: def impl[T: WeakTypeTag, U, V: WeakTypeTag](c: Context)(x: c.Expr[T], y: c.Tree): (U, V) = ??? + // `signature` will be equal to List(List(Other), List(Lifted, Other), List(Tagged(0), Tagged(2))) + signature: List[List[Fingerprint]], // 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 - targs: List[Tree]) + targs: List[Tree]) { + + // Was this binding derived from a `def ... = macro ???` definition? + def is_??? = className == Predef_???.owner.javaClassName && methName == Predef_???.name.encoded + } /** Macro def -> macro impl bindings are serialized into a `macroImpl` annotation * with synthetic content that carries the payload described in `MacroImplBinding`. @@ -102,31 +117,35 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * * @scala.reflect.macros.internal.macroImpl( * `macro`( - * "signature" = List(-1), + * "isBundle" = false, + * "signature" = List(Other), * "methodName" = "impl", - * "versionFormat" = 1, + * "versionFormat" = <current version format>, * "className" = "Macros$")) */ - private object MacroImplBinding { - val versionFormat = 1 + object MacroImplBinding { + val versionFormat = 4.0 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)) + case d: Double => Literal(Constant(d)) + case b: Boolean => Literal(Constant(b)) + case f: Fingerprint => Literal(Constant(f.value)) } 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 + case Literal(Constant(d: Double)) => d + case Literal(Constant(b: Boolean)) => b + case Literal(Constant(i: Int)) => new Fingerprint(i) } def pickle(macroImplRef: Tree): Tree = { - val MacroImplReference(owner, macroImpl, targs) = macroImplRef - val paramss = macroImpl.paramss + val MacroImplReference(isBundle, owner, macroImpl, targs) = macroImplRef // todo. refactor when fixing SI-5498 def className: String = { @@ -142,13 +161,20 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { loop(owner) } - def signature: List[Int] = { - val transformed = transformTypeTagEvidenceParams(paramss, (param, tparam) => tparam) - transformed.flatten map (p => if (p.isTerm) -1 else p.paramPos) + def signature: List[List[Fingerprint]] = { + def fingerprint(tpe: Type): Fingerprint = tpe.dealiasWiden match { + case TypeRef(_, RepeatedParamClass, underlying :: Nil) => fingerprint(underlying) + case ExprClassOf(_) => Lifted + case _ => Other + } + + val transformed = transformTypeTagEvidenceParams(macroImplRef, (param, tparam) => tparam) + mmap(transformed)(p => if (p.isTerm) fingerprint(p.info) else Tagged(p.paramPos)) } val payload = List[(String, Any)]( "versionFormat" -> versionFormat, + "isBundle" -> isBundle, "className" -> className, "methodName" -> macroImpl.name.toString, "signature" -> signature @@ -188,362 +214,119 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { 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(s"macro impl binding format mismatch: expected $versionFormat, actual $pickleVersionFormat") + def fail(msg: String) = abort(s"bad macro impl binding: $msg") + def unpickle[T](field: String, clazz: Class[T]): T = { + def failField(msg: String) = fail(s"$field $msg") + if (!payload.contains(field)) failField("is supposed to be there") + val raw: Any = payload(field) + if (raw == null) failField(s"is not supposed to be null") + val expected = ScalaRunTime.box(clazz) + val actual = raw.getClass + if (!expected.isAssignableFrom(actual)) failField(s"has wrong type: expected $expected, actual $actual") + raw.asInstanceOf[T] + } + + val pickleVersionFormat = unpickle("versionFormat", classOf[Double]) + if (versionFormat != pickleVersionFormat) fail(s"expected version format $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) + val isBundle = unpickle("isBundle", classOf[Boolean]) + val className = unpickle("className", classOf[String]) + val methodName = unpickle("methodName", classOf[String]) + val signature = unpickle("signature", classOf[List[List[Fingerprint]]]) + MacroImplBinding(isBundle, className, methodName, signature, targs) } } - private def bindMacroImpl(macroDef: Symbol, macroImplRef: Tree): Unit = { + 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 = { + 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 + def computeMacroDefTypeFromMacroImplRef(macroDdef: DefDef, macroImplRef: Tree): Type = { + macroImplRef match { + case MacroImplReference(_, _, macroImpl, targs) => + // Step I. Transform c.Expr[T] to T and everything else to Any + var runtimeType = decreaseMetalevel(macroImpl.info.finalResultType) + + // Step II. Transform type parameters of a macro implementation into type arguments in a macro definition's body + runtimeType = runtimeType.substituteTypes(macroImpl.typeParams, 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(macroImplRef, (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 _ => - pre + mapOver(tp) } - val args1 = args map mapOver - TypeRef(pre1, sym, args1) - case _ => - mapOver(tp) - } - } - - UnsigmaTypeMap(tpe) - case _ => - tpe - } + } - unsigma(runtimeType) - } + UnsigmaTypeMap(tpe) + case _ => + tpe + } - /** 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 WeakTagClass = getMember(MacroContextClass, tpnme.WeakTypeTag) - def ExprClass = getMember(MacroContextClass, tpnme.Expr) - val cache = scala.collection.mutable.Map[Symbol, Symbol]() - val ctxParam = makeParam(nme.macroContext, macroDef.pos, MacroContextClass.tpe, SYNTHETIC) - val paramss = List(ctxParam) :: mmap(vparamss)(param) - val implReturnType = typeRef(singleType(NoPrefix, ctxParam), ExprClass, List(sigma(retTpe))) - - object SigmaTypeMap extends TypeMap { - def mapPrefix(pre: Type) = pre match { - case ThisType(sym) if sym == macroDef.owner => - singleType(singleType(singleType(NoPrefix, ctxParam), MacroContextPrefix), ExprValue) - case SingleType(NoPrefix, sym) => - mfind(vparamss)(_.symbol == sym).fold(pre)(p => singleType(singleType(NoPrefix, param(p)), ExprValue)) - case _ => - mapOver(pre) - } - def apply(tp: Type): Type = tp match { - case TypeRef(pre, sym, args) => - val pre1 = mapPrefix(pre) - val args1 = mapOverArgs(args, sym.typeParams) - if ((pre eq pre1) && (args eq args1)) tp - else typeRef(pre1, sym, args1) - case _ => - mapOver(tp) - } - } - def sigma(tpe: Type): Type = SigmaTypeMap(tpe) - - def makeParam(name: Name, pos: Position, tpe: Type, flags: Long) = - macroDef.newValueParameter(name.toTermName, pos, flags) setInfo tpe - def implType(isType: Boolean, origTpe: Type): Type = { - def tsym = if (isType) WeakTagClass else ExprClass - def targ = origTpe.typeArgs.headOption getOrElse NoType - - if (isRepeatedParamType(origTpe)) - scalaRepeatedType(implType(isType, sigma(targ))) - else - typeRef(singleType(NoPrefix, ctxParam), tsym, List(sigma(origTpe))) - } - def param(tree: Tree): Symbol = ( - cache.getOrElseUpdate(tree.symbol, { - val sym = tree.symbol - makeParam(sym.name, sym.pos, implType(sym.isType, sym.tpe), sym.flags) - }) - ) + unsigma(runtimeType) + case _ => + ErrorType } - - import SigGenerator._ - macroLogVerbose(sm""" - |generating macroImplSigs for: $macroDef - |tparams are: $tparams - |vparamss are: $vparamss - |retTpe is: $retTpe - |macroImplSig is: $paramss, $implReturnType - """.trim) - (paramss, implReturnType) } - /** Verifies that the body of a macro def typechecks to a reference to a static public non-overloaded method, + /** Verifies that the body of a macro def typechecks to a reference to a static public non-overloaded method or a top-level macro bundle, * 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. + * @return Macro impl reference for 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 { - private def typed1Expr(tree: Tree) = typer.typed1(tree, EXPRmode, WildcardType) - - // Phase I: sanity checks + def typedMacroBody(typer: Typer, macroDdef: DefDef): Tree = { val macroDef = macroDdef.symbol - macroLogVerbose("typechecking macro def %s at %s".format(macroDef, macroDdef.pos)) assert(macroDef.isMacro, 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 <qualifier>.<method>, 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 = typed1Expr(rhs) - def rhsNeedsMacroExpansion = rhs1.symbol != null && rhs1.symbol.isTermMacro && !rhs1.symbol.isErroneous - while (rhsNeedsMacroExpansion) { - rhs1 = macroExpand1(typer, rhs1) match { - case Success(expanded) => - try { - val typechecked = typed1Expr(expanded) - macroLogVerbose("typechecked1:%n%s%n%s".format(typechecked, showRaw(typechecked))) - typechecked - } finally { - popMacroContext() - } - case Fallback(fallback) => - typed1Expr(fallback) - case Delayed(delayed) => - typer.instantiate(delayed, EXPRmode, WildcardType) - case Skipped(skipped) => - skipped - case Failure(failure) => - failure - } - } - 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(_, meth, _) if meth == Predef_??? => - bindMacroImpl(macroDef, typed) - MacroDefIsQmarkQmarkQmark() - 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 = findMacroClassLoader() - /** 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 = { - macroLogVerbose(s"looking for macro implementation: $macroDef") + macroLogVerbose("typechecking macro def %s at %s".format(macroDef, macroDdef.pos)) if (fastTrack contains macroDef) { - macroLogVerbose("macro expansion is serviced by a fast track") - fastTrack(macroDef) + macroLogVerbose("typecheck terminated unexpectedly: macro is fast track") + assert(!macroDdef.tpt.isEmpty, "fast track macros must provide result type") + EmptyTree } else { - macroRuntimesCache.getOrElseUpdate(macroDef, { - val binding = loadMacroImplBinding(macroDef) - val className = binding.className - val methName = binding.methName - macroLogVerbose(s"resolved implementation as $className.$methName") - - if (binding.className == Predef_???.owner.fullName.toString && binding.methName == Predef_???.name.encoded) { - args => throw new AbortMacroException(args.c.enclosingPosition, "macro implementation is missing") - } else { - // 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 { - macroLogVerbose(s"loading implementation class: $className") - macroLogVerbose(s"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(s"successfully loaded macro impl as ($implObj, $implMeth)") - args => implMeth.invoke(implObj, ((args.c +: args.others) map (_.asInstanceOf[AnyRef])): _*) - } catch { - case ex: Exception => - macroLogVerbose(s"macro runtime failed to load: ${ex.toString}") - macroDef setFlag IS_ERROR - null - } - } - }) + def fail() = { if (macroDef != null) macroDef setFlag IS_ERROR; macroDdef setType ErrorType; EmptyTree } + def success(macroImplRef: Tree) = { bindMacroImpl(macroDef, macroImplRef); macroImplRef } + + if (!typer.checkFeature(macroDdef.pos, MacrosFeature, immediate = true)) { + macroLogVerbose("typecheck terminated unexpectedly: language.experimental.macros feature is not enabled") + fail() + } else { + val macroDdef1: macroDdef.type = macroDdef + val typer1: typer.type = typer + val macroCompiler = new { + val global: self.global.type = self.global + val typer: self.global.analyzer.Typer = typer1.asInstanceOf[self.global.analyzer.Typer] + val macroDdef: self.global.DefDef = macroDdef1 + } with DefaultMacroCompiler + val macroImplRef = macroCompiler.resolveMacroImpl + if (macroImplRef.isEmpty) fail() else success(macroImplRef) + } } } - private def macroContext(typer: Typer, prefixTree: Tree, expandeeTree: Tree): MacroContext = { + 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] @@ -560,92 +343,111 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { 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 macroDef = expandee.symbol + val paramss = macroDef.paramss + val treeInfo.Applied(core, targs, argss) = expandee + val prefix = core match { case Select(qual, _) => qual; case _ => EmptyTree } + val context = expandee.attachments.get[MacroRuntimeAttachment].flatMap(_.macroContext).getOrElse(macroContext(typer, prefix, expandee)) - val argcDoesntMatch = macroDef.paramss.length != exprArgs.length - val nullaryArgsEmptyParams = exprArgs.isEmpty && macroDef.paramss == ListOfNil - if (argcDoesntMatch && !nullaryArgsEmptyParams) { typer.TyperErrorGen.MacroPartialApplicationError(expandee) } + macroLogVerbose(sm""" + |context: $context + |prefix: $prefix + |targs: $targs + |argss: $argss + |paramss: $paramss + """.trim) - val argss: List[List[Any]] = exprArgs.toList - macroLogVerbose(s"context: $context") - macroLogVerbose(s"argss: $argss") + import typer.TyperErrorGen._ + val isNullaryArgsEmptyParams = argss.isEmpty && paramss == ListOfNil + if (paramss.length < argss.length) MacroTooManyArgumentListsError(expandee) + if (paramss.length > argss.length && !isNullaryArgsEmptyParams) MacroTooFewArgumentListsError(expandee) - val preparedArgss: List[List[Any]] = + val macroImplArgs: List[Any] = if (fastTrack contains macroDef) { // Take a dry run of the fast track implementation - if (fastTrack(macroDef) validate expandee) argss - else typer.TyperErrorGen.MacroPartialApplicationError(expandee) + if (fastTrack(macroDef) validate expandee) argss.flatten + else MacroTooFewArgumentListsError(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) - macroLogVerbose(s"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 + def calculateMacroArgs(binding: MacroImplBinding) = { + val signature = if (binding.isBundle) binding.signature else binding.signature.tail + macroLogVerbose(s"binding: $binding") + + // STEP I: prepare value arguments of the macro expansion + // wrap argss in c.Expr if necessary (i.e. if corresponding macro impl param is of type c.Expr[T]) + // expand varargs (nb! varargs can apply to any parameter section, not necessarily to the last one) + val trees = map3(argss, paramss, signature)((args, defParams, implParams) => { + val isVarargs = isVarArgsList(defParams) + if (isVarargs) { + if (defParams.length > args.length + 1) MacroTooFewArgumentsError(expandee) + } else { + if (defParams.length < args.length) MacroTooManyArgumentsError(expandee) + if (defParams.length > args.length) MacroTooFewArgumentsError(expandee) + } + + val wrappedArgs = mapWithIndex(args)((arg, j) => { + val fingerprint = implParams(min(j, implParams.length - 1)) + fingerprint match { + case Lifted => context.Expr[Nothing](arg)(TypeTag.Nothing) // TODO: SI-5752 + case _ => abort(s"unexpected fingerprint $fingerprint in $binding with paramss being $paramss " + + s"corresponding to arg $arg in $argss") + } + }) + + if (isVarargs) { + val (normal, varargs) = wrappedArgs splitAt (defParams.length - 1) + normal :+ varargs // pack all varargs into a single Seq argument (varargs Scala style) + } else wrappedArgs + }) + macroLogVerbose(s"trees: $trees") + + // STEP II: prepare type arguments of the macro expansion + // 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 tags = signature.flatten collect { case f if f.isTag => f.paramPos } 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) + targs(argPos).tpe + } else + targ.tpe.asSeenFrom( + if (prefix == EmptyTree) macroDef.owner.tpe else prefix.tpe, + macroDef.owner) } else - targ.tpe.asSeenFrom( - if (prefixTree == EmptyTree) macroDef.owner.tpe else prefixTree.tpe, - macroDef.owner) - } else - targ.tpe - context.WeakTypeTag(tpe) - }) - macroLogVerbose(s"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 - }) + targ.tpe + context.WeakTypeTag(tpe) + }) + macroLogVerbose(s"tags: $tags") + + // if present, tags always come in a separate parameter/argument list + // that's because macro impls can't have implicit parameters other than c.WeakTypeTag[T] + (trees :+ tags).flatten + } + + val binding = loadMacroImplBinding(macroDef) + if (binding.is_???) Nil + else calculateMacroArgs(binding) } - macroLogVerbose(s"preparedArgss: $preparedArgss") - MacroArgs(context, preparedArgss.flatten) + macroLogVerbose(s"macroImplArgs: $macroImplArgs") + MacroArgs(context, macroImplArgs) } /** Keeps track of macros in-flight. @@ -680,7 +482,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * 1) If necessary desugars the `expandee` to fit into `macroExpand1` * * Then `macroExpand1`: - * 2) Checks whether the expansion needs to be delayed (see `mustDelayMacroExpansion`) + * 2) Checks whether the expansion needs to be delayed * 3) Loads macro implementation using `macroMirror` * 4) Synthesizes invocation arguments for the macro implementation * 5) Checks that the result is a tree or an expr bound to this universe @@ -997,3 +799,21 @@ object MacrosStats { val macroExpandCount = Statistics.newCounter ("#macro expansions", "typer") val macroExpandNanos = Statistics.newSubTimer("time spent in macroExpand", typerNanos) } + +class Fingerprint(val value: Int) extends AnyVal { + def paramPos = { assert(isTag, this); value } + def isTag = value >= 0 + def isOther = this == Other + def isExpr = this == Lifted + override def toString = this match { + case Other => "Other" + case Lifted => "Expr" + case _ => s"Tag($value)" + } +} + +object Fingerprint { + def Tagged(tparamPos: Int) = new Fingerprint(tparamPos) + val Other = new Fingerprint(-1) + val Lifted = new Fingerprint(-2) +} |