All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.tools.nsc.interpreter.IMain.scala Maven / Gradle / Ivy

/*
 * Scala (https://www.scala-lang.org)
 *
 * Copyright EPFL and Lightbend, Inc.
 *
 * Licensed under Apache License 2.0
 * (http://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

// Copyright 2005-2017 LAMP/EPFL and Lightbend, Inc.

package scala.tools.nsc.interpreter

import java.io.{Closeable, PrintWriter, StringWriter}
import java.net.URL
import scala.collection.mutable, mutable.ListBuffer
import scala.language.implicitConversions
import scala.reflect.{ClassTag, classTag}
import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader
import scala.reflect.internal.util.{AbstractFileClassLoader, BatchSourceFile, ListOfNil, Position, ReplBatchSourceFile, SourceFile}
import scala.reflect.internal.{FatalError, Flags, MissingRequirementError, NoPhase}
import scala.reflect.runtime.{universe => ru}
import scala.tools.nsc.{Global, Settings}
import scala.tools.nsc.interpreter.Results.{Error, Incomplete, Result, Success}
import scala.tools.nsc.interpreter.StdReplTags.tagOfStdReplVals
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.reporters.StoreReporter
import scala.tools.nsc.typechecker.{StructuredTypeStrings, TypeStrings}
import scala.tools.nsc.util.Exceptional.rootCause
import scala.tools.nsc.util.{stackTraceString, stringFromWriter}
import scala.tools.util.PathResolver
import scala.util.{Try => Trying}
import scala.util.chaining._
import scala.util.control.NonFatal

/** An interpreter for Scala code.
  *
  *  The main public entry points are compile(), interpret(), and bind().
  *  The compile() method loads a complete Scala file.  The interpret() method
  *  executes one line of Scala code at the request of the user.  The bind()
  *  method binds an object to a variable that can then be used by later
  *  interpreted code.
  *
  *  The overall approach is based on compiling the requested code and then
  *  using a Java classloader and Java reflection to run the code
  *  and access its results.
  *
  *  In more detail, a single compiler instance is used
  *  to accumulate all successfully compiled or interpreted Scala code.  To
  *  "interpret" a line of code, the compiler generates a fresh object that
  *  includes the line of code and which has public member(s) to export
  *  all variables defined by that code.  To extract the result of an
  *  interpreted line to show the user, a second "result object" is created
  *  which imports the variables exported by the above object and then
  *  exports members called "\$eval" and "\$print". To accommodate user expressions
  *  that read from variables or methods defined in previous statements, "import"
  *  statements are used.
  *
  *  This interpreter shares the strengths and weaknesses of using the
  *  full compiler-to-Java.  The main strength is that interpreted code
  *  behaves exactly as does compiled code, including running at full speed.
  *  The main weakness is that redefining classes and methods is not handled
  *  properly, because rebinding at the Java level is technically difficult.
  */
class IMain(val settings: Settings, parentClassLoaderOverride: Option[ClassLoader], compilerSettings: Settings, val reporter: ReplReporter)
  extends Repl with Imports with PresentationCompilation with Closeable {

  def this(interpreterSettings: Settings, reporter: ReplReporter) = this(interpreterSettings, None, interpreterSettings, reporter)

  import reporter.{debug => repldbg}

  private[interpreter] lazy val useMagicImport: Boolean = settings.YreplMagicImport.value

  private var bindExceptions                  = true        // whether to bind the lastException variable
  private var _executionWrapper               = ""          // code to be wrapped around all lines
  private var label                           = "" // compilation unit name for reporting

  /** We're going to go to some trouble to initialize the compiler asynchronously.
    *  It's critical that nothing call into it until it's been initialized or we will
    *  run into unrecoverable issues, but the perceived repl startup time goes
    *  through the roof if we wait for it.  So we initialize it with a future and
    *  use a lazy val to ensure that any attempt to use the compiler object waits
    *  on the future.
    */
  private var _classLoader: AbstractFileClassLoader = null  // active classloader
  private var _runtimeMirror: ru.Mirror = null
  private var _runtimeClassLoader: URLClassLoader = null    // wrapper exposing addURL

  def compilerClasspath: Seq[java.net.URL] = (
    if (_initializeComplete) global.classPath.asURLs
    else new PathResolver(settings, global.closeableRegistry).resultAsURLs  // the compiler's classpath
    )

  // Run the code body with the given boolean settings flipped to true.
  def withoutWarnings[T](body: => T): T =
    reporter.withoutPrintingResults(IMain.withSuppressedSettings(settings, global)(body))

  def withSuppressedSettings(body: => Unit): Unit =
    IMain.withSuppressedSettings(settings, global)(body)

  // Apply a temporary label for compilation (for example, script name)
  override def withLabel[A](temp: String)(body: => A): A = {
    val saved = label
    label = temp
    try body finally label = saved
  }

  override def visibleSettings: List[Setting] = settings.visibleSettings
  override def userSetSettings: List[Setting] = settings.userSetSettings
  override def updateSettings(arguments: List[String]): Boolean = {
    val (ok, rest) = settings.processArguments(arguments, processAll = false)
    ok && rest.isEmpty
  }

  object replOutput extends ReplOutput(settings.Yreploutdir) { }

  override def outputDir = replOutput.dir

  // Used in a test case.
  def showDirectory: String = {
    val writer = new StringWriter()
    replOutput.show(new PrintWriter(writer))
    writer.toString
  }

  lazy val isClassBased: Boolean = settings.Yreplclassbased.value


  override def initializeComplete = _initializeComplete
  private[this] var _initializeComplete = false

  // initializes the compiler, returning false if something went wrong
  override def initializeCompiler(): Boolean = global != null

  lazy val global: Global = {
    compilerSettings.outputDirs.setSingleOutput(replOutput.dir)
    compilerSettings.exposeEmptyPackage.value = true

    // Can't use our own reporter until global is initialized
    val startupReporter = new StoreReporter(compilerSettings)

    val compiler = new Global(compilerSettings, startupReporter) with ReplGlobal

    try {
      val run = new compiler.Run()
      assert(run.typerPhase != NoPhase, "REPL requires a typer phase.")
      IMain.withSuppressedSettings(compilerSettings, compiler) {
        run compileSources List(new BatchSourceFile("", "class $repl_$init { }"))
      }

      // there shouldn't be any errors yet; just in case, print them if we're debugging
      if (reporter.isDebug)
        startupReporter.infos foreach { Console.err.println }

      compiler.reporter = reporter
      _initializeComplete = true
      compiler
    }
    catch AbstractOrMissingHandler()
  }

  import global._
  import definitions.{ ObjectClass, termMember, dropNullaryMethod}

  override def classPathString = global.classPath.asClassPathString

  private def noFatal(body: => Symbol): Symbol = try body catch { case _: FatalError => NoSymbol }

  def getClassIfDefined(path: String)  = (
    noFatal(runtimeMirror staticClass path)
      orElse noFatal(rootMirror staticClass path)
    )
  def getModuleIfDefined(path: String) = (
    noFatal(runtimeMirror staticModule path)
      orElse noFatal(rootMirror staticModule path)
    )

  implicit class ReplTypeOps(tp: Type) {
    def andAlso(fn: Type => Type): Type = if (tp eq NoType) tp else fn(tp)
  }

  // TODO: If we try to make naming a lazy val, we run into big time
  // scalac unhappiness with what look like cycles.  It has not been easy to
  // reduce, but name resolution clearly takes different paths.
  object naming extends {
    val global: IMain.this.global.type = IMain.this.global
  } with Naming {
    // make sure we don't overwrite their unwisely named res3 etc.
    def freshUserTermName(): TermName = {
      val name = newTermName(freshUserVarName())
      if (replScope containsName name) freshUserTermName()
      else name
    }
    def isInternalTermName(name: Name) = isInternalVarName("" + name)
  }
  import naming._
  import Naming._

  object deconstruct extends {
    val global: IMain.this.global.type = IMain.this.global
  } with StructuredTypeStrings

  lazy val memberHandlers = new {
    val intp: IMain.this.type = IMain.this
  } with MemberHandlers
  import memberHandlers._


  override def quietRun(code: String): Result = reporter.withoutPrintingResults(interpret(code))

  /** takes AnyRef because it may be binding a Throwable or an Exceptional */
  private def withLastExceptionLock[T](body: => T, alt: => T): T = {
    assert(bindExceptions, "withLastExceptionLock called incorrectly.")
    bindExceptions = false

    try reporter.withoutPrintingResults(body) catch { case NonFatal(t) =>
      repldbg("withLastExceptionLock: " + rootCause(t))
      reporter.trace(stackTraceString(rootCause(t)))
      alt
    } finally bindExceptions = true
  }

  def executionWrapper = _executionWrapper
  def setExecutionWrapper(code: String) = _executionWrapper = code
  override def clearExecutionWrapper() = _executionWrapper = ""



  /**
    * Adds all specified jars to the compile and runtime classpaths.
    *
    * @note  Currently only supports jars, not directories.
    * @param urls The list of items to add to the compile and runtime classpaths.
    */
  override def addUrlsToClassPath(urls: URL*): Unit = {
    new Run //  force some initialization
    urls.foreach(_runtimeClassLoader.addURL) // Add jars to runtime classloader
    global.extendCompilerClassPath(urls: _*) // Add jars to compile-time classpath
  }

  protected def replClass: Class[_] = this.getClass

  /** Parent classloader.  Overridable. */
  protected def parentClassLoader: ClassLoader = {
    // might be null if we're on the boot classpath
    parentClassLoaderOverride.
      orElse(settings.explicitParentLoader).
      orElse(Option(replClass.getClassLoader())).
      getOrElse(ClassLoader.getSystemClassLoader)
  }

  /* A single class loader is used for all commands interpreted by this Interpreter.
     It would also be possible to create a new class loader for each command
     to interpret.  The advantages of the current approach are:

       - Expressions are only evaluated one time.  This is especially
         significant for I/O, e.g. "val x = Console.readLine"

     The main disadvantage is:

       - Objects, classes, and methods cannot be rebound.  Instead, definitions
         shadow the old ones, and old code objects refer to the old
         definitions.
  */
  def resetClassLoader() = {
    repldbg("Setting new classloader: was " + _classLoader)
    _classLoader = null
    _runtimeMirror = null
    ensureClassLoader()
  }
  final def ensureClassLoader(): Unit =
    if (_classLoader == null)
      _classLoader = makeClassLoader()

  override def classLoader: AbstractFileClassLoader = {
    ensureClassLoader()
    _classLoader
  }
  def runtimeMirror = {
    if (_runtimeMirror == null)
      _runtimeMirror = ru.runtimeMirror(classLoader)
    _runtimeMirror
  }

  def backticked(s: String): String = (
    (s split '.').toList map {
      case "_"                               => "`_`"
      case s if nme.keywords(newTermName(s)) => s"`$s`"
      case s                                 => s
    } mkString "."
    )
  def readRootPath(readPath: String) = getModuleIfDefined(readPath)

  abstract class PhaseDependentOps {
    def shift[T](op: => T): T

    def path(name: => Name): String = shift(path(symbolOfName(name)))
    def path(sym: Symbol): String = backticked(shift(sym.fullName))
    def sig(sym: Symbol): String  = shift(sym.defString)
  }
  object typerOp extends PhaseDependentOps {
    def shift[T](op: => T): T = exitingTyper(op)
  }
  object flatOp extends PhaseDependentOps {
    def shift[T](op: => T): T = exitingFlatten(op)
  }

  override def originalPath(name: String): String = originalPath(TermName(name))
  def originalPath(name: Name): String   = translateOriginalPath(typerOp path name)
  def originalPath(sym: Symbol): String  = translateOriginalPath(typerOp path sym)

  val readInstanceName = ".INSTANCE"
  def translateOriginalPath(p: String): String = {
    p.replace(sessionNames.read, sessionNames.read + readInstanceName)
  }
  def flatPath(sym: Symbol): String = {
    val sym1 = if (sym.isModule) sym.moduleClass else sym
    flatOp shift sym1.javaClassName
  }

  override def translatePath(path: String): Option[String] = {
    val sym = if (path endsWith "$") symbolOfTerm(path.init) else symbolOfIdent(path)
    sym.toOption map flatPath
  }

  /** If path represents a class resource in the default package,
    *  see if the corresponding symbol has a class file that is a REPL artifact
    *  residing at a different resource path. Translate X.class to \$line3/\$read\$\$iw\$\$iw\$X.class.
    */
  def translateSimpleResource(path: String): Option[String] = {
    if (!(path contains '/') && (path endsWith ".class")) {
      val name = path stripSuffix ".class"
      val sym = if (name endsWith "$") symbolOfTerm(name.init) else symbolOfIdent(name)
      def pathOf(s: String) = s"${s.replace('.', '/')}.class"
      sym.toOption map (s => pathOf(flatPath(s)))
    } else {
      None
    }
  }
  override def translateEnclosingClass(n: String): Option[String] = symbolOfTerm(n).enclClass.toOption map flatPath

  /** If unable to find a resource foo.class, try taking foo as a symbol in scope
   *  and use its java class name as a resource to load.
   *
   *  \$intp.classLoader classBytes "Bippy" or \$intp.classLoader getResource "Bippy.class" just work.
   */
  private class TranslatingClassLoader(parent: ClassLoader) extends AbstractFileClassLoader(replOutput.dir, parent) {
    override protected def findAbstractFile(name: String): AbstractFile = super.findAbstractFile(name) match {
      case null if _initializeComplete => translateSimpleResource(name).map(super.findAbstractFile).orNull
      case file => file
    }
    // if the name was mapped by findAbstractFile, supply null name to avoid name check in defineClass
    override protected def findClass(name: String): Class[_] = {
      val bytes = classBytes(name)
      if (bytes.length == 0)
        throw new ClassNotFoundException(name)
      else
        defineClass(/*name=*/null, bytes, 0, bytes.length, protectionDomain)
    }
  }
  private def makeClassLoader(): AbstractFileClassLoader =
    new TranslatingClassLoader({
      _runtimeClassLoader = new URLClassLoader(compilerClasspath, parentClassLoader)
      _runtimeClassLoader
    })

  def allDefinedNames: List[Name]  = exitingTyper(replScope.toList.map(_.name).sorted)
  def unqualifiedIds: List[String] = allDefinedNames.map(_.decode).sorted

  /** Most recent tree handled which wasn't wholly synthetic. */
  private def mostRecentlyHandledTree: Option[Tree] = {
    prevRequests.reverseIterator.map(_.handlers.reverseIterator.collectFirst {
      case x: MemberDefHandler if x.definesValue && !isInternalTermName(x.name) => x.member
    }).find(_.isDefined).flatten
  }

  private val logScope = scala.sys.props contains "scala.repl.scope"
  private def scopelog(msg: String) = if (logScope) Console.err.println(msg)

  private def updateReplScope(sym: Symbol, isDefined: Boolean): Unit = {
    def log(what: String): Unit = {
      val mark = if (sym.isType) "t " else "v "
      val name = exitingTyper(sym.nameString)
      val info = cleanTypeAfterTyper(sym)
      val defn = sym defStringSeenAs info

      scopelog(f"[$mark$what%6s] $name%-25s $defn%s")
    }
    if (ObjectClass isSubClass sym.owner) return
    // unlink previous
    replScope lookupAll sym.name foreach { sym =>
      log("unlink")
      replScope unlink sym
    }
    val what = if (isDefined) "define" else "import"
    log(what)
    replScope enter sym
  }

  def recordRequest(req: Request): Unit = {
    if (req == null)
      return

    prevRequests += req

    // warning about serially defining companions.  It'd be easy
    // enough to just redefine them together but that may not always
    // be what people want so I'm waiting until I can do it better.
    exitingTyper {
      req.defines filterNot (s => req.defines contains s.companionSymbol) foreach { newSym =>
        val oldSym = replScope lookup newSym.name.companionName
        if (Seq(oldSym, newSym).permutations exists { case Seq(s1, s2) => s1.isClass && s2.isModule case _ => false }) {
          replwarn(s"warning: previously defined $oldSym is not a companion to $newSym.")
          replwarn("Companions must be defined together; you may wish to use :paste mode for this.")
        }
      }
    }
    exitingTyper {
      req.imports foreach (sym => updateReplScope(sym, isDefined = false))
      req.defines foreach (sym => updateReplScope(sym, isDefined = true))
    }
  }

  private[nsc] def replwarn(msg: => String): Unit = {
    if (!settings.nowarnings.value)
      reporter.printMessage(msg)
  }

  def compileSourcesKeepingRun(sources: SourceFile*) = {
    val run = new Run()
    assert(run.typerPhase != NoPhase, "REPL requires a typer phase.")
    run compileSources sources.toList
    (!reporter.hasErrors, run)
  }

  /** Compile an nsc SourceFile.  Returns true if there are
    *  no compilation errors, or false otherwise.
    */
  override def compileSources(sources: SourceFile*): Boolean =
    compileSourcesKeepingRun(sources: _*)._1

  /** Compile a string.  Returns true if there are no
    *  compilation errors, or false otherwise.
    */
  override def compileString(code: String): Boolean =
    compileSources(new BatchSourceFile("