Skip to content

Commit 607e4d5

Browse files
committed
Loading symbols from TASTy files directly
1 parent c629090 commit 607e4d5

24 files changed

+197
-92
lines changed

compiler/src/dotty/tools/backend/jvm/PostProcessor.scala

+9-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes:
2323

2424
def postProcessAndSendToDisk(generatedDefs: GeneratedDefs): Unit = {
2525
val GeneratedDefs(classes, tasty) = generatedDefs
26+
if !ctx.settings.YoutputOnlyTasty.value then
27+
postProcessClassesAndSendToDisk(classes)
28+
postProcessTastyAndSendToDisk(tasty)
29+
}
30+
31+
private def postProcessClassesAndSendToDisk(classes: List[GeneratedClass]): Unit = {
2632
for (GeneratedClass(classNode, sourceFile, isArtifact, onFileCreated) <- classes) {
2733
val bytes =
2834
try
@@ -46,8 +52,10 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes:
4652
if clsFile != null then onFileCreated(clsFile)
4753
}
4854
}
55+
}
4956

50-
for (GeneratedTasty(classNode, binaryGen) <- tasty){
57+
private def postProcessTastyAndSendToDisk(tasty: List[GeneratedTasty]): Unit = {
58+
for (GeneratedTasty(classNode, binaryGen) <- tasty) {
5159
classfileWriter.writeTasty(classNode.name.nn, binaryGen())
5260
}
5361
}

compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala

+8-6
Original file line numberDiff line numberDiff line change
@@ -278,15 +278,17 @@ case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[ClassFile
278278

279279
def findClassFile(className: String): Option[AbstractFile] = {
280280
val relativePath = FileUtils.dirPath(className)
281-
val classFile = new JFile(dir, relativePath + ".class")
282-
if (classFile.exists) {
283-
Some(classFile.toPath.toPlainFile)
284-
}
285-
else None
281+
val tastyFile = new JFile(dir, relativePath + ".tasty")
282+
if tastyFile.exists then Some(tastyFile.toPath.toPlainFile)
283+
else
284+
val classFile = new JFile(dir, relativePath + ".class")
285+
if classFile.exists then Some(classFile.toPath.toPlainFile)
286+
else None
286287
}
287288

288289
protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
289-
protected def isMatchingFile(f: JFile): Boolean = f.isClass
290+
protected def isMatchingFile(f: JFile): Boolean =
291+
f.isTasty || (f.isClass && f.classToTasty.isEmpty)
290292

291293
private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage)
292294
}

compiler/src/dotty/tools/dotc/classpath/FileUtils.scala

+34-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ object FileUtils {
2020
def isClass: Boolean = !file.isDirectory && file.hasExtension("class") && !file.name.endsWith("$class.class")
2121
// FIXME: drop last condition when we stop being compatible with Scala 2.11
2222

23+
def isTasty: Boolean = !file.isDirectory && file.hasExtension("tasty")
24+
25+
def isScalaBinary: Boolean = file.isClass || file.isTasty
26+
2327
def isScalaOrJavaSource: Boolean = !file.isDirectory && (file.hasExtension("scala") || file.hasExtension("java"))
2428

2529
// TODO do we need to check also other files using ZipMagicNumber like in scala.tools.nsc.io.Jar.isJarOrZip?
@@ -30,17 +34,34 @@ object FileUtils {
3034
* and returning given default value in other case
3135
*/
3236
def toURLs(default: => Seq[URL] = Seq.empty): Seq[URL] = if (file.file == null) default else Seq(file.toURL)
37+
38+
/** Returns the tasty file associated with this class file */
39+
def classToTasty: Option[AbstractFile] =
40+
assert(file.isClass, s"non-class: $file")
41+
val tastyName = classNameToTasty(file.name)
42+
Option(file.resolveSibling(tastyName))
3343
}
3444

3545
extension (file: JFile) {
3646
def isPackage: Boolean = file.isDirectory && mayBeValidPackage(file.getName)
3747

38-
def isClass: Boolean = file.isFile && file.getName.endsWith(".class") && !file.getName.endsWith("$class.class")
39-
// FIXME: drop last condition when we stop being compatible with Scala 2.11
48+
def isClass: Boolean = file.isFile && file.getName.endsWith(SUFFIX_CLASS) && !file.getName.endsWith("$class.class")
49+
// FIXME: drop last condition when we stop being compatible with Scala 2.11
50+
51+
def isTasty: Boolean = file.isFile && file.getName.endsWith(SUFFIX_TASTY)
52+
53+
/** Returns the tasty file associated with this class file */
54+
def classToTasty: Option[JFile] =
55+
assert(file.isClass, s"non-class: $file")
56+
val tastyName = classNameToTasty(file.getName.stripSuffix(".class"))
57+
val tastyPath = file.toPath.resolveSibling(tastyName)
58+
if java.nio.file.Files.exists(tastyPath) then Some(tastyPath.toFile) else None
59+
4060
}
4161

4262
private val SUFFIX_CLASS = ".class"
4363
private val SUFFIX_SCALA = ".scala"
64+
private val SUFFIX_TASTY = ".tasty"
4465
private val SUFFIX_JAVA = ".java"
4566
private val SUFFIX_SIG = ".sig"
4667

@@ -81,4 +102,15 @@ object FileUtils {
81102
def mkFileFilter(f: JFile => Boolean): FileFilter = new FileFilter {
82103
def accept(pathname: JFile): Boolean = f(pathname)
83104
}
105+
106+
/** Transforms a .class file name to a .tasty file name */
107+
private def classNameToTasty(fileName: String): String =
108+
val classOrModuleName = fileName.stripSuffix(".class")
109+
val className =
110+
if classOrModuleName.endsWith("$")
111+
&& classOrModuleName != "Null$" // scala.runtime.Null$
112+
&& classOrModuleName != "Nothing$" // scala.runtime.Nothing$
113+
then classOrModuleName.stripSuffix("$")
114+
else classOrModuleName
115+
className + SUFFIX_TASTY
84116
}

compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala

+8-3
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,17 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi
4141
override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl.apply
4242

4343
def findClassFile(className: String): Option[AbstractFile] = {
44-
val relativePath = FileUtils.dirPath(className) + ".class"
45-
Option(lookupPath(dir)(relativePath.split(java.io.File.separator).toIndexedSeq, directory = false))
44+
val pathSeq = FileUtils.dirPath(className).split(java.io.File.separator)
45+
val parentDir = lookupPath(dir)(pathSeq.init.toSeq, directory = true)
46+
if parentDir == null then return None
47+
else
48+
Option(lookupPath(parentDir)(pathSeq.last + ".tasty" :: Nil, directory = false))
49+
.orElse(Option(lookupPath(parentDir)(pathSeq.last + ".class" :: Nil, directory = false)))
4650
}
4751

4852
private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage)
4953

5054
protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
51-
protected def isMatchingFile(f: AbstractFile): Boolean = f.isClass
55+
protected def isMatchingFile(f: AbstractFile): Boolean =
56+
f.isTasty || (f.isClass && f.classToTasty.isEmpty)
5257
}

compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala

+7-7
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,21 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory {
4444
extends ZipArchiveFileLookup[ClassFileEntryImpl]
4545
with NoSourcePaths {
4646

47-
override def findClassFile(className: String): Option[AbstractFile] = {
48-
val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
49-
file(PackageName(pkg), simpleClassName + ".class").map(_.file)
50-
}
47+
override def findClassFile(className: String): Option[AbstractFile] =
48+
findClass(className).map(_.file)
5149

5250
// This method is performance sensitive as it is used by SBT's ExtractDependencies phase.
53-
override def findClass(className: String): Option[ClassRepresentation] = {
51+
override def findClass(className: String): Option[ClassFileEntryImpl] = {
5452
val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
55-
file(PackageName(pkg), simpleClassName + ".class")
53+
val binaries = files(PackageName(pkg), simpleClassName + ".tasty", simpleClassName + ".class")
54+
binaries.find(_.file.isTasty).orElse(binaries.find(_.file.isClass))
5655
}
5756

5857
override private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage)
5958

6059
override protected def createFileEntry(file: FileZipArchive#Entry): ClassFileEntryImpl = ClassFileEntryImpl(file)
61-
override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isClass
60+
override protected def isRequiredFileType(file: AbstractFile): Boolean =
61+
file.isTasty || (file.isClass && file.classToTasty.isEmpty)
6262
}
6363

6464
/**

compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala

+9
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends Efficie
4343
}
4444
yield createFileEntry(entry)
4545

46+
protected def files(inPackage: PackageName, names: String*): Seq[FileEntryType] =
47+
for {
48+
dirEntry <- findDirEntry(inPackage).toSeq
49+
name <- names
50+
entry <- Option(dirEntry.lookupName(name, directory = false))
51+
if isRequiredFileType(entry)
52+
}
53+
yield createFileEntry(entry)
54+
4655
protected def file(inPackage: PackageName, name: String): Option[FileEntryType] =
4756
for {
4857
dirEntry <- findDirEntry(inPackage)

compiler/src/dotty/tools/dotc/config/JavaPlatform.scala

+3
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,7 @@ class JavaPlatform extends Platform {
6666

6767
def newClassLoader(bin: AbstractFile)(using Context): SymbolLoader =
6868
new ClassfileLoader(bin)
69+
70+
def newTastyLoader(bin: AbstractFile)(using Context): SymbolLoader =
71+
new TastyLoader(bin)
6972
}

compiler/src/dotty/tools/dotc/config/Platform.scala

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ abstract class Platform {
3636
/** Create a new class loader to load class file `bin` */
3737
def newClassLoader(bin: AbstractFile)(using Context): SymbolLoader
3838

39+
/** Create a new TASTy loader to load class file `bin` */
40+
def newTastyLoader(bin: AbstractFile)(using Context): SymbolLoader
41+
3942
/** The given symbol is a method with the right name and signature to be a runnable program. */
4043
def isMainMethod(sym: Symbol)(using Context): Boolean
4144

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+1
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ private sealed trait YSettings:
369369
val YnoExperimental: Setting[Boolean] = BooleanSetting("-Yno-experimental", "Disable experimental language features.")
370370
val YlegacyLazyVals: Setting[Boolean] = BooleanSetting("-Ylegacy-lazy-vals", "Use legacy (pre 3.3.0) implementation of lazy vals.")
371371
val Yscala2Stdlib: Setting[Boolean] = BooleanSetting("-Yscala2-stdlib", "Used when compiling the Scala 2 standard library.")
372+
val YoutputOnlyTasty: Setting[Boolean] = BooleanSetting("-Youtput-only-tasty", "Used to only generate the TASTy file without the classfiles")
372373

373374
val YprofileEnabled: Setting[Boolean] = BooleanSetting("-Yprofile-enabled", "Enable profiling.")
374375
val YprofileDestination: Setting[String] = StringSetting("-Yprofile-destination", "file", "Where to send profiling output - specify a file, default is to the console.", "")

compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala

+25-14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import java.nio.channels.ClosedByInterruptException
77

88
import scala.util.control.NonFatal
99

10+
import dotty.tools.dotc.classpath.FileUtils.isTasty
1011
import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile }
1112
import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions
1213

@@ -192,10 +193,13 @@ object SymbolLoaders {
192193
if (ctx.settings.verbose.value) report.inform("[symloader] picked up newer source file for " + src.path)
193194
enterToplevelsFromSource(owner, nameOf(classRep), src)
194195
case (None, Some(src)) =>
195-
if (ctx.settings.verbose.value) report.inform("[symloader] no class, picked up source file for " + src.path)
196+
if (ctx.settings.verbose.value) report.inform("[symloader] no class or tasty, picked up source file for " + src.path)
196197
enterToplevelsFromSource(owner, nameOf(classRep), src)
197198
case (Some(bin), _) =>
198-
enterClassAndModule(owner, nameOf(classRep), ctx.platform.newClassLoader(bin))
199+
val completer =
200+
if bin.isTasty then ctx.platform.newTastyLoader(bin)
201+
else ctx.platform.newClassLoader(bin)
202+
enterClassAndModule(owner, nameOf(classRep), completer)
199203
}
200204

201205
def needCompile(bin: AbstractFile, src: AbstractFile): Boolean =
@@ -404,20 +408,27 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader {
404408
def description(using Context): String = "class file " + classfile.toString
405409

406410
override def doComplete(root: SymDenotation)(using Context): Unit =
407-
load(root)
408-
409-
def load(root: SymDenotation)(using Context): Unit = {
410411
val (classRoot, moduleRoot) = rootDenots(root.asClass)
411412
val classfileParser = new ClassfileParser(classfile, classRoot, moduleRoot)(ctx)
412-
val result = classfileParser.run()
413-
if (mayLoadTreesFromTasty)
414-
result match {
415-
case Some(unpickler: tasty.DottyUnpickler) =>
416-
classRoot.classSymbol.rootTreeOrProvider = unpickler
417-
moduleRoot.classSymbol.rootTreeOrProvider = unpickler
418-
case _ =>
419-
}
420-
}
413+
classfileParser.run()
414+
}
415+
416+
class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader {
417+
418+
override def sourceFileOrNull: AbstractFile | Null = tastyFile
419+
420+
def description(using Context): String = "TASTy file " + tastyFile.toString
421+
422+
override def doComplete(root: SymDenotation)(using Context): Unit =
423+
val (classRoot, moduleRoot) = rootDenots(root.asClass)
424+
val unpickler =
425+
val tastyBytes = tastyFile.toByteArray
426+
new tasty.DottyUnpickler(tastyBytes)
427+
unpickler.enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule))(using ctx.withSource(util.NoSource))
428+
if mayLoadTreesFromTasty then
429+
classRoot.classSymbol.rootTreeOrProvider = unpickler
430+
moduleRoot.classSymbol.rootTreeOrProvider = unpickler
431+
// TODO check TASTy UUID matches classfile
421432

422433
private def mayLoadTreesFromTasty(using Context): Boolean =
423434
ctx.settings.YretainTrees.value || ctx.settings.fromTasty.value

compiler/src/dotty/tools/dotc/core/Symbols.scala

+5-4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import io.AbstractFile
3131
import util.{SourceFile, NoSource, Property, SourcePosition, SrcPos, EqHashMap}
3232
import scala.annotation.internal.sharable
3333
import config.Printers.typr
34+
import dotty.tools.dotc.classpath.FileUtils.isScalaBinary
3435

3536
object Symbols {
3637

@@ -151,7 +152,7 @@ object Symbols {
151152
* symbols defined by the user in a prior run of the REPL, that are still valid.
152153
*/
153154
final def isDefinedInSource(using Context): Boolean =
154-
span.exists && isValidInCurrentRun && associatedFileMatches(_.extension != "class")
155+
span.exists && isValidInCurrentRun && associatedFileMatches(!_.isScalaBinary)
155156

156157
/** Is symbol valid in current run? */
157158
final def isValidInCurrentRun(using Context): Boolean =
@@ -272,7 +273,7 @@ object Symbols {
272273
/** The class file from which this class was generated, null if not applicable. */
273274
final def binaryFile(using Context): AbstractFile | Null = {
274275
val file = associatedFile
275-
if (file != null && file.extension == "class") file else null
276+
if file != null && file.isScalaBinary then file else null
276277
}
277278

278279
/** A trap to avoid calling x.symbol on something that is already a symbol.
@@ -285,7 +286,7 @@ object Symbols {
285286

286287
final def source(using Context): SourceFile = {
287288
def valid(src: SourceFile): SourceFile =
288-
if (src.exists && src.file.extension != "class") src
289+
if (src.exists && !src.file.isScalaBinary) src
289290
else NoSource
290291

291292
if (!denot.exists) NoSource
@@ -463,7 +464,7 @@ object Symbols {
463464
if !mySource.exists && !denot.is(Package) then
464465
// this allows sources to be added in annotations after `sourceOfClass` is first called
465466
val file = associatedFile
466-
if file != null && file.extension != "class" then
467+
if file != null && !file.isScalaBinary then
467468
mySource = ctx.getSource(file)
468469
else
469470
mySource = defn.patchSource(this)

0 commit comments

Comments
 (0)