From c8354bb69141b010680dfbd99310bcc9481a033c Mon Sep 17 00:00:00 2001 From: extempore Date: Thu, 1 Sep 2011 02:31:55 +0000 Subject: [PATCH] Made it possible to supply a custom Global to the core scala runners. The absence of "Global pluggability", combined with the fact that most of the functionality in Global is unnecessarily rigid due to the phases being implemented as objects, means that it has been close to impossible to do interesting compiler development in a way which doesn't require modifying the scalac source tree. This then leaves you continually subject to punishment by code drift as the various places you were forced to modify change out from under you. This is somewhat less true now, thanks to new option: -Yglobal-class The primary wielders of Global (fsc/scala/scalac) now instantiate the compiler via a (Settings, Reporter) => Global factory method in the Global companion. If -Yglobal-class was given, that class (which must have a (Settings, Reporter) constructor) will be instantiated if possible, falling back on the standard one. See test/files/pos/CustomGlobal.scala for a working example. (It's not in run because I would have to be able to give partest a different set of flags for successive compiles in the same test.) Review by odersky. git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@25600 5e8d7ff9-d8ef-0310-90f0-a4852d11357a --- .../scala/reflect/internal/Trees.scala | 10 ++++ src/compiler/scala/tools/nsc/Global.scala | 48 +++++++++++++++++-- src/compiler/scala/tools/nsc/Main.scala | 4 +- .../scala/tools/nsc/ScriptRunner.scala | 2 +- .../scala/tools/nsc/interpreter/IMain.scala | 2 +- .../tools/nsc/settings/ScalaSettings.scala | 1 + test/files/pos/CustomGlobal.scala | 33 +++++++++++++ 7 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 test/files/pos/CustomGlobal.scala diff --git a/src/compiler/scala/reflect/internal/Trees.scala b/src/compiler/scala/reflect/internal/Trees.scala index 44b8534e3..f7de4a070 100644 --- a/src/compiler/scala/reflect/internal/Trees.scala +++ b/src/compiler/scala/reflect/internal/Trees.scala @@ -117,6 +117,16 @@ trait Trees extends api.Trees { self: SymbolTable => def shallowDuplicate: Tree = new ShallowDuplicator(tree) transform tree def shortClass: String = tree.getClass.getName split "[.$]" last + /** When you want to know a little more than the class, but a lot + * less than the whole tree. + */ + def summaryString: String = tree match { + case Select(qual, name) => qual.summaryString + "." + name + case Ident(name) => name.longString + case t: DefTree => t.shortClass + " " + t.name + case t: RefTree => t.shortClass + " " + t.name.longString + case t => t.shortClass + } } // ---- values and creators --------------------------------------- diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 4f0ee6e39..e0876ced7 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -9,11 +9,11 @@ import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundE import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException } import compat.Platform.currentTime -import scala.tools.util.Profiling +import scala.tools.util.{ Profiling, PathResolver } import scala.collection.{ mutable, immutable } import io.{ SourceReader, AbstractFile, Path } import reporters.{ Reporter, ConsoleReporter } -import util.{ Exceptional, ClassPath, SourceFile, Statistics, StatisticsInfo, BatchSourceFile, ScriptSourceFile, ShowPickled, returning } +import util.{ NoPosition, Exceptional, ClassPath, SourceFile, Statistics, StatisticsInfo, BatchSourceFile, ScriptSourceFile, ShowPickled, ScalaClassLoader, returning } import scala.reflect.internal.pickling.{ PickleBuffer, PickleFormat } import settings.{ AestheticSettings } @@ -377,11 +377,18 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb val runsRightAfter = None } with SyntaxAnalyzer + // !!! I think we're overdue for all these phase objects being lazy vals. + // There's no way for a Global subclass to provide a custom typer + // despite the existence of a "def newTyper(context: Context): Typer" + // which is clearly designed for that, because it's defined in + // Analyzer and Global's "object analyzer" allows no override. For now + // I only changed analyzer. + // // factory for phases: namer, packageobjects, typer - object analyzer extends { + lazy val analyzer = new { val global: Global.this.type = Global.this } with Analyzer - + // phaseName = "superaccessors" object superAccessors extends { val global: Global.this.type = Global.this @@ -1295,3 +1302,36 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb @deprecated("Use forInteractive or forScaladoc, depending on what you're after", "2.9.0") def onlyPresentation = false } + +object Global { + /** If possible, instantiate the global specified via -Yglobal-class. + * This allows the use of a custom Global subclass with the software which + * wraps Globals, such as scalac, fsc, and the repl. + */ + def fromSettings(settings: Settings, reporter: Reporter): Global = { + // !!! The classpath isn't known until the Global is created, which is too + // late, so we have to duplicate it here. Classpath is too tightly coupled, + // it is a construct external to the compiler and should be treated as such. + val loader = ScalaClassLoader.fromURLs(new PathResolver(settings).result.asURLs) + val name = settings.globalClass.value + val clazz = Class.forName(name, true, loader) + val cons = clazz.getConstructor(classOf[Settings], classOf[Reporter]) + + cons.newInstance(settings, reporter).asInstanceOf[Global] + } + + /** A global instantiated this way honors -Yglobal-class setting, and + * falls back on calling the Global constructor directly. + */ + def apply(settings: Settings, reporter: Reporter): Global = { + val g = ( + if (settings.globalClass.isDefault) null + else try fromSettings(settings, reporter) catch { case x => + reporter.warning(NoPosition, "Failed to instantiate " + settings.globalClass.value + ": " + x) + null + } + ) + if (g != null) g + else new Global(settings, reporter) + } +} diff --git a/src/compiler/scala/tools/nsc/Main.scala b/src/compiler/scala/tools/nsc/Main.scala index 3d950034e..49295ed40 100644 --- a/src/compiler/scala/tools/nsc/Main.scala +++ b/src/compiler/scala/tools/nsc/Main.scala @@ -73,8 +73,8 @@ object Main extends Driver with EvalLoop { override def newCompiler(): Global = if (settings.Yrangepos.value) new interactive.Global(settings, reporter) - else new Global(settings, reporter) - + else Global(settings, reporter) + override def doCompile(compiler: Global) { if (settings.resident.value) resident(compiler) diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala index f58854a17..3b609faaa 100644 --- a/src/compiler/scala/tools/nsc/ScriptRunner.scala +++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala @@ -84,7 +84,7 @@ class ScriptRunner extends HasCompileSocket { } protected def newGlobal(settings: Settings, reporter: Reporter) = - new Global(settings, reporter) + Global(settings, reporter) /** Compile a script and then run the specified closure with * a classpath for the compiled script. diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index ebcfaa006..ec2452749 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -272,7 +272,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends settings.outputDirs setSingleOutput virtualDirectory settings.exposeEmptyPackage.value = true - new Global(settings, reporter) + Global(settings, reporter) } /** Parent classloader. Overridable. */ diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 6b962a51a..0680b9d6f 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -145,6 +145,7 @@ trait ScalaSettings extends AbsScalaSettings val refinementMethodDispatch = ChoiceSetting ("-Ystruct-dispatch", "policy", "structural method dispatch policy", List("no-cache", "mono-cache", "poly-cache", "invoke-dynamic"), "poly-cache") + val globalClass = StringSetting ("-Yglobal-class", "class", "subclass of scala.tools.nsc.Global to use for compiler", "") val Yrangepos = BooleanSetting ("-Yrangepos", "Use range positions for syntax trees.") val YrichExes = BooleanSetting ("-Yrich-exceptions", "Fancier exceptions. Set source search path with -D" + diff --git a/test/files/pos/CustomGlobal.scala b/test/files/pos/CustomGlobal.scala new file mode 100644 index 000000000..30bf22795 --- /dev/null +++ b/test/files/pos/CustomGlobal.scala @@ -0,0 +1,33 @@ +package custom + +import scala.tools.nsc._, reporters._, typechecker._ + +/** Demonstration of a custom Global with a custom Typer, + * decoupled from trunk. Demonstration: + * +{{{ +scalac -d . CustomGlobal.scala && scala -nc -Yglobal-class custom.CustomGlobal \ + -e 'class Bippy(x: Int) ; def f = new Bippy(5)' + +I'm typing a Bippy! It's a ClassDef. +I'm typing a Bippy! It's a Ident. +I'm typing a Bippy! It's a DefDef. +}}} + * + */ +class CustomGlobal(currentSettings: Settings, reporter: Reporter) extends Global(currentSettings, reporter) { + override lazy val analyzer = new { + val global: CustomGlobal.this.type = CustomGlobal.this + } with Analyzer { + override def newTyper(context: Context): Typer = new CustomTyper(context) + + class CustomTyper(context : Context) extends Typer(context) { + override def typed(tree: Tree, mode: Int, pt: Type): Tree = { + if (tree.summaryString contains "Bippy") + println("I'm typing a Bippy! It's a " + tree.shortClass + ".") + + super.typed(tree, mode, pt) + } + } + } +}