/* NSC -- new Scala compiler * Copyright 2005-2013 LAMP/EPFL * @author Martin Odersky */ package scala package tools package nsc import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundException } import java.net.URL import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException } import scala.collection.{ mutable, immutable } import io.{ SourceReader, AbstractFile, Path } import reporters.{ Reporter, ConsoleReporter } import util.{ ClassFileLookup, ClassPath, MergedClassPath, StatisticsInfo, returning } import scala.reflect.ClassTag import scala.reflect.internal.util.{ SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile } import scala.reflect.internal.pickling.PickleBuffer import symtab.{ Flags, SymbolTable, SymbolTrackers } import symtab.classfile.Pickler import plugins.Plugins import ast._ import ast.parser._ import typechecker._ import transform.patmat.PatternMatching import transform._ import backend.icode.{ ICodes, GenICode, ICodeCheckers } import backend.{ ScalaPrimitives, JavaPlatform } import backend.jvm.GenBCode import backend.jvm.GenASM import backend.opt.{ Inliners, InlineExceptionHandlers, ConstantOptimization, ClosureElimination, DeadCodeElimination } import backend.icode.analysis._ import scala.language.postfixOps import scala.tools.nsc.ast.{TreeGen => AstTreeGen} import scala.tools.nsc.classpath.FlatClassPath import scala.tools.nsc.settings.ClassPathRepresentationType class Global(var currentSettings: Settings, var reporter: Reporter) extends SymbolTable with CompilationUnits with Plugins with PhaseAssembly with Trees with Printers with DocComments with Positions with Reporting with Parsing { self => // the mirror -------------------------------------------------- override def isCompilerUniverse = true override val useOffsetPositions = !currentSettings.Yrangepos type RuntimeClass = java.lang.Class[_] implicit val RuntimeClassTag: ClassTag[RuntimeClass] = ClassTag[RuntimeClass](classOf[RuntimeClass]) class GlobalMirror extends Roots(NoSymbol) { val universe: self.type = self def rootLoader: LazyType = { settings.YclasspathImpl.value match { case ClassPathRepresentationType.Flat => new loaders.PackageLoaderUsingFlatClassPath(FlatClassPath.RootPackage, flatClassPath) case ClassPathRepresentationType.Recursive => new loaders.PackageLoader(recursiveClassPath) } } override def toString = "compiler mirror" } implicit val MirrorTag: ClassTag[Mirror] = ClassTag[Mirror](classOf[GlobalMirror]) lazy val rootMirror: Mirror = { val rm = new GlobalMirror rm.init() rm.asInstanceOf[Mirror] } def RootClass: ClassSymbol = rootMirror.RootClass def EmptyPackageClass: ClassSymbol = rootMirror.EmptyPackageClass import definitions.findNamedMember def findMemberFromRoot(fullName: Name): Symbol = rootMirror.findMemberFromRoot(fullName) // alternate constructors ------------------------------------------ override def settings = currentSettings /** Switch to turn on detailed type logs */ var printTypings = settings.Ytyperdebug.value def this(reporter: Reporter) = this(new Settings(err => reporter.error(null, err)), reporter) def this(settings: Settings) = this(settings, new ConsoleReporter(settings)) def picklerPhase: Phase = if (currentRun.isDefined) currentRun.picklerPhase else NoPhase def erasurePhase: Phase = if (currentRun.isDefined) currentRun.erasurePhase else NoPhase /* Override `newStubSymbol` defined in `SymbolTable` to provide us access * to the last tree to typer, whose position is the trigger of stub errors. */ override def newStubSymbol(owner: Symbol, name: Name, missingMessage: String, isPackage: Boolean = false): Symbol = { val stubSymbol = super.newStubSymbol(owner, name, missingMessage, isPackage) val stubErrorPosition = { val lastTreeToTyper = analyzer.lastTreeToTyper if (lastTreeToTyper != EmptyTree) lastTreeToTyper.pos else stubSymbol.pos } stubSymbol.setPos(stubErrorPosition) } // platform specific elements protected class GlobalPlatform extends { val global: Global.this.type = Global.this val settings: Settings = Global.this.settings } with JavaPlatform type ThisPlatform = JavaPlatform { val global: Global.this.type } lazy val platform: ThisPlatform = new GlobalPlatform type PlatformClassPath = ClassPath[AbstractFile] type OptClassPath = Option[PlatformClassPath] def classPath: ClassFileLookup[AbstractFile] = settings.YclasspathImpl.value match { case ClassPathRepresentationType.Flat => flatClassPath case ClassPathRepresentationType.Recursive => recursiveClassPath } private def recursiveClassPath: ClassPath[AbstractFile] = platform.classPath private def flatClassPath: FlatClassPath = platform.flatClassPath // sub-components -------------------------------------------------- /** Tree generation, usually based on existing symbols. */ override object gen extends { val global: Global.this.type = Global.this } with AstTreeGen { def mkAttributedCast(tree: Tree, pt: Type): Tree = typer.typed(mkCast(tree, pt)) } /** A spare instance of TreeBuilder left for backwards compatibility. */ lazy val treeBuilder: TreeBuilder { val global: Global.this.type } = new TreeBuilder { val global: Global.this.type = Global.this; def unit = currentUnit def source = currentUnit.source } /** Fold constants */ object constfold extends { val global: Global.this.type = Global.this } with ConstantFolder /** ICode generator */ object icodes extends { val global: Global.this.type = Global.this } with ICodes /** Scala primitives, used in genicode */ object scalaPrimitives extends { val global: Global.this.type = Global.this } with ScalaPrimitives /** Computing pairs of overriding/overridden symbols */ object overridingPairs extends { val global: Global.this.type = Global.this } with OverridingPairs type SymbolPair = overridingPairs.SymbolPair // Optimizer components /** ICode analysis for optimization */ object analysis extends { val global: Global.this.type = Global.this } with TypeFlowAnalysis /** Copy propagation for optimization */ object copyPropagation extends { val global: Global.this.type = Global.this } with CopyPropagation // Components for collecting and generating output /** Some statistics (normally disabled) set with -Ystatistics */ object statistics extends { val global: Global.this.type = Global.this } with StatisticsInfo /** Print tree in detailed form */ object nodePrinters extends { val global: Global.this.type = Global.this } with NodePrinters { var lastPrintedPhase: Phase = NoPhase var lastPrintedSource: String = "" infolevel = InfoLevel.Verbose def showUnit(unit: CompilationUnit) { print(" // " + unit.source) if (unit.body == null) println(": tree is null") else { val source = util.stringFromWriter(w => newTreePrinter(w) print unit.body) // treePrinter show unit.body if (lastPrintedSource == source) println(": tree is unchanged since " + lastPrintedPhase) else { lastPrintedPhase = phase.prev // since we're running inside "exitingPhase" lastPrintedSource = source println("") println(source) println("") } } } } def withInfoLevel[T](infolevel: nodePrinters.InfoLevel.Value)(op: => T) = { val saved = nodePrinters.infolevel try { nodePrinters.infolevel = infolevel op } finally { nodePrinters.infolevel = saved } } private var propCnt = 0 @inline final def withPropagateCyclicReferences[T](t: => T): T = { try { propCnt = propCnt+1 t } finally { propCnt = propCnt-1 assert(propCnt >= 0) } } def propagateCyclicReferences: Boolean = propCnt > 0 /** Representing ASTs as graphs */ object treeBrowsers extends { val global: Global.this.type = Global.this } with TreeBrowsers val nodeToString = nodePrinters.nodeToString val treeBrowser = treeBrowsers.create() // ------------ Hooks for interactive mode------------------------- /** Called every time an AST node is successfully typechecked in typerPhase. */ def signalDone(context: analyzer.Context, old: Tree, result: Tree) {} /** Called from parser, which signals hereby that a method definition has been parsed. */ def signalParseProgress(pos: Position) {} /** Called by ScaladocAnalyzer when a doc comment has been parsed. */ def signalParsedDocComment(comment: String, pos: Position) = { // TODO: this is all very broken (only works for scaladoc comments, not regular ones) // --> add hooks to parser and refactor Interactive global to handle comments directly // in any case don't use reporter for parser hooks reporter.comment(pos, comment) } /** Register new context; called for every created context */ def registerContext(c: analyzer.Context) { lastSeenContext = c } /** Register top level class (called on entering the class) */ def registerTopLevelSym(sym: Symbol) {} // ------------------ Debugging ------------------------------------- // Getting in front of Predef's asserts to supplement with more info. // This has the happy side effect of masking the one argument forms // of assert and require (but for now I've reproduced them here, // because there are a million to fix.) @inline final def assert(assertion: Boolean, message: => Any) { // calling Predef.assert would send a freshly allocated closure wrapping the one received as argument. if (!assertion) throw new java.lang.AssertionError("assertion failed: "+ supplementErrorMessage("" + message)) } @inline final def assert(assertion: Boolean) { assert(assertion, "") } @inline final def require(requirement: Boolean, message: => Any) { // calling Predef.require would send a freshly allocated closure wrapping the one received as argument. if (!requirement) throw new IllegalArgumentException("requirement failed: "+ supplementErrorMessage("" + message)) } @inline final def require(requirement: Boolean) { require(requirement, "") } @inline final def ifDebug(body: => Unit) { if (settings.debug) body } override protected def isDeveloper = settings.developer || super.isDeveloper /** This is for WARNINGS which should reach the ears of scala developers * whenever they occur, but are not useful for normal users. They should * be precise, explanatory, and infrequent. Please don't use this as a * logging mechanism. !!! is prefixed to all messages issued via this route * to make them visually distinct. */ @inline final override def devWarning(msg: => String): Unit = devWarning(NoPosition, msg) @inline final def devWarning(pos: Position, msg: => String) { def pos_s = if (pos eq NoPosition) "" else s" [@ $pos]" if (isDeveloper) warning(pos, "!!! " + msg) else log(s"!!!$pos_s $msg") // such warnings always at least logged } def logError(msg: String, t: Throwable): Unit = () override def shouldLogAtThisPhase = settings.log.isSetByUser && ( (settings.log containsPhase globalPhase) || (settings.log containsPhase phase) ) // Over 200 closure objects are eliminated by inlining this. @inline final def log(msg: => AnyRef) { if (shouldLogAtThisPhase) inform("[log %s%s] %s".format(globalPhase, atPhaseStackMessage, msg)) } @inline final override def debuglog(msg: => String) { if (settings.debug) log(msg) } @deprecated("Renamed to reportThrowable", "2.10.1") def logThrowable(t: Throwable): Unit = reportThrowable(t) def reportThrowable(t: Throwable): Unit = globalError(throwableAsString(t)) override def throwableAsString(t: Throwable) = util.stackTraceString(t) // ------------ File interface ----------------------------------------- private val reader: SourceReader = { val defaultEncoding = Properties.sourceEncoding def loadCharset(name: String) = try Some(Charset.forName(name)) catch { case _: IllegalCharsetNameException => globalError("illegal charset name '" + name + "'") None case _: UnsupportedCharsetException => globalError("unsupported charset '" + name + "'") None } val charset = settings.encoding.valueSetByUser flatMap loadCharset getOrElse { settings.encoding.value = defaultEncoding // A mandatory charset Charset.forName(defaultEncoding) } def loadReader(name: String): Option[SourceReader] = { def ccon = Class.forName(name).getConstructor(classOf[CharsetDecoder], classOf[Reporter]) try Some(ccon.newInstance(charset.newDecoder(), reporter).asInstanceOf[SourceReader]) catch { case ex: Throwable => globalError("exception while trying to instantiate source reader '" + name + "'") None } } settings.sourceReader.valueSetByUser flatMap loadReader getOrElse { new SourceReader(charset.newDecoder(), reporter) } } if (settings.verbose || settings.Ylogcp) reporter.echo( s"[search path for source files: ${classPath.asSourcePathString}]\n" + s"[search path for class files: ${classPath.asClassPathString}]" ) // The current division between scala.reflect.* and scala.tools.nsc.* is pretty // clunky. It is often difficult to have a setting influence something without having // to create it on that side. For this one my strategy is a constant def at the file // where I need it, and then an override in Global with the setting. override protected val etaExpandKeepsStar = settings.etaExpandKeepsStar.value // Here comes another one... override protected val enableTypeVarExperimentals = settings.Xexperimental.value def getSourceFile(f: AbstractFile): BatchSourceFile = new BatchSourceFile(f, reader read f) def getSourceFile(name: String): SourceFile = { val f = AbstractFile.getFile(name) if (f eq null) throw new FileNotFoundException( "source file '" + name + "' could not be found") getSourceFile(f) } lazy val loaders = new { val global: Global.this.type = Global.this val platform: Global.this.platform.type = Global.this.platform } with GlobalSymbolLoaders /** Returns the mirror that loaded given symbol */ def mirrorThatLoaded(sym: Symbol): Mirror = rootMirror // ------------ Phases -------------------------------------------} var globalPhase: Phase = NoPhase val MaxPhases = 64 val phaseWithId: Array[Phase] = Array.fill(MaxPhases)(NoPhase) abstract class GlobalPhase(prev: Phase) extends Phase(prev) { phaseWithId(id) = this def run() { echoPhaseSummary(this) currentRun.units foreach applyPhase } def apply(unit: CompilationUnit): Unit private val isErased = prev.name == "erasure" || prev.erasedTypes override def erasedTypes: Boolean = isErased private val isFlat = prev.name == "flatten" || prev.flatClasses override def flatClasses: Boolean = isFlat private val isSpecialized = prev.name == "specialize" || prev.specialized override def specialized: Boolean = isSpecialized private val isRefChecked = prev.name == "refchecks" || prev.refChecked override def refChecked: Boolean = isRefChecked /** Is current phase cancelled on this unit? */ def cancelled(unit: CompilationUnit) = { // run the typer only if in `createJavadoc` mode val maxJavaPhase = if (createJavadoc) currentRun.typerPhase.id else currentRun.namerPhase.id reporter.cancelled || unit.isJava && this.id > maxJavaPhase } final def withCurrentUnit(unit: CompilationUnit)(task: => Unit) { if ((unit ne null) && unit.exists) lastSeenSourceFile = unit.source if (settings.debug && (settings.verbose || currentRun.size < 5)) inform("[running phase " + name + " on " + unit + "]") val unit0 = currentUnit try { currentRun.currentUnit = unit if (!cancelled(unit)) { currentRun.informUnitStarting(this, unit) task } currentRun.advanceUnit() } finally { //assert(currentUnit == unit) currentRun.currentUnit = unit0 } } final def applyPhase(unit: CompilationUnit) = withCurrentUnit(unit)(apply(unit)) } // phaseName = "parser" lazy val syntaxAnalyzer = new { val global: Global.this.type = Global.this } with SyntaxAnalyzer { val runsAfter = List[String]() val runsRightAfter = None override val initial = true } import syntaxAnalyzer.{ UnitScanner, UnitParser } // !!! 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 lazy val analyzer = new { val global: Global.this.type = Global.this } with Analyzer // phaseName = "patmat" object patmat extends { val global: Global.this.type = Global.this val runsAfter = List("typer") val runsRightAfter = None // patmat doesn't need to be right after typer, as long as we run before superaccessors // (sbt does need to run right after typer, so don't conflict) } with PatternMatching // phaseName = "superaccessors" object superAccessors extends { val global: Global.this.type = Global.this val runsAfter = List("patmat") val runsRightAfter = None } with SuperAccessors // phaseName = "extmethods" object extensionMethods extends { val global: Global.this.type = Global.this val runsAfter = List("superaccessors") val runsRightAfter = None } with ExtensionMethods // phaseName = "pickler" object pickler extends { val global: Global.this.type = Global.this val runsAfter = List("extmethods") val runsRightAfter = None } with Pickler // phaseName = "refchecks" override object refChecks extends { val global: Global.this.type = Global.this val runsAfter = List("pickler") val runsRightAfter = None } with RefChecks // phaseName = "uncurry" override object uncurry extends { val global: Global.this.type = Global.this val runsAfter = List("refchecks") val runsRightAfter = None } with UnCurry // phaseName = "tailcalls" object tailCalls extends { val global: Global.this.type = Global.this val runsAfter = List("uncurry") val runsRightAfter = None } with TailCalls // phaseName = "explicitouter" object explicitOuter extends { val global: Global.this.type = Global.this val runsAfter = List("tailcalls") val runsRightAfter = None } with ExplicitOuter // phaseName = "specialize" object specializeTypes extends { val global: Global.this.type = Global.this val runsAfter = List("") val runsRightAfter = Some("tailcalls") } with SpecializeTypes // phaseName = "erasure" override object erasure extends { val global: Global.this.type = Global.this val runsAfter = List("explicitouter") val runsRightAfter = Some("explicitouter") } with Erasure // phaseName = "posterasure" override object postErasure extends { val global: Global.this.type = Global.this val runsAfter = List("erasure") val runsRightAfter = Some("erasure") } with PostErasure // phaseName = "lazyvals" object lazyVals extends { val global: Global.this.type = Global.this val runsAfter = List("erasure") val runsRightAfter = None } with LazyVals // phaseName = "lambdalift" object lambdaLift extends { val global: Global.this.type = Global.this val runsAfter = List("lazyvals") val runsRightAfter = None } with LambdaLift // phaseName = "constructors" object constructors extends { val global: Global.this.type = Global.this val runsAfter = List("lambdalift") val runsRightAfter = None } with Constructors // phaseName = "flatten" object flatten extends { val global: Global.this.type = Global.this val runsAfter = List("constructors") val runsRightAfter = None } with Flatten // phaseName = "mixin" object mixer extends { val global: Global.this.type = Global.this val runsAfter = List("flatten", "constructors") val runsRightAfter = None } with Mixin // phaseName = "cleanup" object cleanup extends { val global: Global.this.type = Global.this val runsAfter = List("mixin") val runsRightAfter = None } with CleanUp // phaseName = "delambdafy" object delambdafy extends { val global: Global.this.type = Global.this val runsAfter = List("cleanup") val runsRightAfter = None } with Delambdafy // phaseName = "icode" object genicode extends { val global: Global.this.type = Global.this val runsAfter = List("cleanup") val runsRightAfter = None } with GenICode // phaseName = "inliner" object inliner extends { val global: Global.this.type = Global.this val runsAfter = List("icode") val runsRightAfter = None } with Inliners // phaseName = "inlinehandlers" object inlineExceptionHandlers extends { val global: Global.this.type = Global.this val runsAfter = List("inliner") val runsRightAfter = None } with InlineExceptionHandlers // phaseName = "closelim" object closureElimination extends { val global: Global.this.type = Global.this val runsAfter = List("inlinehandlers") val runsRightAfter = None } with ClosureElimination // phaseName = "constopt" object constantOptimization extends { val global: Global.this.type = Global.this val runsAfter = List("closelim") val runsRightAfter = None } with ConstantOptimization // phaseName = "dce" object deadCode extends { val global: Global.this.type = Global.this val runsAfter = List("closelim") val runsRightAfter = None } with DeadCodeElimination // phaseName = "jvm", ASM-based version object genASM extends { val global: Global.this.type = Global.this val runsAfter = List("dce") val runsRightAfter = None } with GenASM // phaseName = "bcode" object genBCode extends { val global: Global.this.type = Global.this val runsAfter = List("dce") val runsRightAfter = None } with GenBCode // phaseName = "terminal" object terminal extends { val global: Global.this.type = Global.this } with SubComponent { val phaseName = "terminal" val runsAfter = List("jvm") val runsRightAfter = None override val terminal = true def newPhase(prev: Phase): GlobalPhase = { new TerminalPhase(prev) } private class TerminalPhase(prev: Phase) extends GlobalPhase(prev) { def name = phaseName def apply(unit: CompilationUnit) {} } } /** The checkers are for validating the compiler data structures * at phase boundaries. */ /** Tree checker */ object treeChecker extends { val global: Global.this.type = Global.this } with TreeCheckers /** Icode verification */ object icodeCheckers extends { val global: Global.this.type = Global.this } with ICodeCheckers object icodeChecker extends icodeCheckers.ICodeChecker() object typer extends analyzer.Typer( analyzer.NoContext.make(EmptyTree, RootClass, newScope) ) /** Add the internal compiler phases to the phases set. * This implementation creates a description map at the same time. */ protected def computeInternalPhases(): Unit = { // Note: this fits -Xshow-phases into 80 column width, which it is // desirable to preserve. val phs = List( syntaxAnalyzer -> "parse source into ASTs, perform simple desugaring", analyzer.namerFactory -> "resolve names, attach symbols to named trees", analyzer.packageObjects -> "load package objects", analyzer.typerFactory -> "the meat and potatoes: type the trees", patmat -> "translate match expressions", superAccessors -> "add super accessors in traits and nested classes", extensionMethods -> "add extension methods for inline classes", pickler -> "serialize symbol tables", refChecks -> "reference/override checking, translate nested objects", uncurry -> "uncurry, translate function values to anonymous classes", tailCalls -> "replace tail calls by jumps", specializeTypes -> "@specialized-driven class and method specialization", explicitOuter -> "this refs to outer pointers", erasure -> "erase types, add interfaces for traits", postErasure -> "clean up erased inline classes", lazyVals -> "allocate bitmaps, translate lazy vals into lazified defs", lambdaLift -> "move nested functions to top level", constructors -> "move field definitions into constructors", mixer -> "mixin composition", delambdafy -> "remove lambdas", cleanup -> "platform-specific cleanups, generate reflective calls", genicode -> "generate portable intermediate code", inliner -> "optimization: do inlining", inlineExceptionHandlers -> "optimization: inline exception handlers", closureElimination -> "optimization: eliminate uncalled closures", constantOptimization -> "optimization: optimize null and other constants", deadCode -> "optimization: eliminate dead code", terminal -> "the last phase during a compilation run" ) phs foreach (addToPhasesSet _).tupled } // This is slightly inelegant but it avoids adding a new member to SubComponent, // and attractive -Xshow-phases output is unlikely if the descs span 20 files anyway. private val otherPhaseDescriptions = Map( "flatten" -> "eliminate inner classes", "jvm" -> "generate JVM bytecode" ) withDefaultValue "" protected def computePlatformPhases() = platform.platformPhases foreach { sub => addToPhasesSet(sub, otherPhaseDescriptions(sub.phaseName)) } // sequences the phase assembly protected def computePhaseDescriptors: List[SubComponent] = { /** Allow phases to opt out of the phase assembly. */ def cullPhases(phases: List[SubComponent]) = { val enabled = if (settings.debug && settings.isInfo) phases else phases filter (_.enabled) def isEnabled(q: String) = enabled exists (_.phaseName == q) val (satisfied, unhappy) = enabled partition (_.requires forall isEnabled) unhappy foreach (u => globalError(s"Phase '${u.phaseName}' requires: ${u.requires filterNot isEnabled}")) satisfied // they're happy now, but they may need an unhappy phase that was booted } computeInternalPhases() // Global.scala computePlatformPhases() // backend/Platform.scala computePluginPhases() // plugins/Plugins.scala cullPhases(computePhaseAssembly()) // PhaseAssembly.scala } /* The phase descriptor list. Components that are phase factories. */ lazy val phaseDescriptors: List[SubComponent] = computePhaseDescriptors /* The set of phase objects that is the basis for the compiler phase chain */ protected lazy val phasesSet = new mutable.HashSet[SubComponent] protected lazy val phasesDescMap = new mutable.HashMap[SubComponent, String] withDefaultValue "" protected def addToPhasesSet(sub: SubComponent, descr: String) { phasesSet += sub phasesDescMap(sub) = descr } /** The names of the phases. */ lazy val phaseNames = { new Run // force some initialization phaseDescriptors map (_.phaseName) } /** A description of the phases that will run in this configuration, or all if -Ydebug. */ def phaseDescriptions: String = phaseHelp("description", elliptically = true, phasesDescMap) /** Summary of the per-phase values of nextFlags and newFlags, shown under -Xshow-phases -Ydebug. */ def phaseFlagDescriptions: String = { def fmt(ph: SubComponent) = { def fstr1 = if (ph.phaseNewFlags == 0L) "" else "[START] " + Flags.flagsToString(ph.phaseNewFlags) def fstr2 = if (ph.phaseNextFlags == 0L) "" else "[END] " + Flags.flagsToString(ph.phaseNextFlags) if (ph.initial) Flags.flagsToString(Flags.InitialFlags) else if (ph.phaseNewFlags != 0L && ph.phaseNextFlags != 0L) fstr1 + " " + fstr2 else fstr1 + fstr2 } phaseHelp("new flags", elliptically = false, fmt) } /** Emit a verbose phase table. * The table includes the phase id in the current assembly, * or "oo" to indicate a skipped phase, or "xx" to indicate * a disabled phase. * * @param title descriptive header * @param elliptically whether to truncate the description with an ellipsis (...) * @param describe how to describe a component */ def phaseHelp(title: String, elliptically: Boolean, describe: SubComponent => String) = { val Limit = 16 // phase names should not be absurdly long val MaxCol = 80 // because some of us edit on green screens val maxName = phaseNames map (_.length) max val width = maxName min Limit val maxDesc = MaxCol - (width + 6) // descriptions not novels val fmt = if (settings.verbose || !elliptically) s"%${maxName}s %2s %s%n" else s"%${width}.${width}s %2s %.${maxDesc}s%n" val line1 = fmt.format("phase name", "id", title) val line2 = fmt.format("----------", "--", "-" * title.length) // built-in string precision merely truncates import java.util.{ Formattable, FormattableFlags, Formatter } def dotfmt(s: String) = new Formattable { def elliptically(s: String, max: Int) = ( if (max < 0 || s.length <= max) s else if (max < 4) s.take(max) else s.take(max - 3) + "..." ) override def formatTo(formatter: Formatter, flags: Int, width: Int, precision: Int) { val p = elliptically(s, precision) val w = if (width > 0 && p.length < width) { import FormattableFlags.LEFT_JUSTIFY val leftly = (flags & LEFT_JUSTIFY) == LEFT_JUSTIFY val sb = new StringBuilder def pad() = 1 to width - p.length foreach (_ => sb.append(' ')) if (!leftly) pad() sb.append(p) if (leftly) pad() sb.toString } else p formatter.out.append(w) } } // phase id in run, or suitable icon def idOf(p: SubComponent) = ( if (settings.skip contains p.phaseName) "oo" // (currentRun skipPhase p.phaseName) else if (!p.enabled) "xx" else p.ownPhase.id.toString ) def mkText(p: SubComponent) = { val (name, text) = if (elliptically) (dotfmt(p.phaseName), dotfmt(describe(p))) else (p.phaseName, describe(p)) fmt.format(name, idOf(p), text) } line1 :: line2 :: (phaseDescriptors map mkText) mkString } /** Returns List of (phase, value) pairs, including only those * where the value compares unequal to the previous phase's value. */ def afterEachPhase[T](op: => T): List[(Phase, T)] = { // used in tests phaseDescriptors.map(_.ownPhase).filterNot(_ eq NoPhase).foldLeft(List[(Phase, T)]()) { (res, ph) => val value = exitingPhase(ph)(op) if (res.nonEmpty && res.head._2 == value) res else ((ph, value)) :: res } reverse } // ------------ REPL utilities --------------------------------- /** Extend classpath of `platform` and rescan updated packages. */ def extendCompilerClassPath(urls: URL*): Unit = { if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat) throw new UnsupportedOperationException("Flat classpath doesn't support extending the compiler classpath") val newClassPath = platform.classPath.mergeUrlsIntoClassPath(urls: _*) platform.currentClassPath = Some(newClassPath) // Reload all specified jars into this compiler instance invalidateClassPathEntries(urls.map(_.getPath): _*) } // ------------ Invalidations --------------------------------- /** Is given package class a system package class that cannot be invalidated? */ private def isSystemPackageClass(pkg: Symbol) = pkg == RootClass || (pkg.hasTransOwner(definitions.ScalaPackageClass) && !pkg.hasTransOwner(this.rootMirror.staticPackage("scala.tools").moduleClass.asClass)) /** Invalidates packages that contain classes defined in a classpath entry, and * rescans that entry. * * First, the classpath entry referred to by one of the `paths` is rescanned, * so that any new files or changes in subpackages are picked up. * Second, any packages for which one of the following conditions is met is invalidated: * - the classpath entry contained during the last compilation run now contains classfiles * that represent a member in the package; * - the classpath entry now contains classfiles that represent a member in the package; * - the set of subpackages has changed. * * The invalidated packages are reset in their entirety; all member classes and member packages * are re-accessed using the new classpath. * * System packages that the compiler needs to access as part of standard definitions * are not invalidated. A system package is: * Any package rooted in "scala", with the exception of packages rooted in "scala.tools". * * @param paths Fully-qualified names that refer to directories or jar files that are * entries on the classpath. */ def invalidateClassPathEntries(paths: String*): Unit = { if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat) throw new UnsupportedOperationException("Flat classpath doesn't support the classpath invalidation") implicit object ClassPathOrdering extends Ordering[PlatformClassPath] { def compare(a:PlatformClassPath, b:PlatformClassPath) = a.asClassPathString compare b.asClassPathString } val invalidated, failed = new mutable.ListBuffer[ClassSymbol] classPath match { case cp: MergedClassPath[_] => def assoc(path: String): List[(PlatformClassPath, PlatformClassPath)] = { val dir = AbstractFile.getDirectory(path) val canonical = dir.canonicalPath def matchesCanonical(e: ClassPath[_]) = e.origin match { case Some(opath) => AbstractFile.getDirectory(opath).canonicalPath == canonical case None => false } cp.entries find matchesCanonical match { case Some(oldEntry) => List(oldEntry -> cp.context.newClassPath(dir)) case None => error(s"Error adding entry to classpath. During invalidation, no entry named $path in classpath $classPath") List() } } val subst = immutable.TreeMap(paths flatMap assoc: _*) if (subst.nonEmpty) { platform updateClassPath subst informProgress(s"classpath updated on entries [${subst.keys mkString ","}]") def mkClassPath(elems: Iterable[PlatformClassPath]): PlatformClassPath = if (elems.size == 1) elems.head else new MergedClassPath(elems, recursiveClassPath.context) val oldEntries = mkClassPath(subst.keys) val newEntries = mkClassPath(subst.values) mergeNewEntries(newEntries, RootClass, Some(recursiveClassPath), Some(oldEntries), invalidated, failed) } } def show(msg: String, syms: scala.collection.Traversable[Symbol]) = if (syms.nonEmpty) informProgress(s"$msg: ${syms map (_.fullName) mkString ","}") show("invalidated packages", invalidated) show("could not invalidate system packages", failed) } /** Merges new classpath entries into the symbol table * * @param newEntries The new classpath entries * @param root The root symbol to be resynced (a package class) * @param allEntries Optionally, the corresponding package in the complete current classpath * @param oldEntries Optionally, the corresponding package in the old classpath entries * @param invalidated A listbuffer collecting the invalidated package classes * @param failed A listbuffer collecting system package classes which could not be invalidated * * The merging strategy is determined by the absence or presence of classes and packages. * * If either oldEntries or newEntries contains classes, root is invalidated provided that a corresponding package * exists in allEntries. Otherwise it is removed. * Otherwise, the action is determined by the following matrix, with columns: * * old sym action * + + recurse into all child packages of newEntries * - + invalidate root * - - create and enter root * * Here, old means classpath, and sym means symboltable. + is presence of an entry in its column, - is absence. */ private def mergeNewEntries(newEntries: PlatformClassPath, root: ClassSymbol, allEntries: OptClassPath, oldEntries: OptClassPath, invalidated: mutable.ListBuffer[ClassSymbol], failed: mutable.ListBuffer[ClassSymbol]) { ifDebug(informProgress(s"syncing $root, $oldEntries -> $newEntries")) val getName: ClassPath[AbstractFile] => String = (_.name) def hasClasses(cp: OptClassPath) = cp.isDefined && cp.get.classes.nonEmpty def invalidateOrRemove(root: ClassSymbol) = { allEntries match { case Some(cp) => root setInfo new loaders.PackageLoader(cp) case None => root.owner.info.decls unlink root.sourceModule } invalidated += root } def subPackage(cp: PlatformClassPath, name: String): OptClassPath = cp.packages find (cp1 => getName(cp1) == name) val classesFound = hasClasses(oldEntries) || newEntries.classes.nonEmpty if (classesFound && !isSystemPackageClass(root)) { invalidateOrRemove(root) } else { if (classesFound) { if (root.isRoot) invalidateOrRemove(EmptyPackageClass) else failed += root } if (!oldEntries.isDefined) invalidateOrRemove(root) else for (pstr <- newEntries.packages.map(getName)) { val pname = newTermName(pstr) val pkg = (root.info decl pname) orElse { // package does not exist in symbol table, create symbol to track it assert(!subPackage(oldEntries.get, pstr).isDefined) loaders.enterPackage(root, pstr, new loaders.PackageLoader(allEntries.get)) } mergeNewEntries(subPackage(newEntries, pstr).get, pkg.moduleClass.asClass, subPackage(allEntries.get, pstr), subPackage(oldEntries.get, pstr), invalidated, failed) } } } // ----------- Runs --------------------------------------- private var curRun: Run = null private var curRunId = 0 object typeDeconstruct extends { val global: Global.this.type = Global.this } with typechecker.StructuredTypeStrings /** There are common error conditions where when the exception hits * here, currentRun.currentUnit is null. This robs us of the knowledge * of what file was being compiled when it broke. Since I really * really want to know, this hack. */ protected var lastSeenSourceFile: SourceFile = NoSourceFile /** Let's share a lot more about why we crash all over the place. * People will be very grateful. */ protected var lastSeenContext: analyzer.Context = null /** The currently active run */ def currentRun: Run = curRun def currentUnit: CompilationUnit = if (currentRun eq null) NoCompilationUnit else currentRun.currentUnit def currentSource: SourceFile = if (currentUnit.exists) currentUnit.source else lastSeenSourceFile def currentFreshNameCreator = currentUnit.fresh def isGlobalInitialized = ( definitions.isDefinitionsInitialized && rootMirror.isMirrorInitialized ) override def isPastTyper = ( (curRun ne null) && isGlobalInitialized // defense against init order issues && (globalPhase.id > currentRun.typerPhase.id) ) // TODO - trim these to the absolute minimum. @inline final def exitingErasure[T](op: => T): T = exitingPhase(currentRun.erasurePhase)(op) @inline final def exitingPostErasure[T](op: => T): T = exitingPhase(currentRun.posterasurePhase)(op) @inline final def exitingExplicitOuter[T](op: => T): T = exitingPhase(currentRun.explicitouterPhase)(op) @inline final def exitingFlatten[T](op: => T): T = exitingPhase(currentRun.flattenPhase)(op) @inline final def exitingMixin[T](op: => T): T = exitingPhase(currentRun.mixinPhase)(op) @inline final def exitingDelambdafy[T](op: => T): T = exitingPhase(currentRun.delambdafyPhase)(op) @inline final def exitingPickler[T](op: => T): T = exitingPhase(currentRun.picklerPhase)(op) @inline final def exitingRefchecks[T](op: => T): T = exitingPhase(currentRun.refchecksPhase)(op) @inline final def exitingSpecialize[T](op: => T): T = exitingPhase(currentRun.specializePhase)(op) @inline final def exitingTyper[T](op: => T): T = exitingPhase(currentRun.typerPhase)(op) @inline final def exitingUncurry[T](op: => T): T = exitingPhase(currentRun.uncurryPhase)(op) @inline final def enteringErasure[T](op: => T): T = enteringPhase(currentRun.erasurePhase)(op) @inline final def enteringExplicitOuter[T](op: => T): T = enteringPhase(currentRun.explicitouterPhase)(op) @inline final def enteringFlatten[T](op: => T): T = enteringPhase(currentRun.flattenPhase)(op) @inline final def enteringIcode[T](op: => T): T = enteringPhase(currentRun.icodePhase)(op) @inline final def enteringMixin[T](op: => T): T = enteringPhase(currentRun.mixinPhase)(op) @inline final def enteringDelambdafy[T](op: => T): T = enteringPhase(currentRun.delambdafyPhase)(op) @inline final def enteringPickler[T](op: => T): T = enteringPhase(currentRun.picklerPhase)(op) @inline final def enteringSpecialize[T](op: => T): T = enteringPhase(currentRun.specializePhase)(op) @inline final def enteringTyper[T](op: => T): T = enteringPhase(currentRun.typerPhase)(op) @inline final def enteringUncurry[T](op: => T): T = enteringPhase(currentRun.uncurryPhase)(op) // Owners which aren't package classes. private def ownerChainString(sym: Symbol): String = ( if (sym == null) "" else sym.ownerChain takeWhile (!_.isPackageClass) mkString " -> " ) private def formatExplain(pairs: (String, Any)*): String = ( pairs.toList collect { case (k, v) if v != null => "%20s: %s".format(k, v) } mkString "\n" ) /** Don't want to introduce new errors trying to report errors, * so swallow exceptions. */ override def supplementTyperState(errorMessage: String): String = try { val tree = analyzer.lastTreeToTyper val sym = tree.symbol val tpe = tree.tpe val site = lastSeenContext.enclClassOrMethod.owner val pos_s = if (tree.pos.isDefined) s"line ${tree.pos.line} of ${tree.pos.source.file}" else "" val context_s = try { // Taking 3 before, 3 after the fingered line. val start = 0 max (tree.pos.line - 3) val xs = scala.reflect.io.File(tree.pos.source.file.file).lines drop start take 7 val strs = xs.zipWithIndex map { case (line, idx) => f"${start + idx}%6d $line" } strs.mkString("== Source file context for tree position ==\n\n", "\n", "") } catch { case t: Exception => devWarning("" + t) ; "" } val info1 = formatExplain( "while compiling" -> currentSource.path, "during phase" -> ( if (globalPhase eq phase) phase else "globalPhase=%s, enteringPhase=%s".format(globalPhase, phase) ), "library version" -> scala.util.Properties.versionString, "compiler version" -> Properties.versionString, "reconstructed args" -> settings.recreateArgs.mkString(" ") ) val info2 = formatExplain( "last tree to typer" -> tree.summaryString, "tree position" -> pos_s, "tree tpe" -> tpe, "symbol" -> Option(sym).fold("null")(_.debugLocationString), "symbol definition" -> Option(sym).fold("null")(s => s.defString + s" (a ${s.shortSymbolClass})"), "symbol package" -> sym.enclosingPackage.fullName, "symbol owners" -> ownerChainString(sym), "call site" -> (site.fullLocationString + " in " + site.enclosingPackage) ) ("\n " + errorMessage + "\n" + info1) :: info2 :: context_s :: Nil mkString "\n\n" } catch { case _: Exception | _: TypeError => errorMessage } /** The id of the currently active run */ override def currentRunId = curRunId def echoPhaseSummary(ph: Phase) = { /* Only output a summary message under debug if we aren't echoing each file. */ if (settings.debug && !(settings.verbose || currentRun.size < 5)) inform("[running phase " + ph.name + " on " + currentRun.size + " compilation units]") } def newSourceFile(code: String, filename: String = "") = new BatchSourceFile(filename, code) def newCompilationUnit(code: String, filename: String = "") = new CompilationUnit(newSourceFile(code, filename)) def newUnitScanner(unit: CompilationUnit): UnitScanner = new UnitScanner(unit) def newUnitParser(unit: CompilationUnit): UnitParser = new UnitParser(unit) def newUnitParser(code: String, filename: String = ""): UnitParser = newUnitParser(newCompilationUnit(code, filename)) /** A Run is a single execution of the compiler on a set of units. */ class Run extends RunContextApi with RunReporting with RunParsing { /** Have been running into too many init order issues with Run * during erroneous conditions. Moved all these vals up to the * top of the file so at least they're not trivially null. */ var isDefined = false /** The currently compiled unit; set from GlobalPhase */ var currentUnit: CompilationUnit = NoCompilationUnit // used in sbt def uncheckedWarnings: List[(Position, String)] = reporting.uncheckedWarnings // used in sbt def deprecationWarnings: List[(Position, String)] = reporting.deprecationWarnings private class SyncedCompilationBuffer { self => private val underlying = new mutable.ArrayBuffer[CompilationUnit] def size = synchronized { underlying.size } def +=(cu: CompilationUnit): this.type = { synchronized { underlying += cu }; this } def head: CompilationUnit = synchronized{ underlying.head } def apply(i: Int): CompilationUnit = synchronized { underlying(i) } def iterator: Iterator[CompilationUnit] = new collection.AbstractIterator[CompilationUnit] { private var used = 0 def hasNext = self.synchronized{ used < underlying.size } def next = self.synchronized { if (!hasNext) throw new NoSuchElementException("next on empty Iterator") used += 1 underlying(used-1) } } def toList: List[CompilationUnit] = synchronized{ underlying.toList } } private val unitbuf = new SyncedCompilationBuffer val compiledFiles = new mutable.HashSet[String] /** A map from compiled top-level symbols to their source files */ val symSource = new mutable.HashMap[Symbol, AbstractFile] /** A map from compiled top-level symbols to their picklers */ val symData = new mutable.HashMap[Symbol, PickleBuffer] private var phasec: Int = 0 // phases completed private var unitc: Int = 0 // units completed this phase def size = unitbuf.size override def toString = "scalac Run for:\n " + compiledFiles.toList.sorted.mkString("\n ") // Calculate where to stop based on settings -Ystop-before or -Ystop-after. // The result is the phase to stop at BEFORE running it. private lazy val stopPhaseSetting = { def isBefore(pd: SubComponent) = settings.stopBefore contains pd.phaseName phaseDescriptors sliding 2 collectFirst { case xs if xs exists isBefore => (xs find isBefore).get case xs if settings.stopAfter contains xs.head.phaseName => xs.last } } /** Should we stop right before entering the given phase? */ protected def stopPhase(name: String) = stopPhaseSetting exists (_.phaseName == name) /** Should we skip the given phase? */ protected def skipPhase(name: String) = settings.skip contains name private val firstPhase = { // Initialization. definitions.init requires phase != NoPhase import scala.reflect.internal.SomePhase curRunId += 1 curRun = this phase = SomePhase phaseWithId(phase.id) = phase definitions.init() // the components to use, omitting those named by -Yskip and stopping at the -Ystop phase val components = { // stop on a dime, but this test fails if pd is after the stop phase def unstoppable(pd: SubComponent) = { val stoppable = stopPhase(pd.phaseName) if (stoppable && pd.initial) { globalError(s"Cannot stop before initial phase '${pd.phaseName}'.") true } else !stoppable } // skip a component for -Yskip or if not enabled def skippable(pd: SubComponent) = { val skippable = skipPhase(pd.phaseName) if (skippable && (pd.initial || pd.terminal)) { globalError(s"Cannot skip an initial or terminal phase '${pd.phaseName}'.") false } else skippable || !pd.enabled } val phs = phaseDescriptors takeWhile unstoppable filterNot skippable // Ensure there is a terminal phase at the end, since -Ystop may have limited the phases. if (phs.isEmpty || !phs.last.terminal) { val t = if (phaseDescriptors.last.terminal) phaseDescriptors.last else terminal phs :+ t } else phs } // Create phases and link them together. We supply the previous, and the ctor sets prev.next. val last = components.foldLeft(NoPhase: Phase)((prev, c) => c newPhase prev) // rewind (Iterator.iterate(last)(_.prev) dropWhile (_.prev ne NoPhase)).next val first = { var p = last ; while (p.prev ne NoPhase) p = p.prev ; p } val ss = settings // As a final courtesy, see if the settings make any sense at all. // If a setting selects no phase, it's a mistake. If a name prefix // doesn't select a unique phase, that might be surprising too. def checkPhaseSettings(including: Boolean, specs: Seq[String]*) = { def isRange(s: String) = s.forall(c => c.isDigit || c == '-') def isSpecial(s: String) = (s == "all" || isRange(s)) val setting = new ss.PhasesSetting("fake","fake") for (p <- specs.flatten.to[Set]) { setting.value = List(p) val count = ( if (including) first.iterator count (setting containsPhase _) else phaseDescriptors count (setting contains _.phaseName) ) if (count == 0) warning(s"'$p' specifies no phase") if (count > 1 && !isSpecial(p)) warning(s"'$p' selects $count phases") if (!including && isSpecial(p)) globalError(s"-Yskip and -Ystop values must name phases: '$p'") setting.clear() } } // phases that are excluded; for historical reasons, these settings only select by phase name val exclusions = List(ss.stopBefore, ss.stopAfter, ss.skip) val inclusions = ss.visibleSettings collect { case s: ss.PhasesSetting if !(exclusions contains s) => s.value } checkPhaseSettings(including = true, inclusions.toSeq: _*) checkPhaseSettings(including = false, exclusions map (_.value): _*) phase = first //parserPhase first } // --------------- Miscellania ------------------------------- /** Progress tracking. Measured in "progress units" which are 1 per * compilation unit per phase completed. * * @param current number of "progress units" completed * @param total total number of "progress units" in run */ def progress(current: Int, total: Int) {} /** * For subclasses to override. Called when `phase` is about to be run on `unit`. * Variables are passed explicitly to indicate that `globalPhase` and `currentUnit` have been set. */ def informUnitStarting(phase: Phase, unit: CompilationUnit) { } /** take note that phase is completed * (for progress reporting) */ def advancePhase() { unitc = 0 phasec += 1 refreshProgress() } /** take note that a phase on a unit is completed * (for progress reporting) */ def advanceUnit() { unitc += 1 refreshProgress() } // for sbt def cancel() { reporter.cancelled = true } private def currentProgress = (phasec * size) + unitc private def totalProgress = (phaseDescriptors.size - 1) * size // -1: drops terminal phase private def refreshProgress() = if (size > 0) progress(currentProgress, totalProgress) // ----- finding phases -------------------------------------------- def phaseNamed(name: String): Phase = findOrElse(firstPhase.iterator)(_.name == name)(NoPhase) /** All phases as of 3/2012 here for handiness; the ones in * active use uncommented. */ val parserPhase = phaseNamed("parser") val namerPhase = phaseNamed("namer") // val packageobjectsPhase = phaseNamed("packageobjects") val typerPhase = phaseNamed("typer") // val inlineclassesPhase = phaseNamed("inlineclasses") // val superaccessorsPhase = phaseNamed("superaccessors") val picklerPhase = phaseNamed("pickler") val refchecksPhase = phaseNamed("refchecks") // val selectiveanfPhase = phaseNamed("selectiveanf") // val selectivecpsPhase = phaseNamed("selectivecps") val uncurryPhase = phaseNamed("uncurry") // val tailcallsPhase = phaseNamed("tailcalls") val specializePhase = phaseNamed("specialize") val explicitouterPhase = phaseNamed("explicitouter") val erasurePhase = phaseNamed("erasure") val posterasurePhase = phaseNamed("posterasure") // val lazyvalsPhase = phaseNamed("lazyvals") val lambdaliftPhase = phaseNamed("lambdalift") // val constructorsPhase = phaseNamed("constructors") val flattenPhase = phaseNamed("flatten") val mixinPhase = phaseNamed("mixin") val delambdafyPhase = phaseNamed("delambdafy") val cleanupPhase = phaseNamed("cleanup") val icodePhase = phaseNamed("icode") val inlinerPhase = phaseNamed("inliner") val inlineExceptionHandlersPhase = phaseNamed("inlinehandlers") val closelimPhase = phaseNamed("closelim") val dcePhase = phaseNamed("dce") // val jvmPhase = phaseNamed("jvm") def runIsAt(ph: Phase) = globalPhase.id == ph.id def runIsAtOptimiz = { runIsAt(inlinerPhase) || // listing phases in full for robustness when -Ystop-after has been given. runIsAt(inlineExceptionHandlersPhase) || runIsAt(closelimPhase) || runIsAt(dcePhase) } isDefined = true // ----------- Units and top-level classes and objects -------- /** add unit to be compiled in this run */ private def addUnit(unit: CompilationUnit) { unitbuf += unit compiledFiles += unit.source.file.path } private def checkDeprecatedSettings(unit: CompilationUnit) { // issue warnings for any usage of deprecated settings settings.userSetSettings filter (_.isDeprecated) foreach { s => currentRun.reporting.deprecationWarning(NoPosition, s.name + " is deprecated: " + s.deprecationMessage.get) } if (settings.target.value.contains("jvm-1.5")) currentRun.reporting.deprecationWarning(NoPosition, settings.target.name + ":" + settings.target.value + " is deprecated: use target for Java 1.6 or above.") } /* An iterator returning all the units being compiled in this run */ /* !!! Note: changing this to unitbuf.toList.iterator breaks a bunch of tests in tests/res. This is bad, it means the resident compiler relies on an iterator of a mutable data structure reflecting changes made to the underlying structure. */ def units: Iterator[CompilationUnit] = unitbuf.iterator def registerPickle(sym: Symbol): Unit = () /** does this run compile given class, module, or case factory? */ // NOTE: Early initialized members temporarily typechecked before the enclosing class, see typedPrimaryConstrBody! // Here we work around that wrinkle by claiming that a early-initialized member is compiled in // *every* run. This approximation works because this method is exclusively called with `this` == `currentRun`. def compiles(sym: Symbol): Boolean = if (sym == NoSymbol) false else if (symSource.isDefinedAt(sym)) true else if (!sym.isTopLevel) compiles(sym.enclosingTopLevelClassOrDummy) else if (sym.isModuleClass) compiles(sym.sourceModule) else false /** Is this run allowed to redefine the given symbol? Usually this is true * if the run does not already compile `sym`, but for interactive mode * we have a more liberal interpretation. */ def canRedefine(sym: Symbol) = !compiles(sym) // --------------- Compilation methods ---------------------------- protected def runCheckers() { val toCheck = globalPhase.prev val canCheck = toCheck.checkable val fmt = if (canCheck) "[Now checking: %s]" else "[Not checkable: %s]" inform(fmt format toCheck.name) if (canCheck) { phase = globalPhase if (globalPhase.id >= icodePhase.id) icodeChecker.checkICodes() else treeChecker.checkTrees() } } private def showMembers() = { // Allows for syntax like scalac -Xshow-class Random@erasure,typer def splitClassAndPhase(str: String, term: Boolean): Name = { def mkName(s: String) = if (term) newTermName(s) else newTypeName(s) (str indexOf '@') match { case -1 => mkName(str) case idx => val phasePart = str drop (idx + 1) settings.Yshow.tryToSetColon(phasePart split ',' toList) mkName(str take idx) } } if (settings.Xshowcls.isSetByUser) showDef(splitClassAndPhase(settings.Xshowcls.value, term = false), declsOnly = false, globalPhase) if (settings.Xshowobj.isSetByUser) showDef(splitClassAndPhase(settings.Xshowobj.value, term = true), declsOnly = false, globalPhase) } // Similarly, this will only be created under -Yshow-syms. object trackerFactory extends SymbolTrackers { val global: Global.this.type = Global.this lazy val trackers = currentRun.units.toList map (x => SymbolTracker(x)) def snapshot() = { inform("\n[[symbol layout at end of " + phase + "]]") exitingPhase(phase) { trackers foreach { t => t.snapshot() inform(t.show("Heading from " + phase.prev.name + " to " + phase.name)) } } } } /** Caching member symbols that are def-s in Definitions because they might change from Run to Run. */ val runDefinitions: definitions.RunDefinitions = new definitions.RunDefinitions /** Compile list of source files, * unless there is a problem already, * such as a plugin was passed a bad option. */ def compileSources(sources: List[SourceFile]) = if (!reporter.hasErrors) { def checkDeprecations() = { checkDeprecatedSettings(newCompilationUnit("")) reporting.summarizeErrors() } val units = sources map scripted map (new CompilationUnit(_)) units match { case Nil => checkDeprecations() // nothing to compile, report deprecated options case _ => compileUnits(units, firstPhase) } } def compileUnits(units: List[CompilationUnit], fromPhase: Phase): Unit = compileUnitsInternal(units, fromPhase) private def compileUnitsInternal(units: List[CompilationUnit], fromPhase: Phase) { def currentTime = java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) units foreach addUnit val startTime = currentTime reporter.reset() checkDeprecatedSettings(unitbuf.head) globalPhase = fromPhase while (globalPhase.hasNext && !reporter.hasErrors) { val startTime = currentTime phase = globalPhase globalPhase.run() // progress update informTime(globalPhase.description, startTime) val shouldWriteIcode = ( (settings.writeICode.isSetByUser && (settings.writeICode containsPhase globalPhase)) || (!settings.Xprint.doAllPhases && (settings.Xprint containsPhase globalPhase) && runIsAtOptimiz) ) if (shouldWriteIcode) { // Write *.icode files when -Xprint-icode or -Xprint: was given. writeICode() } else if ((settings.Xprint containsPhase globalPhase) || settings.printLate && runIsAt(cleanupPhase)) { // print trees if (settings.Xshowtrees || settings.XshowtreesCompact || settings.XshowtreesStringified) nodePrinters.printAll() else printAllUnits() } // print the symbols presently attached to AST nodes if (settings.Yshowsyms) trackerFactory.snapshot() // print members if (settings.Yshow containsPhase globalPhase) showMembers() // browse trees with swing tree viewer if (settings.browse containsPhase globalPhase) treeBrowser browse (phase.name, units) // move the pointer globalPhase = globalPhase.next // run tree/icode checkers if (settings.check containsPhase globalPhase.prev) runCheckers() // output collected statistics if (settings.YstatisticsEnabled) statistics.print(phase) advancePhase() } reporting.summarizeErrors() if (traceSymbolActivity) units map (_.body) foreach (traceSymbols recordSymbolsInTree _) // In case no phase was specified for -Xshow-class/object, show it now for sure. if (settings.Yshow.isDefault) showMembers() if (reporter.hasErrors) { for ((sym, file) <- symSource.iterator) { if (file != null) sym.reset(new loaders.SourcefileLoader(file)) if (sym.isTerm) sym.moduleClass reset loaders.moduleClassLoader } } symSource.keys foreach (x => resetPackageClass(x.owner)) informTime("total", startTime) // Clear any sets or maps created via perRunCaches. perRunCaches.clearAll() } /** Compile list of abstract files. */ def compileFiles(files: List[AbstractFile]) { try compileSources(files map getSourceFile) catch { case ex: IOException => globalError(ex.getMessage()) } } /** Compile list of files given by their names */ def compile(filenames: List[String]) { try { val sources: List[SourceFile] = if (settings.script.isSetByUser && filenames.size > 1) returning(Nil)(_ => globalError("can only compile one script at a time")) else filenames map getSourceFile compileSources(sources) } catch { case ex: IOException => globalError(ex.getMessage()) } } /** If this compilation is scripted, convert the source to a script source. */ private def scripted(s: SourceFile) = s match { case b: BatchSourceFile if settings.script.isSetByUser => ScriptSourceFile(b) case _ => s } /** Compile abstract file until `globalPhase`, but at least * to phase "namer". */ def compileLate(file: AbstractFile) { if (!compiledFiles(file.path)) compileLate(new CompilationUnit(scripted(getSourceFile(file)))) } /** Compile abstract file until `globalPhase`, but at least to phase "namer". */ def compileLate(unit: CompilationUnit) { addUnit(unit) if (firstPhase ne null) { // we might get here during initialization, is a source is newer than the binary val maxId = math.max(globalPhase.id, typerPhase.id) firstPhase.iterator takeWhile (_.id < maxId) foreach (ph => enteringPhase(ph)(ph.asInstanceOf[GlobalPhase] applyPhase unit)) refreshProgress() } } /** Reset package class to state at typer (not sure what this is needed for?) */ private def resetPackageClass(pclazz: Symbol): Unit = if (typerPhase != NoPhase) { enteringPhase(firstPhase) { pclazz.setInfo(enteringPhase(typerPhase)(pclazz.info)) } if (!pclazz.isRoot) resetPackageClass(pclazz.owner) } } // class Run def printAllUnits() { print("[[syntax trees at end of %25s]]".format(phase)) exitingPhase(phase)(currentRun.units foreach { unit => nodePrinters showUnit unit }) } /** We resolve the class/object ambiguity by passing a type/term name. */ def showDef(fullName: Name, declsOnly: Boolean, ph: Phase) = { val boringOwners = Set[Symbol](definitions.AnyClass, definitions.AnyRefClass, definitions.ObjectClass) def phased[T](body: => T): T = exitingPhase(ph)(body) def boringMember(sym: Symbol) = boringOwners(sym.owner) def symString(sym: Symbol) = if (sym.isTerm) sym.defString else sym.toString def members(sym: Symbol) = phased(sym.info.members filterNot boringMember map symString) def decls(sym: Symbol) = phased(sym.info.decls.toList map symString) def bases(sym: Symbol) = phased(sym.info.baseClasses map (x => x.kindString + " " + x.fullName)) // make the type/term selections walking from the root. val syms = findMemberFromRoot(fullName) match { // The name as given was not found, so we'll sift through every symbol in // the run looking for plausible matches. case NoSymbol => phased(currentRun.symSource.keys map (sym => findNamedMember(fullName, sym)) filterNot (_ == NoSymbol) toList) // The name as given matched, so show only that. case sym => List(sym) } syms foreach { sym => val name = "\n<<-- %s %s after phase '%s' -->>".format(sym.kindString, sym.fullName, ph.name) val baseClasses = bases(sym).mkString("Base classes:\n ", "\n ", "") val contents = if (declsOnly) decls(sym).mkString("Declarations:\n ", "\n ", "") else members(sym).mkString("Members (excluding Any/AnyRef unless overridden):\n ", "\n ", "") inform(List(name, baseClasses, contents) mkString "\n\n") } } def getFile(source: AbstractFile, segments: Array[String], suffix: String): File = { val outDir = Path( settings.outputDirs.outputDirFor(source).path match { case "" => "." case path => path } ) val dir = segments.init.foldLeft(outDir)(_ / _).createDirectory() new File(dir.path, segments.last + suffix) } /** Returns the file with the given suffix for the given class. Used for icode writing. */ def getFile(clazz: Symbol, suffix: String): File = getFile(clazz.sourceFile, clazz.fullName split '.', suffix) private def writeICode() { val printer = new icodes.TextPrinter(writer = null, icodes.linearizer) icodes.classes.values foreach { cls => val file = { val module = if (cls.symbol.hasModuleFlag) "$" else "" val faze = if (settings.debug) phase.name else f"${phase.id}%02d" // avoid breaking windows build with long filename getFile(cls.symbol, s"$module-$faze.icode") } try { val stream = new FileOutputStream(file) printer.setWriter(new PrintWriter(stream, true)) try printer.printClass(cls) finally stream.close() informProgress(s"wrote $file") } catch { case e: IOException => if (settings.debug) e.printStackTrace() globalError(s"could not write file $file") } } } def createJavadoc = false } object Global { def apply(settings: Settings, reporter: Reporter): Global = new Global(settings, reporter) }