Skip to content

Commit 8cdabe4

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 183487b commit 8cdabe4

File tree

29 files changed

+207
-86
lines changed

29 files changed

+207
-86
lines changed

Diff for: compiler/src/dotty/tools/dotc/CompilationUnit.scala

+8-5
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 */

Diff for: compiler/src/dotty/tools/dotc/Driver.scala

+4-4
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
@@ -98,9 +98,9 @@ class Driver {
9898
if !file.exists then
9999
report.error(em"File does not exist: ${file.path}")
100100
None
101-
else file.extension match
102-
case "jar" => Some(file.path)
103-
case "tasty" =>
101+
else file.ext match
102+
case FileExtension.Jar => Some(file.path)
103+
case FileExtension.Tasty =>
104104
TastyFileUtil.getClassPath(file) match
105105
case Some(classpath) => Some(classpath)
106106
case _ =>

Diff for: compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[ClassFile
284284

285285
protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
286286
protected def isMatchingFile(f: JFile): Boolean =
287-
f.isTasty || (f.isClass && f.classToTasty.isEmpty)
287+
f.isTasty || (f.isClass && !f.hasSiblingTasty)
288288

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

Diff for: compiler/src/dotty/tools/dotc/classpath/FileUtils.scala

+22-19
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

Diff for: compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,5 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi
5050

5151
protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
5252
protected def isMatchingFile(f: AbstractFile): Boolean =
53-
f.isTasty || (f.isClass && f.classToTasty.isEmpty)
53+
f.isTasty || (f.isClass && !f.hasSiblingTasty)
5454
}

Diff for: compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory {
5252

5353
override protected def createFileEntry(file: FileZipArchive#Entry): ClassFileEntryImpl = ClassFileEntryImpl(file)
5454
override protected def isRequiredFileType(file: AbstractFile): Boolean =
55-
file.isTasty || (file.isClass && file.classToTasty.isEmpty)
55+
file.isTasty || (file.isClass && !file.hasSiblingTasty)
5656
}
5757

5858
/**

Diff for: compiler/src/dotty/tools/dotc/config/Settings.scala

+1-1
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 {

Diff for: compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala

+2-2
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
}

Diff for: compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

+2-2
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

@@ -1142,7 +1142,7 @@ class ClassfileParser(
11421142

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

Diff for: compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import util.Spans.offsetToInt
1212
import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection, AttributesSection}
1313
import java.nio.file.{Files, Paths}
1414
import dotty.tools.io.{JarArchive, Path}
15+
import dotty.tools.dotc.classpath.FileUtils.hasTastyExtension
1516

1617
object TastyPrinter:
1718

@@ -47,7 +48,7 @@ object TastyPrinter:
4748
else if arg.endsWith(".jar") then
4849
val jar = JarArchive.open(Path(arg), create = false)
4950
try
50-
for file <- jar.iterator() if file.name.endsWith(".tasty") do
51+
for file <- jar.iterator() if file.hasTastyExtension do
5152
printTasty(s"$arg ${file.path}", file.toByteArray)
5253
finally jar.close()
5354
else

Diff for: compiler/src/dotty/tools/dotc/fromtasty/Debug.scala

+1-1
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

Diff for: compiler/src/dotty/tools/dotc/fromtasty/TASTYRun.scala

+5-5
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

Diff for: compiler/src/dotty/tools/dotc/fromtasty/TastyFileUtil.scala

+2-1
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) =>

Diff for: compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

+2-2
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)))

Diff for: compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala

+6-6
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

Diff for: compiler/src/dotty/tools/dotc/util/EnumFlags.scala

+14
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

Diff for: compiler/src/dotty/tools/io/AbstractFile.scala

+10-5
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

Diff for: compiler/src/dotty/tools/io/File.scala

-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ object File {
3939
*/
4040
class File(jpath: JPath)(implicit constructorCodec: Codec) extends Path(jpath) with Streamable.Chars {
4141
override val creationCodec: io.Codec = constructorCodec
42-
43-
override def addExtension(ext: String): File = super.addExtension(ext).toFile
4442
override def toAbsolute: File = if (isAbsolute) this else super.toAbsolute.toFile
4543
override def toDirectory: Directory = new Directory(jpath)
4644
override def toFile: File = this

0 commit comments

Comments
 (0)