package xsbt import scala.tools.nsc.Global import scala.tools.nsc.symtab.Flags /** * Collection of hacks that make it possible for the compiler interface * to stay source compatible with Scala compiler 2.9, 2.10 and 2.11. * * One common technique used in `Compat` class is use of implicit conversions to deal * with methods that got renamed or moved between different Scala compiler versions. * * Let's pick a specific example. In Scala 2.9 and 2.10 there was a method called `toplevelClass` * defined on `Symbol`. In 2.10 that method has been deprecated and `enclosingTopLevelClass` * method has been introduce as a replacement. In Scala 2.11 the old `toplevelClass` method has * been removed. How can we pick the right version based on availability of those two methods? * * We define an implicit conversion from Symbol to a class that contains both method definitions: * * implicit def symbolCompat(sym: Symbol): SymbolCompat = new SymbolCompat(sym) * class SymbolCompat(sym: Symbol) { * def enclosingTopLevelClass: Symbol = sym.toplevelClass * def toplevelClass: Symbol = * throw new RuntimeException("For source compatibility only: should not get here.") * } * * We assume that client code (code in compiler interface) should always call `enclosingTopLevelClass` * method. If we compile that code against 2.11 it will just directly link against method provided by * Symbol. However, if we compile against 2.9 or 2.10 `enclosingTopLevelClass` won't be found so the * implicit conversion defined above will kick in. That conversion will provide `enclosingTopLevelClass` * that simply forwards to the old `toplevelClass` method that is available in 2.9 and 2.10 so that * method will be called in the end. There's one twist: since `enclosingTopLevelClass` forwards to * `toplevelClass` which doesn't exist in 2.11! Therefore, we need to also define `toplevelClass` * that will be provided by an implicit conversion as well. However, we should never reach that method * at runtime if either `enclosingTopLevelClass` or `toplevelClass` is available on Symbol so this * is purely source compatibility stub. * * The technique described above is used in several places below. * */ abstract class Compat { val global: Global import global._ val LocalChild = global.tpnme.LOCAL_CHILD val Nullary = global.NullaryMethodType val ScalaObjectClass = definitions.ScalaObjectClass private[this] final class MiscCompat { // in 2.9, nme.LOCALCHILD was renamed to tpnme.LOCAL_CHILD def tpnme = nme def LOCAL_CHILD = nme.LOCALCHILD def LOCALCHILD = sourceCompatibilityOnly // in 2.10, ScalaObject was removed def ScalaObjectClass = definitions.ObjectClass def NullaryMethodType = NullaryMethodTpe def MACRO = DummyValue // in 2.10, sym.moduleSuffix exists, but genJVM.moduleSuffix(Symbol) does not def moduleSuffix(sym: Symbol): String = sourceCompatibilityOnly // in 2.11 genJVM does not exist def genJVM = this } // in 2.9, NullaryMethodType was added to Type object NullaryMethodTpe { def unapply(t: Type): Option[Type] = None } protected implicit def symbolCompat(sym: Symbol): SymbolCompat = new SymbolCompat(sym) protected final class SymbolCompat(sym: Symbol) { // before 2.10, sym.moduleSuffix doesn't exist, but genJVM.moduleSuffix does def moduleSuffix = global.genJVM.moduleSuffix(sym) def enclosingTopLevelClass: Symbol = sym.toplevelClass def toplevelClass: Symbol = sourceCompatibilityOnly } val DummyValue = 0 def hasMacro(s: Symbol): Boolean = { val MACRO = Flags.MACRO // will be DummyValue for versions before 2.10 MACRO != DummyValue && s.hasFlag(MACRO) } def moduleSuffix(s: Symbol): String = s.moduleSuffix private[this] def sourceCompatibilityOnly: Nothing = throw new RuntimeException("For source compatibility only: should not get here.") private[this] final implicit def miscCompat(n: AnyRef): MiscCompat = new MiscCompat object MacroExpansionOf { def unapply(tree: Tree): Option[Tree] = { // MacroExpansionAttachment (MEA) compatibility for 2.8.x and 2.9.x object Compat { class MacroExpansionAttachment(val original: Tree) // Trees have no attachments in 2.8.x and 2.9.x implicit def withAttachments(tree: Tree): WithAttachments = new WithAttachments(tree) class WithAttachments(val tree: Tree) { object EmptyAttachments { def all = Set.empty[Any] } val attachments = EmptyAttachments } } import Compat._ locally { // Wildcard imports are necessary since 2.8.x and 2.9.x don't have `MacroExpansionAttachment` at all import global._ // this is where MEA lives in 2.10.x // `original` has been renamed to `expandee` in 2.11.x implicit def withExpandee(att: MacroExpansionAttachment): WithExpandee = new WithExpandee(att) class WithExpandee(att: MacroExpansionAttachment) { def expandee: Tree = att.original } locally { import analyzer._ // this is where MEA lives in 2.11.x tree.attachments.all.collect { case att: MacroExpansionAttachment => att.expandee } headOption } } } } }