Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove OldTastyInspector from scaladoc #17623

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ case class Module(rootPackage: Member, members: Map[DRI, Member])

object ScalaModuleProvider:
def mkModule()(using ctx: DocContext): Module =
val (result, rootDoc) = ScaladocTastyInspector().result()
val (result, rootDoc) = ScaladocTastyInspector.loadDocs()
val (rootPck, rest) = result.partition(_.name == "API")
val (emptyPackages, nonemptyPackages) = (rest ++ rootPck.flatMap(_.members))
.filter(p => p.members.nonEmpty || p.docs.nonEmpty).sortBy(_.name)
Expand Down
65 changes: 34 additions & 31 deletions scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package tasty
import java.util.regex.Pattern

import scala.util.{Try, Success, Failure}
import scala.tasty.inspector.DocTastyInspector
import scala.tasty.inspector.{TastyInspector, Inspector, Tasty}
import scala.quoted._

import dotty.tools.dotc
Expand All @@ -24,24 +24,12 @@ import ScaladocSupport._
*
* Delegates most of the work to [[TastyParser]] [[dotty.tools.scaladoc.tasty.TastyParser]].
*/
case class ScaladocTastyInspector()(using ctx: DocContext) extends DocTastyInspector:
case class ScaladocTastyInspector()(using ctx: DocContext) extends Inspector:

private val topLevels = Seq.newBuilder[(String, Member)]
private var rootDoc: Option[Comment] = None

def processCompilationUnit(using Quotes)(root: reflect.Tree): Unit = ()

override def postProcess(using Quotes): Unit =
// hack into the compiler to get a list of all top-level trees
// in principle, to do this, one would collect trees in processCompilationUnit
// however, path-dependent types disallow doing so w/o using casts
inline def hackForeachTree(thunk: reflect.Tree => Unit): Unit =
given dctx: dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
dctx.run.nn.units.foreach { compilationUnit =>
// mirrors code from TastyInspector
thunk(compilationUnit.tpdTree.asInstanceOf[reflect.Tree])
}

def inspect(using Quotes)(tastys: List[scala.tasty.inspector.Tasty[quotes.type]]): Unit =
val symbolsToSkip: Set[reflect.Symbol] =
ctx.args.identifiersToSkip.flatMap { ref =>
val qrSymbol = reflect.Symbol
Expand Down Expand Up @@ -116,7 +104,8 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends DocTastyInspe
rootDoc = Some(parseCommentString(using parser.qctx, summon[DocContext])(content, topLevelPck, None))
}

hackForeachTree { root =>
for tasty <- tastys do {
val root = tasty.ast
if !isSkipped(root.symbol) then
val treeRoot = root.asInstanceOf[parser.qctx.reflect.Tree]
processRootDocIfNeeded(treeRoot)
Expand All @@ -138,33 +127,47 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends DocTastyInspe
topLevels += "scala" -> Member(scalaPckg.fullName, "", scalaPckg.dri, Kind.Package)
topLevels += mergeAnyRefAliasAndObject(parser)

def result(): (List[Member], Option[Comment]) =
topLevels.clear()
rootDoc = None


def mergeAnyRefAliasAndObject(parser: TastyParser) =
import parser.qctx.reflect._
val javaLangObjectDef = defn.ObjectClass.tree.asInstanceOf[ClassDef]
val objectMembers = parser.extractPatchedMembers(javaLangObjectDef)
val aM = parser.parseTypeDef(defn.AnyRefClass.tree.asInstanceOf[TypeDef])
"scala" -> aM.copy(
kind = Kind.Class(Nil, Nil),
members = objectMembers
)

object ScaladocTastyInspector:

def loadDocs()(using ctx: DocContext): (List[Member], Option[Comment]) =
val filePaths = ctx.args.tastyFiles.map(_.getAbsolutePath).toList
val classpath = ctx.args.classpath.split(java.io.File.pathSeparator).toList

if filePaths.nonEmpty then inspectFilesInContext(classpath, filePaths)
val inspector = new ScaladocTastyInspector

val (tastyPaths, nonTastyPaths) = filePaths.partition(_.endsWith(".tasty"))
val (jarPaths, invalidPaths) = nonTastyPaths.partition(_.endsWith(".jar"))
Copy link
Contributor Author

@nicolasstucki nicolasstucki May 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I could see, we do not have any tests that use JARs. Either we are missing a test or scaladoc unpack the JAR before hand. Unpacking the jar would be less efficient than reading the JAR directly.


for invalidPath <- invalidPaths do
report.error("File extension is not `tasty` or `jar`: " + invalidPath)

if tastyPaths.nonEmpty then
TastyInspector.inspectAllTastyFiles(tastyPaths, jarPaths, classpath)(inspector)

val all = topLevels.result()
val all = inspector.topLevels.result()
all.groupBy(_._1).map { case (pckName, members) =>
val (pcks, rest) = members.map(_._2).partition(_.kind == Kind.Package)
val basePck = pcks.reduce( (p1, p2) =>
val withNewMembers = p1.withNewMembers(p2.members)
if withNewMembers.docs.isEmpty then withNewMembers.withDocs(p2.docs) else withNewMembers
)
basePck.withMembers((basePck.members ++ rest).sortBy(_.name))
}.toList -> rootDoc
}.toList -> inspector.rootDoc

end ScaladocTastyInspector

def mergeAnyRefAliasAndObject(parser: TastyParser) =
import parser.qctx.reflect._
val javaLangObjectDef = defn.ObjectClass.tree.asInstanceOf[ClassDef]
val objectMembers = parser.extractPatchedMembers(javaLangObjectDef)
val aM = parser.parseTypeDef(defn.AnyRefClass.tree.asInstanceOf[TypeDef])
"scala" -> aM.copy(
kind = Kind.Class(Nil, Nil),
members = objectMembers
)
/** Parses a single Tasty compilation unit. */
case class TastyParser(
qctx: Quotes,
Expand Down
9 changes: 0 additions & 9 deletions scaladoc/src/scala/tasty/inspector/DocTastyInspector.scala

This file was deleted.

33 changes: 33 additions & 0 deletions scaladoc/src/scala/tasty/inspector/Inspector.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copy of tasty-inspector/src/scala/tasty/inspector/Inspector.scala
// FIXME remove this copy of the file
Copy link
Contributor Author

@nicolasstucki nicolasstucki May 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scaladoc project depends on scala3-tasty-inspector, but for some reason, some of the use cases do not include this dependency at runtime. It fails with Caused by: java.lang.ClassNotFoundException: scala.tasty.inspector.Inspector (see community-build and sbt-test/scripted). The previous version did not use the proper inspector classes either and therefore did not encounter this issue.

It is fine to have a copy of these files here for now, as it is equivalent to the current state. Someone will need to investigate and fix this issue later. I will open an issue for this.


package scala.tasty.inspector

import scala.quoted._
import scala.quoted.runtime.impl.QuotesImpl

import dotty.tools.dotc.Compiler
import dotty.tools.dotc.Driver
import dotty.tools.dotc.Run
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Mode
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.fromtasty._
import dotty.tools.dotc.util.ClasspathFromClassloader
import dotty.tools.dotc.CompilationUnit
import dotty.tools.unsupported
import dotty.tools.dotc.report

import java.io.File.pathSeparator

trait Inspector:

/** Inspect all TASTy files using `Quotes` reflect API.
*
* Note: Within this method `quotes.reflect.SourceFile.current` will not work, hence the explicit source paths.
*
* @param tastys List of `Tasty` containing `.tasty`file path and AST
*/
def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit

end Inspector
20 changes: 20 additions & 0 deletions scaladoc/src/scala/tasty/inspector/Tasty.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copy of tasty-inspector/src/scala/tasty/inspector/Tasty.scala
// FIXME remove this copy of the file

package scala.tasty.inspector

import scala.quoted._

/** `.tasty` file representation containing file path and the AST */
trait Tasty[Q <: Quotes & Singleton]:

/** Instance of `Quotes` used to load the AST */
val quotes: Q

/** Path to the `.tasty` file */
def path: String

/** Abstract Syntax Tree contained in the `.tasty` file */
def ast: quotes.reflect.Tree

end Tasty
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copy of tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala
// FIXME remove this copy of the file

package scala.tasty.inspector

import scala.quoted._
Expand All @@ -10,45 +13,43 @@ import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Mode
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.fromtasty._
import dotty.tools.dotc.quoted.QuotesCache
import dotty.tools.dotc.util.ClasspathFromClassloader
import dotty.tools.dotc.CompilationUnit
import dotty.tools.unsupported
import dotty.tools.dotc.report

import java.io.File.pathSeparator

// COPY OF OLD IMPLEMENTATION
// TODO: update to new implementation
trait OldTastyInspector:
self =>

/** Process a TASTy file using TASTy reflect */
protected def processCompilationUnit(using Quotes)(root: quotes.reflect.Tree): Unit

/** Called after all compilation units are processed */
protected def postProcess(using Quotes): Unit = ()
object TastyInspector:

/** Load and process TASTy files using TASTy reflect
*
* @param tastyFiles List of paths of `.tasty` files
*
* @return boolean value indicating whether the process succeeded
*/
def inspectTastyFiles(tastyFiles: List[String]): Boolean =
inspectAllTastyFiles(tastyFiles, Nil, Nil)
def inspectTastyFiles(tastyFiles: List[String])(inspector: Inspector): Boolean =
inspectAllTastyFiles(tastyFiles, Nil, Nil)(inspector)

/** Load and process TASTy files in a `jar` file using TASTy reflect
*
* @param jars Path of `.jar` file
*
* @return boolean value indicating whether the process succeeded
*/
def inspectTastyFilesInJar(jar: String): Boolean =
inspectAllTastyFiles(Nil, List(jar), Nil)
def inspectTastyFilesInJar(jar: String)(inspector: Inspector): Boolean =
inspectAllTastyFiles(Nil, List(jar), Nil)(inspector)

/** Load and process TASTy files using TASTy reflect
*
* @param tastyFiles List of paths of `.tasty` files
* @param jars List of path of `.jar` files
* @param dependenciesClasspath Classpath with extra dependencies needed to load class in the `.tasty` files
*
* @return boolean value indicating whether the process succeeded
*/
def inspectAllTastyFiles(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String]): Boolean =
def inspectAllTastyFiles(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String])(inspector: Inspector): Boolean =
def checkFile(fileName: String, ext: String): Unit =
val file = dotty.tools.io.Path(fileName)
if file.extension != ext then
Expand All @@ -58,40 +59,30 @@ trait OldTastyInspector:
tastyFiles.foreach(checkFile(_, "tasty"))
jars.foreach(checkFile(_, "jar"))
val files = tastyFiles ::: jars
files.nonEmpty && inspectFiles(dependenciesClasspath, files)

/** Load and process TASTy files using TASTy reflect and provided context
*
* Used in doctool to reuse reporter and setup provided by sbt
*
* @param classes List of paths of `.tasty` and `.jar` files (no validation is performed)
* @param classpath Classpath with extra dependencies needed to load class in the `.tasty` files
*/
protected[inspector] def inspectFilesInContext(classpath: List[String], classes: List[String])(using Context): Unit =
if (classes.isEmpty) report.error("Parameter classes should no be empty")
inspectorDriver().process(inspectorArgs(classpath, classes), summon[Context])

inspectFiles(dependenciesClasspath, files)(inspector)

private def inspectorDriver() =
private def inspectorDriver(inspector: Inspector) =
class InspectorDriver extends Driver:
override protected def newCompiler(implicit ctx: Context): Compiler = new TastyFromClass

class TastyInspectorPhase extends Phase:
override def phaseName: String = "tastyInspector"

override def run(implicit ctx: Context): Unit =
val qctx = QuotesImpl()
self.processCompilationUnit(using qctx)(ctx.compilationUnit.tpdTree.asInstanceOf[qctx.reflect.Tree])

class TastyInspectorFinishPhase extends Phase:
override def phaseName: String = "tastyInspectorFinish"

override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] =
val qctx = QuotesImpl()
self.postProcess(using qctx)
override def runOn(units: List[CompilationUnit])(using ctx0: Context): List[CompilationUnit] =
val ctx = QuotesCache.init(ctx0.fresh)
runOnImpl(units)(using ctx)

private def runOnImpl(units: List[CompilationUnit])(using Context): List[CompilationUnit] =
val quotesImpl = QuotesImpl()
class TastyImpl(val path: String, val ast: quotesImpl.reflect.Tree) extends Tasty[quotesImpl.type] {
val quotes = quotesImpl
}
val tastys = units.map(unit => new TastyImpl(unit.source.path , unit.tpdTree.asInstanceOf[quotesImpl.reflect.Tree]))
inspector.inspect(using quotesImpl)(tastys)
units

override def run(implicit ctx: Context): Unit = unsupported("run")
end TastyInspectorPhase

class TastyFromClass extends TASTYCompiler:

Expand All @@ -105,7 +96,6 @@ trait OldTastyInspector:

override protected def backendPhases: List[List[Phase]] =
List(new TastyInspectorPhase) :: // Perform a callback for each compilation unit
List(new TastyInspectorFinishPhase) :: // Perform a final callback
Nil

override def newRun(implicit ctx: Context): Run =
Expand All @@ -123,14 +113,14 @@ trait OldTastyInspector:
("-from-tasty" :: "-Yretain-trees" :: "-classpath" :: fullClasspath :: classes).toArray


private def inspectFiles(classpath: List[String], classes: List[String]): Boolean =
if (classes.isEmpty)
throw new IllegalArgumentException("Parameter classes should no be empty")

val reporter = inspectorDriver().process(inspectorArgs(classpath, classes))
reporter.hasErrors
private def inspectFiles(classpath: List[String], classes: List[String])(inspector: Inspector): Boolean =
classes match
case Nil => true
case _ =>
val reporter = inspectorDriver(inspector).process(inspectorArgs(classpath, classes))
!reporter.hasErrors

end inspectFiles


end OldTastyInspector
end TastyInspector
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package dotty.tools.scaladoc
package tasty.comments

import scala.quoted.Quotes
import scala.quoted.*

import org.junit.{Test, Rule}
import org.junit.Assert.{assertSame, assertTrue}
Expand Down Expand Up @@ -198,14 +198,11 @@ class MemberLookupTests {

@Test
def test(): Unit = {
import scala.tasty.inspector.OldTastyInspector
class Inspector extends OldTastyInspector:
var alreadyRan: Boolean = false
import scala.tasty.inspector.*
class MyInspector extends Inspector:

override def processCompilationUnit(using ctx: quoted.Quotes)(root: ctx.reflect.Tree): Unit =
if !alreadyRan then
this.test()
alreadyRan = true
def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit =
this.test()

def test()(using q: Quotes): Unit = {
import dotty.tools.scaladoc.tasty.comments.MemberLookup
Expand All @@ -215,6 +212,6 @@ class MemberLookupTests {
cases.testAll()
}

Inspector().inspectTastyFiles(TestUtils.listOurClasses())
TastyInspector.inspectTastyFiles(TestUtils.listOurClasses())(new MyInspector)
}
}
Loading