Skip to content

Instantly share code, notes, and snippets.

@bishabosha
Last active November 1, 2023 10:42
Show Gist options
  • Save bishabosha/dfaf3ff55a1a6c6fb39f57927e1236d0 to your computer and use it in GitHub Desktop.
Save bishabosha/dfaf3ff55a1a6c6fb39f57927e1236d0 to your computer and use it in GitHub Desktop.
tasty-query classpath from dotty classpath
//> using toolkit 0.2.1
//> using scala 3.3.1
//> using dep ch.epfl.scala::tasty-query:0.11.0
//> using dep org.scala-lang::scala3-compiler:3.3.1
//> using dep io.get-coursier:coursier_2.13:2.1.7
package tastyquery.jdk.classpaths
import tastyquery.Classpaths.Classpath
import tastyquery.Classpaths.ClasspathEntry
import tastyquery.Classpaths.PackageData
import tastyquery.Classpaths.ClassData
import tastyquery.Contexts.Context
import tastyquery.Contexts.ctx
import dotty.tools.io.ClassPath as DottyClassPath
import dotty.tools.dotc.core.Contexts.ContextBase
import dotty.tools.dotc.core.Contexts.Context as DottyContext
import dotty.tools.dotc.core.Contexts.inContext
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
import coursier.*
import dotty.tools.dotc.classpath.ClassFileEntry
import LazyClasspathSuite.*
object LazyClasspathSuite:
val classpathEntries = Fetch()
.addDependencies(
Dependency(
Module(Organization("org.scala-lang"), ModuleName("scala3-compiler_3")),
"3.3.1"
)
)
.run()
.map(_.toString)
enum ForceEvent:
case Package(name: String)
case Class(name: String)
case Tasty(name: String)
class DotcEntry(debugName: String, cp: DottyClassPath, callback: ForceEvent => Unit) extends ClasspathEntry:
override def toString(): String = debugName
lazy val _packages: List[DotcPackageData] =
def loadPackage(pkg: PackageEntry): DotcPackageData =
val name = pkg.name
DotcPackageData(s"$debugName:$name", name, cp, callback)
def loadSubPackages(name: String): List[DotcPackageData] =
cp.packages(name).toList.flatMap(pkg => loadPackage(pkg) :: loadSubPackages(pkg.name))
loadSubPackages("")
override def listAllPackages(): List[DotcPackageData] = _packages
class DotcPackageData(val debugName: String, override val dotSeparatedName: String, cp: DottyClassPath, callback: ForceEvent => Unit) extends PackageData:
override def toString(): String = debugName
def loadClasses(): List[DotcClassData] =
cp.classes(dotSeparatedName).toList.map(cls =>
DotcClassData(s"$debugName:$dotSeparatedName.${cls.name}", cls, callback)
)
lazy val _classes =
try loadClasses()
finally callback(ForceEvent.Package(debugName))
lazy val _byName = _classes.map(cls => cls.binaryName -> cls).toMap
override def listAllClassDatas(): List[DotcClassData] = _classes
override def getClassDataByBinaryName(binaryName: String): Option[DotcClassData] = _byName.get(binaryName)
class DotcClassData(val debugName: String, entry: ClassFileEntry, callback: ForceEvent => Unit) extends ClassData {
override def toString(): String = debugName
override val binaryName: String = entry.name
private def sibling(cls: AbstractFile): Option[AbstractFile] =
val dir = cls match
case cls: FileZipArchive#Entry => cls.parent
case cls: AbstractFile => cls.container
val name = cls.name.stripSuffix(".class") + ".tasty"
Option(dir.lookupName(name, directory = false))
lazy val cls = entry.binary
lazy val tsty = cls.flatMap(sibling)
override def readClassFileBytes(): IArray[Byte] =
try IArray.unsafeFromArray(cls.get.toByteArray)
finally callback(ForceEvent.Class(debugName))
override def hasClassFile: Boolean = cls.exists(_.exists)
override def readTastyFileBytes(): IArray[Byte] =
try IArray.unsafeFromArray(tsty.get.toByteArray)
finally callback(ForceEvent.Tasty(debugName))
override def hasTastyFile: Boolean = tsty.exists(_.exists)
}
class LazyClasspathSuite extends munit.FunSuite:
def initialClasspath: DottyClassPath =
val ictx = contextFromClasspath(LazyClasspathSuite.classpathEntries)
ictx.base.platform.classPath(using ictx)
def contextFromClasspath(entries: Seq[String]): DottyContext =
val cp = entries.mkString(java.io.File.pathSeparator.nn)
val ictx = ContextBase().initialCtx.fresh
val summary = ScalacCommand.distill(Array("-classpath", cp), ictx.settings)(ictx.settingsState)(using ictx)
ictx.setSettings(summary.sstate)
val _ = Compiler().newRun(using ictx) // side effect: initializes the classpath
ictx
def makeClasspath(callback: ForceEvent => Unit): Classpath =
def flattenClasspath(cp: DottyClassPath): List[DottyClassPath] = cp match
case ag: AggregateClassPath => ag.aggregates.flatMap(flattenClasspath).toList
case _ => List(cp)
flattenClasspath(initialClasspath).map(cp => DotcEntry(cp.asClassPathString, cp, callback))
test("load lazy from dotty classpath") {
val events = collection.mutable.ListBuffer.empty[ForceEvent]
val classpath = makeClasspath: event =>
println(s"received event: $event")
events += event
given Context = Context.initialize(classpath)
// scala 2 classes
val TryClass = ctx.findTopLevelClass("scala.util.Try")
val SuccessClass = ctx.findTopLevelClass("scala.util.Success")
assert(SuccessClass.isSubClass(TryClass), clue(events.toList))
// scala 3 classes
val TupleClass = ctx.findTopLevelClass("scala.Tuple")
val ConsClass = ctx.findTopLevelClass("scala.*:")
assert(ConsClass.isSubClass(TupleClass), clue(events.toList))
// java classes
val ListClass = ctx.findTopLevelClass("java.util.List")
val ArrayListClass = ctx.findTopLevelClass("java.util.ArrayList")
assert(ArrayListClass.isSubClass(ListClass), clue(events.toList))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment