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

dotty.tools.dotc.core.SymbolLoaders.scala Maven / Gradle / Ivy

The newest version!
package dotty.tools
package dotc
package core

import java.io.{IOException, File}
import scala.compat.Platform.currentTime
import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile }
import config.Config
import Contexts._, Symbols._, Flags._, SymDenotations._, Types._, Scopes._, Names._
import NameOps._
import StdNames.str
import Decorators.{PreNamedString, StringInterpolators}
import classfile.ClassfileParser
import util.Stats
import Decorators._
import scala.util.control.NonFatal
import ast.Trees._
import parsing.JavaParsers.OutlineJavaParser
import parsing.Parsers.OutlineParser
import reporting.trace

object SymbolLoaders {
  import ast.untpd._

  /** A marker trait for a completer that replaces the original
   *  Symbol loader for an unpickled root.
   */
  trait SecondCompleter

  private def enterNew(
      owner: Symbol, member: Symbol,
      completer: SymbolLoader, scope: Scope = EmptyScope)(implicit ctx: Context): Symbol = {
    val comesFromScan =
      completer.isInstanceOf[SourcefileLoader] && ctx.settings.scansource.value
    assert(comesFromScan || scope.lookup(member.name) == NoSymbol,
           s"${owner.fullName}.${member.name} already has a symbol")
    owner.asClass.enter(member, scope)
    member
  }

  /** Enter class with given `name` into scope of `owner`.
   */
  def enterClass(
      owner: Symbol, name: PreName, completer: SymbolLoader,
      flags: FlagSet = EmptyFlags, scope: Scope = EmptyScope)(implicit ctx: Context): Symbol = {
    val cls = ctx.newClassSymbol(owner, name.toTypeName.unmangleClassName.decode, flags, completer, assocFile = completer.sourceFileOrNull)
    enterNew(owner, cls, completer, scope)
  }

  /** Enter module with given `name` into scope of `owner`.
   */
  def enterModule(
      owner: Symbol, name: PreName, completer: SymbolLoader,
      modFlags: FlagSet = EmptyFlags, clsFlags: FlagSet = EmptyFlags, scope: Scope = EmptyScope)(implicit ctx: Context): Symbol = {
    val module = ctx.newModuleSymbol(
      owner, name.toTermName.decode, modFlags, clsFlags,
      (module, _) => completer.proxy withDecls newScope withSourceModule (_ => module),
      assocFile = completer.sourceFileOrNull)
    enterNew(owner, module, completer, scope)
    enterNew(owner, module.moduleClass, completer, scope)
  }

  /** Enter package with given `name` into scope of `owner`
   *  and give them `completer` as type.
   */
  def enterPackage(owner: Symbol, pname: TermName, completer: (TermSymbol, ClassSymbol) => PackageLoader)(implicit ctx: Context): Symbol = {
    val preExisting = owner.info.decls lookup pname
    if (preExisting != NoSymbol) {
      // Some jars (often, obfuscated ones) include a package and
      // object with the same name. Rather than render them unusable,
      // offer a setting to resolve the conflict one way or the other.
      // This was motivated by the desire to use YourKit probes, which
      // require yjp.jar at runtime. See SI-2089.
      if (ctx.settings.YtermConflict.value == "package" || ctx.mode.is(Mode.Interactive)) {
        ctx.warning(
          s"Resolving package/object name conflict in favor of package ${preExisting.fullName}. The object will be inaccessible.")
        owner.asClass.delete(preExisting)
      } else if (ctx.settings.YtermConflict.value == "object") {
        ctx.warning(
          s"Resolving package/object name conflict in favor of object ${preExisting.fullName}.  The package will be inaccessible.")
        return NoSymbol
      }
      else
        throw new TypeError(
          i"""$owner contains object and package with same name: $pname
             |one of them needs to be removed from classpath""")
    }
    ctx.newModuleSymbol(owner, pname, PackageCreationFlags, PackageCreationFlags,
      completer).entered
  }

  /** Enter class and module with given `name` into scope of `owner`
   *  and give them `completer` as type.
   */
  def enterClassAndModule(
      owner: Symbol, name: PreName, completer: SymbolLoader,
      flags: FlagSet = EmptyFlags, scope: Scope = EmptyScope)(implicit ctx: Context): Unit = {
    val clazz = enterClass(owner, name, completer, flags, scope)
    val module = enterModule(
      owner, name, completer,
      modFlags = flags.toTermFlags & RetainedModuleValFlags,
      clsFlags = flags.toTypeFlags & RetainedModuleClassFlags,
      scope = scope)
  }

  /** If setting -scansource is set:
   *    Enter all toplevel classes and objects in file `src` into package `owner`, provided
   *    they are in the right package. Issue a warning if a class or object is in the wrong
   *    package, i.e. if the file path differs from the declared package clause.
   *  If -scansource is not set:
   *    Enter class and module with given `name` into scope of `owner`.
   *
   *  All entered symbols are given a source completer of `src` as info.
   */
  def enterToplevelsFromSource(
      owner: Symbol, name: PreName, src: AbstractFile,
      scope: Scope = EmptyScope)(implicit ctx: Context): Unit = {

    val completer = new SourcefileLoader(src)
    if (ctx.settings.scansource.value && ctx.run != null) {
      if (src.exists && !src.isDirectory) {
        val filePath = owner.ownersIterator.takeWhile(!_.isRoot).map(_.name.toTermName).toList

        def addPrefix(pid: RefTree, path: List[TermName]): List[TermName] = pid match {
          case Ident(name: TermName) => name :: path
          case Select(qual: RefTree, name: TermName) => name :: addPrefix(qual, path)
          case _ => path
        }

        def enterScanned(unit: CompilationUnit)(implicit ctx: Context) = {

          def checkPathMatches(path: List[TermName], what: String, tree: MemberDef): Boolean = {
            val ok = filePath == path
            if (!ok)
              ctx.warning(i"""$what ${tree.name} is in the wrong directory.
                              |It was declared to be in package ${path.reverse.mkString(".")}
                              |But it is found in directory     ${filePath.reverse.mkString(File.separator)}""",
                          tree.sourcePos)
            ok
          }

          def traverse(tree: Tree, path: List[TermName]): Unit = tree match {
            case PackageDef(pid, body) =>
              val path1 = addPrefix(pid, path)
              for (stat <- body) traverse(stat, path1)
            case tree: TypeDef if tree.isClassDef =>
              if (checkPathMatches(path, "class", tree))
                enterClassAndModule(owner, tree.name, completer, scope = scope)
                  // It might be a case class or implicit class,
                  // so enter class and module to be on the safe side
            case tree: ModuleDef =>
              if (checkPathMatches(path, "object", tree))
                enterModule(owner, tree.name, completer, scope = scope)
            case _ =>
          }

          traverse(
            if (unit.isJava) new OutlineJavaParser(unit.source).parse()
            else new OutlineParser(unit.source).parse(),
            Nil)
        }

        val unit = CompilationUnit(ctx.getSource(src.path))
        enterScanned(unit)(ctx.run.runContext.fresh.setCompilationUnit(unit))
      }
    }
    else enterClassAndModule(owner, name, completer, scope = scope)
  }

  /** The package objects of scala and scala.reflect should always
   *  be loaded in binary if classfiles are available, even if sourcefiles
   *  are newer. Late-compiling these objects from source leads to compilation
   *  order issues.
   *  Note: We do a name-base comparison here because the method is called before we even
   *  have ReflectPackage defined.
   */
  def binaryOnly(owner: Symbol, name: String)(implicit ctx: Context): Boolean =
    name == "package" &&
      (owner.fullName.toString == "scala" || owner.fullName.toString == "scala.reflect")

  /** Initialize toplevel class and module symbols in `owner` from class path representation `classRep`
   */
  def initializeFromClassPath(owner: Symbol, classRep: ClassRepresentation)(implicit ctx: Context): Unit = {
    ((classRep.binary, classRep.source): @unchecked) match {
      case (Some(bin), Some(src)) if needCompile(bin, src) && !binaryOnly(owner, classRep.name) =>
        if (ctx.settings.verbose.value) ctx.inform("[symloader] picked up newer source file for " + src.path)
        enterToplevelsFromSource(owner, classRep.name, src)
      case (None, Some(src)) =>
        if (ctx.settings.verbose.value) ctx.inform("[symloader] no class, picked up source file for " + src.path)
        enterToplevelsFromSource(owner, classRep.name, src)
      case (Some(bin), _) =>
        enterClassAndModule(owner, classRep.name, ctx.platform.newClassLoader(bin))
    }
  }

  def needCompile(bin: AbstractFile, src: AbstractFile): Boolean =
    src.lastModified >= bin.lastModified

  /** Load contents of a package
   */
  class PackageLoader(_sourceModule: TermSymbol, classPath: ClassPath)
      extends SymbolLoader {
    override def sourceModule(implicit ctx: Context): TermSymbol = _sourceModule
    def description(implicit ctx: Context): String = "package loader " + sourceModule.fullName

    private[this] var enterFlatClasses: Option[Context => Unit] = None

    Stats.record("package scopes")

    /** The scope of a package. This is different from a normal scope
  	 *  in two aspects:
	   *
	   *   1. Names of scope entries are kept in mangled form.
	   *   2. Some function types in the `scala` package are synthesized.
  	 */
    final class PackageScope extends MutableScope {
      override def newScopeEntry(name: Name, sym: Symbol)(implicit ctx: Context): ScopeEntry =
        super.newScopeEntry(name.mangled, sym)

      override def lookupEntry(name: Name)(implicit ctx: Context): ScopeEntry = {
        val mangled = name.mangled
        val e = super.lookupEntry(mangled)
        if (e != null) e
        else if (isFlatName(mangled.toSimpleName) && enterFlatClasses.isDefined) {
          Stats.record("package scopes with flatnames entered")
          enterFlatClasses.get(ctx)
          lookupEntry(name)
        }
        else e
      }

      override def ensureComplete()(implicit ctx: Context): Unit =
        for (enter <- enterFlatClasses) enter(ctx)

      override def newScopeLikeThis(): PackageScope = new PackageScope
    }

    private[core] val currentDecls: MutableScope = new PackageScope()

    private def isFlatName(name: SimpleName): Boolean = {
      val idx = name.lastIndexOf('$', name.length - 2)
      idx >= 0 &&
      (idx + str.TOPLEVEL_SUFFIX.length + 1 != name.length || !name.endsWith(str.TOPLEVEL_SUFFIX))
    }

    /** Name of class contains `$`, excepted names ending in `$package` */
    def hasFlatName(classRep: ClassRepresentation): Boolean = {
      val name = classRep.name
      val idx = name.lastIndexOf('$', name.length - 2)
      idx >= 0 &&
      (idx + str.TOPLEVEL_SUFFIX.length + 1 != name.length || !name.endsWith(str.TOPLEVEL_SUFFIX))
    }

    def maybeModuleClass(classRep: ClassRepresentation): Boolean = classRep.name.last == '$'

    private def enterClasses(root: SymDenotation, packageName: String, flat: Boolean)(implicit ctx: Context) = {
      def isAbsent(classRep: ClassRepresentation) =
        !root.unforcedDecls.lookup(classRep.name.toTypeName).exists

      if (!root.isRoot) {
        val classReps = classPath.list(packageName).classesAndSources

        for (classRep <- classReps)
          if (!maybeModuleClass(classRep) && hasFlatName(classRep) == flat &&
            (!flat || isAbsent(classRep))) // on 2nd enter of flat names, check that the name has not been entered before
            initializeFromClassPath(root.symbol, classRep)
        for (classRep <- classReps)
          if (maybeModuleClass(classRep) && hasFlatName(classRep) == flat &&
              isAbsent(classRep))
            initializeFromClassPath(root.symbol, classRep)
      }
    }

    def doComplete(root: SymDenotation)(implicit ctx: Context): Unit = {
      assert(root is PackageClass, root)
      val pre = root.owner.thisType
      root.info = ClassInfo(pre, root.symbol.asClass, Nil, currentDecls, pre select sourceModule)
      if (!sourceModule.isCompleted)
        sourceModule.completer.complete(sourceModule)

      val packageName = if (root.isEffectiveRoot) "" else root.fullName.mangledString

      enterFlatClasses = Some { ctx =>
        enterFlatClasses = None
        enterClasses(root, packageName, flat = true)(ctx)
      }
      enterClasses(root, packageName, flat = false)
      if (!root.isEmptyPackage)
        for (pkg <- classPath.packages(packageName)) {
          val fullName = pkg.name
          val name =
            if (packageName.isEmpty) fullName
            else fullName.substring(packageName.length + 1)

          enterPackage(root.symbol, name.toTermName,
            (module, modcls) => new PackageLoader(module, classPath))
        }
    }
  }
}

/** A lazy type that completes itself by calling parameter doComplete.
 *  Any linked modules/classes or module classes are also initialized.
 */
abstract class SymbolLoader extends LazyType {

  /** Load source or class file for `root`, return */
  def doComplete(root: SymDenotation)(implicit ctx: Context): Unit

  def sourceFileOrNull: AbstractFile = null

  /** Description of the resource (ClassPath, AbstractFile)
   *  being processed by this loader
   */
  def description(implicit ctx: Context): String

  override def complete(root: SymDenotation)(implicit ctx: Context): Unit = {
    def signalError(ex: Exception): Unit = {
      if (ctx.debug) ex.printStackTrace()
      val msg = ex.getMessage()
      ctx.error(
        if (msg eq null) "i/o error while loading " + root.name
        else "error while loading " + root.name + ",\n" + msg)
    }
    try {
      val start = currentTime
      if (Config.tracingEnabled && ctx.settings.YdebugTrace.value)
        trace(s">>>> loading ${root.debugString}", _ => s"<<<< loaded ${root.debugString}") {
          doComplete(root)
        }
      else
        doComplete(root)
      ctx.informTime("loaded " + description, start)
    } catch {
      case ex: IOException =>
        signalError(ex)
      case NonFatal(ex: TypeError) =>
        println(s"exception caught when loading $root: ${ex.toMessage}")
        throw ex
      case NonFatal(ex) =>
        println(s"exception caught when loading $root: $ex")
        throw ex
    } finally {
      def postProcess(denot: SymDenotation) =
        if (!denot.isCompleted &&
            !denot.completer.isInstanceOf[SymbolLoaders.SecondCompleter])
          denot.markAbsent()
      postProcess(root)
      if (!root.isRoot)
        postProcess(root.scalacLinkedClass.denot)
    }
  }

  protected def rootDenots(rootDenot: ClassDenotation)(implicit ctx: Context): (ClassDenotation, ClassDenotation) = {
    val linkedDenot = rootDenot.scalacLinkedClass.denot match {
      case d: ClassDenotation => d
      case d =>
        // this can happen if the companion if shadowed by a val or type
        // in a package object; in this case, we make up some dummy denotation
        // as a stand in for loading.
        // An example for this situation is scala.reflect.Manifest, which exists
        // as a class in scala.reflect and as a val in scala.reflect.package.
        if (rootDenot.is(ModuleClass))
          ctx.newClassSymbol(
            rootDenot.owner, rootDenot.name.stripModuleClassSuffix.asTypeName, Synthetic,
              _ => NoType).classDenot
        else
          ctx.newModuleSymbol(
            rootDenot.owner, rootDenot.name.toTermName, Synthetic, Synthetic,
            (module, _) => new NoCompleter() withDecls newScope withSourceModule (_ => module))
            .moduleClass.denot.asClass
    }
    if (rootDenot.is(ModuleClass)) (linkedDenot, rootDenot)
    else (rootDenot, linkedDenot)
  }
}

class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader {

  override def sourceFileOrNull: AbstractFile = classfile

  def description(implicit ctx: Context): String = "class file " + classfile.toString

  override def doComplete(root: SymDenotation)(implicit ctx: Context): Unit =
    load(root)

  def load(root: SymDenotation)(implicit ctx: Context): Unit = {
    val (classRoot, moduleRoot) = rootDenots(root.asClass)
    val classfileParser = new ClassfileParser(classfile, classRoot, moduleRoot)(ctx)
    val result = classfileParser.run()
    if (mayLoadTreesFromTasty) {
      result match {
        case Some(unpickler: tasty.DottyUnpickler) =>
          classRoot.classSymbol.rootTreeOrProvider = unpickler
          moduleRoot.classSymbol.rootTreeOrProvider = unpickler
        case _ =>
      }
    }
  }

  private def mayLoadTreesFromTasty(implicit ctx: Context): Boolean =
    ctx.settings.YretainTrees.value || ctx.settings.fromTasty.value
}

class SourcefileLoader(val srcfile: AbstractFile) extends SymbolLoader {
  def description(implicit ctx: Context): String = "source file " + srcfile.toString
  override def sourceFileOrNull: AbstractFile = srcfile
  def doComplete(root: SymDenotation)(implicit ctx: Context): Unit =
    ctx.run.lateCompile(srcfile, typeCheck = ctx.settings.YretainTrees.value)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy