Skip to content

[WIP] try to make scala._ package loading lazier #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: 2.12.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,10 @@ lazy val junit = project.in(file("test") / "junit")
javaOptions in Test += "-Xss1M",
(forkOptions in Test) := (forkOptions in Test).value.withWorkingDirectory((baseDirectory in ThisBuild).value),
(forkOptions in Test in testOnly) := (forkOptions in Test in testOnly).value.withWorkingDirectory((baseDirectory in ThisBuild).value),
(forkOptions in Test in run) := {
val existing = (forkOptions in Test in run).value
existing.withRunJVMOptions(existing.runJVMOptions ++ List("-Xmx2G", "-Xss1M"))
},
libraryDependencies ++= Seq(junitDep, junitInterfaceDep, jolDep),
testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-v"),
unmanagedSourceDirectories in Compile := Nil,
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/scala/tools/nsc/Global.scala
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,9 @@ class Global(var currentSettings: Settings, reporter0: Reporter)

// ------------ Phases -------------------------------------------}

var globalPhase: Phase = NoPhase
private[this] var _globalPhase: Phase = NoPhase
override final def globalPhase: Phase = _globalPhase
final def globalPhase_=(ph: Phase): Unit = _globalPhase = ph

abstract class GlobalPhase(prev: Phase) extends Phase(prev) {
phaseWithId(id) = this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import scala.reflect.internal.Flags.{DEFERRED, SYNTHESIZE_IMPL_IN_SUBCLASS}
import scala.tools.asm
import scala.tools.nsc.backend.jvm.BTypes._
import scala.tools.nsc.backend.jvm.BackendReporting._
import scala.tools.nsc.symtab.Flags

/**
* This class mainly contains the method classBTypeFromSymbol, which extracts the necessary
Expand Down Expand Up @@ -701,7 +702,7 @@ abstract class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
// new instances via outerClassInstance.new InnerModuleClass$().
// TODO: do this early, mark the symbol private.
val privateFlag =
sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModuleClass(sym.owner))
(sym.isPrivate || sym.hasFlag(Flags.notPRIVATE)) || (sym.isPrimaryConstructor && isTopLevelModuleClass(sym.owner))

// Symbols marked in source as `final` have the FINAL flag. (In the past, the flag was also
// added to modules and module classes, not anymore since 296b706).
Expand Down
32 changes: 28 additions & 4 deletions src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ abstract class SymbolLoaders {
protected def compileLate(srcfile: AbstractFile): Unit

protected def enterIfNew(owner: Symbol, member: Symbol, completer: SymbolLoader): Symbol = {
assert(owner.info.decls.lookup(member.name) == NoSymbol, owner.fullName + "." + member.name)
owner.info.decls enter member
member
owner.info.decls.lookup(member.name) match {
case NoSymbol =>
owner.info.decls enter member
member
case member =>
member
}
}

protected def signalError(root: Symbol, ex: Throwable) {
Expand Down Expand Up @@ -280,10 +284,30 @@ abstract class SymbolLoaders {
val shownPackageName = if (packageName == ClassPath.RootPackage) "<root package>" else packageName
s"package loader $shownPackageName"
}
override lazy val decls = {

val classPathEntries = classPath.list(packageName)

if (!packageName == "")
for (entry <- classPathEntries.classesAndSources) initializeFromClassPath(root, entry)
if (!root.isEmptyPackageClass) {
for (pkg <- classPathEntries.packages) {
val fullName = pkg.name

val name =
if (packageName == ClassPath.RootPackage) fullName
else fullName.substring(packageName.length + 1)
val packageLoader = new PackageLoader(fullName, classPath)
enterPackage(root, name, packageLoader)
}

openPackageModule(root)
}
}

protected def doComplete(root: Symbol) {
assert(root.isPackageClass, root)
root.setInfo(new PackageClassInfoType(newScope, root))
root.setInfo(new PackageClassInfoType(decls, root))

val classPathEntries = classPath.list(packageName)

Expand Down
3 changes: 3 additions & 0 deletions src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ abstract class Pickler extends SubComponent {
syms.foreach { sym =>
pickle.putDecl(sym)
}
if (sym.name.string_==("th"))
println(_entries.toList.iterator.filter(_ != null).map(_.toString.replace('\n', ';')).mkString("\n"))

}
}

Expand Down
2 changes: 2 additions & 0 deletions src/compiler/scala/tools/nsc/typechecker/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,8 @@ trait Contexts { self: Analyzer =>
}
// we have a winner: record the symbol depth
symbolDepth = (cx.depth - cx.scope.nestingLevel) + e1.depth
if (name.string_==("Throwable") && e1.sym.id == 2216)
getClass

if (syms eq null) e1Sym
else owner.newOverloaded(pre, syms.toList)
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/Namers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,8 @@ trait Namers extends MethodSynthesis {
def enterPackage(tree: PackageDef) {
val sym = createPackageSymbol(tree.pos, tree.pid)
tree.symbol = sym
newNamer(context.make(tree, sym.moduleClass, sym.info.decls)) enterSyms tree.stats
val scope = sym.rawInfo.typeSymbolDirect.rawInfo.decls
newNamer(context.make(tree, sym.moduleClass, scope)) enterSyms tree.stats
}

private def enterImport(tree: Import) = {
Expand Down Expand Up @@ -2161,7 +2162,7 @@ trait Namers extends MethodSynthesis {
// scala/bug#7264 Force the info of owners from previous compilation runs.
// Doing this generally would trigger cycles; that's what we also
// use the lower-level scan through the current Context as a fall back.
if (!currentRun.compiles(owner)) owner.initialize
if (!owner.isPackageClass && !currentRun.compiles(owner)) owner.initialize

if (original.isModuleClass) original.sourceModule
else if (!owner.isTerm && owner.hasCompleteInfo)
Expand Down
9 changes: 7 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1863,9 +1863,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
def typedModuleDef(mdef: ModuleDef): Tree = {
// initialize all constructors of the linked class: the type completer (Namer.methodSig)
// might add default getters to this object. example: "object T; class T(x: Int = 1)"
val linkedClass = companionSymbolOf(mdef.symbol, context)
if (linkedClass != NoSymbol)
val linkedClass = companionSymbolOf(mdef.symbol, context).suchThat { sym =>
val isStale = sym.rawInfo.isInstanceOf[loaders.ClassfileLoader]
if (isStale) sym.owner.info.decls.unlink(sym)
!isStale
}
if (linkedClass != NoSymbol) {
linkedClass.info.decl(nme.CONSTRUCTOR).alternatives foreach (_.initialize)
}

val clazz = mdef.symbol.moduleClass
currentRun.profiler.beforeTypedImplDef(clazz)
Expand Down
16 changes: 14 additions & 2 deletions src/reflect/scala/reflect/internal/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,15 @@ trait Definitions extends api.StandardDefinitions {
lazy val JavaLangPackageClass = JavaLangPackage.moduleClass.asClass
lazy val ScalaPackage = getPackage("scala")
lazy val ScalaPackageClass = ScalaPackage.moduleClass.asClass
lazy val RuntimePackage = getPackage("scala.runtime")
lazy val RuntimePackage = getOrCreatePackage(ScalaPackageClass, nme.runtime)
lazy val RuntimePackageClass = RuntimePackage.moduleClass.asClass
lazy val AnnotationPackage = getOrCreatePackage(ScalaPackageClass, nme.annotation)
lazy val AnnotationPackageClass = AnnotationPackage.moduleClass.asClass

private def getOrCreatePackage(owner: Symbol, name: TermName): Symbol = {
val rawInfo = owner.rawInfo
rawInfo.decl(name).orElse(rawInfo.decls.enter(owner.newPackage(name)))
}

def javaTypeToValueClass(jtype: Class[_]): Symbol = jtype match {
case java.lang.Void.TYPE => UnitClass
Expand Down Expand Up @@ -1501,7 +1508,12 @@ trait Definitions extends api.StandardDefinitions {
def init() {
if (isInitialized) return
ObjectClass.initialize
ScalaPackageClass.initialize
if (!isCompilerUniverse) {
ScalaPackageClass.initialize
} else {
RuntimePackageClass
AnnotationPackageClass
}
symbolsNotPresentInBytecode
NoSymbol
isInitialized = true
Expand Down
5 changes: 4 additions & 1 deletion src/reflect/scala/reflect/internal/Mirrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ trait Mirrors extends api.Mirrors {
else RootClass

val name = toName(path.substring(point + 1, len))
val sym = owner.info member name
val sym = if (!globalPhase.named && owner.safeOwner == RootClass && owner.name == nme.scala_) {
owner.rawInfo.typeSymbolDirect.rawInfo.decls.lookup(name)
} else
owner.info member name
val result = if (name.isTermName) sym.suchThat(_ hasFlag MODULE) else sym
if (result != NoSymbol) result
else {
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/internal/Phase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ abstract class Phase(val prev: Phase) extends Ordered[Phase] {
final val flatClasses: Boolean = ((prev ne null) && (prev ne NoPhase)) && (prev.name == "flatten" || prev.flatClasses)
final val specialized: Boolean = ((prev ne null) && (prev ne NoPhase)) && (prev.name == "specialize" || prev.specialized)
final val refChecked: Boolean = ((prev ne null) && (prev ne NoPhase)) && (prev.name == "refchecks" || prev.refChecked)
final val named: Boolean = ((prev ne null) && (prev ne NoPhase)) && (prev.name == "namer" || prev.named)

// are we past the fields phase, so that:
// - we should allow writing to vals (as part of type checking trait setters)
Expand Down
2 changes: 2 additions & 0 deletions src/reflect/scala/reflect/internal/SymbolTable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ abstract class SymbolTable extends macros.Universe
final def phase: Phase = {
ph
}
def globalPhase: Phase = phase

def atPhaseStackMessage = atPhaseStack match {
case Nil => ""
Expand Down Expand Up @@ -352,6 +353,7 @@ abstract class SymbolTable extends macros.Universe

def openPackageModule(container: Symbol, dest: Symbol) {
// unlink existing symbols in the package
assert(globalPhase.name != "namer" && globalPhase.name != "<no phase>")
for (member <- container.info.decls.iterator) {
if (!member.isPrivate && !member.isConstructor) {
// todo: handle overlapping definitions in some way: mark as errors
Expand Down
8 changes: 7 additions & 1 deletion src/reflect/scala/reflect/internal/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1583,7 +1583,11 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
/** Set the info and enter this symbol into the owner's scope. */
def setInfoAndEnter(info: Type): this.type = {
setInfo(info)
owner.info.decls enter this
if (!phase.named && owner == ScalaPackageClass) {
owner.rawInfo.decls.enter(this)
} else {
owner.info.decls enter this
}
this
}

Expand Down Expand Up @@ -3054,6 +3058,8 @@ trait Symbols extends api.Symbols { self: SymbolTable =>

class AliasTypeSymbol protected[Symbols] (initOwner: Symbol, initPos: Position, initName: TypeName)
extends TypeSymbol(initOwner, initPos, initName) {
if (initName.string_==("Throwable"))
getClass
type TypeOfClonedSymbol = TypeSymbol
override def variance = if (isLocalToThis) Bivariant else info.typeSymbol.variance
override def isContravariant = variance.isContravariant
Expand Down
2 changes: 1 addition & 1 deletion src/reflect/scala/reflect/internal/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3541,7 +3541,7 @@ trait Types
// Creators ---------------------------------------------------------------

/** Rebind symbol `sym` to an overriding member in type `pre`. */
private def rebind(pre: Type, sym: Symbol): Symbol = {
private[scala] def rebind(pre: Type, sym: Symbol): Symbol = {
if (!sym.isOverridableMember || sym.owner == pre.typeSymbol) sym
else pre.nonPrivateMember(sym.name).suchThat { sym =>
// scala/bug#7928 `isModuleNotMethod` is here to avoid crashing with spuriously "overloaded" module accessor and module symbols.
Expand Down
104 changes: 37 additions & 67 deletions test/junit/scala/tools/nsc/DeterminismTest.scala
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
package scala.tools.nsc

import java.io.OutputStreamWriter
import java.nio.charset.Charset
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor}
import java.nio.file.{Files, Path, Paths}

import javax.tools.ToolProvider
import org.junit.Test
import org.junit.{Ignore, Test}

import scala.collection.JavaConverters.seqAsJavaListConverter
import scala.reflect.internal.util.{BatchSourceFile, SourceFile}
import scala.tools.nsc.reporters.StoreReporter
import FileUtils._
import scala.reflect.io.AbstractFile

class DeterminismTest {
private val tester = new DeterminismTester
import tester.test

@Test def testLambdaLift(): Unit = {
def code = List[SourceFile](
source("a.scala",
Expand Down Expand Up @@ -300,65 +297,38 @@ class DeterminismTest {
test(List(code))
}

def source(name: String, code: String): SourceFile = new BatchSourceFile(name, code)
private def test(groups: List[List[SourceFile]]): Unit = {
val referenceOutput = Files.createTempDirectory("reference")

def compile(output: Path, files: List[SourceFile]): Unit = {
val g = new Global(new Settings)
g.settings.usejavacp.value = true
g.settings.classpath.value = output.toAbsolutePath.toString
g.settings.outputDirs.setSingleOutput(output.toString)
val storeReporter = new StoreReporter
g.reporter = storeReporter
import g._
val r = new Run
// println("scalac " + files.mkString(" "))
r.compileSources(files)
Predef.assert(!storeReporter.hasErrors, storeReporter.infos.mkString("\n"))
files.filter(_.file.name.endsWith(".java")) match {
case Nil =>
case javaSources =>
def tempFileFor(s: SourceFile): Path = {
val f = output.resolve(s.file.name)
Files.write(f, new String(s.content).getBytes(Charset.defaultCharset()))
}
val options = List("-d", output.toString)
val javac = ToolProvider.getSystemJavaCompiler
assert(javac != null, "No javac from getSystemJavaCompiler. If the java on your path isn't a JDK version, but $JAVA_HOME is, launch sbt with --java-home \"$JAVA_HOME\"")
val fileMan = javac.getStandardFileManager(null, null, null)
val javaFileObjects = fileMan.getJavaFileObjects(javaSources.map(s => tempFileFor(s).toAbsolutePath.toString): _*)
val task = javac.getTask(new OutputStreamWriter(System.out), fileMan, null, options.asJava, Nil.asJava, javaFileObjects)
val result = task.call()
Predef.assert(result)
}
}

for (group <- groups.init) {
compile(referenceOutput, group)
}
compile(referenceOutput, groups.last)
@Test def testReferenceToInnerClassMadeNonPrivate(): Unit = {
def code = List[SourceFile](
source("t.scala",
"""
| trait T {
| private class Inner
| class OtherInner { new Inner } // triggers makeNotPrivate of Inner
| private val v: Option[Inner] = None
| }
""".stripMargin),
source("c.scala","""class C extends T""")
)
test(List(code))
}

class CopyVisitor(src: Path, dest: Path) extends SimpleFileVisitor[Path] {
override def preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult = {
Files.createDirectories(dest.resolve(src.relativize(dir)))
super.preVisitDirectory(dir, attrs)
}
override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = {
Files.copy(file, dest.resolve(src.relativize(file)))
super.visitFile(file, attrs)
}
}
for (permutation <- permutationsWithSubsets(groups.last)) {
val recompileOutput = Files.createTempDirectory("recompileOutput")
copyRecursive(referenceOutput, recompileOutput)
compile(recompileOutput, permutation)
assertDirectorySame(referenceOutput, recompileOutput, permutation.toString)
deleteRecursive(recompileOutput)
}
deleteRecursive(referenceOutput)
@Test def testPackageObject(): Unit = {
val root = List(Paths.get("."), Paths.get("../..")).find(x => Files.exists(x.resolve(".git"))).get.toAbsolutePath.normalize()
def code = List[SourceFile](
source(root.resolve("src/library/scala/package.scala")),
source("th.scala", "package scala; class th[T <: Throwable](cause: T = null)")
)
test(code :: Nil)
}

@Test def testPackageObjectUserLand(): Unit = {
def code = List[SourceFile](
source("package.scala", "package userland; object `package` { type Throwy = java.lang.Throwable }"),
source("th.scala", "package userland; class th[T <: Throwy](cause: T = null)")
)
test(code :: Nil, permuter = _.reverse :: Nil)
}
def permutationsWithSubsets[A](as: List[A]): List[List[A]] =
as.permutations.toList.flatMap(_.inits.filter(_.nonEmpty)).distinct

def source(name: String, code: String): SourceFile = new BatchSourceFile(name, code)
def source(path: Path): SourceFile = new BatchSourceFile(AbstractFile.getFile(path.toFile))
}
Loading