diff --git a/compile/interface/src/main/scala/xsbt/Compat.scala b/compile/interface/src/main/scala/xsbt/Compat.scala index 8849430e8..17a1a8f6b 100644 --- a/compile/interface/src/main/scala/xsbt/Compat.scala +++ b/compile/interface/src/main/scala/xsbt/Compat.scala @@ -4,8 +4,39 @@ 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. + * 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 { @@ -39,10 +70,13 @@ abstract class Compat def unapply(t: Type): Option[Type] = None } - // before 2.10, sym.moduleSuffix doesn't exist, but genJVM.moduleSuffix does - private[this] implicit def symbolCompat(sym: Symbol): SymbolCompat = new SymbolCompat(sym) - private[this] final class SymbolCompat(sym: Symbol) { + 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 } diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index 535a6b822..edb33197f 100644 --- a/compile/interface/src/main/scala/xsbt/Dependency.scala +++ b/compile/interface/src/main/scala/xsbt/Dependency.scala @@ -156,9 +156,7 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile val traverser = new ExtractDependenciesByMemberRefTraverser traverser.traverse(unit.body) val dependencies = traverser.dependencies - // we capture enclosing classes only because that's what CompilationUnit.depends does and we don't want - // to deviate from old behaviour too much for now - dependencies.map(_.toplevelClass) + dependencies.map(enclosingTopLevelClass) } /** Copied straight from Scala 2.10 as it does not exist in Scala 2.9 compiler */ @@ -184,7 +182,15 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile val traverser = new ExtractDependenciesByInheritanceTraverser traverser.traverse(unit.body) val dependencies = traverser.dependencies - dependencies.map(_.toplevelClass) + dependencies.map(enclosingTopLevelClass) } + /** + * We capture enclosing classes only because that's what CompilationUnit.depends does and we don't want + * to deviate from old behaviour too much for now. + */ + private def enclosingTopLevelClass(sym: Symbol): Symbol = + // for Scala 2.8 and 2.9 this method is provided through SymbolCompat + sym.enclosingTopLevelClass + }