Skip to content
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
19 changes: 19 additions & 0 deletions benchmark/PerformanceOfSemanticDBGenerationUsingTastyQuery.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Metric,File,Trial 1,Trial 2,Trial 3,Trial 4,Trial 5,Trial 6,Trial 7,Trial 8,Trial 9,Trial 10,Mean,Error Margin (95% Confidence),Standard Deviation,Confidence Interval (95%)
Elapsed Time Creating Ctx (millisecond),VariousFeatures.scala,89,85,84,80,89,92,81,84,85,83,85.2,2.21,3.57,"[82.99, 87.41]"
Elapsed Time Creating Ctx (millisecond),LargeClass.scala,103,90,91,96,94,97,94,98,91,91,94.5,2.40,3.88,"[92.1, 96.9]"
Elapsed Time Creating Ctx (millisecond),SimpleClass.scala,88,84,83,83,83,84,106,91,88,93,88.3,4.22,6.81,"[84.08, 92.52]"
Elapsed Time Symbol List (millisecond),VariousFeatures.scala,453,434,423,415,462,443,444,484,472,492,452.2,14.93,24.09,"[437.27, 467.13]"
Elapsed Time Symbol List (millisecond),LargeClass.scala,452,457,450,414,441,497,449,430,437,428,445.5,13.13,21.19,"[432.37, 458.63]"
Elapsed Time Symbol List (millisecond),SimpleClass.scala,417,412,407,398,425,421,429,446,447,431,423.3,9.30,15.00,"[414., 432.6]"
Elapsed Time Creating TastyExtractSemanticDB (millisecond),VariousFeatures.scala,544,521,508,496,552,536,526,569,560,577,538.9,15.55,25.08,"[523.35, 554.45]"
Elapsed Time Creating TastyExtractSemanticDB (millisecond),LargeClass.scala,557,549,543,511,537,595,544,530,530,520,541.6,13.64,22.01,"[527.96, 555.24]"
Elapsed Time Creating TastyExtractSemanticDB (millisecond),SimpleClass.scala,506,497,491,482,509,507,537,538,536,525,512.8,11.88,19.17,"[500.92, 524.68]"
Elapsed Time writeSemanticDB (millisecond),VariousFeatures.scala,189,183,169,171,178,179,172,175,175,175,176.6,3.51,5.66,"[173.09, 180.11]"
Elapsed Time writeSemanticDB (millisecond),LargeClass.scala,319,316,337,339,328,325,333,336,345,329,330.7,5.34,8.61,"[325.36, 336.04]"
Elapsed Time writeSemanticDB (millisecond),SimpleClass.scala,102,105,99,103,108,121,108,107,101,106,106,3.58,5.78,"[102.42, 109.58]"
New Time (millisecond),VariousFeatures.scala,744,715,687,675,742,728,708,754,744,762,725.9,17.04,27.49,"[708.86, 742.94]"
New Time (millisecond),LargeClass.scala,888,878,889,863,875,932,889,876,888,862,884,11.58,18.69,"[872.42, 895.58]"
New Time (millisecond),SimpleClass.scala,618,612,599,595,626,636,657,656,646,644,628.9,13.22,21.33,"[615.68, 642.12]"
Reference Time (millisecond),VariousFeatures.scala,89,93,80,81,78,102,91,91,92,88,88.5,4.23,6.83,"[84.27, 92.73]"
Reference Time (millisecond),SimpleClass.scala,86,90,92,82,93,84,84,97,81,90,87.9,3.10,5.01,"[84.8, 91.]"
Reference Time (millisecond),LargeClass.scala,167,165,163,166,168,181,167,169,161,173,168,3.30,5.33,"[164.7, 171.3]"
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -506,5 +506,7 @@ private sealed trait YSettings:
// Deprecated: Scheduled for removal
@deprecated(message = "Scheduled for removal.", since = "3.5.0")
val YoutputOnlyTasty: Setting[Boolean] = BooleanSetting(ForkSetting, "Youtput-only-tasty", "Used to only generate the TASTy file without the classfiles", deprecation = Deprecation.removed())
val YproduceSemanticdbUsingTasty: Setting[Boolean] = BooleanSetting(ForkSetting, "Ysdb-using-tasty", "Produce the SemanticDB using Tasty")

end YSettings

137 changes: 137 additions & 0 deletions compiler/src/dotty/tools/dotc/sbt/LazyTastyQueryClasspath.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
Adapted from:
https://github.com/scalacenter/tasty-query/pull/371
https://github.com/scalacenter/scala-debug-adapter/blob/main/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/CustomClasspath.scala
*/

package dotty.tools.dotc
package sbt

import tastyquery.Classpaths as tqcp
import tastyquery.Classpaths.{Classpath as TQClasspath, ClasspathEntry as TQClasspathEntry,
PackageData as TQPackageData, ClassData as TQClassData}
import tastyquery.Contexts as tqctxs
import tastyquery.Contexts.{Context as TQContext, ctx as tqctx}

import dotty.tools.io.ClassPath
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.ScalacCommand
import dotty.tools.io.AbstractFile
import dotty.tools.dotc.classpath.PackageEntry
import dotty.tools.dotc.Compiler
import dotty.tools.dotc.classpath.AggregateClassPath
import dotty.tools.io.FileZipArchive

object LazyTastyQueryClasspath:
class DotcEntry(debugName: String, cp: ClassPath) extends TQClasspathEntry:
override def toString(): String = debugName

def replaceSuffix(cls: AbstractFile, oldSuffix: String, newSuffix: String): Option[AbstractFile] =
val dir = cls match
case cls: FileZipArchive#Entry => cls.parent
case cls: AbstractFile => cls.container
val name = cls.name.stripSuffix(oldSuffix) + newSuffix
Option(dir.lookupName(name, directory = false))

lazy val _packages: List[DotcPackageData] =
def loadClasses(pkg: PackageEntry): List[DotcClassData] =
cp.classes(pkg.name).toList.map(cls =>
val binary = cls.binary
val suffix = binary.flatMap(_.name.split('.').lastOption)
val (classRaw, tastyRaw) = suffix match
case Some("class") =>
(binary, binary.flatMap(replaceSuffix(_, ".class", ".tasty")))
case Some("tasty") =>
(binary.flatMap(replaceSuffix(_, ".tasty", ".class")), binary)
case _ =>
(None, None)

DotcClassData(s"$debugName:${pkg.name}.${cls.name}", cls.name, classRaw, tastyRaw)
)

def loadPackage(pkg: PackageEntry): DotcPackageData =
val name = pkg.name
DotcPackageData(s"$debugName:$name", name, () => loadClasses(pkg))
def loadSubPackages(name: String): List[DotcPackageData] =
cp.packages(name).toList.flatMap(pkg => loadPackage(pkg) :: loadSubPackages(pkg.name))
loadSubPackages("")

override def listAllPackages(): List[DotcPackageData] = _packages
end DotcEntry

class DotcPackageData(val debugName: String, override val dotSeparatedName: String, fetchClasses: () => List[DotcClassData]) extends TQPackageData:
override def toString(): String = debugName

private lazy val _classes: List[DotcClassData] =
fetchClasses()

private lazy val _byName: Map[String, DotcClassData] = _classes.map(cls => cls.binaryName -> cls).toMap

override def listAllClassDatas(): List[DotcClassData] = _classes

override def getClassDataByBinaryName(binaryName: String): Option[DotcClassData] = _byName.get(binaryName)
end DotcPackageData


def patchBytes(bytes: Array[Byte]): IArray[Byte] =
bytes(4) = (28 | 0x80).toByte // major version
bytes(5) = (5 | 0x80).toByte // minor version
bytes(6) = (0 | 0x80).toByte // experimental
IArray.unsafeFromArray(bytes)

class DotcClassData(val debugName: String, override val binaryName: String, cls: Option[AbstractFile], tsty: Option[AbstractFile]) extends TQClassData:
override def toString(): String = debugName

private lazy val _tastyFileBytesPatched: IArray[Byte] = patchBytes(tsty.get.toByteArray)

override def readClassFileBytes(): IArray[Byte] =
IArray.unsafeFromArray(cls.get.toByteArray)

override def hasClassFile: Boolean = cls.exists(_.exists)

override def readTastyFileBytes(): IArray[Byte] =
_tastyFileBytesPatched

override def hasTastyFile: Boolean = tsty.exists(_.exists)
end DotcClassData

class InMemoryEntry(debugName: String, packages: List[InMemoryPackageData]) extends TQClasspathEntry:
override def toString(): String = debugName

override def listAllPackages(): List[InMemoryPackageData] = packages
end InMemoryEntry

class InMemoryPackageData(val debugName: String, override val dotSeparatedName: String, fetchClasses: () => List[InMemoryTasty]) extends TQPackageData:
override def toString(): String = debugName

private lazy val _classes: List[InMemoryTasty] = fetchClasses()

private lazy val _byName: Map[String, InMemoryTasty] = _classes.map(cls => cls.binaryName -> cls).toMap

override def listAllClassDatas(): List[InMemoryTasty] = _classes

override def getClassDataByBinaryName(binaryName: String): Option[InMemoryTasty] = _byName.get(binaryName)
end InMemoryPackageData

class InMemoryTasty(val debugName: String, override val binaryName: String, val tsty: () => Array[Byte]) extends TQClassData:
override def toString(): String = debugName

lazy val _tastyFileBytesPatched: IArray[Byte] = patchBytes(tsty().clone())

override def readClassFileBytes(): IArray[Byte] = IArray.empty

override def hasClassFile: Boolean = false

override def hasTastyFile: Boolean = true

override def readTastyFileBytes(): IArray[Byte] = _tastyFileBytesPatched
end InMemoryTasty


def makeClasspath(using Context): TQClasspath =
def flattenClasspath(cp: ClassPath): List[ClassPath] = cp match
case ag: AggregateClassPath => ag.aggregates.flatMap(flattenClasspath).toList
case _ => List(cp)
flattenClasspath(ctx.base.platform.classPath).map(cp => DotcEntry(cp.asClassPathString, cp))

end LazyTastyQueryClasspath
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package dotty.tools.dotc

import tastyquery.Contexts.*
import tastyquery.Symbols.*
import tastyquery.Types.*
import tastyquery.Names.*
import tastyquery.Modifiers.*
import tastyquery.Trees.*

import scala.annotation.tailrec
import tastyquery.Names
import dotty.tools.dotc.core.SourceLanguage
import dotty.tools.dotc.core.StdNames.str

extension (name: TermName) def isPackageObjectName: Boolean = name match
case SimpleName(name) => name == "package" || name.endsWith("package$")
case _ => false

extension (name: TypeName) def isPackageObjectClassName: Boolean = name match
case ObjectClassTypeName(underlying) => underlying.toTermName.isPackageObjectName
case _ => false

extension (sym: dotty.tools.dotc.semanticdb.Scala3.TastyFakeSymbol){
def SDBFakeSymName (using builder: SDBSymbolNameBuilder)(using Context): String =
builder.symbolName(sym)
}
object Extensions:
extension (name: Name)(using Context)
def toTermName: TermName = name match
case name: TypeName => name.toTermName
case name: TermName => name
end toTermName

def toTypeName: TypeName = name match
case name: TypeName => name
case name: TermName => name.toTypeName
end toTypeName

end extension

extension (sym: Symbol)(using Context)
def SDBname (using builder: SDBSymbolNameBuilder): String =
builder.symbolName(sym)

/** Is this symbol the root class or its companion object? */
def isRoot: Boolean =
(sym.owner == null) && sym.name.toTermName == Names.nme.RootName || sym.name == Names.nme.UserLandRootPackageName

/** Is this symbol the empty package class or its companion object? */
def isEmptyPackage: Boolean =
val owner = sym.owner
sym.name.toTermName == Names.nme.EmptyPackageName && owner != null && owner.isRoot
end isEmptyPackage

/** Is this symbol the empty package class or its companion object? */
def isEffectiveRoot: Boolean = sym.isRoot || sym.isEmptyPackage

def isConstructor: Boolean =
sym.name == nme.Constructor
end isConstructor
def isPrimary: Boolean =
sym.owner match
case owner: ClassSymbol => owner.tree.get.rhs.constr.symbol == sym
case _ => false
def isTopLevel: Boolean =
val owner = sym.owner
owner != null && owner.isPackage

/** Is this symbol directly owner by a term symbol, i.e., is it local to a block? */
def isLocalToBlock: Boolean =
val owner = sym.owner
owner != null && owner.isTerm

/** Is symbol directly or indirectly owned by a term symbol? */
@tailrec final def isLocal: Boolean = {
val owner = sym.owner
if (owner == null) false
else if (isLocalToBlock) true
else if (owner.isPackage) false
else owner.isLocal
}

private inline def predicateAs[T <: Symbol](inline p: T => Boolean): Boolean = (sym: @unchecked) match
case sym: T => p(sym)
case _ => false
end predicateAs

// `ClassSymbol` predicates
def isTrait: Boolean =
predicateAs[ClassSymbol](_.isTrait)
end isTrait

def isAbstractClass: Boolean =
predicateAs[ClassSymbol](_.isAbstractClass)
end isAbstractClass

private inline def hasOpenLevel(inline level: OpenLevel): Boolean =
predicateAs[ClassSymbol](_.openLevel == level)
end hasOpenLevel

def isFinal: Boolean = sym match
case sym: ClassSymbol => sym.openLevel == OpenLevel.Final
case sym: TermOrTypeSymbol => sym.isFinalMember
case _ => false
end isFinal

def isSealed: Boolean =
hasOpenLevel(OpenLevel.Sealed)
end isSealed

def isPackageClass: Boolean =
sym.isPackage
end isPackageClass

// `TermSymbol` predicates

def isAbstractOverride: Boolean =
predicateAs[TermSymbol](_.isAbstractOverride)
end isAbstractOverride

def isAbstractMember: Boolean =
predicateAs[TermSymbol](_.isAbstractMember)
end isAbstractMember

def isCovariant: Boolean =
predicateAs[ClassTypeParamSymbol](_.variance == Variance.Covariant)
end isCovariant
def isContravariant: Boolean =
predicateAs[ClassTypeParamSymbol](_.variance == Variance.Contravariant)
end isContravariant

def isEnum: Boolean =
predicateAs[ClassSymbol](_.isEnum)
end isEnum

def isGivenOrUsing: Boolean =
predicateAs[TermSymbol](_.isGivenOrUsing)
end isGivenOrUsing

def isJavaDefined: Boolean =
predicateAs[TermOrTypeSymbol](_.sourceLanguage == SourceLanguage.Java)
end isJavaDefined

def isImplicit: Boolean =
predicateAs[TermSymbol](_.isImplicit)
end isImplicit

private inline def hasKind(kind: TermSymbolKind): Boolean =
predicateAs[TermSymbol](_.kind == kind)
end hasKind

def isLazyVal: Boolean =
hasKind(TermSymbolKind.LazyVal)
end isLazyVal

def isMacro: Boolean =
predicateAs[TermSymbol](_.isMacro)
end isMacro
def isStableMember: Boolean =
predicateAs[TermSymbol](_.isStableMember)
end isStableMember
def isInline: Boolean =
predicateAs[TermSymbol](_.isInline)
end isInline

def isMethod: Boolean =
predicateAs[TermSymbol](_.isMethod)
end isMethod

def isVar: Boolean =
predicateAs[TermSymbol](_.kind == TermSymbolKind.Var)
end isVar

def isModuleVal: Boolean =
predicateAs[TermSymbol](_.isModuleVal)
end isModuleVal


def isParamWithDefault: Boolean =
predicateAs[TermSymbol](_.isParamWithDefault)
end isParamWithDefault

def isParamAccessor: Boolean =
predicateAs[TermSymbol](_.isParamAccessor)
end isParamAccessor
def isGlobal: Boolean =
sym.isPackage || ((sym.isInstanceOf[TermOrTypeSymbol] && sym.asInstanceOf[TermOrTypeSymbol].isParam) || (sym.owner!= null && (sym.owner.nn.isClass || sym.owner.isPackage)))
&& (sym.owner!= null && sym.owner.nn.isGlobal)
end isGlobal
end extension

extension (sym: TermOrTypeSymbol)
def isParam: Boolean =
sym.owner.isTerm && sym.owner.asTerm.isTerm
&& sym.owner.asTerm.paramSymss.exists { p =>
p match
case Left(termParams) => termParams.contains(sym)
case Right(typeParams) => typeParams.contains(sym)
}

end Extensions
Loading