Skip to content

Commit fcf750a

Browse files
committed
use an enum to store file extensions,
This caches common file extensions, while still being extensible. Also fixes many operations with unexpected behavior (manipulation of file extensions where toLowerCase behaves differently with certain locales.)
1 parent 084dd15 commit fcf750a

File tree

31 files changed

+212
-90
lines changed

31 files changed

+212
-90
lines changed

compiler/src/dotty/tools/dotc/CompilationUnit.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@ class CompilationUnit protected (val source: SourceFile, val info: CompilationUn
2828
var tpdTree: tpd.Tree = tpd.EmptyTree
2929

3030
/** Is this the compilation unit of a Java file */
31-
def isJava: Boolean = source.file.name.endsWith(".java")
31+
def isJava: Boolean = source.file.ext.isJava
3232

3333
/** Is this the compilation unit of a Java file, or TASTy derived from a Java file */
34-
def typedAsJava = isJava || {
35-
val infoNN = info
36-
infoNN != null && infoNN.tastyInfo.exists(_.attributes.isJava)
37-
}
34+
def typedAsJava =
35+
val ext = source.file.ext
36+
ext.isJavaOrTasty && (ext.isJava || tastyInfo.exists(_.attributes.isJava))
37+
38+
def tastyInfo: Option[TastyInfo] =
39+
val local = info
40+
if local == null then None else local.tastyInfo
3841

3942

4043
/** The source version for this unit, as determined by a language import */

compiler/src/dotty/tools/dotc/Driver.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import core.Comments.{ContextDoc, ContextDocstrings}
66
import core.Contexts.*
77
import core.{MacroClassLoader, TypeError}
88
import dotty.tools.dotc.ast.Positioned
9-
import dotty.tools.io.AbstractFile
9+
import dotty.tools.io.{AbstractFile, FileExtension}
1010
import reporting.*
1111
import core.Decorators.*
1212
import config.Feature
@@ -97,9 +97,9 @@ class Driver {
9797
if !file.exists then
9898
report.error(em"File does not exist: ${file.path}")
9999
None
100-
else file.extension match
101-
case "jar" => Some(file.path)
102-
case "tasty" =>
100+
else file.ext match
101+
case FileExtension.Jar => Some(file.path)
102+
case FileExtension.Tasty =>
103103
TastyFileUtil.getClassPath(file) match
104104
case Some(classpath) => Some(classpath)
105105
case _ =>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package dotty.tools.dotc.classpath
66
import dotty.tools.dotc.classpath.FileUtils.isTasty
77
import dotty.tools.io.AbstractFile
88
import dotty.tools.io.ClassRepresentation
9+
import dotty.tools.io.FileExtension
910

1011
case class ClassPathEntries(packages: scala.collection.Seq[PackageEntry], classesAndSources: scala.collection.Seq[ClassRepresentation]) {
1112
def toTuple: (scala.collection.Seq[PackageEntry], scala.collection.Seq[ClassRepresentation]) = (packages, classesAndSources)
@@ -52,7 +53,7 @@ sealed trait BinaryFileEntry extends ClassRepresentation {
5253
object BinaryFileEntry {
5354
def apply(file: AbstractFile): BinaryFileEntry =
5455
if file.isTasty then
55-
if file.resolveSiblingWithExtension("class") != null then TastyWithClassFileEntry(file)
56+
if file.resolveSiblingWithExtension(FileExtension.Class) != null then TastyWithClassFileEntry(file)
5657
else StandaloneTastyFileEntry(file)
5758
else
5859
ClassFileEntry(file)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[BinaryFil
285285
protected def createFileEntry(file: AbstractFile): BinaryFileEntry = BinaryFileEntry(file)
286286

287287
protected def isMatchingFile(f: JFile): Boolean =
288-
f.isTasty || (f.isClass && f.classToTasty.isEmpty)
288+
f.isTasty || (f.isClass && !f.hasSiblingTasty)
289289

290290
private[dotty] def classes(inPackage: PackageName): Seq[BinaryFileEntry] = files(inPackage)
291291
}

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

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,49 +17,52 @@ object FileUtils {
1717
extension (file: AbstractFile) {
1818
def isPackage: Boolean = file.isDirectory && mayBeValidPackage(file.name)
1919

20-
def isClass: Boolean = !file.isDirectory && hasClassExtension && !file.name.endsWith("$class.class")
21-
// FIXME: drop last condition when we stop being compatible with Scala 2.11
20+
def isClass: Boolean = !file.isDirectory && hasClassExtension
2221

23-
def hasClassExtension: Boolean = file.hasExtension("class")
22+
def hasClassExtension: Boolean = file.ext.isClass
2423

25-
def hasTastyExtension: Boolean = file.hasExtension("tasty")
24+
def hasTastyExtension: Boolean = file.ext.isTasty
2625

2726
def isTasty: Boolean = !file.isDirectory && hasTastyExtension
2827

2928
def isScalaBinary: Boolean = file.isClass || file.isTasty
3029

31-
def isScalaOrJavaSource: Boolean = !file.isDirectory && (file.hasExtension("scala") || file.hasExtension("java"))
30+
def isScalaOrJavaSource: Boolean = !file.isDirectory && file.ext.isScalaOrJava
3231

3332
// TODO do we need to check also other files using ZipMagicNumber like in scala.tools.nsc.io.Jar.isJarOrZip?
34-
def isJarOrZip: Boolean = file.hasExtension("jar") || file.hasExtension("zip")
33+
def isJarOrZip: Boolean = file.ext.isJarOrZip
3534

3635
/**
3736
* Safe method returning a sequence containing one URL representing this file, when underlying file exists,
3837
* and returning given default value in other case
3938
*/
4039
def toURLs(default: => Seq[URL] = Seq.empty): Seq[URL] = if (file.file == null) default else Seq(file.toURL)
4140

42-
/** Returns the tasty file associated with this class file */
43-
def classToTasty: Option[AbstractFile] =
44-
assert(file.isClass, s"non-class: $file")
45-
val tastyName = classNameToTasty(file.name)
46-
Option(file.resolveSibling(tastyName))
41+
/**
42+
* Returns if there is an existing sibling `.tasty` file.
43+
*/
44+
def hasSiblingTasty: Boolean =
45+
assert(file.hasClassExtension, s"non-class: $file")
46+
file.resolveSibling(classNameToTasty(file.name)) != null
4747
}
4848

4949
extension (file: JFile) {
5050
def isPackage: Boolean = file.isDirectory && mayBeValidPackage(file.getName)
5151

52-
def isClass: Boolean = file.isFile && file.getName.endsWith(SUFFIX_CLASS) && !file.getName.endsWith("$class.class")
53-
// FIXME: drop last condition when we stop being compatible with Scala 2.11
52+
def isClass: Boolean = file.isFile && hasClassExtension
53+
54+
def hasClassExtension: Boolean = file.getName.endsWith(SUFFIX_CLASS)
5455

5556
def isTasty: Boolean = file.isFile && file.getName.endsWith(SUFFIX_TASTY)
5657

57-
/** Returns the tasty file associated with this class file */
58-
def classToTasty: Option[JFile] =
59-
assert(file.isClass, s"non-class: $file")
60-
val tastyName = classNameToTasty(file.getName.stripSuffix(".class"))
61-
val tastyPath = file.toPath.resolveSibling(tastyName)
62-
if java.nio.file.Files.exists(tastyPath) then Some(tastyPath.toFile) else None
58+
/**
59+
* Returns if there is an existing sibling `.tasty` file.
60+
*/
61+
def hasSiblingTasty: Boolean =
62+
assert(file.hasClassExtension, s"non-class: $file")
63+
val path = file.toPath
64+
val tastyPath = path.resolveSibling(classNameToTasty(file.getName))
65+
java.nio.file.Files.exists(tastyPath)
6366

6467
}
6568

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,5 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi
5151
protected def createFileEntry(file: AbstractFile): BinaryFileEntry = BinaryFileEntry(file)
5252

5353
protected def isMatchingFile(f: AbstractFile): Boolean =
54-
f.isTasty || (f.isClass && f.classToTasty.isEmpty)
54+
f.isTasty || (f.isClass && !f.hasSiblingTasty)
5555
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory {
5353
override protected def createFileEntry(file: FileZipArchive#Entry): BinaryFileEntry = BinaryFileEntry(file)
5454

5555
override protected def isRequiredFileType(file: AbstractFile): Boolean =
56-
file.isTasty || (file.isClass && file.classToTasty.isEmpty)
56+
file.isTasty || (file.isClass && !file.hasSiblingTasty)
5757
}
5858

5959
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ object Settings:
162162
else setString(arg2, args2)
163163
case (OutputTag, arg :: args) =>
164164
val path = Directory(arg)
165-
val isJar = path.extension == "jar"
165+
val isJar = path.ext.isJar
166166
if (!isJar && !path.isDirectory)
167167
fail(s"'$arg' does not exist or is not a directory or .jar file", args)
168168
else {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import java.nio.channels.ClosedByInterruptException
77

88
import scala.util.control.NonFatal
99

10-
import dotty.tools.dotc.classpath.FileUtils.isTasty
10+
import dotty.tools.dotc.classpath.FileUtils.hasTastyExtension
1111
import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile }
1212
import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions
1313

@@ -198,7 +198,7 @@ object SymbolLoaders {
198198
enterToplevelsFromSource(owner, nameOf(classRep), src)
199199
case (Some(bin), _) =>
200200
val completer =
201-
if bin.isTasty then ctx.platform.newTastyLoader(bin)
201+
if bin.hasTastyExtension then ctx.platform.newTastyLoader(bin)
202202
else ctx.platform.newClassLoader(bin)
203203
enterClassAndModule(owner, nameOf(classRep), completer)
204204
}

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import scala.annotation.switch
2323
import typer.Checking.checkNonCyclic
2424
import io.{AbstractFile, ZipArchive}
2525
import scala.util.control.NonFatal
26-
import dotty.tools.dotc.classpath.FileUtils.classToTasty
26+
import dotty.tools.dotc.classpath.FileUtils.hasSiblingTasty
2727

2828
import scala.compiletime.uninitialized
2929

@@ -1143,7 +1143,7 @@ class ClassfileParser(
11431143

11441144
if (scan(tpnme.TASTYATTR)) {
11451145
val hint =
1146-
if classfile.classToTasty.isDefined then "This is likely a bug in the compiler. Please report."
1146+
if classfile.hasSiblingTasty then "This is likely a bug in the compiler. Please report."
11471147
else "This `.tasty` file is missing. Try cleaning the project to fix this issue."
11481148
report.error(s"Loading Scala 3 binary from $classfile. It should have been loaded from `.tasty` file. $hint", NoSourcePosition)
11491149
return None

compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import dotty.tools.tasty.TastyFormat.header
1616

1717
import scala.compiletime.uninitialized
1818
import dotty.tools.tasty.TastyBuffer.Addr
19+
import dotty.tools.dotc.classpath.FileUtils.hasTastyExtension
1920

2021
object TastyPrinter:
2122

@@ -51,7 +52,7 @@ object TastyPrinter:
5152
else if arg.endsWith(".jar") then
5253
val jar = JarArchive.open(Path(arg), create = false)
5354
try
54-
for file <- jar.iterator() if file.name.endsWith(".tasty") do
55+
for file <- jar.iterator() if file.hasTastyExtension do
5556
printTasty(s"$arg ${file.path}", file.toByteArray)
5657
finally jar.close()
5758
else

compiler/src/dotty/tools/dotc/fromtasty/Debug.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ object Debug {
4040

4141
val tastyFiles =
4242
Directory(fromSourcesOut).walk
43-
.filter(x => x.isFile && "tasty".equalsIgnoreCase(x.extension))
43+
.filter(x => x.isFile && x.ext.isTasty)
4444
.map(_.toString)
4545
.toList
4646

compiler/src/dotty/tools/dotc/fromtasty/TASTYRun.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package fromtasty
44

55
import scala.language.unsafeNulls
66

7-
import io.{JarArchive, AbstractFile, Path}
7+
import io.{JarArchive, AbstractFile, Path, FileExtension}
88
import core.Contexts.*
99
import core.Decorators.em
1010
import java.io.File
@@ -19,14 +19,14 @@ class TASTYRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) {
1919
val fromTastyIgnoreList = ctx.settings.YfromTastyIgnoreList.value.toSet
2020
// Resolve class names of tasty and jar files
2121
val classNames = files.flatMap { file =>
22-
file.extension match
23-
case "jar" =>
22+
file.ext match
23+
case FileExtension.Jar =>
2424
JarArchive.open(Path(file.path), create = false).allFileNames()
2525
.map(_.stripPrefix(File.separator)) // change paths from absolute to relative
26-
.filter(e => Path.extension(e) == "tasty" && !fromTastyIgnoreList(e))
26+
.filter(e => Path.fileExtension(e).isTasty && !fromTastyIgnoreList(e))
2727
.map(e => e.stripSuffix(".tasty").replace(File.separator, "."))
2828
.toList
29-
case "tasty" => TastyFileUtil.getClassName(file)
29+
case FileExtension.Tasty => TastyFileUtil.getClassName(file)
3030
case _ =>
3131
report.error(em"File extension is not `tasty` or `jar`: ${file.path}")
3232
Nil

compiler/src/dotty/tools/dotc/fromtasty/TastyFileUtil.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import scala.language.unsafeNulls
66
import dotty.tools.dotc.core.tasty.TastyClassName
77
import dotty.tools.dotc.core.StdNames.nme.EMPTY_PACKAGE
88
import dotty.tools.io.AbstractFile
9+
import dotty.tools.dotc.classpath.FileUtils.hasTastyExtension
910

1011
object TastyFileUtil {
1112
/** Get the class path of a tasty file
@@ -34,7 +35,7 @@ object TastyFileUtil {
3435
*/
3536
def getClassName(file: AbstractFile): Option[String] = {
3637
assert(file.exists)
37-
assert(file.extension == "tasty")
38+
assert(file.hasTastyExtension)
3839
val bytes = file.toByteArray
3940
val names = new TastyClassName(bytes).readName()
4041
names.map { case (packageName, className) =>

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import Names.*
1818
import NameOps.*
1919
import inlines.Inlines
2020
import transform.ValueClasses
21-
import dotty.tools.io.File
21+
import dotty.tools.io.{File, FileExtension}
2222
import java.io.PrintWriter
2323

2424

@@ -76,7 +76,7 @@ class ExtractAPI extends Phase {
7676

7777
if (ctx.settings.YdumpSbtInc.value) {
7878
// Append to existing file that should have been created by ExtractDependencies
79-
val pw = new PrintWriter(File(sourceFile.file.jpath).changeExtension("inc").toFile
79+
val pw = new PrintWriter(File(sourceFile.file.jpath).changeExtension(FileExtension.Inc).toFile
8080
.bufferedWriter(append = true), true)
8181
try {
8282
classes.foreach(source => pw.println(DefaultShowAPI(source)))

compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import java.nio.file.Path
88
import java.util.{Arrays, EnumSet}
99

1010
import dotty.tools.dotc.ast.tpd
11-
import dotty.tools.dotc.classpath.FileUtils.{isTasty, hasClassExtension, hasTastyExtension}
11+
import dotty.tools.dotc.classpath.FileUtils.{hasClassExtension, hasTastyExtension}
1212
import dotty.tools.dotc.core.Contexts.*
1313
import dotty.tools.dotc.core.Decorators.*
1414
import dotty.tools.dotc.core.Flags.*
@@ -21,7 +21,7 @@ import dotty.tools.dotc.core.Types.*
2121

2222
import dotty.tools.dotc.util.{SrcPos, NoSourcePosition}
2323
import dotty.tools.io
24-
import dotty.tools.io.{AbstractFile, PlainFile, ZipArchive, NoAbstractFile}
24+
import dotty.tools.io.{AbstractFile, PlainFile, ZipArchive, NoAbstractFile, FileExtension}
2525
import xsbti.UseScope
2626
import xsbti.api.DependencyContext
2727
import xsbti.api.DependencyContext.*
@@ -84,7 +84,7 @@ class ExtractDependencies extends Phase {
8484
Arrays.sort(deps)
8585
Arrays.sort(names)
8686

87-
val pw = io.File(unit.source.file.jpath).changeExtension("inc").toFile.printWriter()
87+
val pw = io.File(unit.source.file.jpath).changeExtension(FileExtension.Inc).toFile.printWriter()
8888
// val pw = Console.out
8989
try {
9090
pw.println("Used Names:")
@@ -495,7 +495,7 @@ class DependencyRecorder {
495495
if depFile != null then {
496496
// Cannot ignore inheritance relationship coming from the same source (see sbt/zinc#417)
497497
def allowLocal = depCtx == DependencyByInheritance || depCtx == LocalDependencyByInheritance
498-
val isTasty = depFile.hasTastyExtension
498+
val isTastyOrSig = depFile.hasTastyExtension
499499

500500
def processExternalDependency() = {
501501
val binaryClassName = depClass.binaryClassName
@@ -506,13 +506,13 @@ class DependencyRecorder {
506506
binaryDependency(zip.jpath, binaryClassName)
507507
case _ =>
508508
case pf: PlainFile => // The dependency comes from a class file, Zinc handles JRT filesystem
509-
binaryDependency(if isTasty then cachedSiblingClass(pf) else pf.jpath, binaryClassName)
509+
binaryDependency(if isTastyOrSig then cachedSiblingClass(pf) else pf.jpath, binaryClassName)
510510
case _ =>
511511
internalError(s"Ignoring dependency $depFile of unknown class ${depFile.getClass}}", fromClass.srcPos)
512512
}
513513
}
514514

515-
if isTasty || depFile.hasClassExtension then
515+
if isTastyOrSig || depFile.hasClassExtension then
516516
processExternalDependency()
517517
else if allowLocal || depFile != sourceFile.file then
518518
// We cannot ignore dependencies coming from the same source file because
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package dotty.tools.dotc.util
2+
3+
object EnumFlags:
4+
5+
opaque type FlagSet[E <: reflect.Enum] = Int
6+
7+
object FlagSet:
8+
9+
extension [E <: reflect.Enum](set: FlagSet[E])
10+
def is(flag: E): Boolean = (set & (1 << flag.ordinal)) != 0
11+
def |(flag: E): FlagSet[E] = (set | (1 << flag.ordinal))
12+
13+
def empty[E <: reflect.Enum]: FlagSet[E] =
14+
0

compiler/src/dotty/tools/io/AbstractFile.scala

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,16 @@ abstract class AbstractFile extends Iterable[AbstractFile] {
9797
/** Returns the path of this abstract file in a canonical form. */
9898
def canonicalPath: String = if (jpath == null) path else jpath.normalize.toString
9999

100-
/** Checks extension case insensitively. TODO: change to enum */
101-
def hasExtension(other: String): Boolean = extension == other.toLowerCase
100+
/** Checks extension case insensitively. */
101+
@deprecated("prefer queries on ext")
102+
def hasExtension(other: String): Boolean = ext.toLowerCase.equalsIgnoreCase(other)
102103

103-
/** Returns the extension of this abstract file. TODO: store as an enum to avoid costly comparisons */
104-
val extension: String = Path.extension(name)
104+
/** Returns the extension of this abstract file. */
105+
val ext: FileExtension = Path.fileExtension(name)
106+
107+
/** Returns the extension of this abstract file as a String. */
108+
@deprecated("use ext instead.")
109+
def extension: String = ext.toLowerCase
105110

106111
/** The absolute file, if this is a relative file. */
107112
def absolute: AbstractFile
@@ -129,7 +134,7 @@ abstract class AbstractFile extends Iterable[AbstractFile] {
129134
}
130135

131136
/** Does this abstract file represent something which can contain classfiles? */
132-
def isClassContainer: Boolean = isDirectory || (jpath != null && (extension == "jar" || extension == "zip"))
137+
def isClassContainer: Boolean = isDirectory || (jpath != null && ext.isJarOrZip)
133138

134139
/** Create a file on disk, if one does not exist already. */
135140
def create(): Unit
@@ -258,8 +263,8 @@ abstract class AbstractFile extends Iterable[AbstractFile] {
258263
final def resolveSibling(name: String): AbstractFile | Null =
259264
container.lookupName(name, directory = false)
260265

261-
final def resolveSiblingWithExtension(extension: String): AbstractFile | Null =
262-
resolveSibling(name.stripSuffix(this.extension) + extension)
266+
final def resolveSiblingWithExtension(extension: FileExtension): AbstractFile | Null =
267+
resolveSibling(Path.fileName(name) + "." + extension)
263268

264269
private def fileOrSubdirectoryNamed(name: String, isDir: Boolean): AbstractFile =
265270
lookupName(name, isDir) match {

0 commit comments

Comments
 (0)