package scala.reflect.macros package compiler import scala.tools.nsc.Global abstract class DefaultMacroCompiler extends Resolvers with Validators with Errors { val global: Global import global._ import analyzer._ import treeInfo._ import definitions._ val runDefinitions = currentRun.runDefinitions import runDefinitions.Predef_??? val typer: global.analyzer.Typer val context = typer.context val macroDdef: DefDef lazy val macroDef = macroDdef.symbol case class MacroImplRefCompiler(untypedMacroImplRef: Tree, isImplBundle: Boolean) extends Resolver with Validator with Error private case class MacroImplResolutionException(pos: Position, msg: String) extends Exception def abort(pos: Position, msg: String) = throw MacroImplResolutionException(pos, msg) /** 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 impl ref * 2) [].[[]] // shiny new macro bundle impl ref * * 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). */ def resolveMacroImpl: Tree = { def tryCompile(compiler: MacroImplRefCompiler): scala.util.Try[Tree] = { try { compiler.validateMacroImplRef(); scala.util.Success(compiler.macroImplRef) } catch { case ex: MacroImplResolutionException => scala.util.Failure(ex) } } val vanillaImplRef = MacroImplRefCompiler(macroDdef.rhs.duplicate, isImplBundle = false) val (maybeBundleRef, methName, targs) = macroDdef.rhs.duplicate match { case Applied(Select(Applied(RefTree(qual, bundleName), _, Nil), 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 bundleImplRef = MacroImplRefCompiler( atPos(macroDdef.rhs.pos)(gen.mkTypeApply(Select(New(maybeBundleRef, List(List(Literal(Constant(null))))), methName), targs)), isImplBundle = true ) val vanillaResult = tryCompile(vanillaImplRef) val bundleResult = tryCompile(bundleImplRef) def ensureUnambiguousSuccess() = { // we now face a hard choice of whether to report ambiguity: // 1) when there are eponymous methods in both bundle and object // 2) when both references to eponymous methods are resolved successfully // doing #1 would cause less confusion in the long run, but it would also cause more frequent source incompatibilities // e.g. it would fail to compile https://github.com/ReifyIt/basis // therefore here we go for #2 // if (vanillaImplRef.looksCredible && bundleImplRef.looksCredible) MacroImplAmbiguousError() if (vanillaResult.isSuccess && bundleResult.isSuccess) MacroImplAmbiguousError() } def reportMostAppropriateFailure() = { typer.silent(_.typedTypeConstructor(maybeBundleRef)) match { case SilentResultValue(result) if looksLikeMacroBundleType(result.tpe) => val bundle = result.tpe.typeSymbol if (!isMacroBundleType(bundle.tpe)) MacroBundleWrongShapeError() if (!bundle.owner.isStaticOwner) MacroBundleNonStaticError() bundleResult.get case _ => vanillaResult.get } } try { if (vanillaResult.isSuccess || bundleResult.isSuccess) ensureUnambiguousSuccess() if (vanillaResult.isFailure && bundleResult.isFailure) reportMostAppropriateFailure() vanillaResult.orElse(bundleResult).get } catch { case MacroImplResolutionException(pos, msg) => context.error(pos, msg) EmptyTree } } }