From e1d9805c91dbe74317e2f4f22ad59056d64d12b3 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Mon, 14 Jan 2013 17:29:42 +0100 Subject: macro engine refactoring Macro impl bindings now store more information in signatures. Previously it was a flattened List[Int] corresponding to flattened paramss, now it's List[List[Int]] to preserve the lengths of parameter lists. Also now we distinguish between c.Expr parameters and others. Previously actual and reference macro signatures were represented as tuples of vparamss, rets, and sometimes tparams. Now they are all abstracted behind MacroImplSig. Finally this patch provides better error messages in cases of argsc <-> paramsc and argc <-> paramc mismatches. --- .../tools/nsc/typechecker/ContextErrors.scala | 38 ++- .../scala/tools/nsc/typechecker/Macros.scala | 366 +++++++++++++-------- .../scala/tools/nsc/typechecker/RefChecks.scala | 2 +- .../scala/tools/nsc/typechecker/Typers.scala | 2 +- 4 files changed, 252 insertions(+), 156 deletions(-) (limited to 'src/compiler/scala/tools/nsc/typechecker') diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 135a79124d..054f96c7b7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -476,7 +476,7 @@ trait ContextErrors { // doTypeApply //tryNamesDefaults def NamedAndDefaultArgumentsNotSupportedForMacros(tree: Tree, fun: Tree) = - NormalTypeError(tree, "macros application do not support named and/or default arguments") + NormalTypeError(tree, "macro applications do not support named and/or default arguments") def TooManyArgsNamesDefaultsError(tree: Tree, fun: Tree) = NormalTypeError(tree, "too many arguments for "+treeSymTypeMsg(fun)) @@ -571,7 +571,7 @@ trait ContextErrors { //adapt def MissingArgsForMethodTpeError(tree: Tree, meth: Symbol) = { val message = - if (meth.isMacro) MacroPartialApplicationErrorMessage + if (meth.isMacro) MacroTooFewArgumentListsMessage else "missing arguments for " + meth.fullLocationString + ( if (meth.isConstructor) "" else ";\nfollow this method with `_' if you want to treat it as a partially applied function" @@ -703,15 +703,24 @@ trait ContextErrors { throw MacroExpansionException } - def MacroPartialApplicationErrorMessage = "macros cannot be partially applied" - def MacroPartialApplicationError(expandee: Tree) = { + private def macroExpansionError2(expandee: Tree, msg: String) = { // macroExpansionError won't work => swallows positions, hence needed to do issueTypeError // kinda contradictory to the comment in `macroExpansionError`, but this is how it works - issueNormalTypeError(expandee, MacroPartialApplicationErrorMessage) + issueNormalTypeError(expandee, msg) setError(expandee) throw MacroExpansionException } + private def MacroTooFewArgumentListsMessage = "too few argument lists for macro invocation" + def MacroTooFewArgumentLists(expandee: Tree) = macroExpansionError2(expandee, MacroTooFewArgumentListsMessage) + + private def MacroTooManyArgumentListsMessage = "too many argument lists for macro invocation" + def MacroTooManyArgumentLists(expandee: Tree) = macroExpansionError2(expandee, MacroTooManyArgumentListsMessage) + + def MacroTooFewArguments(expandee: Tree) = macroExpansionError2(expandee, "too few arguments for macro invocation") + + def MacroTooManyArguments(expandee: Tree) = macroExpansionError2(expandee, "too many arguments for macro invocation") + def MacroGeneratedAbort(expandee: Tree, ex: AbortMacroException) = { // errors have been reported by the macro itself, so we do nothing here macroLogVerbose("macro expansion has been aborted") @@ -1257,10 +1266,17 @@ trait ContextErrors { // not exactly an error generator, but very related // and I dearly wanted to push it away from Macros.scala - private def checkSubType(slot: String, rtpe: Type, atpe: Type) = { - val ok = if (macroDebugVerbose) { - withTypesExplained(rtpe <:< atpe) - } else rtpe <:< atpe + private def checkConforms(slot: String, rtpe: Type, atpe: Type) = { + val verbose = macroDebugVerbose || settings.explaintypes.value + + def check(rtpe: Type, atpe: Type): Boolean = { + if (rtpe eq atpe) { if (verbose) println(rtpe + " <: " + atpe + "?" + EOL + "true"); true } + else rtpe <:< atpe + } + + val ok = + if (verbose) withTypesExplained(check(rtpe, atpe)) + else check(rtpe, atpe) if (!ok) { if (!macroDebugVerbose) explainTypes(rtpe, atpe) @@ -1341,9 +1357,9 @@ trait ContextErrors { def MacroImplMissingParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(abbreviateCoreAliases(lengthMsg("value", "required", rparams(aparams.length)))) - def checkMacroImplParamTypeMismatch(atpe: Type, rparam: Symbol) = checkSubType("parameter " + rparam.name, rparam.tpe, atpe) + def checkMacroImplParamTypeMismatch(atpe: Type, rparam: Symbol) = checkConforms("parameter " + rparam.name, rparam.tpe, atpe) - def checkMacroImplResultTypeMismatch(atpe: Type, rret: Type) = checkSubType("return type", atpe, rret) + def checkMacroImplResultTypeMismatch(atpe: Type, rret: Type) = checkConforms("return type", atpe, rret) def MacroImplParamNameMismatchError(aparam: Symbol, rparam: Symbol) = compatibilityError("parameter names differ: " + rparam.name + " != " + aparam.name) diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 5ac37251ee..93f73f1bbe 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 @@ -82,15 +83,22 @@ 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(-1), List(-1, -2), List(0, 2)) + signature: List[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 targs: List[Tree]) + private final val IMPLPARAM_TAG = 0 // actually it's zero and above, this is just a lower bound for >= checks + private final val IMPLPARAM_OTHER = -1 + private final val IMPLPARAM_EXPR = -2 + /** Macro def -> macro impl bindings are serialized into a `macroImpl` annotation * with synthetic content that carries the payload described in `MacroImplBinding`. * @@ -104,11 +112,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * `macro`( * "signature" = List(-1), * "methodName" = "impl", - * "versionFormat" = 1, + * "versionFormat" = , * "className" = "Macros$")) */ private object MacroImplBinding { - val versionFormat = 1 + val versionFormat = 2 def pickleAtom(obj: Any): Tree = obj match { @@ -142,9 +150,15 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { loop(owner) } - def signature: List[Int] = { + def signature: List[List[Int]] = { + def fingerprint(tpe: Type): Int = tpe.dealiasWiden match { + case TypeRef(_, RepeatedParamClass, underlying :: Nil) => fingerprint(underlying) + case ExprClassOf(_) => IMPLPARAM_EXPR + case _ => IMPLPARAM_OTHER + } + val transformed = transformTypeTagEvidenceParams(paramss, (param, tparam) => tparam) - transformed.flatten map (p => if (p.isTerm) -1 else p.paramPos) + mmap(transformed)(p => if (p.isTerm) fingerprint(p.info) else p.paramPos) } val payload = List[(String, Any)]( @@ -193,7 +207,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { val className = payload("className").asInstanceOf[String] val methodName = payload("methodName").asInstanceOf[String] - val signature = payload("signature").asInstanceOf[List[Int]] + val signature = payload("signature").asInstanceOf[List[List[Int]]] MacroImplBinding(className, methodName, signature, targs) } } @@ -235,19 +249,40 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { 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 - } + private def dealiasAndRewrap(tp: Type)(fn: Type => Type): Type = { + if (isRepeatedParamType(tp)) scalaRepeatedType(fn(tp.typeArgs.head.dealias)) + else fn(tp.dealias) + } + + /** Increases metalevel of the type, i.e. transforms: + * * T to c.Expr[T] + * + * @see Metalevels.scala for more information and examples about metalevels + */ + private def increaseMetalevel(c: Symbol, tp: Type): Type = dealiasAndRewrap(tp) { + case tp => typeRef(SingleType(NoPrefix, c), MacroContextExprClass, List(tp)) + } + + /** Decreases metalevel of the type, i.e. transforms: + * * c.Expr[T] to T + * + * @see Metalevels.scala for more information and examples about metalevels + */ + private def decreaseMetalevel(tp: Type): Type = dealiasAndRewrap(tp) { + case ExprClassOf(runtimeType) => runtimeType + case _ => AnyClass.tpe // so that macro impls with rhs = ??? don't screw up our inference + } + + def computeMacroDefTypeFromMacroImpl(macroDdef: DefDef, macroImplSig: MacroImplSig): Type = { + // Step I. Transform c.Expr[T] to T and c.Tree to + var runtimeType = decreaseMetalevel(macroImplSig.ret) // 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)) + runtimeType = runtimeType.substituteTypes(macroImplSig.tparams, 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 { + transformTypeTagEvidenceParams(macroImplSig.paramss, (param, tparam) => NoSymbol) match { case (implCtxParam :: Nil) :: implParamss => val implToDef = flatMap2(implParamss, macroDdef.vparamss)(map2(_, _)((_, _))).toMap object UnsigmaTypeMap extends TypeMap { @@ -276,39 +311,83 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { unsigma(runtimeType) } - /** A reference macro implementation signature compatible with a given macro definition. + /** Signature of a macro implementation, used to check def <-> impl correspondence. + * + * Technically it can be just an alias to MethodType, but promoting it to a first-class entity + * provides better encapsulation and convenient syntax for pattern matching. + */ + case class MacroImplSig(tparams: List[Symbol], paramss: List[List[Symbol]], ret: Type) + + /** An actual macro implementation signature extracted from a macro implementation method. + * + * In the example above for the following macro impl: + * def fooBar[T: c.WeakTypeTag] + * (c: scala.reflect.macros.Context) + * (xs: c.Expr[List[T]]) + * : c.Expr[T] = ... + * + * 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. + * + * This method cannot be reduced to just macroImpl.info, because macro implementations might + * come in different shapes. If the implementation is an apply method of a Macro-compatible object, + * then it won't have (c: Context) in its parameters, but will rather refer to Macro.c. + * + * @param macroImpl The macro implementation symbol + */ + def macroImplSig(macroImpl: Symbol): MacroImplSig = { + val tparams = macroImpl.typeParams + val paramss = transformTypeTagEvidenceParams(macroImpl.paramss, (param, tparam) => NoSymbol) + val ret = macroImpl.info.finalResultType + MacroImplSig(tparams, paramss, ret) + } + + /** A reference macro implementation signature extracted from 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] + * (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 + * Also note that we need a DefDef, not the corresponding MethodSymbol, because that symbol would be of no use for us. + * Macro signatures are verified when typechecking macro defs, which means that at that moment inspecting macroDef.info + * means asking for cyclic reference errors. + * + * We need macro implementation symbol as well, because the return type of the macro definition might be omitted, + * and in that case we'd need to infer it from the return type of the macro implementation. Luckily for us, we can + * use that symbol without a risk of running into cycles. + * + * @param typer Typechecker of `macroDdef` + * @param macroDdef The macro definition tree + * @param macroImpl The macro implementation symbol */ - private def macroImplSig(macroDef: Symbol, tparams: List[TypeDef], vparamss: List[List[ValDef]], retTpe: Type): (List[List[Symbol]], Type) = { + def referenceMacroImplSig(typer: Typer, macroDdef: DefDef, macroImpl: Symbol): MacroImplSig = { // 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))) + val cache = scala.collection.mutable.Map[Symbol, Symbol]() + val macroDef = macroDdef.symbol + val ctxParam = makeParam(nme.macroContext, macroDdef.pos, MacroContextClass.tpe, SYNTHETIC) + val paramss = List(ctxParam) :: mmap(macroDdef.vparamss)(param) + val macroDefRet = + if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe + else computeMacroDefTypeFromMacroImpl(macroDdef, macroImplSig(macroImpl)) + val implReturnType = sigma(increaseMetalevel(ctxParam, macroDefRet)) 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)) + mfind(macroDdef.vparamss)(_.symbol == sym).fold(pre)(p => singleType(singleType(NoPrefix, param(p)), ExprValue)) case _ => mapOver(pre) } @@ -326,32 +405,22 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { 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) + assert(sym.isTerm, s"sym = $sym, tree = $tree") + makeParam(sym.name, sym.pos, sigma(increaseMetalevel(ctxParam, sym.tpe)), sym.flags) }) ) } import SigGenerator._ + val result = MacroImplSig(macroDdef.tparams map (_.symbol), paramss, implReturnType) macroLogVerbose(sm""" - |generating macroImplSigs for: $macroDef - |tparams are: $tparams - |vparamss are: $vparamss - |retTpe is: $retTpe - |macroImplSig is: $paramss, $implReturnType + |generating macroImplSig for: $macroDdef + |result is: $result """.trim) - (paramss, implReturnType) + result } /** Verifies that the body of a macro def typechecks to a reference to a static public non-overloaded method, @@ -435,31 +504,26 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // 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") + // aXXX (e.g. aparamss) => characteristics of the actual macro impl signature extracted from the macro impl ("a" stands for "actual") + // rXXX (e.g. rparamss) => characteristics of the 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 MacroImplSig(atparams, aparamss, aret) = macroImplSig(macroImpl) + val MacroImplSig(_, rparamss, rret) = referenceMacroImplSig(typer, macroDdef, macroImpl) + val atvars = atparams map freshVar + def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars) + // we only check correspondence between value parameterss + // type parameters of macro defs and macro impls don't have to coincide with each other 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) + map2(aparamss, rparamss)((aparams, rparams) => { + if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams) + if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams) + }) 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 + // cannot fuse this map2 and the map2 above 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) @@ -560,92 +624,108 @@ 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) MacroTooManyArgumentLists(expandee) + if (paramss.length > argss.length && !isNullaryArgsEmptyParams) MacroTooFewArgumentLists(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 typer.TyperErrorGen.MacroTooFewArgumentLists(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 + if (binding.className == Predef_???.owner.fullName.toString && binding.methName == Predef_???.name.encoded) Nil + else { + 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, binding.signature.tail)((args, defParams, implParams) => { + val isVarargs = isVarArgsList(defParams) + if (isVarargs) { + if (defParams.length > args.length + 1) MacroTooFewArguments(expandee) + } else { + if (defParams.length < args.length) MacroTooManyArguments(expandee) + if (defParams.length > args.length) MacroTooFewArguments(expandee) + } + + val wrappedArgs = mapWithIndex(args)((arg, j) => { + val fingerprint = implParams(min(j, implParams.length - 1)) + fingerprint match { + case IMPLPARAM_EXPR => 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 = binding.signature.flatten filter (_ >= IMPLPARAM_TAG) 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 + } } - macroLogVerbose(s"preparedArgss: $preparedArgss") - MacroArgs(context, preparedArgss.flatten) + macroLogVerbose(s"macroImplArgs: $macroImplArgs") + MacroArgs(context, macroImplArgs) } /** Keeps track of macros in-flight. @@ -680,7 +760,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 diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 4ccfb31878..c9849eebb5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -126,7 +126,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans val defaultMethodNames = defaultGetters map (sym => nme.defaultGetterToMethod(sym.name)) defaultMethodNames.toList.distinct foreach { name => - val methods = clazz.info.findMember(name, 0L, METHOD, false).alternatives + val methods = clazz.info.findMember(name, 0L, METHOD, stableOnly = false).alternatives def hasDefaultParam(tpe: Type): Boolean = tpe match { case MethodType(params, restpe) => (params exists (_.hasDefault)) || hasDefaultParam(restpe) case _ => false diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 238727d2e9..5916116ef3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -5534,7 +5534,7 @@ trait Typers extends Adaptations with Tags { val isMacroBodyOkay = !tree.symbol.isErroneous && !(tree1 exists (_.isErroneous)) && tree1 != EmptyTree val shouldInheritMacroImplReturnType = ddef.tpt.isEmpty - if (isMacroBodyOkay && shouldInheritMacroImplReturnType) computeMacroDefTypeFromMacroImpl(ddef, tree1.symbol) else AnyTpe + if (isMacroBodyOkay && shouldInheritMacroImplReturnType) computeMacroDefTypeFromMacroImpl(ddef, macroImplSig(tree1.symbol)) else AnyTpe } def transformedOr(tree: Tree, op: => Tree): Tree = transformed remove tree match { -- cgit v1.2.3 From 10229316dbf7afa7545d8e279b5960da6ee3db7d Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 19 Jan 2013 12:28:19 +0100 Subject: refactors macro compilation Upgrades the way that macro defs are compiled by factoring out most of the logic in typedMacroBody and related errors in ContextErrors into an standalone cake. This leads to tighter cohesion and better code reuse as the cake is isolated from the rest of the compiler and is much easier to evolve than just a method body. Increased convenience of coding macro compilation allowed me to further clarify the implementation of the macro engine (e.g. take a look at Validators.scala) and to easily implement additional features, namely: 1) Parameters and return type of macro implementations can now be plain c.Tree's instead of previously mandatory c.Expr's. This makes macros more lightweight as there are a lot of situations when one doesn't need to splice macro params (the only motivation to use exprs over trees). Also as we're on the verge of having quasiquotes in trunk, there soon will be no reason to use exprs at all, since quasiquotes can splice everything. 2) Macro implementations can now be defined in bundles, standalone cakes built around a macro context: http://docs.scala-lang.org/overviews/macros/bundles.html. This further reduces boilerplate by simplifying implementations complex macros due to the fact that macro programmers no longer need to play path-dependent games to use helpers. --- .../macros/compiler/DefaultMacroCompiler.scala | 33 ++ .../scala/reflect/macros/compiler/Errors.scala | 113 ++++++ .../scala/reflect/macros/compiler/Resolvers.scala | 91 +++++ .../scala/reflect/macros/compiler/Validators.scala | 193 +++++++++ .../scala/reflect/macros/runtime/Synthetics.scala | 19 +- .../scala/reflect/macros/util/Helpers.scala | 71 ++++ src/compiler/scala/tools/nsc/Global.scala | 21 + .../tools/nsc/typechecker/ContextErrors.scala | 158 +------- .../scala/tools/nsc/typechecker/Macros.scala | 443 +++++---------------- .../tools/nsc/typechecker/StdAttachments.scala | 27 ++ .../scala/tools/nsc/typechecker/Typers.scala | 7 +- src/compiler/scala/tools/reflect/FastTrack.scala | 1 + src/reflect/scala/reflect/api/Trees.scala | 14 + .../scala/reflect/internal/Definitions.scala | 14 + src/reflect/scala/reflect/internal/StdNames.scala | 5 + src/reflect/scala/reflect/internal/TreeInfo.scala | 13 +- src/reflect/scala/reflect/internal/Trees.scala | 13 + src/reflect/scala/reflect/macros/Macro.scala | 39 ++ .../scala/reflect/runtime/JavaMirrors.scala | 4 +- test/files/neg/macro-invalidimpl.check | 30 +- test/files/neg/macro-invalidret.check | 4 +- test/files/neg/macro-invalidshape.check | 14 +- .../neg/macro-invalidsig-params-badtype.check | 6 +- .../Impls_Macros_1.scala | 2 +- test/files/neg/macro-invalidsig.check | 26 +- test/files/neg/t5689.check | 2 +- .../run/macro-bodyexpandstoimpl/Impls_1.scala | 2 + test/files/run/macro-bundle.check | 3 + test/files/run/macro-bundle.flags | 1 + test/files/run/macro-bundle/Impls_Macros_1.scala | 13 + test/files/run/macro-bundle/Test_2.scala | 5 + .../run/macro-toplevel-companion-b/Test_2.scala | 2 +- test/files/run/macro-toplevel-companion-c.scala | 2 +- 33 files changed, 837 insertions(+), 554 deletions(-) create mode 100644 src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala create mode 100644 src/compiler/scala/reflect/macros/compiler/Errors.scala create mode 100644 src/compiler/scala/reflect/macros/compiler/Resolvers.scala create mode 100644 src/compiler/scala/reflect/macros/compiler/Validators.scala create mode 100644 src/compiler/scala/reflect/macros/util/Helpers.scala create mode 100644 src/reflect/scala/reflect/macros/Macro.scala create mode 100644 test/files/run/macro-bundle.check create mode 100644 test/files/run/macro-bundle.flags create mode 100644 test/files/run/macro-bundle/Impls_Macros_1.scala create mode 100644 test/files/run/macro-bundle/Test_2.scala (limited to 'src/compiler/scala/tools/nsc/typechecker') diff --git a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala new file mode 100644 index 0000000000..749e730c0e --- /dev/null +++ b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala @@ -0,0 +1,33 @@ +package scala.reflect.macros +package compiler + +import scala.tools.nsc.Global +import scala.reflect.macros.runtime.Context + +abstract class DefaultMacroCompiler extends Resolvers + with Validators + with Errors { + val global: Global + import global._ + + val typer: global.analyzer.Typer + private implicit val context0 = typer.context + val context = typer.context + + val macroDdef: DefDef + lazy val macroDef = macroDdef.symbol + + private case class MacroImplResolutionException(pos: Position, msg: String) extends Exception + def abort(pos: Position, msg: String) = throw MacroImplResolutionException(pos, msg) + + def resolveMacroImpl: Tree = { + try { + validateMacroImplRef() + macroImplRef + } catch { + case MacroImplResolutionException(pos, msg) => + context.error(pos, msg) + EmptyTree + } + } +} \ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/compiler/Errors.scala b/src/compiler/scala/reflect/macros/compiler/Errors.scala new file mode 100644 index 0000000000..dd3142127e --- /dev/null +++ b/src/compiler/scala/reflect/macros/compiler/Errors.scala @@ -0,0 +1,113 @@ +package scala.reflect.macros +package compiler + +import scala.compat.Platform.EOL +import scala.reflect.macros.util.Traces + +trait Errors extends Traces { + self: DefaultMacroCompiler => + + import global._ + import analyzer._ + import definitions._ + import typer.TyperErrorGen._ + import typer.infer.InferErrorGen._ + def globalSettings = global.settings + + // sanity check errors + + private def implRefError(message: String) = abort(macroDdef.pos, message) + + def MacroImplReferenceWrongShapeError() = implRefError( + "macro implementation reference has wrong shape. required:\n"+ + "macro [].[[]] or\n" + + "macro [].[[]]") + + def MacroImplNotPublicError() = implRefError("macro implementation must be public") + + def MacroImplOverloadedError() = implRefError("macro implementation cannot be overloaded") + + def MacroImplWrongNumberOfTypeArgumentsError() = implRefError(TypedApplyWrongNumberOfTpeParametersErrorMessage(macroImplRef)) + + // compatibility errors + + // helpers + + private def lengthMsg(flavor: String, violation: String, extra: Symbol) = { + val noun = if (flavor == "value") "parameter" else "type parameter" + val message = noun + " lists have different length, " + violation + " extra " + noun + val suffix = if (extra ne NoSymbol) " " + extra.defString else "" + message + suffix + } + + private def abbreviateCoreAliases(s: String): String = List("WeakTypeTag", "Expr").foldLeft(s)((res, x) => res.replace("c.universe." + x, "c." + x)) + + private def showMeth(pss: List[List[Symbol]], restpe: Type, abbreviate: Boolean) = { + var argsPart = (pss map (ps => ps map (_.defString) mkString ("(", ", ", ")"))).mkString + if (abbreviate) argsPart = abbreviateCoreAliases(argsPart) + var retPart = restpe.toString + if (abbreviate || macroDdef.tpt.tpe == null) retPart = abbreviateCoreAliases(retPart) + argsPart + ": " + retPart + } + + // not exactly an error generator, but very related + // and I dearly wanted to push it away from Macros.scala + private def checkConforms(slot: String, rtpe: Type, atpe: Type) = { + val verbose = macroDebugVerbose || settings.explaintypes.value + + def check(rtpe: Type, atpe: Type): Boolean = { + def success() = { if (verbose) println(rtpe + " <: " + atpe + "?" + EOL + "true"); true } + (rtpe, atpe) match { + case _ if rtpe eq atpe => success() + case (TypeRef(_, RepeatedParamClass, rtpe :: Nil), TypeRef(_, RepeatedParamClass, atpe :: Nil)) => check(rtpe, atpe) + case (ExprClassOf(_), TreeType()) => success() + case (TreeType(), ExprClassOf(_)) => success() + case _ => rtpe <:< atpe + } + } + + val ok = + if (verbose) withTypesExplained(check(rtpe, atpe)) + else check(rtpe, atpe) + if (!ok) { + if (!macroDebugVerbose) + explainTypes(rtpe, atpe) + compatibilityError("type mismatch for %s: %s does not conform to %s".format(slot, abbreviateCoreAliases(rtpe.toString), abbreviateCoreAliases(atpe.toString))) + } + } + + private def compatibilityError(message: String) = + implRefError( + "macro implementation has wrong shape:"+ + "\n required: " + showMeth(rparamss, rret, abbreviate = true) + + "\n found : " + showMeth(aparamss, aret, abbreviate = false) + + "\n" + message) + + def MacroImplNonTagImplicitParameters(params: List[Symbol]) = compatibilityError("macro implementations cannot have implicit parameters other than WeakTypeTag evidences") + + def MacroImplParamssMismatchError() = compatibilityError("number of parameter sections differ") + + def MacroImplExtraParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(lengthMsg("value", "found", aparams(rparams.length))) + + def MacroImplMissingParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(abbreviateCoreAliases(lengthMsg("value", "required", rparams(aparams.length)))) + + def checkMacroImplParamTypeMismatch(atpe: Type, rparam: Symbol) = checkConforms("parameter " + rparam.name, rparam.tpe, atpe) + + def checkMacroImplResultTypeMismatch(atpe: Type, rret: Type) = checkConforms("return type", atpe, rret) + + def MacroImplParamNameMismatchError(aparam: Symbol, rparam: Symbol) = compatibilityError("parameter names differ: " + rparam.name + " != " + aparam.name) + + def MacroImplVarargMismatchError(aparam: Symbol, rparam: Symbol) = { + def fail(paramName: Name) = compatibilityError("types incompatible for parameter " + paramName + ": corresponding is not a vararg parameter") + if (isRepeated(rparam) && !isRepeated(aparam)) fail(rparam.name) + if (!isRepeated(rparam) && isRepeated(aparam)) fail(aparam.name) + } + + def MacroImplTargMismatchError(atargs: List[Type], atparams: List[Symbol]) = + compatibilityError(NotWithinBoundsErrorMessage("", atargs, atparams, macroDebugVerbose || settings.explaintypes.value)) + + def MacroImplTparamInstantiationError(atparams: List[Symbol], ex: NoInstance) = + compatibilityError( + "type parameters "+(atparams map (_.defString) mkString ", ")+" cannot be instantiated\n"+ + ex.getMessage) +} \ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/compiler/Resolvers.scala b/src/compiler/scala/reflect/macros/compiler/Resolvers.scala new file mode 100644 index 0000000000..3025eb52a1 --- /dev/null +++ b/src/compiler/scala/reflect/macros/compiler/Resolvers.scala @@ -0,0 +1,91 @@ +package scala.reflect.macros +package compiler + +import scala.reflect.internal.Flags._ +import scala.reflect.macros.TypecheckException + +trait Resolvers { + self: DefaultMacroCompiler => + + import global._ + import analyzer._ + import definitions._ + import treeInfo._ + import gen._ + + /** Determines the type of context implied by the macro def. + */ + val ctxTpe = MacroContextClass.tpe + + /** Resolves a macro impl reference provided in the right-hand side of the given macro definition. + * + * Acceptable shapes of the right-hand side: + * 1) [].[[]] // vanilla macro def + * 2) [].[[]] // shiny new macro bundle + * + * Produces a tree, which represents a reference to a macro implementation if everything goes well, + * otherwise reports found errors and returns EmptyTree. The resulting tree should have the following format: + * + * qualifier.method[targs] + * + * Qualifier here might be omitted (local macro defs), be a static object (vanilla macro defs) + * or be a dummy instance of a macro bundle (e.g. new MyMacro(???).expand). + */ + lazy val macroImplRef: Tree = { + val (maybeBundleRef, methName, targs) = macroDdef.rhs match { + case Applied(methRef @ Select(bundleRef @ RefTree(qual, bundleName), methName), targs, Nil) => + (RefTree(qual, bundleName.toTypeName), methName, targs) + case Applied(Ident(methName), targs, Nil) => + (Ident(context.owner.enclClass), methName, targs) + case _ => + (EmptyTree, TermName(""), Nil) + } + + val untypedImplRef = typer.silent(_.typedType(maybeBundleRef)) match { + case SilentResultValue(result) if isMacroBundleType(result.tpe) => + val bundleClass = result.tpe.typeSymbol + if (!bundleClass.owner.isPackageClass) abort(macroDef.pos, "macro bundles can only be defined as top-level classes or traits") + + // synthesize the invoker, i.e. given a top-level `trait Foo extends Macro { def expand = ... } ` + // create a top-level definition `class FooInvoker(val c: Context) extends Foo` in MACRO_INVOKER_PACKAGE + val invokerPid = gen.mkUnattributedRef(nme.MACRO_INVOKER_PACKAGE) + val invokerName = TypeName(bundleClass.fullName.split('.').map(_.capitalize).mkString("") + nme.MACRO_INVOKER_SUFFIX) + val invokerFullName = TypeName(s"$invokerPid.$invokerName") + val existingInvoker = rootMirror.getClassIfDefined(invokerFullName) + if (!currentRun.compiles(existingInvoker)) { + def mkContextValDef(flags: Long) = ValDef(Modifiers(flags), nme.c, TypeTree(ctxTpe), EmptyTree) + val contextField = mkContextValDef(PARAMACCESSOR) + val contextParam = mkContextValDef(PARAM | PARAMACCESSOR) + val invokerCtor = DefDef(Modifiers(), nme.CONSTRUCTOR, Nil, List(List(contextParam)), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))) + val invoker = atPos(bundleClass.pos)(ClassDef(NoMods, invokerName, Nil, Template(List(Ident(bundleClass)), emptyValDef, List(contextField, invokerCtor)))) + currentRun.compileLate(PackageDef(invokerPid, List(invoker))) + } + + // synthesize the macro impl reference, which is going to look like: + // `new Foo$invoker(???).expand` plus the optional type arguments + val instanceOfInvoker = New(Select(invokerPid, invokerName), List(List(Select(scalaDot(nme.Predef), nme.???)))) + gen.mkTypeApply(Select(instanceOfInvoker, methName), targs) + case _ => + macroDdef.rhs + } + + val typedImplRef = typer.silent(_.typed(markMacroImplRef(untypedImplRef)), reportAmbiguousErrors = false) + typedImplRef match { + case SilentResultValue(success) => success + case SilentTypeError(err) => abort(err.errPos, err.errMsg) + } + } + + // FIXME: cannot write this concisely because of SI-7507 + // lazy val (isImplBundle, macroImplOwner, macroImpl, macroImplTargs) = + private lazy val dissectedMacroImplRef = + macroImplRef match { + case MacroImplReference(isBundle, owner, meth, targs) => (isBundle, owner, meth, targs) + case _ => MacroImplReferenceWrongShapeError() + } + lazy val isImplBundle = dissectedMacroImplRef._1 + lazy val isImplMethod = !isImplBundle + lazy val macroImplOwner = dissectedMacroImplRef._2 + lazy val macroImpl = dissectedMacroImplRef._3 + lazy val targs = dissectedMacroImplRef._4 +} diff --git a/src/compiler/scala/reflect/macros/compiler/Validators.scala b/src/compiler/scala/reflect/macros/compiler/Validators.scala new file mode 100644 index 0000000000..60cfc94a23 --- /dev/null +++ b/src/compiler/scala/reflect/macros/compiler/Validators.scala @@ -0,0 +1,193 @@ +package scala.reflect.macros +package compiler + +import java.util.UUID.randomUUID +import scala.reflect.internal.Flags._ +import scala.reflect.macros.TypecheckException + +trait Validators { + self: DefaultMacroCompiler => + + import global._ + import analyzer._ + import definitions._ + import treeInfo._ + import typer.infer._ + + def validateMacroImplRef() = { + sanityCheck() + if (macroImpl != Predef_???) checkMacroDefMacroImplCorrespondence() + } + + private def sanityCheck() = { + if (!macroImpl.isMethod) MacroImplReferenceWrongShapeError() + if (!macroImpl.isPublic) MacroImplNotPublicError() + if (macroImpl.isOverloaded) MacroImplOverloadedError() + if (macroImpl.typeParams.length != targs.length) MacroImplWrongNumberOfTypeArgumentsError() + val declaredInStaticObject = isImplMethod && (macroImplOwner.isStaticOwner || macroImplOwner.moduleClass.isStaticOwner) + val declaredInTopLevelClass = isImplBundle && macroImplOwner.owner.isPackageClass + if (!declaredInStaticObject && !declaredInTopLevelClass) MacroImplReferenceWrongShapeError() + } + + private def checkMacroDefMacroImplCorrespondence() = { + val atvars = atparams map freshVar + def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars) + + // we only check strict correspondence between value parameterss + // type parameters of macro defs and macro impls don't have to coincide with each other + val implicitParams = aparamss.flatten filter (_.isImplicit) + if (implicitParams.nonEmpty) MacroImplNonTagImplicitParameters(implicitParams) + if (aparamss.length != rparamss.length) MacroImplParamssMismatchError() + map2(aparamss, rparamss)((aparams, rparams) => { + if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams) + if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams) + }) + + try { + // cannot fuse this map2 and the map2 above 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 =:= ctxTpe && 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) + } + } + + // aXXX (e.g. aparamss) => characteristics of the actual macro impl signature extracted from the macro impl ("a" stands for "actual") + // rXXX (e.g. rparamss) => characteristics of the reference macro impl signature synthesized from the macro def ("r" stands for "reference") + // FIXME: cannot write this concisely because of SI-7507 + //lazy val MacroImplSig(atparams, aparamss, aret) = macroImplSig + //lazy val MacroImplSig(_, rparamss, rret) = referenceMacroImplSig + lazy val atparams = macroImplSig.tparams + lazy val aparamss = macroImplSig.paramss + lazy val aret = macroImplSig.ret + lazy val rparamss = referenceMacroImplSig.paramss + lazy val rret = referenceMacroImplSig.ret + + // Technically this can be just an alias to MethodType, but promoting it to a first-class entity + // provides better encapsulation and convenient syntax for pattern matching. + private case class MacroImplSig(tparams: List[Symbol], paramss: List[List[Symbol]], ret: Type) + + /** An actual macro implementation signature extracted from a macro implementation method. + * + * For the following macro impl: + * def fooBar[T: c.WeakTypeTag] + * (c: scala.reflect.macros.Context) + * (xs: c.Expr[List[T]]) + * : c.Expr[T] = ... + * + * 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. + * + * This method cannot be reduced to just macroImpl.info, because macro implementations might + * come in different shapes. If the implementation is an apply method of a Macro-compatible object, + * then it won't have (c: Context) in its parameters, but will rather refer to Macro.c. + * + * @param macroImpl The macro implementation symbol + */ + private lazy val macroImplSig: MacroImplSig = { + val tparams = macroImpl.typeParams + val paramss = transformTypeTagEvidenceParams(macroImplRef, (param, tparam) => NoSymbol) + val ret = macroImpl.info.finalResultType + MacroImplSig(tparams, paramss, ret) + } + + /** A reference macro implementation signature extracted from a given macro definition. + * + * 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. + * + * Also note that we need a DefDef, not the corresponding MethodSymbol, because that symbol would be of no use for us. + * Macro signatures are verified when typechecking macro defs, which means that at that moment inspecting macroDef.info + * means asking for cyclic reference errors. + * + * We need macro implementation symbol as well, because the return type of the macro definition might be omitted, + * and in that case we'd need to infer it from the return type of the macro implementation. Luckily for us, we can + * use that symbol without a risk of running into cycles. + * + * @param typer Typechecker of `macroDdef` + * @param macroDdef The macro definition tree + * @param macroImpl The macro implementation symbol + */ + private lazy val referenceMacroImplSig: MacroImplSig = { + // had to move method's body to an object because of the recursive dependencies between sigma and param + object SigGenerator { + val cache = scala.collection.mutable.Map[Symbol, Symbol]() + val ctxPrefix = + if (isImplMethod) singleType(NoPrefix, makeParam(nme.macroContext, macroDdef.pos, ctxTpe, SYNTHETIC)) + else singleType(ThisType(macroImpl.owner), macroImpl.owner.tpe.member(nme.c)) + var paramss = + if (isImplMethod) List(ctxPrefix.termSymbol) :: mmap(macroDdef.vparamss)(param) + else mmap(macroDdef.vparamss)(param) + val macroDefRet = + if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe + else computeMacroDefTypeFromMacroImplRef(macroDdef, macroImplRef) + val implReturnType = sigma(increaseMetalevel(ctxPrefix, macroDefRet)) + + object SigmaTypeMap extends TypeMap { + def mapPrefix(pre: Type) = pre match { + case ThisType(sym) if sym == macroDef.owner => + singleType(singleType(ctxPrefix, MacroContextPrefix), ExprValue) + case SingleType(NoPrefix, sym) => + mfind(macroDdef.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 param(tree: Tree): Symbol = ( + cache.getOrElseUpdate(tree.symbol, { + val sym = tree.symbol + assert(sym.isTerm, s"sym = $sym, tree = $tree") + makeParam(sym.name, sym.pos, sigma(increaseMetalevel(ctxPrefix, sym.tpe)), sym.flags) + }) + ) + } + + import SigGenerator._ + macroLogVerbose(s"generating macroImplSigs for: $macroDdef") + val result = MacroImplSig(macroDdef.tparams map (_.symbol), paramss, implReturnType) + macroLogVerbose(s"result is: $result") + result + } +} diff --git a/src/compiler/scala/reflect/macros/runtime/Synthetics.scala b/src/compiler/scala/reflect/macros/runtime/Synthetics.scala index 73f3ab8d20..1156769a80 100644 --- a/src/compiler/scala/reflect/macros/runtime/Synthetics.scala +++ b/src/compiler/scala/reflect/macros/runtime/Synthetics.scala @@ -5,7 +5,6 @@ package scala.reflect.macros package runtime -import java.util.UUID._ import scala.reflect.internal.Flags._ import scala.reflect.internal.util.BatchSourceFile import scala.reflect.io.VirtualFile @@ -42,11 +41,6 @@ trait Synthetics { else EmptyTree } - // TODO: provide a way to specify a pretty name for debugging purposes - private def randomFileName() = ( - "macroSynthetic-" + randomUUID().toString.replace("-", "") + ".scala" - ) - def introduceTopLevel[T: PackageSpec](packagePrototype: T, definition: universe.ImplDef): RefTree = introduceTopLevel(packagePrototype, List(definition)).head @@ -55,18 +49,7 @@ trait Synthetics { private def introduceTopLevel[T: PackageSpec](packagePrototype: T, definitions: List[universe.ImplDef]): List[RefTree] = { val code @ PackageDef(pid, _) = implicitly[PackageSpec[T]].mkPackageDef(packagePrototype, definitions) - val syntheticFileName = randomFileName() - // compatibility with SBT - // on the one hand, we need to specify some jfile here, otherwise sbt crashes with an NPE (SI-6870) - // on the other hand, we can't specify the obvious enclosingUnit, because then sbt somehow fails to run tests using type macros - // okay, now let's specify a guaranteedly non-existent file in an existing directory (so that we don't run into permission problems) - val relatedJfile = enclosingUnit.source.file.file - val fakeJfile = if (relatedJfile != null) new java.io.File(relatedJfile.getParent, syntheticFileName) else null - val virtualFile = new VirtualFile(syntheticFileName) { override def file = fakeJfile } - val sourceFile = new BatchSourceFile(virtualFile, code.toString) - val unit = new CompilationUnit(sourceFile) - unit.body = code - universe.currentRun.compileLate(unit) + universe.currentRun.compileLate(code) definitions map (definition => Select(pid, definition.name)) } diff --git a/src/compiler/scala/reflect/macros/util/Helpers.scala b/src/compiler/scala/reflect/macros/util/Helpers.scala new file mode 100644 index 0000000000..ada8efa833 --- /dev/null +++ b/src/compiler/scala/reflect/macros/util/Helpers.scala @@ -0,0 +1,71 @@ +package scala.reflect.macros +package util + +import scala.tools.nsc.typechecker.Analyzer + +trait Helpers { + self: Analyzer => + + import global._ + import definitions._ + + /** 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. + */ + def transformTypeTagEvidenceParams(macroImplRef: Tree, transform: (Symbol, Symbol) => Symbol): List[List[Symbol]] = { + val treeInfo.MacroImplReference(isBundle, owner, macroImpl, _) = macroImplRef + val paramss = macroImpl.paramss + if (paramss.isEmpty || paramss.last.isEmpty) return paramss // no implicit parameters in the signature => nothing to do + val rc = + if (isBundle) macroImpl.owner.tpe.member(nme.c) + else { + def cparam = paramss.head.head + if (paramss.head.isEmpty || !(cparam.tpe <:< MacroContextClass.tpe)) return paramss // no context parameter in the signature => nothing to do + cparam + } + def transformTag(param: Symbol): Symbol = param.tpe.dealias match { + case TypeRef(SingleType(SingleType(_, ac), universe), WeakTypeTagClass, targ :: Nil) + if ac == rc && 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 + } + + private def dealiasAndRewrap(tp: Type)(fn: Type => Type): Type = { + if (isRepeatedParamType(tp)) scalaRepeatedType(fn(tp.typeArgs.head.dealias)) + else fn(tp.dealias) + } + + /** Increases metalevel of the type, i.e. transforms: + * * T to c.Expr[T] + * + * @see Metalevels.scala for more information and examples about metalevels + */ + def increaseMetalevel(pre: Type, tp: Type): Type = dealiasAndRewrap(tp) { + case tp => typeRef(pre, MacroContextExprClass, List(tp)) + } + + /** Decreases metalevel of the type, i.e. transforms: + * * c.Expr[T] to T + * * Anything else to Any + * + * @see Metalevels.scala for more information and examples about metalevels + */ + def decreaseMetalevel(tp: Type): Type = dealiasAndRewrap(tp) { + case ExprClassOf(runtimeType) => runtimeType + case _ => AnyClass.tpe // so that macro impls with rhs = ??? don't screw up our inference + } +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index f1fccd6069..a08633ffc8 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -9,6 +9,7 @@ package nsc import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundException } import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException } +import java.util.UUID._ import scala.compat.Platform.currentTime import scala.collection.{ mutable, immutable } import io.{ SourceReader, AbstractFile, Path } @@ -16,6 +17,7 @@ import reporters.{ Reporter, ConsoleReporter } import util.{ ClassPath, MergedClassPath, StatisticsInfo, returning, stackTraceString, stackTraceHeadString } import scala.reflect.internal.util.{ OffsetPosition, SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile } import scala.reflect.internal.pickling.{ PickleBuffer, PickleFormat } +import scala.reflect.io.VirtualFile import symtab.{ Flags, SymbolTable, SymbolLoaders, SymbolTrackers } import symtab.classfile.Pickler import plugins.Plugins @@ -1621,6 +1623,25 @@ class Global(var currentSettings: Settings, var reporter: Reporter) } } + // TODO: provide a way to specify a pretty name for debugging purposes + private def randomFileName() = ( + "compileLateSynthetic-" + randomUUID().toString.replace("-", "") + ".scala" + ) + + def compileLate(code: PackageDef) { + // compatibility with SBT + // on the one hand, we need to specify some jfile here, otherwise sbt crashes with an NPE (SI-6870) + // on the other hand, we can't specify the obvious enclosingUnit, because then sbt somehow fails to run tests using type macros + // okay, now let's specify a guaranteedly non-existent file in an existing directory (so that we don't run into permission problems) + val syntheticFileName = randomFileName() + val fakeJfile = new java.io.File(syntheticFileName) + val virtualFile = new VirtualFile(syntheticFileName) { override def file = fakeJfile } + val sourceFile = new BatchSourceFile(virtualFile, code.toString) + val unit = new CompilationUnit(sourceFile) + unit.body = code + compileLate(unit) + } + /** Reset package class to state at typer (not sure what this * is needed for?) */ diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 054f96c7b7..09ac13982d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -460,7 +460,7 @@ trait ContextErrors { def AbstractionFromVolatileTypeError(vd: ValDef) = issueNormalTypeError(vd, "illegal abstraction from value with volatile type "+vd.symbol.tpe) - private[ContextErrors] def TypedApplyWrongNumberOfTpeParametersErrorMessage(fun: Tree) = + private[scala] def TypedApplyWrongNumberOfTpeParametersErrorMessage(fun: Tree) = "wrong number of type parameters for "+treeSymTypeMsg(fun) def TypedApplyWrongNumberOfTpeParametersError(tree: Tree, fun: Tree) = { @@ -692,7 +692,6 @@ trait ContextErrors { issueNormalTypeError(expandee, s"macro in $role role can only expand into $allowedExpansions") } - // same reason as for MacroBodyTypecheckException case object MacroExpansionException extends Exception with scala.util.control.ControlThrowable protected def macroExpansionError(expandee: Tree, msg: String, pos: Position = NoPosition) = { @@ -712,14 +711,14 @@ trait ContextErrors { } private def MacroTooFewArgumentListsMessage = "too few argument lists for macro invocation" - def MacroTooFewArgumentLists(expandee: Tree) = macroExpansionError2(expandee, MacroTooFewArgumentListsMessage) + def MacroTooFewArgumentListsError(expandee: Tree) = macroExpansionError2(expandee, MacroTooFewArgumentListsMessage) private def MacroTooManyArgumentListsMessage = "too many argument lists for macro invocation" - def MacroTooManyArgumentLists(expandee: Tree) = macroExpansionError2(expandee, MacroTooManyArgumentListsMessage) + def MacroTooManyArgumentListsError(expandee: Tree) = macroExpansionError2(expandee, MacroTooManyArgumentListsMessage) - def MacroTooFewArguments(expandee: Tree) = macroExpansionError2(expandee, "too few arguments for macro invocation") + def MacroTooFewArgumentsError(expandee: Tree) = macroExpansionError2(expandee, "too few arguments for macro invocation") - def MacroTooManyArguments(expandee: Tree) = macroExpansionError2(expandee, "too many arguments for macro invocation") + def MacroTooManyArgumentsError(expandee: Tree) = macroExpansionError2(expandee, "too many arguments for macro invocation") def MacroGeneratedAbort(expandee: Tree, ex: AbortMacroException) = { // errors have been reported by the macro itself, so we do nothing here @@ -926,7 +925,7 @@ trait ContextErrors { kindErrors.toList.mkString("\n", ", ", "")) } - private[ContextErrors] def NotWithinBoundsErrorMessage(prefix: String, targs: List[Type], tparams: List[Symbol], explaintypes: Boolean) = { + private[scala] def NotWithinBoundsErrorMessage(prefix: String, targs: List[Type], tparams: List[Symbol], explaintypes: Boolean) = { if (explaintypes) { val bounds = tparams map (tp => tp.info.instantiateTypeParams(tparams, targs).bounds) (targs, bounds).zipped foreach ((targ, bound) => explainTypes(bound.lo, targ)) @@ -1233,149 +1232,4 @@ trait ContextErrors { setError(arg) } } - - // using an exception here is actually a good idea - // because the lifespan of this exception is extremely small and controlled - // moreover exceptions let us avoid an avalanche of "if (!hasError) do stuff" checks - case object MacroBodyTypecheckException extends Exception with scala.util.control.ControlThrowable - - trait MacroErrors { - self: MacroTyper => - - private implicit val context0 = typer.context - val context = typer.context - - // helpers - - private def lengthMsg(flavor: String, violation: String, extra: Symbol) = { - val noun = if (flavor == "value") "parameter" else "type parameter" - val message = noun + " lists have different length, " + violation + " extra " + noun - val suffix = if (extra ne NoSymbol) " " + extra.defString else "" - message + suffix - } - - private def abbreviateCoreAliases(s: String): String = List("WeakTypeTag", "Expr").foldLeft(s)((res, x) => res.replace("c.universe." + x, "c." + x)) - - private def showMeth(pss: List[List[Symbol]], restpe: Type, abbreviate: Boolean) = { - var argsPart = (pss map (ps => ps map (_.defString) mkString ("(", ", ", ")"))).mkString - if (abbreviate) argsPart = abbreviateCoreAliases(argsPart) - var retPart = restpe.toString - if (abbreviate || macroDdef.tpt.tpe == null) retPart = abbreviateCoreAliases(retPart) - argsPart + ": " + retPart - } - - // not exactly an error generator, but very related - // and I dearly wanted to push it away from Macros.scala - private def checkConforms(slot: String, rtpe: Type, atpe: Type) = { - val verbose = macroDebugVerbose || settings.explaintypes.value - - def check(rtpe: Type, atpe: Type): Boolean = { - if (rtpe eq atpe) { if (verbose) println(rtpe + " <: " + atpe + "?" + EOL + "true"); true } - else rtpe <:< atpe - } - - val ok = - if (verbose) withTypesExplained(check(rtpe, atpe)) - else check(rtpe, atpe) - if (!ok) { - if (!macroDebugVerbose) - explainTypes(rtpe, atpe) - compatibilityError("type mismatch for %s: %s does not conform to %s".format(slot, abbreviateCoreAliases(rtpe.toString), abbreviateCoreAliases(atpe.toString))) - } - } - - // errors - - private def fail() = { - // need to set the IS_ERROR flag to prohibit spurious expansions - if (macroDef != null) macroDef setFlag IS_ERROR - // not setting ErrorSymbol as in `infer.setError`, because we still need to know that it's a macro - // otherwise assignTypeToTree in Namers might fail if macroDdef.tpt == EmptyTree - macroDdef setType ErrorType - throw MacroBodyTypecheckException - } - - private def genericError(tree: Tree, message: String) = { - issueNormalTypeError(tree, message) - fail() - } - - private def implRefError(message: String) = { - val treeInfo.Applied(implRef, _, _) = macroDdef.rhs - genericError(implRef, message) - } - - private def compatibilityError(message: String) = - implRefError( - "macro implementation has wrong shape:"+ - "\n required: " + showMeth(rparamss, rret, abbreviate = true) + - "\n found : " + showMeth(aparamss, aret, abbreviate = false) + - "\n" + message) - - // Phase I: sanity checks - - def MacroDefIsFastTrack() = { - macroLogVerbose("typecheck terminated unexpectedly: macro is fast track") - assert(!macroDdef.tpt.isEmpty, "fast track macros must provide result type") - throw MacroBodyTypecheckException // don't call fail, because we don't need IS_ERROR - } - - def MacroDefIsQmarkQmarkQmark() = { - macroLogVerbose("typecheck terminated unexpectedly: macro is ???") - throw MacroBodyTypecheckException - } - - def MacroFeatureNotEnabled() = { - macroLogVerbose("typecheck terminated unexpectedly: language.experimental.macros feature is not enabled") - fail() - } - - // Phase II: typecheck the right-hand side of the macro def - - // do nothing, just fail. relevant typecheck errors have already been reported - def MacroDefUntypeableBodyError() = fail() - - def MacroDefInvalidBodyError() = genericError(macroDdef, "macro body has wrong shape:\n required: macro [].[[]]") - - def MacroImplNotPublicError() = implRefError("macro implementation must be public") - - def MacroImplOverloadedError() = implRefError("macro implementation cannot be overloaded") - - def MacroImplWrongNumberOfTypeArgumentsError(macroImplRef: Tree) = implRefError(typer.TyperErrorGen.TypedApplyWrongNumberOfTpeParametersErrorMessage(macroImplRef)) - - def MacroImplNotStaticError() = implRefError("macro implementation must be in statically accessible object") - - // Phase III: check compatibility between the macro def and its macro impl - // aXXX (e.g. aparams) => characteristics of the macro impl ("a" stands for "actual") - // rXXX (e.g. rparams) => characteristics of a reference macro impl signature synthesized from the macro def ("r" stands for "reference") - - def MacroImplNonTagImplicitParameters(params: List[Symbol]) = compatibilityError("macro implementations cannot have implicit parameters other than WeakTypeTag evidences") - - def MacroImplParamssMismatchError() = compatibilityError("number of parameter sections differ") - - def MacroImplExtraParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(lengthMsg("value", "found", aparams(rparams.length))) - - def MacroImplMissingParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(abbreviateCoreAliases(lengthMsg("value", "required", rparams(aparams.length)))) - - def checkMacroImplParamTypeMismatch(atpe: Type, rparam: Symbol) = checkConforms("parameter " + rparam.name, rparam.tpe, atpe) - - def checkMacroImplResultTypeMismatch(atpe: Type, rret: Type) = checkConforms("return type", atpe, rret) - - def MacroImplParamNameMismatchError(aparam: Symbol, rparam: Symbol) = compatibilityError("parameter names differ: " + rparam.name + " != " + aparam.name) - - def MacroImplVarargMismatchError(aparam: Symbol, rparam: Symbol) = { - if (isRepeated(rparam) && !isRepeated(aparam)) - compatibilityError("types incompatible for parameter " + rparam.name + ": corresponding is not a vararg parameter") - if (!isRepeated(rparam) && isRepeated(aparam)) - compatibilityError("types incompatible for parameter " + aparam.name + ": corresponding is not a vararg parameter") - } - - def MacroImplTargMismatchError(atargs: List[Type], atparams: List[Symbol]) = - compatibilityError(typer.infer.InferErrorGen.NotWithinBoundsErrorMessage("", atargs, atparams, macroDebugVerbose || settings.explaintypes)) - - def MacroImplTparamInstantiationError(atparams: List[Symbol], ex: NoInstance) = - compatibilityError( - "type parameters "+(atparams map (_.defString) mkString ", ")+" cannot be instantiated\n"+ - ex.getMessage) - } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 93f73f1bbe..40f284c94c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -11,6 +11,8 @@ 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.runtime.{universe => ru} +import scala.reflect.macros.compiler.DefaultMacroCompiler /** * Code to deal with macros, namely with: @@ -37,7 +39,7 @@ import scala.reflect.macros.runtime.AbortMacroException * (Expr(elems)) * (TypeTag(Int)) */ -trait Macros extends scala.tools.reflect.FastTrack with Traces { +trait Macros extends scala.tools.reflect.FastTrack with Traces with Helpers { self: Analyzer => import global._ @@ -76,6 +78,8 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * and various accounting information necessary when composing an argument list for the reflective invocation. */ private 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, @@ -110,19 +114,21 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { * * @scala.reflect.macros.internal.macroImpl( * `macro`( + * "isBundle" = false, * "signature" = List(-1), * "methodName" = "impl", * "versionFormat" = , * "className" = "Macros$")) */ private object MacroImplBinding { - val versionFormat = 2 + val versionFormat = 3 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 b: Boolean => Literal(Constant(b)) } def unpickleAtom(tree: Tree): Any = @@ -130,11 +136,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { 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(b: Boolean)) => b } 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 = { @@ -157,12 +163,13 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { case _ => IMPLPARAM_OTHER } - val transformed = transformTypeTagEvidenceParams(paramss, (param, tparam) => tparam) + val transformed = transformTypeTagEvidenceParams(macroImplRef, (param, tparam) => tparam) mmap(transformed)(p => if (p.isTerm) fingerprint(p.info) else p.paramPos) } val payload = List[(String, Any)]( "versionFormat" -> versionFormat, + "isBundle" -> isBundle, "className" -> className, "methodName" -> macroImpl.name.toString, "signature" -> signature @@ -205,10 +212,11 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { val pickleVersionFormat = payload("versionFormat").asInstanceOf[Int] if (versionFormat != pickleVersionFormat) throw new Error(s"macro impl binding format mismatch: expected $versionFormat, actual $pickleVersionFormat") + val isBundle = payload("isBundle").asInstanceOf[Boolean] val className = payload("className").asInstanceOf[String] val methodName = payload("methodName").asInstanceOf[String] val signature = payload("signature").asInstanceOf[List[List[Int]]] - MacroImplBinding(className, methodName, signature, targs) + MacroImplBinding(isBundle, className, methodName, signature, targs) } } @@ -222,330 +230,82 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { 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 - } - - private def dealiasAndRewrap(tp: Type)(fn: Type => Type): Type = { - if (isRepeatedParamType(tp)) scalaRepeatedType(fn(tp.typeArgs.head.dealias)) - else fn(tp.dealias) - } - - /** Increases metalevel of the type, i.e. transforms: - * * T to c.Expr[T] - * - * @see Metalevels.scala for more information and examples about metalevels - */ - private def increaseMetalevel(c: Symbol, tp: Type): Type = dealiasAndRewrap(tp) { - case tp => typeRef(SingleType(NoPrefix, c), MacroContextExprClass, List(tp)) - } - - /** Decreases metalevel of the type, i.e. transforms: - * * c.Expr[T] to T - * - * @see Metalevels.scala for more information and examples about metalevels - */ - private def decreaseMetalevel(tp: Type): Type = dealiasAndRewrap(tp) { - case ExprClassOf(runtimeType) => runtimeType - case _ => AnyClass.tpe // so that macro impls with rhs = ??? don't screw up our inference - } - - def computeMacroDefTypeFromMacroImpl(macroDdef: DefDef, macroImplSig: MacroImplSig): Type = { - // Step I. Transform c.Expr[T] to T and c.Tree to - var runtimeType = decreaseMetalevel(macroImplSig.ret) - - // Step II. Transform type parameters of a macro implementation into type arguments in a macro definition's body - runtimeType = runtimeType.substituteTypes(macroImplSig.tparams, 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(macroImplSig.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) - } - - /** Signature of a macro implementation, used to check def <-> impl correspondence. - * - * Technically it can be just an alias to MethodType, but promoting it to a first-class entity - * provides better encapsulation and convenient syntax for pattern matching. - */ - case class MacroImplSig(tparams: List[Symbol], paramss: List[List[Symbol]], ret: Type) + } - /** An actual macro implementation signature extracted from a macro implementation method. - * - * In the example above for the following macro impl: - * def fooBar[T: c.WeakTypeTag] - * (c: scala.reflect.macros.Context) - * (xs: c.Expr[List[T]]) - * : c.Expr[T] = ... - * - * 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. - * - * This method cannot be reduced to just macroImpl.info, because macro implementations might - * come in different shapes. If the implementation is an apply method of a Macro-compatible object, - * then it won't have (c: Context) in its parameters, but will rather refer to Macro.c. - * - * @param macroImpl The macro implementation symbol - */ - def macroImplSig(macroImpl: Symbol): MacroImplSig = { - val tparams = macroImpl.typeParams - val paramss = transformTypeTagEvidenceParams(macroImpl.paramss, (param, tparam) => NoSymbol) - val ret = macroImpl.info.finalResultType - MacroImplSig(tparams, paramss, ret) - } + UnsigmaTypeMap(tpe) + case _ => + tpe + } - /** A reference macro implementation signature extracted from 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. - * - * Also note that we need a DefDef, not the corresponding MethodSymbol, because that symbol would be of no use for us. - * Macro signatures are verified when typechecking macro defs, which means that at that moment inspecting macroDef.info - * means asking for cyclic reference errors. - * - * We need macro implementation symbol as well, because the return type of the macro definition might be omitted, - * and in that case we'd need to infer it from the return type of the macro implementation. Luckily for us, we can - * use that symbol without a risk of running into cycles. - * - * @param typer Typechecker of `macroDdef` - * @param macroDdef The macro definition tree - * @param macroImpl The macro implementation symbol - */ - def referenceMacroImplSig(typer: Typer, macroDdef: DefDef, macroImpl: Symbol): MacroImplSig = { - // had to move method's body to an object because of the recursive dependencies between sigma and param - object SigGenerator { - val cache = scala.collection.mutable.Map[Symbol, Symbol]() - val macroDef = macroDdef.symbol - val ctxParam = makeParam(nme.macroContext, macroDdef.pos, MacroContextClass.tpe, SYNTHETIC) - val paramss = List(ctxParam) :: mmap(macroDdef.vparamss)(param) - val macroDefRet = - if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe - else computeMacroDefTypeFromMacroImpl(macroDdef, macroImplSig(macroImpl)) - val implReturnType = sigma(increaseMetalevel(ctxParam, macroDefRet)) - - 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(macroDdef.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 param(tree: Tree): Symbol = ( - cache.getOrElseUpdate(tree.symbol, { - val sym = tree.symbol - assert(sym.isTerm, s"sym = $sym, tree = $tree") - makeParam(sym.name, sym.pos, sigma(increaseMetalevel(ctxParam, sym.tpe)), sym.flags) - }) - ) + unsigma(runtimeType) + case _ => + ErrorType } - - import SigGenerator._ - val result = MacroImplSig(macroDdef.tparams map (_.symbol), paramss, implReturnType) - macroLogVerbose(sm""" - |generating macroImplSig for: $macroDdef - |result is: $result - """.trim) - result } - /** 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 ., 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 actual macro impl signature extracted from the macro impl ("a" stands for "actual") - // rXXX (e.g. rparamss) => characteristics of the reference macro impl signature synthesized from the macro def ("r" stands for "reference") - val macroImpl = typed.symbol - val MacroImplSig(atparams, aparamss, aret) = macroImplSig(macroImpl) - val MacroImplSig(_, rparamss, rret) = referenceMacroImplSig(typer, macroDdef, macroImpl) - val atvars = atparams map freshVar - def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars) - - // we only check correspondence between value parameterss - // type parameters of macro defs and macro impls don't have to coincide with each other - val implicitParams = aparamss.flatten filter (_.isImplicit) - if (implicitParams.nonEmpty) MacroImplNonTagImplicitParameters(implicitParams) - if (aparamss.length != rparamss.length) MacroImplParamssMismatchError() - map2(aparamss, rparamss)((aparams, rparams) => { - if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams) - if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams) - }) - - try { - // cannot fuse this map2 and the map2 above 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) + macroLogVerbose("typechecking macro def %s at %s".format(macroDef, macroDdef.pos)) + if (fastTrack contains macroDef) { + macroLogVerbose("typecheck terminated unexpectedly: macro is fast track") + assert(!macroDdef.tpt.isEmpty, "fast track macros must provide result type") + EmptyTree + } else { + 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) } - } catch { - case ex: NoInstance => MacroImplTparamInstantiationError(atparams, ex) } } @@ -555,6 +315,10 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { */ lazy val macroClassloader: ClassLoader = findMacroClassLoader() + /** Reflective mirror built from `macroClassloader`. + */ + private lazy val macroMirror: ru.JavaMirror = ru.runtimeMirror(macroClassloader) + /** 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. @@ -574,28 +338,32 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { } else { macroRuntimesCache.getOrElseUpdate(macroDef, { val binding = loadMacroImplBinding(macroDef) + val isBundle = binding.isBundle 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) { + if (binding.className == Predef_???.owner.javaClassName && 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])): _*) + val implContainerSym = macroMirror.classSymbol(Class.forName(className, true, macroClassloader)) + val implMethSym = implContainerSym.typeSignature.member(ru.TermName(methName)).asMethod + macroLogVerbose(s"successfully loaded macro impl as ($implContainerSym, $implMethSym)") + args => { + val implContainer = + if (isBundle) { + val implCtorSym = implContainerSym.typeSignature.member(ru.nme.CONSTRUCTOR).asMethod + macroMirror.reflectClass(implContainerSym).reflectConstructor(implCtorSym)(args.c) + } else { + macroMirror.reflectModule(implContainerSym.module.asModule).instance + } + val implMeth = macroMirror.reflect(implContainer).reflectMethod(implMethSym) + val implArgs = if (isBundle) args.others else args.c +: args.others + implMeth(implArgs: _*) + } } catch { case ex: Exception => macroLogVerbose(s"macro runtime failed to load: ${ex.toString}") @@ -640,31 +408,32 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { import typer.TyperErrorGen._ val isNullaryArgsEmptyParams = argss.isEmpty && paramss == ListOfNil - if (paramss.length < argss.length) MacroTooManyArgumentLists(expandee) - if (paramss.length > argss.length && !isNullaryArgsEmptyParams) MacroTooFewArgumentLists(expandee) + if (paramss.length < argss.length) MacroTooManyArgumentListsError(expandee) + if (paramss.length > argss.length && !isNullaryArgsEmptyParams) MacroTooFewArgumentListsError(expandee) val macroImplArgs: List[Any] = if (fastTrack contains macroDef) { // Take a dry run of the fast track implementation if (fastTrack(macroDef) validate expandee) argss.flatten - else typer.TyperErrorGen.MacroTooFewArgumentLists(expandee) + else MacroTooFewArgumentListsError(expandee) } else { val binding = loadMacroImplBinding(macroDef) - if (binding.className == Predef_???.owner.fullName.toString && binding.methName == Predef_???.name.encoded) Nil + if (binding.className == Predef_???.owner.javaClassName && binding.methName == Predef_???.name.encoded) Nil else { + 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, binding.signature.tail)((args, defParams, implParams) => { + val trees = map3(argss, paramss, signature)((args, defParams, implParams) => { val isVarargs = isVarArgsList(defParams) if (isVarargs) { - if (defParams.length > args.length + 1) MacroTooFewArguments(expandee) + if (defParams.length > args.length + 1) MacroTooFewArgumentsError(expandee) } else { - if (defParams.length < args.length) MacroTooManyArguments(expandee) - if (defParams.length > args.length) MacroTooFewArguments(expandee) + if (defParams.length < args.length) MacroTooManyArgumentsError(expandee) + if (defParams.length > args.length) MacroTooFewArgumentsError(expandee) } val wrappedArgs = mapWithIndex(args)((arg, j) => { @@ -700,7 +469,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { // 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 = binding.signature.flatten filter (_ >= IMPLPARAM_TAG) map (paramPos => { + val tags = signature.flatten filter (_ >= IMPLPARAM_TAG) map (paramPos => { val targ = binding.targs(paramPos).tpe.typeSymbol val tpe = if (targ.isTypeParameterOrSkolem) { if (targ.owner == macroDef) { diff --git a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala index 64fcda3b80..a208924acb 100644 --- a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala +++ b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala @@ -127,4 +127,31 @@ trait StdAttachments { /** Determines whether the given tree has an associated SuperArgsAttachment. */ def hasSuperArgs(tree: Tree): Boolean = superArgs(tree).nonEmpty + + /** @see markMacroImplRef + */ + case object MacroImplRefAttachment + + /** Marks the tree as a macro impl reference, which is a naked reference to a method. + * + * This is necessary for typechecking macro impl references (see `DefaultMacroCompiler.defaultResolveMacroImpl`), + * because otherwise typing a naked reference will result in the "follow this method with `_' if you want to + * treat it as a partially applied function" errors. + * + * This mark suppresses adapt except for when the annottee is a macro application. + */ + def markMacroImplRef(tree: Tree): Tree = tree.updateAttachment(MacroImplRefAttachment) + + /** Unmarks the tree as a macro impl reference (see `markMacroImplRef` for more information). + * + * This is necessary when a tree that was previously deemed to be a macro impl reference, + * typechecks to be a macro application. Then we need to unmark it, expand it and try to treat + * its expansion as a macro impl reference. + */ + def unmarkMacroImplRef(tree: Tree): Tree = tree.removeAttachment[MacroImplRefAttachment.type] + + /** Determines whether a tree should or should not be adapted, + * because someone has put MacroImplRefAttachment on it. + */ + def isMacroImplRef(tree: Tree): Boolean = tree.attachments.get[MacroImplRefAttachment.type].isDefined } \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 5916116ef3..c9aa27cf49 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1234,7 +1234,10 @@ trait Typers extends Adaptations with Tags { } // begin adapt - tree.tpe match { + if (isMacroImplRef(tree)) { + if (treeInfo.isMacroApplication(tree)) adapt(unmarkMacroImplRef(tree), mode, pt, original) + else tree + } else tree.tpe match { case atp @ AnnotatedType(_, _, _) if canAdaptAnnotations(tree, this, mode, pt) => // (-1) adaptAnnotations(tree, this, mode, pt) case ct @ ConstantType(value) if mode.inNone(TYPEmode | FUNmode) && (ct <:< pt) && canAdaptConstantTypeToLiteral => // (0) @@ -5534,7 +5537,7 @@ trait Typers extends Adaptations with Tags { val isMacroBodyOkay = !tree.symbol.isErroneous && !(tree1 exists (_.isErroneous)) && tree1 != EmptyTree val shouldInheritMacroImplReturnType = ddef.tpt.isEmpty - if (isMacroBodyOkay && shouldInheritMacroImplReturnType) computeMacroDefTypeFromMacroImpl(ddef, macroImplSig(tree1.symbol)) else AnyTpe + if (isMacroBodyOkay && shouldInheritMacroImplReturnType) computeMacroDefTypeFromMacroImplRef(ddef, tree1) else AnyTpe } def transformedOr(tree: Tree, op: => Tree): Tree = transformed remove tree match { diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 3cf19396ee..5a0ff4f6db 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -5,6 +5,7 @@ import scala.reflect.reify.Taggers import scala.tools.nsc.typechecker.{ Analyzer, Macros } import scala.reflect.runtime.Macros.currentMirror import scala.reflect.api.Universe +import scala.reflect.macros.compiler.DefaultMacroCompiler /** Optimizes system macro expansions by hardwiring them directly to their implementations * bypassing standard reflective load and invoke to avoid the overhead of Java/Scala reflection. diff --git a/src/reflect/scala/reflect/api/Trees.scala b/src/reflect/scala/reflect/api/Trees.scala index f4ada814af..f7a6a68946 100644 --- a/src/reflect/scala/reflect/api/Trees.scala +++ b/src/reflect/scala/reflect/api/Trees.scala @@ -296,6 +296,20 @@ trait Trees { self: Universe => def name: Name } + /** The constructor/extractor for `RefTree` instances. + * @group Extractors + */ + val RefTree: RefTreeExtractor + + /** An extractor class to create and pattern match with syntax `RefTree(qual, name)`. + * This AST node corresponds to either Ident, Select or SelectFromTypeTree. + * @group Extractors + */ + abstract class RefTreeExtractor { + def apply(qualifier: Tree, name: Name): RefTree + def unapply(refTree: RefTree): Option[(Tree, Name)] + } + /** A tree which defines a symbol-carrying entity. * @group Trees * @template diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 630572464d..5478c54eec 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -501,6 +501,13 @@ trait Definitions extends api.StandardDefinitions { lazy val OptManifestClass = requiredClass[scala.reflect.OptManifest[_]] lazy val NoManifest = requiredModule[scala.reflect.NoManifest.type] + lazy val TreesClass = getClassIfDefined("scala.reflect.api.Trees") // defined in scala-reflect.jar, so we need to be careful + lazy val TreesTreeType = if (TreesClass != NoSymbol) getTypeMember(TreesClass, tpnme.Tree) else NoSymbol + object TreeType { + def unapply(tpe: Type): Boolean = unapply(tpe.typeSymbol) + def unapply(sym: Symbol): Boolean = sym.overrideChain contains TreesTreeType + } + lazy val ExprsClass = getClassIfDefined("scala.reflect.api.Exprs") // defined in scala-reflect.jar, so we need to be careful lazy val ExprClass = if (ExprsClass != NoSymbol) getMemberClass(ExprsClass, tpnme.Expr) else NoSymbol def ExprSplice = if (ExprsClass != NoSymbol) getMemberMethod(ExprClass, nme.splice) else NoSymbol @@ -533,6 +540,7 @@ trait Definitions extends api.StandardDefinitions { lazy val TypeCreatorClass = getClassIfDefined("scala.reflect.api.TypeCreator") // defined in scala-reflect.jar, so we need to be careful lazy val TreeCreatorClass = getClassIfDefined("scala.reflect.api.TreeCreator") // defined in scala-reflect.jar, so we need to be careful + lazy val MacroClass = getClassIfDefined("scala.reflect.macros.Macro") // defined in scala-reflect.jar, so we need to be careful lazy val MacroContextClass = getClassIfDefined("scala.reflect.macros.Context") // defined in scala-reflect.jar, so we need to be careful def MacroContextPrefix = if (MacroContextClass != NoSymbol) getMemberMethod(MacroContextClass, nme.prefix) else NoSymbol def MacroContextPrefixType = if (MacroContextClass != NoSymbol) getTypeMember(MacroContextClass, tpnme.PrefixType) else NoSymbol @@ -650,6 +658,12 @@ trait Definitions extends api.StandardDefinitions { } def isTupleType(tp: Type) = isTupleTypeDirect(tp.dealiasWiden) + def isMacroBundleType(tp: Type) = { + val isNonTrivial = tp != ErrorType && tp != NothingTpe && tp != NullTpe + val isMacroCompatible = MacroClass != NoSymbol && tp <:< MacroClass.tpe + isNonTrivial && isMacroCompatible + } + lazy val ProductRootClass: ClassSymbol = requiredClass[scala.Product] def Product_productArity = getMemberMethod(ProductRootClass, nme.productArity) def Product_productElement = getMemberMethod(ProductRootClass, nme.productElement) diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index a20307882d..81fffc833c 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -8,6 +8,7 @@ package reflect package internal import java.security.MessageDigest +import java.util.UUID.randomUUID import Chars.isOperatorPart import scala.annotation.switch import scala.language.implicitConversions @@ -290,6 +291,9 @@ trait StdNames { val FAKE_LOCAL_THIS: NameType = "this$" val LAZY_LOCAL: NameType = "$lzy" val LAZY_SLOW_SUFFIX: NameType = "$lzycompute" + val MACRO_INVOKER_PACKAGE: NameType = "scala.reflect.macros.synthetic" + // TODO: if I use dollars in MACRO_INVOKER_SUFFIX, as in "$Invoker$", then Scala reflection fails to load implementations + val MACRO_INVOKER_SUFFIX: NameType = "Invoker" val UNIVERSE_BUILD_PREFIX: NameType = "$u.build." val UNIVERSE_PREFIX: NameType = "$u." val UNIVERSE_SHORT: NameType = "$u" @@ -584,6 +588,7 @@ trait StdNames { val box: NameType = "box" val build : NameType = "build" val bytes: NameType = "bytes" + val c: NameType = "c" val canEqual_ : NameType = "canEqual" val checkInitialized: NameType = "checkInitialized" val classOf: NameType = "classOf" diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index e9ef9c7945..d1e8a04553 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -836,8 +836,17 @@ abstract class TreeInfo { } def unapply(tree: Tree) = refPart(tree) match { - case ref: RefTree => Some((ref.qualifier.symbol, ref.symbol, dissectApplied(tree).targs)) - case _ => None + case ref: RefTree => { + val isBundle = definitions.isMacroBundleType(ref.qualifier.tpe) + val owner = + if (isBundle) ref.qualifier.tpe.typeSymbol + else { + val sym = ref.qualifier.symbol + if (sym.isModule) sym.moduleClass else sym + } + Some((isBundle, owner, ref.symbol, dissectApplied(tree).targs)) + } + case _ => None } } diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index de0b4e8247..979ee54b4f 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -253,6 +253,19 @@ trait Trees extends api.Trees { self: SymbolTable => def name: Name } + object RefTree extends RefTreeExtractor { + def apply(qualifier: Tree, name: Name): RefTree = qualifier match { + case EmptyTree => + Ident(name) + case qual if qual.isTerm => + Select(qual, name) + case qual if qual.isType => + assert(name.isTypeName, s"qual = $qual, name = $name") + SelectFromTypeTree(qual, name.toTypeName) + } + def unapply(refTree: RefTree): Option[(Tree, Name)] = Some((refTree.qualifier, refTree.name)) + } + abstract class DefTree extends SymTree with NameTree with DefTreeApi { def name: Name override def isDef = true diff --git a/src/reflect/scala/reflect/macros/Macro.scala b/src/reflect/scala/reflect/macros/Macro.scala new file mode 100644 index 0000000000..44bedf483d --- /dev/null +++ b/src/reflect/scala/reflect/macros/Macro.scala @@ -0,0 +1,39 @@ +package scala.reflect +package macros + +/** + * EXPERIMENTAL + * + * Traditionally macro implementations are defined as methods, + * but this trait provides an alternative way of encoding macro impls as + * bundles, traits which extend `scala.reflect.macros.Macro`. + * + * Instead of: + * + * def impl[T: c.WeakTypeTag](c: Context)(x: c.Expr[Int]) = ... + * + * One can write: + * + * trait Impl extends Macro { + * def apply[T: c.WeakTypeTag](x: c.Expr[Int]) = ... + * } + * + * Without changing anything else at all. + * + * This language feature is useful in itself in cases when macro implementations + * are complex and need to be modularized. State of the art technique of addressing this need is quite heavyweight: + * http://docs.scala-lang.org/overviews/macros/overview.html#writing_bigger_macros. + * + * However utility of this approach to writing macros isn't limited to just convenience. + * When a macro implementation becomes not just a function, but a full-fledged module, + * it can define callbacks that will be called by the compiler upon interesting events. + * In subsequent commits I will add support for programmable type inference + */ +trait Macro { + /** The context to be used by the macro implementation. + * + * Vanilla macro implementations have to carry it in their signatures, however when a macro is a full-fledged module, + * it can define the context next to the implementation, makes implementation signature more lightweight. + */ + val c: Context +} diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index adee155db0..a3684f602f 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -972,8 +972,8 @@ private[reflect] trait JavaMirrors extends internal.SymbolTable with api.JavaUni javaTypeToValueClass(jclazz) orElse lookupClass assert (cls.isType, - sm"""${if (cls == NoSymbol) "not a type: symbol" else "no symbol could be"} - | loaded from $jclazz in $owner with name $simpleName and classloader $classLoader""") + (if (cls != NoSymbol) s"not a type: symbol $cls" else "no symbol could be") + + s" loaded from $jclazz in $owner with name $simpleName and classloader $classLoader") cls.asClass } diff --git a/test/files/neg/macro-invalidimpl.check b/test/files/neg/macro-invalidimpl.check index 7177a8cdee..aaf4f88fc2 100644 --- a/test/files/neg/macro-invalidimpl.check +++ b/test/files/neg/macro-invalidimpl.check @@ -1,15 +1,23 @@ -Macros_Test_2.scala:5: error: macro implementation must be in statically accessible object +Macros_Test_2.scala:5: error: macro implementation reference has wrong shape. required: +macro [].[[]] or +macro [].[[]] def foo(x: Any) = macro impls.foo - ^ -Macros_Test_2.scala:10: error: macro implementation must be in statically accessible object + ^ +Macros_Test_2.scala:10: error: macro implementation reference has wrong shape. required: +macro [].[[]] or +macro [].[[]] def foo(x: Any) = macro impls.foo - ^ -Macros_Test_2.scala:18: error: macro implementation must be in statically accessible object + ^ +Macros_Test_2.scala:18: error: macro implementation reference has wrong shape. required: +macro [].[[]] or +macro [].[[]] def foo(x: Any) = macro Impls3.foo - ^ -Macros_Test_2.scala:22: error: macro implementation must be in statically accessible object + ^ +Macros_Test_2.scala:22: error: macro implementation reference has wrong shape. required: +macro [].[[]] or +macro [].[[]] def foo(x: Any) = macro Impls4.foo - ^ + ^ Macros_Test_2.scala:26: error: ambiguous reference to overloaded definition, both method foo in object Impls5 of type (c: scala.reflect.macros.Context)(x: c.Expr[Any], y: c.Expr[Any])Nothing and method foo in object Impls5 of type (c: scala.reflect.macros.Context)(x: c.Expr[Any])Nothing @@ -27,17 +35,17 @@ Macros_Test_2.scala:31: error: macro implementation has wrong shape: found : (c: scala.reflect.macros.Context)(): c.Expr[Unit] number of parameter sections differ def foo1 = macro Impls6.fooEmpty - ^ + ^ Macros_Test_2.scala:32: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context)(): c.Expr[Unit] found : (c: scala.reflect.macros.Context): c.Expr[Unit] number of parameter sections differ def bar1() = macro Impls6.fooNullary - ^ + ^ Macros_Test_2.scala:36: error: type arguments [String] do not conform to method foo's type parameter bounds [U <: Int] def foo = macro Impls7.foo[String] ^ Macros_Test_2.scala:53: error: macro implementation must be public def foo = macro Impls8.impl - ^ + ^ 10 errors found diff --git a/test/files/neg/macro-invalidret.check b/test/files/neg/macro-invalidret.check index d6664e6882..8c6ed4eb45 100644 --- a/test/files/neg/macro-invalidret.check +++ b/test/files/neg/macro-invalidret.check @@ -3,11 +3,11 @@ Macros_Test_2.scala:2: error: macro implementation has wrong shape: found : (c: scala.reflect.macros.Context): Int type mismatch for return type: Int does not conform to c.Expr[Any] def foo1 = macro Impls.foo1 - ^ + ^ Macros_Test_2.scala:3: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context): c.Expr[Any] found : (c: scala.reflect.macros.Context): reflect.runtime.universe.Literal type mismatch for return type: reflect.runtime.universe.Literal does not conform to c.Expr[Any] def foo2 = macro Impls.foo2 - ^ + ^ two errors found diff --git a/test/files/neg/macro-invalidshape.check b/test/files/neg/macro-invalidshape.check index cefc95b763..40a2952569 100644 --- a/test/files/neg/macro-invalidshape.check +++ b/test/files/neg/macro-invalidshape.check @@ -1,17 +1,15 @@ -Macros_Test_2.scala:2: error: macro body has wrong shape: - required: macro [].[[]] +Macros_Test_2.scala:2: error: macro implementation reference has wrong shape. required: +macro [].[[]] or +macro [].[[]] def foo1(x: Any) = macro 2 ^ -Macros_Test_2.scala:3: error: macro body has wrong shape: - required: macro [].[[]] +Macros_Test_2.scala:3: error: macro implementation reference has wrong shape. required: +macro [].[[]] or +macro [].[[]] def foo2(x: Any) = macro Impls.foo(null)(null) ^ -Macros_Test_2.scala:4: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses - def foo3(x: Any) = macro {2; Impls.foo} - ^ Macros_Test_2.scala:4: error: missing arguments for method foo in object Impls; follow this method with `_' if you want to treat it as a partially applied function def foo3(x: Any) = macro {2; Impls.foo} ^ -one warning found three errors found diff --git a/test/files/neg/macro-invalidsig-params-badtype.check b/test/files/neg/macro-invalidsig-params-badtype.check index 3ec40d7e5b..3cc1c9abf1 100644 --- a/test/files/neg/macro-invalidsig-params-badtype.check +++ b/test/files/neg/macro-invalidsig-params-badtype.check @@ -1,7 +1,7 @@ Impls_Macros_1.scala:8: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context)(x: c.Expr[Int]): c.Expr[Any] - found : (c: scala.reflect.macros.Context)(x: c.universe.Tree): Nothing -type mismatch for parameter x: c.Expr[Int] does not conform to c.universe.Tree + found : (c: scala.reflect.macros.Context)(x: Int): Nothing +type mismatch for parameter x: c.Expr[Int] does not conform to Int def foo(x: Int) = macro Impls.foo - ^ + ^ one error found diff --git a/test/files/neg/macro-invalidsig-params-badtype/Impls_Macros_1.scala b/test/files/neg/macro-invalidsig-params-badtype/Impls_Macros_1.scala index ab90b85881..175683d6d3 100644 --- a/test/files/neg/macro-invalidsig-params-badtype/Impls_Macros_1.scala +++ b/test/files/neg/macro-invalidsig-params-badtype/Impls_Macros_1.scala @@ -1,7 +1,7 @@ import scala.reflect.macros.{Context => Ctx} object Impls { - def foo(c: Ctx)(x: c.universe.Tree) = ??? + def foo(c: Ctx)(x: Int) = ??? } object Macros { diff --git a/test/files/neg/macro-invalidsig.check b/test/files/neg/macro-invalidsig.check index 52074cf783..cbdaf51081 100644 --- a/test/files/neg/macro-invalidsig.check +++ b/test/files/neg/macro-invalidsig.check @@ -3,67 +3,67 @@ Macros_Test_2.scala:2: error: macro implementation has wrong shape: found : (c: scala.reflect.macros.Context)(implicit evidence$2: Numeric[U]): c.universe.Literal macro implementations cannot have implicit parameters other than WeakTypeTag evidences def foo[U] = macro Impls1.foo[U] - ^ + ^ Macros_Test_2.scala:6: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context): c.Expr[Any] found : : Nothing number of parameter sections differ def foo = macro Impls2.foo - ^ + ^ Macros_Test_2.scala:10: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context): c.Expr[Any] found : (c: scala.reflect.api.Universe): Nothing type mismatch for parameter c: scala.reflect.macros.Context does not conform to scala.reflect.api.Universe def foo = macro Impls3.foo - ^ + ^ Macros_Test_2.scala:14: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context): c.Expr[Any] found : (cs: scala.reflect.macros.Context*): Nothing types incompatible for parameter cs: corresponding is not a vararg parameter def foo = macro Impls4.foo - ^ + ^ Macros_Test_2.scala:18: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context)(x: c.Expr[Any]): c.Expr[Any] found : (c: scala.reflect.macros.Context): Nothing number of parameter sections differ def foo(x: Any) = macro Impls5.foo - ^ + ^ Macros_Test_2.scala:22: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context)(x: c.Expr[Int]): c.Expr[Unit] found : (c: scala.reflect.macros.Context)(implicit x: c.Expr[Int]): c.Expr[Unit] macro implementations cannot have implicit parameters other than WeakTypeTag evidences def foo[U](x: Int) = macro Impls6.foo[T, U] - ^ + ^ Macros_Test_2.scala:26: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context)(x: c.Expr[Int]): c.Expr[Any] found : (c: scala.reflect.macros.Context)(x: c.Expr[Int], y: c.Expr[Int]): Nothing parameter lists have different length, found extra parameter y: c.Expr[Int] def foo(x: Int) = macro Impls7.foo - ^ + ^ Macros_Test_2.scala:30: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context)(x: c.Expr[Int]): c.Expr[Any] found : (c: scala.reflect.macros.Context)(x: c.universe.Symbol): Nothing type mismatch for parameter x: c.Expr[Int] does not conform to c.universe.Symbol def foo(x: Int) = macro Impls8.foo - ^ + ^ Macros_Test_2.scala:34: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context)(x: c.Expr[Int], y: c.Expr[Int]): c.Expr[Any] found : (c: scala.reflect.macros.Context)(xs: c.Expr[Int]*): Nothing parameter lists have different length, required extra parameter y: c.Expr[Int] def foo(x: Int, y: Int) = macro Impls9.foo - ^ + ^ Macros_Test_2.scala:38: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context)(x: c.Expr[Int], y: c.Expr[Int]): c.Expr[Any] found : (c: scala.reflect.macros.Context)(y: c.Expr[Int], x: c.Expr[Int]): Nothing parameter names differ: x != y def foo(x: Int, y: Int) = macro Impls10.foo - ^ + ^ Macros_Test_2.scala:42: error: macro implementation has wrong shape: required: (c: scala.reflect.macros.Context): c.Expr[Any] found : (c: scala.reflect.macros.Context)(U: c.universe.Type): Nothing number of parameter sections differ def foo[U] = macro Impls11.foo[U] - ^ + ^ Macros_Test_2.scala:46: error: type arguments [U] do not conform to method foo's type parameter bounds [U <: String] def foo[U] = macro Impls12.foo[U] ^ @@ -72,10 +72,10 @@ Macros_Test_2.scala:50: error: type arguments [U] do not conform to method foo's ^ Macros_Test_2.scala:54: error: wrong number of type parameters for method foo: [U](c: scala.reflect.macros.Context)(implicit evidence$4: c.WeakTypeTag[U])Nothing def foo = macro Impls14.foo - ^ + ^ Macros_Test_2.scala:59: error: wrong number of type parameters for method foo: [T, U, V](c: scala.reflect.macros.Context)(implicit evidence$5: c.WeakTypeTag[T], implicit evidence$6: c.WeakTypeTag[U], implicit V: c.WeakTypeTag[V])c.Expr[Unit] def foo15[V] = macro Impls15.foo - ^ + ^ Macros_Test_2.scala:60: error: wrong number of type parameters for method foo: [T, U, V](c: scala.reflect.macros.Context)(implicit evidence$7: c.WeakTypeTag[T], implicit evidence$8: c.WeakTypeTag[U], implicit V: c.WeakTypeTag[V])c.Expr[Unit] def foo16[V] = macro Impls16.foo[V] ^ diff --git a/test/files/neg/t5689.check b/test/files/neg/t5689.check index 50aaa7dbfe..ad9b79cdcb 100644 --- a/test/files/neg/t5689.check +++ b/test/files/neg/t5689.check @@ -3,5 +3,5 @@ t5689.scala:4: error: macro implementation has wrong shape: found : (c: scala.reflect.macros.Context)(i: c.Expr[Double]): c.Expr[Int] type mismatch for return type: c.Expr[Int] does not conform to c.Expr[String] def returnsString(i: Double): String = macro returnsIntImpl - ^ + ^ one error found diff --git a/test/files/run/macro-bodyexpandstoimpl/Impls_1.scala b/test/files/run/macro-bodyexpandstoimpl/Impls_1.scala index 9c1e4ee46d..56c5252f31 100644 --- a/test/files/run/macro-bodyexpandstoimpl/Impls_1.scala +++ b/test/files/run/macro-bodyexpandstoimpl/Impls_1.scala @@ -7,6 +7,8 @@ object Impls { def refToFoo_impl(c: Ctx)(dummy: c.Expr[Int]) = { import c.universe._ val body = Select(Ident(TermName("Impls")), TermName("foo")) + val global = c.universe.asInstanceOf[scala.tools.nsc.Global] + global.analyzer.markMacroImplRef(body.asInstanceOf[global.Tree]) c.Expr[Int](body) } } \ No newline at end of file diff --git a/test/files/run/macro-bundle.check b/test/files/run/macro-bundle.check new file mode 100644 index 0000000000..2107454960 --- /dev/null +++ b/test/files/run/macro-bundle.check @@ -0,0 +1,3 @@ +() +Int +() diff --git a/test/files/run/macro-bundle.flags b/test/files/run/macro-bundle.flags new file mode 100644 index 0000000000..cd66464f2f --- /dev/null +++ b/test/files/run/macro-bundle.flags @@ -0,0 +1 @@ +-language:experimental.macros \ No newline at end of file diff --git a/test/files/run/macro-bundle/Impls_Macros_1.scala b/test/files/run/macro-bundle/Impls_Macros_1.scala new file mode 100644 index 0000000000..3f651c9a43 --- /dev/null +++ b/test/files/run/macro-bundle/Impls_Macros_1.scala @@ -0,0 +1,13 @@ +import scala.reflect.macros.Context +import scala.reflect.macros.Macro + +trait Impl extends Macro { + def mono = c.literalUnit + def poly[T: c.WeakTypeTag] = c.literal(c.weakTypeOf[T].toString) + def weird = macro mono +} + +object Macros { + def mono = macro Impl.mono + def poly[T] = macro Impl.poly[T] +} \ No newline at end of file diff --git a/test/files/run/macro-bundle/Test_2.scala b/test/files/run/macro-bundle/Test_2.scala new file mode 100644 index 0000000000..428f809f9d --- /dev/null +++ b/test/files/run/macro-bundle/Test_2.scala @@ -0,0 +1,5 @@ +object Test extends App { + println(Macros.mono) + println(Macros.poly[Int]) + println(new Impl{val c = ???}.weird) +} \ No newline at end of file diff --git a/test/files/run/macro-toplevel-companion-b/Test_2.scala b/test/files/run/macro-toplevel-companion-b/Test_2.scala index ca202d053f..4e766bde89 100644 --- a/test/files/run/macro-toplevel-companion-b/Test_2.scala +++ b/test/files/run/macro-toplevel-companion-b/Test_2.scala @@ -7,5 +7,5 @@ import Macros._ object Test extends App { val tb = cm.mkToolBox() try tb.compile(Select(Ident(TermName("Macros")), TermName("foo"))) - catch { case ToolBoxError(message, _) => println("""macroSynthetic-.*?\.scala""".r.replaceAllIn(message, "")) } + catch { case ToolBoxError(message, _) => println("""(Found in|and) .*?compileLateSynthetic-.*?\.scala""".r.replaceAllIn(message, m => m.group(1) + " ")) } } \ No newline at end of file diff --git a/test/files/run/macro-toplevel-companion-c.scala b/test/files/run/macro-toplevel-companion-c.scala index 0e99903158..c315f8b942 100644 --- a/test/files/run/macro-toplevel-companion-c.scala +++ b/test/files/run/macro-toplevel-companion-c.scala @@ -45,7 +45,7 @@ object Test extends DirectTest { log("Compiling Test_2...") if (compileTest()) log("Success!") else log("Failed...") } - println("""macroSynthetic-.*?\.scala""".r.replaceAllIn(baos.toString, "")) + println("""(Found in|and) .*?compileLateSynthetic-.*?\.scala""".r.replaceAllIn(baos.toString, m => m.group(1) + " ")) System.setErr(prevErr) } } \ No newline at end of file -- cgit v1.2.3 From 463ef75e2f8e15d2e835dd3c2467206fd52b6246 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Wed, 27 Feb 2013 15:15:36 +0100 Subject: refactors macro runtimes Following typedMacroBody, macroRuntime along with its friends has also been moved out into a separate component. --- .../macros/compiler/DefaultMacroCompiler.scala | 2 +- .../scala/reflect/macros/contexts/Aliases.scala | 35 +++++++++ .../scala/reflect/macros/contexts/Context.scala | 29 ++++++++ .../scala/reflect/macros/contexts/Enclosures.scala | 36 +++++++++ .../scala/reflect/macros/contexts/Evals.scala | 18 +++++ .../scala/reflect/macros/contexts/ExprUtils.scala | 34 +++++++++ .../scala/reflect/macros/contexts/FrontEnds.scala | 22 ++++++ .../reflect/macros/contexts/Infrastructure.scala | 16 ++++ .../scala/reflect/macros/contexts/Names.scala | 26 +++++++ .../scala/reflect/macros/contexts/Parsers.scala | 24 ++++++ .../scala/reflect/macros/contexts/Reifiers.scala | 77 +++++++++++++++++++ .../scala/reflect/macros/contexts/Synthetics.scala | 66 ++++++++++++++++ .../scala/reflect/macros/contexts/Traces.scala | 8 ++ .../scala/reflect/macros/contexts/Typers.scala | 52 +++++++++++++ .../scala/reflect/macros/runtime/Aliases.scala | 35 --------- .../scala/reflect/macros/runtime/Context.scala | 29 -------- .../scala/reflect/macros/runtime/Enclosures.scala | 36 --------- .../scala/reflect/macros/runtime/Evals.scala | 18 ----- .../scala/reflect/macros/runtime/ExprUtils.scala | 34 --------- .../scala/reflect/macros/runtime/FrontEnds.scala | 20 ----- .../reflect/macros/runtime/Infrastructure.scala | 16 ---- .../macros/runtime/JavaReflectionRuntimes.scala | 31 ++++++++ .../reflect/macros/runtime/MacroRuntimes.scala | 74 ++++++++++++++++++ .../scala/reflect/macros/runtime/Names.scala | 26 ------- .../scala/reflect/macros/runtime/Parsers.scala | 24 ------ .../scala/reflect/macros/runtime/Reifiers.scala | 77 ------------------- .../macros/runtime/ScalaReflectionRuntimes.scala | 33 ++++++++ .../scala/reflect/macros/runtime/Synthetics.scala | 66 ---------------- .../scala/reflect/macros/runtime/Traces.scala | 8 -- .../scala/reflect/macros/runtime/Typers.scala | 52 ------------- .../scala/reflect/macros/runtime/package.scala | 5 ++ src/compiler/scala/reflect/reify/Taggers.scala | 2 +- .../scala/tools/nsc/typechecker/Macros.scala | 87 +++------------------- .../tools/nsc/typechecker/StdAttachments.scala | 2 +- .../scala/tools/reflect/MacroImplementations.scala | 2 +- 35 files changed, 601 insertions(+), 521 deletions(-) create mode 100644 src/compiler/scala/reflect/macros/contexts/Aliases.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/Context.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/Enclosures.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/Evals.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/ExprUtils.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/FrontEnds.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/Infrastructure.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/Names.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/Parsers.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/Reifiers.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/Synthetics.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/Traces.scala create mode 100644 src/compiler/scala/reflect/macros/contexts/Typers.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Aliases.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Context.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Enclosures.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Evals.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/ExprUtils.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/FrontEnds.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Infrastructure.scala create mode 100644 src/compiler/scala/reflect/macros/runtime/JavaReflectionRuntimes.scala create mode 100644 src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Names.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Parsers.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Reifiers.scala create mode 100644 src/compiler/scala/reflect/macros/runtime/ScalaReflectionRuntimes.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Synthetics.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Traces.scala delete mode 100644 src/compiler/scala/reflect/macros/runtime/Typers.scala create mode 100644 src/compiler/scala/reflect/macros/runtime/package.scala (limited to 'src/compiler/scala/tools/nsc/typechecker') diff --git a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala index 749e730c0e..32c6da8007 100644 --- a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala +++ b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala @@ -2,7 +2,7 @@ package scala.reflect.macros package compiler import scala.tools.nsc.Global -import scala.reflect.macros.runtime.Context +import scala.reflect.macros.contexts.Context abstract class DefaultMacroCompiler extends Resolvers with Validators diff --git a/src/compiler/scala/reflect/macros/contexts/Aliases.scala b/src/compiler/scala/reflect/macros/contexts/Aliases.scala new file mode 100644 index 0000000000..cc64d97d85 --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Aliases.scala @@ -0,0 +1,35 @@ +package scala.reflect.macros +package contexts + +trait Aliases { + self: Context => + + override type Symbol = universe.Symbol + override type Type = universe.Type + override type Name = universe.Name + override type TermName = universe.TermName + override type TypeName = universe.TypeName + override type Tree = universe.Tree + override type Position = universe.Position + override type Scope = universe.Scope + override type Modifiers = universe.Modifiers + + override type Expr[+T] = universe.Expr[T] + override val Expr = universe.Expr + def Expr[T: WeakTypeTag](tree: Tree): Expr[T] = universe.Expr[T](mirror, universe.FixedMirrorTreeCreator(mirror, tree)) + + override type WeakTypeTag[T] = universe.WeakTypeTag[T] + override type TypeTag[T] = universe.TypeTag[T] + override val WeakTypeTag = universe.WeakTypeTag + override val TypeTag = universe.TypeTag + def WeakTypeTag[T](tpe: Type): WeakTypeTag[T] = universe.WeakTypeTag[T](mirror, universe.FixedMirrorTypeCreator(mirror, tpe)) + def TypeTag[T](tpe: Type): TypeTag[T] = universe.TypeTag[T](mirror, universe.FixedMirrorTypeCreator(mirror, tpe)) + override def weakTypeTag[T](implicit attag: WeakTypeTag[T]) = attag + override def typeTag[T](implicit ttag: TypeTag[T]) = ttag + override def weakTypeOf[T](implicit attag: WeakTypeTag[T]): Type = attag.tpe + override def typeOf[T](implicit ttag: TypeTag[T]): Type = ttag.tpe + + implicit class RichOpenImplicit(oi: universe.analyzer.OpenImplicit) { + def toImplicitCandidate = ImplicitCandidate(oi.info.pre, oi.info.sym, oi.pt, oi.tree) + } +} \ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/contexts/Context.scala b/src/compiler/scala/reflect/macros/contexts/Context.scala new file mode 100644 index 0000000000..bd1d7d5248 --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Context.scala @@ -0,0 +1,29 @@ +package scala.reflect.macros +package contexts + +import scala.tools.nsc.Global + +abstract class Context extends scala.reflect.macros.Context + with Aliases + with Enclosures + with Names + with Reifiers + with FrontEnds + with Infrastructure + with Typers + with Parsers + with Evals + with ExprUtils + with Synthetics + with Traces { + + val universe: Global + + val mirror: universe.Mirror = universe.rootMirror + + val callsiteTyper: universe.analyzer.Typer + + val prefix: Expr[PrefixType] + + val expandee: Tree +} diff --git a/src/compiler/scala/reflect/macros/contexts/Enclosures.scala b/src/compiler/scala/reflect/macros/contexts/Enclosures.scala new file mode 100644 index 0000000000..bb88c8d5e1 --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Enclosures.scala @@ -0,0 +1,36 @@ +package scala.reflect.macros +package contexts + +import scala.reflect.{ClassTag, classTag} + +trait Enclosures { + self: Context => + + import universe._ + + type MacroRole = analyzer.MacroRole + def APPLY_ROLE = analyzer.APPLY_ROLE + def macroRole: MacroRole + + private lazy val site = callsiteTyper.context + private lazy val enclTrees = site.enclosingContextChain map (_.tree) + private lazy val enclPoses = enclosingMacros map (_.macroApplication.pos) filterNot (_ eq NoPosition) + + private def lenientEnclosure[T <: Tree : ClassTag]: Tree = enclTrees collectFirst { case x: T => x } getOrElse EmptyTree + private def strictEnclosure[T <: Tree : ClassTag]: T = enclTrees collectFirst { case x: T => x } getOrElse (throw new EnclosureException(classTag[T].runtimeClass, enclTrees)) + + // vals are eager to simplify debugging + // after all we wouldn't save that much time by making them lazy + val macroApplication: Tree = expandee + def enclosingPackage: PackageDef = strictEnclosure[PackageDef] + val enclosingClass: Tree = lenientEnclosure[ImplDef] + def enclosingImpl: ImplDef = strictEnclosure[ImplDef] + def enclosingTemplate: Template = strictEnclosure[Template] + val enclosingImplicits: List[ImplicitCandidate] = site.openImplicits.map(_.toImplicitCandidate) + val enclosingMacros: List[Context] = this :: universe.analyzer.openMacros // include self + val enclosingMethod: Tree = lenientEnclosure[DefDef] + def enclosingDef: DefDef = strictEnclosure[DefDef] + val enclosingPosition: Position = if (enclPoses.isEmpty) NoPosition else enclPoses.head.pos + val enclosingUnit: CompilationUnit = universe.currentRun.currentUnit + val enclosingRun: Run = universe.currentRun +} diff --git a/src/compiler/scala/reflect/macros/contexts/Evals.scala b/src/compiler/scala/reflect/macros/contexts/Evals.scala new file mode 100644 index 0000000000..84928ddf86 --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Evals.scala @@ -0,0 +1,18 @@ +package scala.reflect.macros +package contexts + +import scala.reflect.runtime.{universe => ru} +import scala.tools.reflect.ToolBox + +trait Evals { + self: Context => + + private lazy val evalMirror = ru.runtimeMirror(universe.analyzer.defaultMacroClassloader) + private lazy val evalToolBox = evalMirror.mkToolBox() + private lazy val evalImporter = ru.mkImporter(universe).asInstanceOf[ru.Importer { val from: universe.type }] + + def eval[T](expr: Expr[T]): T = { + val imported = evalImporter.importTree(expr.tree) + evalToolBox.eval(imported).asInstanceOf[T] + } +} \ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/contexts/ExprUtils.scala b/src/compiler/scala/reflect/macros/contexts/ExprUtils.scala new file mode 100644 index 0000000000..4846325d1e --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/ExprUtils.scala @@ -0,0 +1,34 @@ +package scala.reflect.macros +package contexts + +trait ExprUtils { + self: Context => + + import universe._ + + def literalNull = Expr[Null](Literal(Constant(null)))(TypeTag.Null) + + def literalUnit = Expr[Unit](Literal(Constant(())))(TypeTag.Unit) + + def literalTrue = Expr[Boolean](Literal(Constant(true)))(TypeTag.Boolean) + + def literalFalse = Expr[Boolean](Literal(Constant(false)))(TypeTag.Boolean) + + def literal(x: Boolean) = Expr[Boolean](Literal(Constant(x)))(TypeTag.Boolean) + + def literal(x: Byte) = Expr[Byte](Literal(Constant(x)))(TypeTag.Byte) + + def literal(x: Short) = Expr[Short](Literal(Constant(x)))(TypeTag.Short) + + def literal(x: Int) = Expr[Int](Literal(Constant(x)))(TypeTag.Int) + + def literal(x: Long) = Expr[Long](Literal(Constant(x)))(TypeTag.Long) + + def literal(x: Float) = Expr[Float](Literal(Constant(x)))(TypeTag.Float) + + def literal(x: Double) = Expr[Double](Literal(Constant(x)))(TypeTag.Double) + + def literal(x: String) = Expr[String](Literal(Constant(x)))(TypeTag[String](definitions.StringClass.toTypeConstructor)) + + def literal(x: Char) = Expr[Char](Literal(Constant(x)))(TypeTag.Char) +} diff --git a/src/compiler/scala/reflect/macros/contexts/FrontEnds.scala b/src/compiler/scala/reflect/macros/contexts/FrontEnds.scala new file mode 100644 index 0000000000..fda05de09c --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/FrontEnds.scala @@ -0,0 +1,22 @@ +package scala.reflect.macros +package contexts + +import scala.reflect.macros.runtime.AbortMacroException + +trait FrontEnds { + self: Context => + + def echo(pos: Position, msg: String): Unit = universe.reporter.echo(pos, msg) + + def info(pos: Position, msg: String, force: Boolean): Unit = universe.reporter.info(pos, msg, force) + + def hasWarnings: Boolean = universe.reporter.hasErrors + + def hasErrors: Boolean = universe.reporter.hasErrors + + def warning(pos: Position, msg: String): Unit = callsiteTyper.context.warning(pos, msg) + + def error(pos: Position, msg: String): Unit = callsiteTyper.context.error(pos, msg) + + def abort(pos: Position, msg: String): Nothing = throw new AbortMacroException(pos, msg) +} diff --git a/src/compiler/scala/reflect/macros/contexts/Infrastructure.scala b/src/compiler/scala/reflect/macros/contexts/Infrastructure.scala new file mode 100644 index 0000000000..df7aa4d2be --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Infrastructure.scala @@ -0,0 +1,16 @@ +package scala.reflect.macros +package contexts + +trait Infrastructure { + self: Context => + + def settings: List[String] = { + val us = universe.settings + import us._ + userSetSettings collectFirst { case x: MultiStringSetting if x.name == XmacroSettings.name => x.value } getOrElse Nil + } + + def compilerSettings: List[String] = universe.settings.recreateArgs + + def classPath: List[java.net.URL] = global.classPath.asURLs +} diff --git a/src/compiler/scala/reflect/macros/contexts/Names.scala b/src/compiler/scala/reflect/macros/contexts/Names.scala new file mode 100644 index 0000000000..e535754a98 --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Names.scala @@ -0,0 +1,26 @@ +package scala.reflect.macros +package contexts + +trait Names { + self: Context => + + lazy val freshNameCreator = callsiteTyper.context.unit.fresh + + def fresh(): String = + freshName() + + def fresh(name: String): String = + freshName(name) + + def fresh[NameType <: Name](name: NameType): NameType = + freshName[NameType](name) + + def freshName(): String = + freshNameCreator.newName() + + def freshName(name: String): String = + freshNameCreator.newName(name) + + def freshName[NameType <: Name](name: NameType): NameType = + name.mapName(freshNameCreator.newName(_)).asInstanceOf[NameType] +} \ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/contexts/Parsers.scala b/src/compiler/scala/reflect/macros/contexts/Parsers.scala new file mode 100644 index 0000000000..3dab02beba --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Parsers.scala @@ -0,0 +1,24 @@ +package scala.reflect.macros +package contexts + +import scala.language.existentials +import scala.tools.reflect.ToolBox +import scala.tools.reflect.ToolBoxError + +trait Parsers { + self: Context => + + def parse(code: String): Tree = + // todo. provide decent implementation + // see `Typers.typedUseCase` for details + try { + import scala.reflect.runtime.{universe => ru} + val parsed = ru.rootMirror.mkToolBox().parse(code) + val importer = universe.mkImporter(ru) + importer.importTree(parsed) + } catch { + case ToolBoxError(msg, cause) => + // todo. provide a position + throw new ParseException(universe.NoPosition, msg) + } +} diff --git a/src/compiler/scala/reflect/macros/contexts/Reifiers.scala b/src/compiler/scala/reflect/macros/contexts/Reifiers.scala new file mode 100644 index 0000000000..ecef1c7289 --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Reifiers.scala @@ -0,0 +1,77 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author Gilles Dubochet + */ + +package scala.reflect.macros +package contexts + +trait Reifiers { + self: Context => + + val global: universe.type = universe + import universe._ + import definitions._ + + def reifyTree(universe: Tree, mirror: Tree, tree: Tree): Tree = { + assert(ExprClass != NoSymbol) + val result = scala.reflect.reify.`package`.reifyTree(self.universe)(callsiteTyper, universe, mirror, tree) + logFreeVars(enclosingPosition, result) + result + } + + def reifyType(universe: Tree, mirror: Tree, tpe: Type, concrete: Boolean = false): Tree = { + assert(TypeTagsClass != NoSymbol) + val result = scala.reflect.reify.`package`.reifyType(self.universe)(callsiteTyper, universe, mirror, tpe, concrete) + logFreeVars(enclosingPosition, result) + result + } + + def reifyRuntimeClass(tpe: Type, concrete: Boolean = true): Tree = + scala.reflect.reify.`package`.reifyRuntimeClass(universe)(callsiteTyper, tpe, concrete = concrete) + + def reifyEnclosingRuntimeClass: Tree = + scala.reflect.reify.`package`.reifyEnclosingRuntimeClass(universe)(callsiteTyper) + + def unreifyTree(tree: Tree): Tree = { + assert(ExprSplice != NoSymbol) + Select(tree, ExprSplice) + } + + // fixme: if I put utils here, then "global" from utils' early initialization syntax + // and "global" that comes from here conflict with each other when incrementally compiling + // the problem is that both are pickled with the same owner - trait Reifiers + // and this upsets the compiler, so that oftentimes it throws assertion failures + // Martin knows the details + // + // object utils extends { + // val global: self.global.type = self.global + // val typer: global.analyzer.Typer = self.callsiteTyper + // } with scala.reflect.reify.utils.Utils + // import utils._ + + private def logFreeVars(position: Position, reification: Tree): Unit = { + object utils extends { + val global: self.global.type = self.global + val typer: global.analyzer.Typer = self.callsiteTyper + } with scala.reflect.reify.utils.Utils + import utils._ + + def logFreeVars(symtab: SymbolTable): Unit = + // logging free vars only when they are untyped prevents avalanches of duplicate messages + symtab.syms map (sym => symtab.symDef(sym)) foreach { + case FreeTermDef(_, _, binding, _, origin) if universe.settings.logFreeTerms && binding.tpe == null => + reporter.echo(position, "free term: %s %s".format(showRaw(binding), origin)) + case FreeTypeDef(_, _, binding, _, origin) if universe.settings.logFreeTypes && binding.tpe == null => + reporter.echo(position, "free type: %s %s".format(showRaw(binding), origin)) + case _ => + // do nothing + } + + if (universe.settings.logFreeTerms || universe.settings.logFreeTypes) + reification match { + case ReifiedTree(_, _, symtab, _, _, _, _) => logFreeVars(symtab) + case ReifiedType(_, _, symtab, _, _, _) => logFreeVars(symtab) + } + } +} diff --git a/src/compiler/scala/reflect/macros/contexts/Synthetics.scala b/src/compiler/scala/reflect/macros/contexts/Synthetics.scala new file mode 100644 index 0000000000..ada16a8113 --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Synthetics.scala @@ -0,0 +1,66 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + */ + +package scala.reflect.macros +package contexts + +import scala.reflect.internal.Flags._ +import scala.reflect.internal.util.BatchSourceFile +import scala.reflect.io.VirtualFile + +trait Synthetics { + self: Context => + + import global._ + import mirror.wrapMissing + + // getClassIfDefined and getModuleIfDefined cannot be used here + // because they don't work for stuff declared in the empty package + // (as specified in SLS, code inside non-empty packages cannot see + // declarations from the empty package, so compiler internals + // default to ignoring contents of the empty package) + // to the contrast, staticModule and staticClass are designed + // to be a part of the reflection API and, therefore, they + // correctly resolve all names + private def topLevelSymbol(name: Name): Symbol = wrapMissing { + if (name.isTermName) mirror.staticModule(name.toString) + else mirror.staticClass(name.toString) + } + + def topLevelDef(name: Name): Tree = + enclosingRun.units.toList.map(_.body).flatMap { + // it's okay to check `stat.symbol` here, because currently macros expand strictly after namer + // which means that by the earliest time one can call this method all top-level definitions will have already been entered + case PackageDef(_, stats) => stats filter (stat => stat.symbol != NoSymbol && stat.symbol == topLevelSymbol(name)) + case _ => Nil // should never happen, but better be safe than sorry + }.headOption getOrElse EmptyTree + + def topLevelRef(name: Name): Tree = { + if (topLevelDef(name).nonEmpty) gen.mkUnattributedRef(name) + else EmptyTree + } + + def introduceTopLevel[T: PackageSpec](packagePrototype: T, definition: universe.ImplDef): RefTree = + introduceTopLevel(packagePrototype, List(definition)).head + + def introduceTopLevel[T: PackageSpec](packagePrototype: T, definitions: universe.ImplDef*): List[RefTree] = + introduceTopLevel(packagePrototype, definitions.toList) + + private def introduceTopLevel[T: PackageSpec](packagePrototype: T, definitions: List[universe.ImplDef]): List[RefTree] = { + val code @ PackageDef(pid, _) = implicitly[PackageSpec[T]].mkPackageDef(packagePrototype, definitions) + universe.currentRun.compileLate(code) + definitions map (definition => Select(pid, definition.name)) + } + + protected def mkPackageDef(name: String, stats: List[Tree]) = gen.mkPackageDef(name, stats) + + protected def mkPackageDef(name: TermName, stats: List[Tree]) = gen.mkPackageDef(name.toString, stats) + + protected def mkPackageDef(tree: RefTree, stats: List[Tree]) = PackageDef(tree, stats) + + protected def mkPackageDef(sym: Symbol, stats: List[Tree]) = { + assert(sym hasFlag PACKAGE, s"expected a package or package class symbol, found: $sym") + gen.mkPackageDef(sym.fullName.toString, stats) + } +} diff --git a/src/compiler/scala/reflect/macros/contexts/Traces.scala b/src/compiler/scala/reflect/macros/contexts/Traces.scala new file mode 100644 index 0000000000..df47f6ba81 --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Traces.scala @@ -0,0 +1,8 @@ +package scala.reflect.macros +package contexts + +trait Traces extends util.Traces { + self: Context => + + def globalSettings = universe.settings +} diff --git a/src/compiler/scala/reflect/macros/contexts/Typers.scala b/src/compiler/scala/reflect/macros/contexts/Typers.scala new file mode 100644 index 0000000000..4a1122b913 --- /dev/null +++ b/src/compiler/scala/reflect/macros/contexts/Typers.scala @@ -0,0 +1,52 @@ +package scala.reflect.macros +package contexts + +import scala.reflect.internal.Mode + +trait Typers { + self: Context => + + def openMacros: List[Context] = this :: universe.analyzer.openMacros + + def openImplicits: List[ImplicitCandidate] = callsiteTyper.context.openImplicits.map(_.toImplicitCandidate) + + /** + * @see [[scala.tools.reflect.ToolBox.typeCheck]] + */ + def typeCheck(tree: Tree, pt: Type = universe.WildcardType, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): Tree = { + macroLogVerbose("typechecking %s with expected type %s, implicit views = %s, macros = %s".format(tree, pt, !withImplicitViewsDisabled, !withMacrosDisabled)) + val context = callsiteTyper.context + val wrapper1 = if (!withImplicitViewsDisabled) (context.withImplicitsEnabled[Tree] _) else (context.withImplicitsDisabled[Tree] _) + val wrapper2 = if (!withMacrosDisabled) (context.withMacrosEnabled[Tree] _) else (context.withMacrosDisabled[Tree] _) + def wrapper (tree: => Tree) = wrapper1(wrapper2(tree)) + // if you get a "silent mode is not available past typer" here + // don't rush to change the typecheck not to use the silent method when the silent parameter is false + // typechecking uses silent anyways (e.g. in typedSelect), so you'll only waste your time + // I'd advise fixing the root cause: finding why the context is not set to report errors + // (also see reflect.runtime.ToolBoxes.typeCheckExpr for a workaround that might work for you) + wrapper(callsiteTyper.silent(_.typed(tree, pt), reportAmbiguousErrors = false) match { + case universe.analyzer.SilentResultValue(result) => + macroLogVerbose(result) + result + case error @ universe.analyzer.SilentTypeError(_) => + macroLogVerbose(error.err.errMsg) + if (!silent) throw new TypecheckException(error.err.errPos, error.err.errMsg) + universe.EmptyTree + }) + } + + def inferImplicitValue(pt: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = { + macroLogVerbose("inferring implicit value of type %s, macros = %s".format(pt, !withMacrosDisabled)) + universe.analyzer.inferImplicit(universe.EmptyTree, pt, false, callsiteTyper.context, silent, withMacrosDisabled, pos, (pos, msg) => throw TypecheckException(pos, msg)) + } + + def inferImplicitView(tree: Tree, from: Type, to: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = { + macroLogVerbose("inferring implicit view from %s to %s for %s, macros = %s".format(from, to, tree, !withMacrosDisabled)) + val viewTpe = universe.appliedType(universe.definitions.FunctionClass(1).toTypeConstructor, List(from, to)) + universe.analyzer.inferImplicit(tree, viewTpe, true, callsiteTyper.context, silent, withMacrosDisabled, pos, (pos, msg) => throw TypecheckException(pos, msg)) + } + + def resetAllAttrs(tree: Tree): Tree = universe.resetAllAttrs(tree) + + def resetLocalAttrs(tree: Tree): Tree = universe.resetLocalAttrs(tree) +} diff --git a/src/compiler/scala/reflect/macros/runtime/Aliases.scala b/src/compiler/scala/reflect/macros/runtime/Aliases.scala deleted file mode 100644 index 1c6703aeee..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Aliases.scala +++ /dev/null @@ -1,35 +0,0 @@ -package scala.reflect.macros -package runtime - -trait Aliases { - self: Context => - - override type Symbol = universe.Symbol - override type Type = universe.Type - override type Name = universe.Name - override type TermName = universe.TermName - override type TypeName = universe.TypeName - override type Tree = universe.Tree - override type Position = universe.Position - override type Scope = universe.Scope - override type Modifiers = universe.Modifiers - - override type Expr[+T] = universe.Expr[T] - override val Expr = universe.Expr - def Expr[T: WeakTypeTag](tree: Tree): Expr[T] = universe.Expr[T](mirror, universe.FixedMirrorTreeCreator(mirror, tree)) - - override type WeakTypeTag[T] = universe.WeakTypeTag[T] - override type TypeTag[T] = universe.TypeTag[T] - override val WeakTypeTag = universe.WeakTypeTag - override val TypeTag = universe.TypeTag - def WeakTypeTag[T](tpe: Type): WeakTypeTag[T] = universe.WeakTypeTag[T](mirror, universe.FixedMirrorTypeCreator(mirror, tpe)) - def TypeTag[T](tpe: Type): TypeTag[T] = universe.TypeTag[T](mirror, universe.FixedMirrorTypeCreator(mirror, tpe)) - override def weakTypeTag[T](implicit attag: WeakTypeTag[T]) = attag - override def typeTag[T](implicit ttag: TypeTag[T]) = ttag - override def weakTypeOf[T](implicit attag: WeakTypeTag[T]): Type = attag.tpe - override def typeOf[T](implicit ttag: TypeTag[T]): Type = ttag.tpe - - implicit class RichOpenImplicit(oi: universe.analyzer.OpenImplicit) { - def toImplicitCandidate = ImplicitCandidate(oi.info.pre, oi.info.sym, oi.pt, oi.tree) - } -} \ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/runtime/Context.scala b/src/compiler/scala/reflect/macros/runtime/Context.scala deleted file mode 100644 index 76c684f6d7..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Context.scala +++ /dev/null @@ -1,29 +0,0 @@ -package scala.reflect.macros -package runtime - -import scala.tools.nsc.Global - -abstract class Context extends scala.reflect.macros.Context - with Aliases - with Enclosures - with Names - with Reifiers - with FrontEnds - with Infrastructure - with Typers - with Parsers - with Evals - with ExprUtils - with Synthetics - with Traces { - - val universe: Global - - val mirror: universe.Mirror = universe.rootMirror - - val callsiteTyper: universe.analyzer.Typer - - val prefix: Expr[PrefixType] - - val expandee: Tree -} diff --git a/src/compiler/scala/reflect/macros/runtime/Enclosures.scala b/src/compiler/scala/reflect/macros/runtime/Enclosures.scala deleted file mode 100644 index f3f92550de..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Enclosures.scala +++ /dev/null @@ -1,36 +0,0 @@ -package scala.reflect.macros -package runtime - -import scala.reflect.{ClassTag, classTag} - -trait Enclosures { - self: Context => - - import universe._ - - type MacroRole = analyzer.MacroRole - def APPLY_ROLE = analyzer.APPLY_ROLE - def macroRole: MacroRole - - private lazy val site = callsiteTyper.context - private lazy val enclTrees = site.enclosingContextChain map (_.tree) - private lazy val enclPoses = enclosingMacros map (_.macroApplication.pos) filterNot (_ eq NoPosition) - - private def lenientEnclosure[T <: Tree : ClassTag]: Tree = enclTrees collectFirst { case x: T => x } getOrElse EmptyTree - private def strictEnclosure[T <: Tree : ClassTag]: T = enclTrees collectFirst { case x: T => x } getOrElse (throw new EnclosureException(classTag[T].runtimeClass, enclTrees)) - - // vals are eager to simplify debugging - // after all we wouldn't save that much time by making them lazy - val macroApplication: Tree = expandee - def enclosingPackage: PackageDef = strictEnclosure[PackageDef] - val enclosingClass: Tree = lenientEnclosure[ImplDef] - def enclosingImpl: ImplDef = strictEnclosure[ImplDef] - def enclosingTemplate: Template = strictEnclosure[Template] - val enclosingImplicits: List[ImplicitCandidate] = site.openImplicits.map(_.toImplicitCandidate) - val enclosingMacros: List[Context] = this :: universe.analyzer.openMacros // include self - val enclosingMethod: Tree = lenientEnclosure[DefDef] - def enclosingDef: DefDef = strictEnclosure[DefDef] - val enclosingPosition: Position = if (enclPoses.isEmpty) NoPosition else enclPoses.head.pos - val enclosingUnit: CompilationUnit = universe.currentRun.currentUnit - val enclosingRun: Run = universe.currentRun -} diff --git a/src/compiler/scala/reflect/macros/runtime/Evals.scala b/src/compiler/scala/reflect/macros/runtime/Evals.scala deleted file mode 100644 index 1f7b5f2ff1..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Evals.scala +++ /dev/null @@ -1,18 +0,0 @@ -package scala.reflect.macros -package runtime - -import scala.reflect.runtime.{universe => ru} -import scala.tools.reflect.ToolBox - -trait Evals { - self: Context => - - private lazy val evalMirror = ru.runtimeMirror(universe.analyzer.macroClassloader) - private lazy val evalToolBox = evalMirror.mkToolBox() - private lazy val evalImporter = ru.mkImporter(universe).asInstanceOf[ru.Importer { val from: universe.type }] - - def eval[T](expr: Expr[T]): T = { - val imported = evalImporter.importTree(expr.tree) - evalToolBox.eval(imported).asInstanceOf[T] - } -} \ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/runtime/ExprUtils.scala b/src/compiler/scala/reflect/macros/runtime/ExprUtils.scala deleted file mode 100644 index a719beed97..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/ExprUtils.scala +++ /dev/null @@ -1,34 +0,0 @@ -package scala.reflect.macros -package runtime - -trait ExprUtils { - self: Context => - - import universe._ - - def literalNull = Expr[Null](Literal(Constant(null)))(TypeTag.Null) - - def literalUnit = Expr[Unit](Literal(Constant(())))(TypeTag.Unit) - - def literalTrue = Expr[Boolean](Literal(Constant(true)))(TypeTag.Boolean) - - def literalFalse = Expr[Boolean](Literal(Constant(false)))(TypeTag.Boolean) - - def literal(x: Boolean) = Expr[Boolean](Literal(Constant(x)))(TypeTag.Boolean) - - def literal(x: Byte) = Expr[Byte](Literal(Constant(x)))(TypeTag.Byte) - - def literal(x: Short) = Expr[Short](Literal(Constant(x)))(TypeTag.Short) - - def literal(x: Int) = Expr[Int](Literal(Constant(x)))(TypeTag.Int) - - def literal(x: Long) = Expr[Long](Literal(Constant(x)))(TypeTag.Long) - - def literal(x: Float) = Expr[Float](Literal(Constant(x)))(TypeTag.Float) - - def literal(x: Double) = Expr[Double](Literal(Constant(x)))(TypeTag.Double) - - def literal(x: String) = Expr[String](Literal(Constant(x)))(TypeTag[String](definitions.StringClass.toTypeConstructor)) - - def literal(x: Char) = Expr[Char](Literal(Constant(x)))(TypeTag.Char) -} diff --git a/src/compiler/scala/reflect/macros/runtime/FrontEnds.scala b/src/compiler/scala/reflect/macros/runtime/FrontEnds.scala deleted file mode 100644 index a6a198e1b4..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/FrontEnds.scala +++ /dev/null @@ -1,20 +0,0 @@ -package scala.reflect.macros -package runtime - -trait FrontEnds { - self: Context => - - def echo(pos: Position, msg: String): Unit = universe.reporter.echo(pos, msg) - - def info(pos: Position, msg: String, force: Boolean): Unit = universe.reporter.info(pos, msg, force) - - def hasWarnings: Boolean = universe.reporter.hasErrors - - def hasErrors: Boolean = universe.reporter.hasErrors - - def warning(pos: Position, msg: String): Unit = callsiteTyper.context.warning(pos, msg) - - def error(pos: Position, msg: String): Unit = callsiteTyper.context.error(pos, msg) - - def abort(pos: Position, msg: String): Nothing = throw new AbortMacroException(pos, msg) -} diff --git a/src/compiler/scala/reflect/macros/runtime/Infrastructure.scala b/src/compiler/scala/reflect/macros/runtime/Infrastructure.scala deleted file mode 100644 index 7781693822..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Infrastructure.scala +++ /dev/null @@ -1,16 +0,0 @@ -package scala.reflect.macros -package runtime - -trait Infrastructure { - self: Context => - - def settings: List[String] = { - val us = universe.settings - import us._ - userSetSettings collectFirst { case x: MultiStringSetting if x.name == XmacroSettings.name => x.value } getOrElse Nil - } - - def compilerSettings: List[String] = universe.settings.recreateArgs - - def classPath: List[java.net.URL] = global.classPath.asURLs -} diff --git a/src/compiler/scala/reflect/macros/runtime/JavaReflectionRuntimes.scala b/src/compiler/scala/reflect/macros/runtime/JavaReflectionRuntimes.scala new file mode 100644 index 0000000000..3ef11fad9d --- /dev/null +++ b/src/compiler/scala/reflect/macros/runtime/JavaReflectionRuntimes.scala @@ -0,0 +1,31 @@ +package scala.reflect.macros +package runtime + +import scala.reflect.runtime.ReflectionUtils +import scala.reflect.macros.{Context => ApiContext} + +trait JavaReflectionRuntimes { + self: scala.tools.nsc.typechecker.Analyzer => + + trait JavaReflectionResolvers { + self: MacroRuntimeResolver => + + import global._ + + def resolveJavaReflectionRuntime(classLoader: ClassLoader): MacroRuntime = { + val implClass = Class.forName(className, true, classLoader) + val implMeths = implClass.getDeclaredMethods.find(_.getName == methName) + // relies on the fact that macro impls cannot be overloaded + // so every methName can resolve to at maximum one method + val implMeth = implMeths getOrElse { throw new NoSuchMethodException(s"$className.$methName") } + macroLogVerbose(s"successfully loaded macro impl as ($implClass, $implMeth)") + args => { + val implObj = + if (isBundle) implClass.getConstructor(classOf[ApiContext]).newInstance(args.c) + else ReflectionUtils.staticSingletonInstance(implClass) + val implArgs = if (isBundle) args.others else args.c +: args.others + implMeth.invoke(implObj, implArgs.asInstanceOf[Seq[AnyRef]]: _*) + } + } + } +} \ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala b/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala new file mode 100644 index 0000000000..0f89163803 --- /dev/null +++ b/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala @@ -0,0 +1,74 @@ +package scala.reflect.macros +package runtime + +import scala.collection.mutable.{Map => MutableMap} +import scala.reflect.internal.Flags._ +import scala.reflect.runtime.ReflectionUtils +import scala.tools.nsc.util.ScalaClassLoader +import scala.tools.nsc.util.AbstractFileClassLoader + +trait MacroRuntimes extends JavaReflectionRuntimes with ScalaReflectionRuntimes { + self: scala.tools.nsc.typechecker.Analyzer => + + import global._ + import definitions._ + + /** 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. + */ + private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, MacroRuntime] + def macroRuntime(macroDef: Symbol): MacroRuntime = { + macroLogVerbose(s"looking for macro implementation: $macroDef") + if (fastTrack contains macroDef) { + macroLogVerbose("macro expansion is serviced by a fast track") + fastTrack(macroDef) + } else { + macroRuntimesCache.getOrElseUpdate(macroDef, new MacroRuntimeResolver(macroDef).resolveRuntime()) + } + } + + /** 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. + * + * When -Xmacro-jit is enabled, we sometimes fallback to on-the-fly compilation of macro implementations, + * which compiles implementations into a virtual directory (very much like REPL does) and then conjures + * a classloader mapped to that virtual directory. + */ + lazy val defaultMacroClassloader: ClassLoader = findMacroClassLoader() + + /** Abstracts away resolution of macro runtimes. + */ + type MacroRuntime = MacroArgs => Any + class MacroRuntimeResolver(val macroDef: Symbol) extends JavaReflectionResolvers + with ScalaReflectionResolvers { + val binding = loadMacroImplBinding(macroDef) + val isBundle = binding.isBundle + val className = binding.className + val methName = binding.methName + + def resolveRuntime(): MacroRuntime = { + if (className == Predef_???.owner.javaClassName && methName == Predef_???.name.encoded) { + args => throw new AbortMacroException(args.c.enclosingPosition, "macro implementation is missing") + } else { + try { + macroLogVerbose(s"resolving macro implementation as $className.$methName (isBundle = $isBundle)") + macroLogVerbose(s"classloader is: ${ReflectionUtils.show(defaultMacroClassloader)}") + // resolveScalaReflectionRuntime(defaultMacroClassloader) + resolveJavaReflectionRuntime(defaultMacroClassloader) + } catch { + case ex: Exception => + macroLogVerbose(s"macro runtime failed to load: ${ex.toString}") + macroDef setFlag IS_ERROR + null + } + } + } + } +} \ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/runtime/Names.scala b/src/compiler/scala/reflect/macros/runtime/Names.scala deleted file mode 100644 index 635e8bcd45..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Names.scala +++ /dev/null @@ -1,26 +0,0 @@ -package scala.reflect.macros -package runtime - -trait Names { - self: Context => - - lazy val freshNameCreator = callsiteTyper.context.unit.fresh - - def fresh(): String = - freshName() - - def fresh(name: String): String = - freshName(name) - - def fresh[NameType <: Name](name: NameType): NameType = - freshName[NameType](name) - - def freshName(): String = - freshNameCreator.newName() - - def freshName(name: String): String = - freshNameCreator.newName(name) - - def freshName[NameType <: Name](name: NameType): NameType = - name.mapName(freshNameCreator.newName(_)).asInstanceOf[NameType] -} \ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/runtime/Parsers.scala b/src/compiler/scala/reflect/macros/runtime/Parsers.scala deleted file mode 100644 index 566bcde73d..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Parsers.scala +++ /dev/null @@ -1,24 +0,0 @@ -package scala.reflect.macros -package runtime - -import scala.language.existentials -import scala.tools.reflect.ToolBox -import scala.tools.reflect.ToolBoxError - -trait Parsers { - self: Context => - - def parse(code: String): Tree = - // todo. provide decent implementation - // see `Typers.typedUseCase` for details - try { - import scala.reflect.runtime.{universe => ru} - val parsed = ru.rootMirror.mkToolBox().parse(code) - val importer = universe.mkImporter(ru) - importer.importTree(parsed) - } catch { - case ToolBoxError(msg, cause) => - // todo. provide a position - throw new ParseException(universe.NoPosition, msg) - } -} diff --git a/src/compiler/scala/reflect/macros/runtime/Reifiers.scala b/src/compiler/scala/reflect/macros/runtime/Reifiers.scala deleted file mode 100644 index 7ec3457c6a..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Reifiers.scala +++ /dev/null @@ -1,77 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Gilles Dubochet - */ - -package scala.reflect.macros -package runtime - -trait Reifiers { - self: Context => - - val global: universe.type = universe - import universe._ - import definitions._ - - def reifyTree(universe: Tree, mirror: Tree, tree: Tree): Tree = { - assert(ExprClass != NoSymbol) - val result = scala.reflect.reify.`package`.reifyTree(self.universe)(callsiteTyper, universe, mirror, tree) - logFreeVars(enclosingPosition, result) - result - } - - def reifyType(universe: Tree, mirror: Tree, tpe: Type, concrete: Boolean = false): Tree = { - assert(TypeTagsClass != NoSymbol) - val result = scala.reflect.reify.`package`.reifyType(self.universe)(callsiteTyper, universe, mirror, tpe, concrete) - logFreeVars(enclosingPosition, result) - result - } - - def reifyRuntimeClass(tpe: Type, concrete: Boolean = true): Tree = - scala.reflect.reify.`package`.reifyRuntimeClass(universe)(callsiteTyper, tpe, concrete = concrete) - - def reifyEnclosingRuntimeClass: Tree = - scala.reflect.reify.`package`.reifyEnclosingRuntimeClass(universe)(callsiteTyper) - - def unreifyTree(tree: Tree): Tree = { - assert(ExprSplice != NoSymbol) - Select(tree, ExprSplice) - } - - // fixme: if I put utils here, then "global" from utils' early initialization syntax - // and "global" that comes from here conflict with each other when incrementally compiling - // the problem is that both are pickled with the same owner - trait Reifiers - // and this upsets the compiler, so that oftentimes it throws assertion failures - // Martin knows the details - // - // object utils extends { - // val global: self.global.type = self.global - // val typer: global.analyzer.Typer = self.callsiteTyper - // } with scala.reflect.reify.utils.Utils - // import utils._ - - private def logFreeVars(position: Position, reification: Tree): Unit = { - object utils extends { - val global: self.global.type = self.global - val typer: global.analyzer.Typer = self.callsiteTyper - } with scala.reflect.reify.utils.Utils - import utils._ - - def logFreeVars(symtab: SymbolTable): Unit = - // logging free vars only when they are untyped prevents avalanches of duplicate messages - symtab.syms map (sym => symtab.symDef(sym)) foreach { - case FreeTermDef(_, _, binding, _, origin) if universe.settings.logFreeTerms && binding.tpe == null => - reporter.echo(position, "free term: %s %s".format(showRaw(binding), origin)) - case FreeTypeDef(_, _, binding, _, origin) if universe.settings.logFreeTypes && binding.tpe == null => - reporter.echo(position, "free type: %s %s".format(showRaw(binding), origin)) - case _ => - // do nothing - } - - if (universe.settings.logFreeTerms || universe.settings.logFreeTypes) - reification match { - case ReifiedTree(_, _, symtab, _, _, _, _) => logFreeVars(symtab) - case ReifiedType(_, _, symtab, _, _, _) => logFreeVars(symtab) - } - } -} diff --git a/src/compiler/scala/reflect/macros/runtime/ScalaReflectionRuntimes.scala b/src/compiler/scala/reflect/macros/runtime/ScalaReflectionRuntimes.scala new file mode 100644 index 0000000000..1999e525ff --- /dev/null +++ b/src/compiler/scala/reflect/macros/runtime/ScalaReflectionRuntimes.scala @@ -0,0 +1,33 @@ +package scala.reflect.macros +package runtime + +import scala.reflect.runtime.{universe => ru} + +trait ScalaReflectionRuntimes { + self: scala.tools.nsc.typechecker.Analyzer => + + trait ScalaReflectionResolvers { + self: MacroRuntimeResolver => + + import global._ + + def resolveScalaReflectionRuntime(classLoader: ClassLoader): MacroRuntime = { + val macroMirror: ru.JavaMirror = ru.runtimeMirror(classLoader) + val implContainerSym = macroMirror.classSymbol(Class.forName(className, true, classLoader)) + val implMethSym = implContainerSym.typeSignature.member(ru.TermName(methName)).asMethod + macroLogVerbose(s"successfully loaded macro impl as ($implContainerSym, $implMethSym)") + args => { + val implContainer = + if (isBundle) { + val implCtorSym = implContainerSym.typeSignature.member(ru.nme.CONSTRUCTOR).asMethod + macroMirror.reflectClass(implContainerSym).reflectConstructor(implCtorSym)(args.c) + } else { + macroMirror.reflectModule(implContainerSym.module.asModule).instance + } + val implMeth = macroMirror.reflect(implContainer).reflectMethod(implMethSym) + val implArgs = if (isBundle) args.others else args.c +: args.others + implMeth(implArgs: _*) + } + } + } +} diff --git a/src/compiler/scala/reflect/macros/runtime/Synthetics.scala b/src/compiler/scala/reflect/macros/runtime/Synthetics.scala deleted file mode 100644 index 1156769a80..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Synthetics.scala +++ /dev/null @@ -1,66 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - */ - -package scala.reflect.macros -package runtime - -import scala.reflect.internal.Flags._ -import scala.reflect.internal.util.BatchSourceFile -import scala.reflect.io.VirtualFile - -trait Synthetics { - self: Context => - - import global._ - import mirror.wrapMissing - - // getClassIfDefined and getModuleIfDefined cannot be used here - // because they don't work for stuff declared in the empty package - // (as specified in SLS, code inside non-empty packages cannot see - // declarations from the empty package, so compiler internals - // default to ignoring contents of the empty package) - // to the contrast, staticModule and staticClass are designed - // to be a part of the reflection API and, therefore, they - // correctly resolve all names - private def topLevelSymbol(name: Name): Symbol = wrapMissing { - if (name.isTermName) mirror.staticModule(name.toString) - else mirror.staticClass(name.toString) - } - - def topLevelDef(name: Name): Tree = - enclosingRun.units.toList.map(_.body).flatMap { - // it's okay to check `stat.symbol` here, because currently macros expand strictly after namer - // which means that by the earliest time one can call this method all top-level definitions will have already been entered - case PackageDef(_, stats) => stats filter (stat => stat.symbol != NoSymbol && stat.symbol == topLevelSymbol(name)) - case _ => Nil // should never happen, but better be safe than sorry - }.headOption getOrElse EmptyTree - - def topLevelRef(name: Name): Tree = { - if (topLevelDef(name).nonEmpty) gen.mkUnattributedRef(name) - else EmptyTree - } - - def introduceTopLevel[T: PackageSpec](packagePrototype: T, definition: universe.ImplDef): RefTree = - introduceTopLevel(packagePrototype, List(definition)).head - - def introduceTopLevel[T: PackageSpec](packagePrototype: T, definitions: universe.ImplDef*): List[RefTree] = - introduceTopLevel(packagePrototype, definitions.toList) - - private def introduceTopLevel[T: PackageSpec](packagePrototype: T, definitions: List[universe.ImplDef]): List[RefTree] = { - val code @ PackageDef(pid, _) = implicitly[PackageSpec[T]].mkPackageDef(packagePrototype, definitions) - universe.currentRun.compileLate(code) - definitions map (definition => Select(pid, definition.name)) - } - - protected def mkPackageDef(name: String, stats: List[Tree]) = gen.mkPackageDef(name, stats) - - protected def mkPackageDef(name: TermName, stats: List[Tree]) = gen.mkPackageDef(name.toString, stats) - - protected def mkPackageDef(tree: RefTree, stats: List[Tree]) = PackageDef(tree, stats) - - protected def mkPackageDef(sym: Symbol, stats: List[Tree]) = { - assert(sym hasFlag PACKAGE, s"expected a package or package class symbol, found: $sym") - gen.mkPackageDef(sym.fullName.toString, stats) - } -} diff --git a/src/compiler/scala/reflect/macros/runtime/Traces.scala b/src/compiler/scala/reflect/macros/runtime/Traces.scala deleted file mode 100644 index 0238e9f84e..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Traces.scala +++ /dev/null @@ -1,8 +0,0 @@ -package scala.reflect.macros -package runtime - -trait Traces extends util.Traces { - self: Context => - - def globalSettings = universe.settings -} diff --git a/src/compiler/scala/reflect/macros/runtime/Typers.scala b/src/compiler/scala/reflect/macros/runtime/Typers.scala deleted file mode 100644 index f92d99a3f2..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/Typers.scala +++ /dev/null @@ -1,52 +0,0 @@ -package scala.reflect.macros -package runtime - -import scala.reflect.internal.Mode - -trait Typers { - self: Context => - - def openMacros: List[Context] = this :: universe.analyzer.openMacros - - def openImplicits: List[ImplicitCandidate] = callsiteTyper.context.openImplicits.map(_.toImplicitCandidate) - - /** - * @see [[scala.tools.reflect.ToolBox.typeCheck]] - */ - def typeCheck(tree: Tree, pt: Type = universe.WildcardType, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): Tree = { - macroLogVerbose("typechecking %s with expected type %s, implicit views = %s, macros = %s".format(tree, pt, !withImplicitViewsDisabled, !withMacrosDisabled)) - val context = callsiteTyper.context - val wrapper1 = if (!withImplicitViewsDisabled) (context.withImplicitsEnabled[Tree] _) else (context.withImplicitsDisabled[Tree] _) - val wrapper2 = if (!withMacrosDisabled) (context.withMacrosEnabled[Tree] _) else (context.withMacrosDisabled[Tree] _) - def wrapper (tree: => Tree) = wrapper1(wrapper2(tree)) - // if you get a "silent mode is not available past typer" here - // don't rush to change the typecheck not to use the silent method when the silent parameter is false - // typechecking uses silent anyways (e.g. in typedSelect), so you'll only waste your time - // I'd advise fixing the root cause: finding why the context is not set to report errors - // (also see reflect.runtime.ToolBoxes.typeCheckExpr for a workaround that might work for you) - wrapper(callsiteTyper.silent(_.typed(tree, pt), reportAmbiguousErrors = false) match { - case universe.analyzer.SilentResultValue(result) => - macroLogVerbose(result) - result - case error @ universe.analyzer.SilentTypeError(_) => - macroLogVerbose(error.err.errMsg) - if (!silent) throw new TypecheckException(error.err.errPos, error.err.errMsg) - universe.EmptyTree - }) - } - - def inferImplicitValue(pt: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = { - macroLogVerbose("inferring implicit value of type %s, macros = %s".format(pt, !withMacrosDisabled)) - universe.analyzer.inferImplicit(universe.EmptyTree, pt, false, callsiteTyper.context, silent, withMacrosDisabled, pos, (pos, msg) => throw TypecheckException(pos, msg)) - } - - def inferImplicitView(tree: Tree, from: Type, to: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = { - macroLogVerbose("inferring implicit view from %s to %s for %s, macros = %s".format(from, to, tree, !withMacrosDisabled)) - val viewTpe = universe.appliedType(universe.definitions.FunctionClass(1).toTypeConstructor, List(from, to)) - universe.analyzer.inferImplicit(tree, viewTpe, true, callsiteTyper.context, silent, withMacrosDisabled, pos, (pos, msg) => throw TypecheckException(pos, msg)) - } - - def resetAllAttrs(tree: Tree): Tree = universe.resetAllAttrs(tree) - - def resetLocalAttrs(tree: Tree): Tree = universe.resetLocalAttrs(tree) -} diff --git a/src/compiler/scala/reflect/macros/runtime/package.scala b/src/compiler/scala/reflect/macros/runtime/package.scala new file mode 100644 index 0000000000..9ef8200760 --- /dev/null +++ b/src/compiler/scala/reflect/macros/runtime/package.scala @@ -0,0 +1,5 @@ +package scala.reflect.macros + +package object runtime { + type Context = scala.reflect.macros.contexts.Context +} \ No newline at end of file diff --git a/src/compiler/scala/reflect/reify/Taggers.scala b/src/compiler/scala/reflect/reify/Taggers.scala index 9659134e5b..0bffe55403 100644 --- a/src/compiler/scala/reflect/reify/Taggers.scala +++ b/src/compiler/scala/reflect/reify/Taggers.scala @@ -1,7 +1,7 @@ package scala.reflect.reify import scala.reflect.macros.{ReificationException, UnexpectedReificationException, TypecheckException} -import scala.reflect.macros.runtime.Context +import scala.reflect.macros.contexts.Context abstract class Taggers { val c: Context diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 40f284c94c..8d650b3653 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -10,9 +10,10 @@ 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 /** * Code to deal with macros, namely with: @@ -39,7 +40,7 @@ import scala.reflect.macros.compiler.DefaultMacroCompiler * (Expr(elems)) * (TypeTag(Int)) */ -trait Macros extends scala.tools.reflect.FastTrack with Traces with Helpers { +trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { self: Analyzer => import global._ @@ -77,7 +78,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces with Helpers { * 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 @@ -99,9 +100,9 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces with Helpers { // these trees don't refer to a macro impl, so we can pickle them as is targs: List[Tree]) - private final val IMPLPARAM_TAG = 0 // actually it's zero and above, this is just a lower bound for >= checks - private final val IMPLPARAM_OTHER = -1 - private final val IMPLPARAM_EXPR = -2 + final val IMPLPARAM_TAG = 0 // actually it's zero and above, this is just a lower bound for >= checks + final val IMPLPARAM_OTHER = -1 + final val IMPLPARAM_EXPR = -2 /** Macro def -> macro impl bindings are serialized into a `macroImpl` annotation * with synthetic content that carries the payload described in `MacroImplBinding`. @@ -120,7 +121,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces with Helpers { * "versionFormat" = , * "className" = "Macros$")) */ - private object MacroImplBinding { + object MacroImplBinding { val versionFormat = 3 def pickleAtom(obj: Any): Tree = @@ -220,12 +221,12 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces with Helpers { } } - 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) } @@ -309,73 +310,7 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces with Helpers { } } - /** 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() - - /** Reflective mirror built from `macroClassloader`. - */ - private lazy val macroMirror: ru.JavaMirror = ru.runtimeMirror(macroClassloader) - - /** 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") - if (fastTrack contains macroDef) { - macroLogVerbose("macro expansion is serviced by a fast track") - fastTrack(macroDef) - } else { - macroRuntimesCache.getOrElseUpdate(macroDef, { - val binding = loadMacroImplBinding(macroDef) - val isBundle = binding.isBundle - val className = binding.className - val methName = binding.methName - macroLogVerbose(s"resolved implementation as $className.$methName") - - if (binding.className == Predef_???.owner.javaClassName && binding.methName == Predef_???.name.encoded) { - args => throw new AbortMacroException(args.c.enclosingPosition, "macro implementation is missing") - } else { - try { - macroLogVerbose(s"loading implementation class: $className") - macroLogVerbose(s"classloader is: ${ReflectionUtils.show(macroClassloader)}") - val implContainerSym = macroMirror.classSymbol(Class.forName(className, true, macroClassloader)) - val implMethSym = implContainerSym.typeSignature.member(ru.TermName(methName)).asMethod - macroLogVerbose(s"successfully loaded macro impl as ($implContainerSym, $implMethSym)") - args => { - val implContainer = - if (isBundle) { - val implCtorSym = implContainerSym.typeSignature.member(ru.nme.CONSTRUCTOR).asMethod - macroMirror.reflectClass(implContainerSym).reflectConstructor(implCtorSym)(args.c) - } else { - macroMirror.reflectModule(implContainerSym.module.asModule).instance - } - val implMeth = macroMirror.reflect(implContainer).reflectMethod(implMethSym) - val implArgs = if (isBundle) args.others else args.c +: args.others - implMeth(implArgs: _*) - } - } catch { - case ex: Exception => - macroLogVerbose(s"macro runtime failed to load: ${ex.toString}") - macroDef setFlag IS_ERROR - null - } - } - }) - } - } - - 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] diff --git a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala index a208924acb..c616ded704 100644 --- a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala +++ b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala @@ -10,7 +10,7 @@ trait StdAttachments { * At times we need to store this info, because macro expansion can be delayed until its targs are inferred. * After a macro application has been successfully expanded, this attachment is destroyed. */ - type UnaffiliatedMacroContext = scala.reflect.macros.runtime.Context + type UnaffiliatedMacroContext = scala.reflect.macros.contexts.Context type MacroContext = UnaffiliatedMacroContext { val universe: self.global.type } case class MacroRuntimeAttachment(delayed: Boolean, typerContext: Context, macroContext: Option[MacroContext]) diff --git a/src/compiler/scala/tools/reflect/MacroImplementations.scala b/src/compiler/scala/tools/reflect/MacroImplementations.scala index 109c148b7e..8e1bcb5f87 100644 --- a/src/compiler/scala/tools/reflect/MacroImplementations.scala +++ b/src/compiler/scala/tools/reflect/MacroImplementations.scala @@ -1,6 +1,6 @@ package scala.tools.reflect -import scala.reflect.macros.runtime.Context +import scala.reflect.macros.contexts.Context import scala.collection.mutable.ListBuffer import scala.collection.mutable.Stack import scala.reflect.internal.util.OffsetPosition -- cgit v1.2.3 From d5bfbd597e5fd4707b322c8bc13fd57f084ff1e4 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Mon, 27 May 2013 23:07:03 +0200 Subject: applying Jason's aesthetics suggestion --- src/compiler/scala/tools/nsc/typechecker/Macros.scala | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'src/compiler/scala/tools/nsc/typechecker') diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 8d650b3653..baef84bc8e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -98,7 +98,11 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { signature: List[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 - 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 + } final val IMPLPARAM_TAG = 0 // actually it's zero and above, this is just a lower bound for >= checks final val IMPLPARAM_OTHER = -1 @@ -353,9 +357,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { else MacroTooFewArgumentListsError(expandee) } else { - val binding = loadMacroImplBinding(macroDef) - if (binding.className == Predef_???.owner.javaClassName && binding.methName == Predef_???.name.encoded) Nil - else { + def calculateMacroArgs(binding: MacroImplBinding) = { val signature = if (binding.isBundle) binding.signature else binding.signature.tail macroLogVerbose(s"binding: $binding") @@ -427,6 +429,10 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { // 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"macroImplArgs: $macroImplArgs") MacroArgs(context, macroImplArgs) -- cgit v1.2.3 From 82f0925b69db8b5f9a3b10f58926c574433ca423 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 28 May 2013 11:42:33 +0200 Subject: refactors IMPLPARAM_xxx constants into value classes --- .../scala/tools/nsc/typechecker/Macros.scala | 76 +++++++++++++++------- src/library/scala/runtime/ScalaRunTime.scala | 13 ++++ 2 files changed, 66 insertions(+), 23 deletions(-) (limited to 'src/compiler/scala/tools/nsc/typechecker') diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index baef84bc8e..6c4d1e20aa 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -14,6 +14,8 @@ 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: @@ -94,8 +96,8 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { // * 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(-1), List(-1, -2), List(0, 2)) - signature: List[List[Int]], + // `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]) { @@ -104,10 +106,6 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { def is_??? = className == Predef_???.owner.javaClassName && methName == Predef_???.name.encoded } - final val IMPLPARAM_TAG = 0 // actually it's zero and above, this is just a lower bound for >= checks - final val IMPLPARAM_OTHER = -1 - final val IMPLPARAM_EXPR = -2 - /** Macro def -> macro impl bindings are serialized into a `macroImpl` annotation * with synthetic content that carries the payload described in `MacroImplBinding`. * @@ -120,28 +118,30 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * @scala.reflect.macros.internal.macroImpl( * `macro`( * "isBundle" = false, - * "signature" = List(-1), + * "signature" = List(Other), * "methodName" = "impl", * "versionFormat" = , * "className" = "Macros$")) */ object MacroImplBinding { - val versionFormat = 3 + 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 = { @@ -161,15 +161,15 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { loop(owner) } - def signature: List[List[Int]] = { - def fingerprint(tpe: Type): Int = tpe.dealiasWiden match { + def signature: List[List[Fingerprint]] = { + def fingerprint(tpe: Type): Fingerprint = tpe.dealiasWiden match { case TypeRef(_, RepeatedParamClass, underlying :: Nil) => fingerprint(underlying) - case ExprClassOf(_) => IMPLPARAM_EXPR - case _ => IMPLPARAM_OTHER + case ExprClassOf(_) => Lifted + case _ => Other } val transformed = transformTypeTagEvidenceParams(macroImplRef, (param, tparam) => tparam) - mmap(transformed)(p => if (p.isTerm) fingerprint(p.info) else p.paramPos) + mmap(transformed)(p => if (p.isTerm) fingerprint(p.info) else Tagged(p.paramPos)) } val payload = List[(String, Any)]( @@ -214,13 +214,25 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { 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 isBundle = payload("isBundle").asInstanceOf[Boolean] - val className = payload("className").asInstanceOf[String] - val methodName = payload("methodName").asInstanceOf[String] - val signature = payload("signature").asInstanceOf[List[List[Int]]] + 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) } } @@ -376,7 +388,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { val wrappedArgs = mapWithIndex(args)((arg, j) => { val fingerprint = implParams(min(j, implParams.length - 1)) fingerprint match { - case IMPLPARAM_EXPR => context.Expr[Nothing](arg)(TypeTag.Nothing) // TODO: SI-5752 + 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") } @@ -406,7 +418,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { // 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 filter (_ >= IMPLPARAM_TAG) map (paramPos => { + 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) { @@ -787,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) +} diff --git a/src/library/scala/runtime/ScalaRunTime.scala b/src/library/scala/runtime/ScalaRunTime.scala index ea1f392e2b..3a85207235 100644 --- a/src/library/scala/runtime/ScalaRunTime.scala +++ b/src/library/scala/runtime/ScalaRunTime.scala @@ -350,4 +350,17 @@ object ScalaRunTime { } } } + + def box[T](clazz: jClass[T]): jClass[_] = clazz match { + case java.lang.Byte.TYPE => classOf[java.lang.Byte] + case java.lang.Short.TYPE => classOf[java.lang.Short] + case java.lang.Character.TYPE => classOf[java.lang.Character] + case java.lang.Integer.TYPE => classOf[java.lang.Integer] + case java.lang.Long.TYPE => classOf[java.lang.Long] + case java.lang.Float.TYPE => classOf[java.lang.Float] + case java.lang.Double.TYPE => classOf[java.lang.Double] + case java.lang.Void.TYPE => classOf[scala.runtime.BoxedUnit] + case java.lang.Boolean.TYPE => classOf[java.lang.Boolean] + case _ => clazz + } } -- cgit v1.2.3