From ff9dd6e9ddd842f9f85f280be643318748433a20 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Sat, 30 Nov 2013 13:58:03 +0100 Subject: [PATCH 1/2] Make incremental compiler compatible with Scala 2.11. The scala/scala@2d4f0f1859b957b744f9b9f222dec8e8c478a4a8 removes the `toplevelClass` method. The recent change from aac19fd02be94f4ef6ba98187c9cbbc2b66a60f9 introduces dependency on that method. Combination of both changes makes incremental compiler incompatible with Scala 2.11. This change introduces a compatibility hack that brings back source compatibility of incremental compiler with Scala 2.8, 2.9, 2.10 and 2.11. The compatibility hack is making clever use implicit conversions that can provide dummy method definitions for methods removed from Scala compiler. Also, the code that depends on `enclosingTopLevelClass` has been refactored so the dependency is more centralized. --- compile/interface/src/main/scala/xsbt/Compat.scala | 13 ++++++++++--- .../interface/src/main/scala/xsbt/Dependency.scala | 14 ++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/compile/interface/src/main/scala/xsbt/Compat.scala b/compile/interface/src/main/scala/xsbt/Compat.scala index 8849430e8..aae105e44 100644 --- a/compile/interface/src/main/scala/xsbt/Compat.scala +++ b/compile/interface/src/main/scala/xsbt/Compat.scala @@ -39,10 +39,17 @@ 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 + // this for compatibility with Scala 2.11 where Symbol.enclosingTopLevelClass method exist + // so we won't be ever calling SymbolCompat.enclosingTopLevelClass but we need to compile + // it hence we need dummy forwarder target, the `toplevelClass` method defined + // in Scala 2.9 and 2.10 the `Symbol.toplevelClass` exists so the dummy forwarder target + // won't be used + def toplevelClass: Symbol = throw new UnsupportedOperationException("We should never have gotten here") } 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 + } From 7e303f86920832cda845298abcd0cd53a5d147ee Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Mon, 2 Dec 2013 17:55:11 +0100 Subject: [PATCH 2/2] Add more documentation to Compat class in compiler interface. Add documentation which explains how a general technique using implicits conversions is employed in Compat class. Previously, it was hidden inside of Compat class. Also, I changed `toplevelClass` implementation to call `sourceCompatibilityOnly` method that is designed for the purpose of being a compatibility stub. --- .../src/main/scala/xsbt/Compat.scala | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/compile/interface/src/main/scala/xsbt/Compat.scala b/compile/interface/src/main/scala/xsbt/Compat.scala index aae105e44..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 { @@ -43,13 +74,9 @@ abstract class Compat 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 - // this for compatibility with Scala 2.11 where Symbol.enclosingTopLevelClass method exist - // so we won't be ever calling SymbolCompat.enclosingTopLevelClass but we need to compile - // it hence we need dummy forwarder target, the `toplevelClass` method defined - // in Scala 2.9 and 2.10 the `Symbol.toplevelClass` exists so the dummy forwarder target - // won't be used - def toplevelClass: Symbol = throw new UnsupportedOperationException("We should never have gotten here") + def toplevelClass: Symbol = sourceCompatibilityOnly }