diff options
Diffstat (limited to 'src/compiler/scala/reflect/macros')
23 files changed, 690 insertions, 61 deletions
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..32c6da8007 --- /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.contexts.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 [<static object>].<method name>[[<type args>]] or\n" + + "macro [<macro bundle>].<method name>[[<type args>]]") + + 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) [<static object>].<method name>[[<type args>]] // vanilla macro def + * 2) [<macro bundle>].<method name>[[<type args>]] // 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/Aliases.scala b/src/compiler/scala/reflect/macros/contexts/Aliases.scala index 1c6703aeee..cc64d97d85 100644 --- a/src/compiler/scala/reflect/macros/runtime/Aliases.scala +++ b/src/compiler/scala/reflect/macros/contexts/Aliases.scala @@ -1,5 +1,5 @@ package scala.reflect.macros -package runtime +package contexts trait Aliases { self: Context => 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/runtime/Enclosures.scala b/src/compiler/scala/reflect/macros/contexts/Enclosures.scala index f3f92550de..bb88c8d5e1 100644 --- a/src/compiler/scala/reflect/macros/runtime/Enclosures.scala +++ b/src/compiler/scala/reflect/macros/contexts/Enclosures.scala @@ -1,5 +1,5 @@ package scala.reflect.macros -package runtime +package contexts import scala.reflect.{ClassTag, classTag} diff --git a/src/compiler/scala/reflect/macros/runtime/Evals.scala b/src/compiler/scala/reflect/macros/contexts/Evals.scala index 1f7b5f2ff1..84928ddf86 100644 --- a/src/compiler/scala/reflect/macros/runtime/Evals.scala +++ b/src/compiler/scala/reflect/macros/contexts/Evals.scala @@ -1,5 +1,5 @@ package scala.reflect.macros -package runtime +package contexts import scala.reflect.runtime.{universe => ru} import scala.tools.reflect.ToolBox @@ -7,7 +7,7 @@ import scala.tools.reflect.ToolBox trait Evals { self: Context => - private lazy val evalMirror = ru.runtimeMirror(universe.analyzer.macroClassloader) + 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 }] diff --git a/src/compiler/scala/reflect/macros/runtime/ExprUtils.scala b/src/compiler/scala/reflect/macros/contexts/ExprUtils.scala index a719beed97..4846325d1e 100644 --- a/src/compiler/scala/reflect/macros/runtime/ExprUtils.scala +++ b/src/compiler/scala/reflect/macros/contexts/ExprUtils.scala @@ -1,5 +1,5 @@ package scala.reflect.macros -package runtime +package contexts trait ExprUtils { self: Context => diff --git a/src/compiler/scala/reflect/macros/runtime/FrontEnds.scala b/src/compiler/scala/reflect/macros/contexts/FrontEnds.scala index a6a198e1b4..fda05de09c 100644 --- a/src/compiler/scala/reflect/macros/runtime/FrontEnds.scala +++ b/src/compiler/scala/reflect/macros/contexts/FrontEnds.scala @@ -1,5 +1,7 @@ package scala.reflect.macros -package runtime +package contexts + +import scala.reflect.macros.runtime.AbortMacroException trait FrontEnds { self: Context => diff --git a/src/compiler/scala/reflect/macros/runtime/Infrastructure.scala b/src/compiler/scala/reflect/macros/contexts/Infrastructure.scala index 7781693822..df7aa4d2be 100644 --- a/src/compiler/scala/reflect/macros/runtime/Infrastructure.scala +++ b/src/compiler/scala/reflect/macros/contexts/Infrastructure.scala @@ -1,5 +1,5 @@ package scala.reflect.macros -package runtime +package contexts trait Infrastructure { self: Context => diff --git a/src/compiler/scala/reflect/macros/runtime/Names.scala b/src/compiler/scala/reflect/macros/contexts/Names.scala index 635e8bcd45..e535754a98 100644 --- a/src/compiler/scala/reflect/macros/runtime/Names.scala +++ b/src/compiler/scala/reflect/macros/contexts/Names.scala @@ -1,5 +1,5 @@ package scala.reflect.macros -package runtime +package contexts trait Names { self: Context => diff --git a/src/compiler/scala/reflect/macros/runtime/Parsers.scala b/src/compiler/scala/reflect/macros/contexts/Parsers.scala index 566bcde73d..3dab02beba 100644 --- a/src/compiler/scala/reflect/macros/runtime/Parsers.scala +++ b/src/compiler/scala/reflect/macros/contexts/Parsers.scala @@ -1,5 +1,5 @@ package scala.reflect.macros -package runtime +package contexts import scala.language.existentials import scala.tools.reflect.ToolBox diff --git a/src/compiler/scala/reflect/macros/runtime/Reifiers.scala b/src/compiler/scala/reflect/macros/contexts/Reifiers.scala index 7ec3457c6a..ecef1c7289 100644 --- a/src/compiler/scala/reflect/macros/runtime/Reifiers.scala +++ b/src/compiler/scala/reflect/macros/contexts/Reifiers.scala @@ -4,7 +4,7 @@ */ package scala.reflect.macros -package runtime +package contexts trait Reifiers { self: Context => diff --git a/src/compiler/scala/reflect/macros/runtime/Synthetics.scala b/src/compiler/scala/reflect/macros/contexts/Synthetics.scala index 73f3ab8d20..ada16a8113 100644 --- a/src/compiler/scala/reflect/macros/runtime/Synthetics.scala +++ b/src/compiler/scala/reflect/macros/contexts/Synthetics.scala @@ -3,9 +3,8 @@ */ package scala.reflect.macros -package runtime +package contexts -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/runtime/Traces.scala b/src/compiler/scala/reflect/macros/contexts/Traces.scala index 0238e9f84e..df47f6ba81 100644 --- a/src/compiler/scala/reflect/macros/runtime/Traces.scala +++ b/src/compiler/scala/reflect/macros/contexts/Traces.scala @@ -1,5 +1,5 @@ package scala.reflect.macros -package runtime +package contexts trait Traces extends util.Traces { self: Context => diff --git a/src/compiler/scala/reflect/macros/runtime/Typers.scala b/src/compiler/scala/reflect/macros/contexts/Typers.scala index f60b8dfeb4..4a1122b913 100644 --- a/src/compiler/scala/reflect/macros/runtime/Typers.scala +++ b/src/compiler/scala/reflect/macros/contexts/Typers.scala @@ -1,5 +1,5 @@ package scala.reflect.macros -package runtime +package contexts import scala.reflect.internal.Mode @@ -24,7 +24,7 @@ trait Typers { // 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)) match { + wrapper(callsiteTyper.silent(_.typed(tree, pt), reportAmbiguousErrors = false) match { case universe.analyzer.SilentResultValue(result) => macroLogVerbose(result) result 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/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/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/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/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 |