Skip to content

Commit 26ad497

Browse files
authored
Merge pull request #10593 from dotty-staging/scala3doc/improve-stdlib-loading
Scala3doc: improve stdlib loading
2 parents 5c96a7c + 8b7467c commit 26ad497

File tree

6 files changed

+96
-45
lines changed

6 files changed

+96
-45
lines changed

.github/workflows/scala3doc.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@ jobs:
4545
- name: Generate Scala 3 documentation
4646
run: ./project/scripts/sbt scala3doc/generateScala3Documentation
4747

48-
- name: Generate Scala 3 stdlib documentation
49-
run: ./project/scripts/sbt scala3doc/generateScala3StdlibDocumentation
50-
5148
- name: Generate documentation for example project using dotty-sbt
5249
run: ./project/scripts/sbt "sbt-dotty/scripted sbt-dotty/scala3doc"
5350

library/src/scala/quoted/Quotes.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
176176
* +- Flags
177177
*
178178
* ```
179+
*
180+
* @syntax markdown
179181
*/
180182
trait Reflection { self: reflect.type =>
181183

project/Build.scala

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,16 @@ object Build {
841841
settings(
842842
moduleName := "scala-library",
843843
javaOptions := (javaOptions in `scala3-compiler-bootstrapped`).value,
844+
Compile/scalacOptions += "-Yerased-terms",
845+
Compile/scalacOptions ++= {
846+
Seq(
847+
"-sourcepath",
848+
Seq(
849+
(Compile/sourceManaged).value / "scala-library-src",
850+
(Compile/sourceManaged).value / "dotty-library-src",
851+
).mkString(File.pathSeparator),
852+
)
853+
},
844854
scalacOptions -= "-Xfatal-warnings",
845855
ivyConfigurations += SourceDeps.hide,
846856
transitiveClassifiers := Seq("sources"),
@@ -870,6 +880,30 @@ object Build {
870880
((trgDir ** "*.scala") +++ (trgDir ** "*.java")).get.toSet
871881
} (Set(scalaLibrarySourcesJar)).toSeq
872882
}.taskValue,
883+
sourceGenerators in Compile += Def.task {
884+
val s = streams.value
885+
val cacheDir = s.cacheDirectory
886+
val trgDir = (sourceManaged in Compile).value / "dotty-library-src"
887+
888+
// NOTE `sourceDirectory` is used for actual copying,
889+
// but `sources` are used as cache keys
890+
val dottyLibSourceDir = (`scala3-library-bootstrapped`/sourceDirectory).value
891+
val dottyLibSources = (`scala3-library-bootstrapped`/Compile/sources).value
892+
893+
val cachedFun = FileFunction.cached(
894+
cacheDir / s"copyDottyLibrarySrc",
895+
FilesInfo.lastModified,
896+
FilesInfo.exists,
897+
) { _ =>
898+
s.log.info(s"Copying scala3-library sources from $dottyLibSourceDir to $trgDir...")
899+
if (trgDir.exists) IO.delete(trgDir)
900+
IO.copyDirectory(dottyLibSourceDir, trgDir)
901+
902+
((trgDir ** "*.scala") +++ (trgDir ** "*.java")).get.toSet
903+
}
904+
905+
cachedFun(dottyLibSources.toSet).toSeq
906+
}.taskValue,
873907
sources in Compile ~= (_.filterNot(file =>
874908
// sources from https://github.com/scala/scala/tree/2.13.x/src/library-aux
875909
file.getPath.endsWith("scala-library-src/scala/Any.scala") ||
@@ -1182,7 +1216,6 @@ object Build {
11821216
val generateSelfDocumentation = taskKey[Unit]("Generate example documentation")
11831217
// Note: the two tasks below should be one, but a bug in Tasty prevents that
11841218
val generateScala3Documentation = inputKey[Unit]("Generate documentation for dotty lib")
1185-
val generateScala3StdlibDocumentation = taskKey[Unit]("Generate documentation for Scala3 standard library")
11861219
val generateTestcasesDocumentation = taskKey[Unit]("Generate documentation for testcases, usefull for debugging tests")
11871220
lazy val `scala3doc` = project.in(file("scala3doc")).asScala3doc
11881221
lazy val `scala3doc-testcases` = project.in(file("scala3doc-testcases")).asScala3docTestcases
@@ -1489,7 +1522,7 @@ object Build {
14891522
}
14901523

14911524
def joinProducts(products: Seq[java.io.File]): String =
1492-
products.iterator.map(_.getAbsolutePath.toString).mkString(java.io.File.pathSeparator)
1525+
products.iterator.map(_.getAbsolutePath.toString).mkString(" ")
14931526

14941527
val dokkaVersion = "1.4.10.2"
14951528

@@ -1532,12 +1565,12 @@ object Build {
15321565
val majorVersion = (scalaBinaryVersion in LocalProject("scala3-library-bootstrapped")).value
15331566

15341567
val dottyJars: Seq[java.io.File] = Seq(
1568+
(`stdlib-bootstrapped`/Compile/products).value,
15351569
(`scala3-interfaces`/Compile/products).value,
15361570
(`tasty-core-bootstrapped`/Compile/products).value,
1537-
(`scala3-library-bootstrapped`/Compile/products).value,
15381571
).flatten
15391572

1540-
val roots = dottyJars.mkString(" ")
1573+
val roots = joinProducts(dottyJars)
15411574

15421575
if (dottyJars.isEmpty) Def.task { streams.value.log.error("Dotty lib wasn't found") }
15431576
else Def.task{
@@ -1547,24 +1580,9 @@ object Build {
15471580
IO.write(dest / "CNAME", "dotty.epfl.ch")
15481581
}.dependsOn(generateDocumentation(
15491582
roots, "Scala 3", dest.getAbsolutePath, "master",
1550-
"-siteroot scala3doc/scala3-docs -project-logo scala3doc/scala3-docs/logo.svg"))
1583+
"-comment-syntax wiki -siteroot scala3doc/scala3-docs -project-logo scala3doc/scala3-docs/logo.svg"))
15511584
}.evaluated,
15521585

1553-
1554-
generateScala3StdlibDocumentation:= Def.taskDyn {
1555-
val dottyJars: Seq[java.io.File] = Seq(
1556-
(`stdlib-bootstrapped`/Compile/products).value,
1557-
).flatten
1558-
1559-
val roots = joinProducts(dottyJars)
1560-
1561-
if (dottyJars.isEmpty) Def.task { streams.value.log.error("Dotty lib wasn't found") }
1562-
else generateDocumentation(
1563-
roots, "Scala 3", "scala3doc/output/scala3-stdlib", "maser",
1564-
"-siteroot scala3doc/scala3-docs -comment-syntax wiki -project-logo scala3doc/scala3-docs/logo.svg "
1565-
)
1566-
}.value,
1567-
15681586
generateTestcasesDocumentation := Def.taskDyn {
15691587
generateDocumentation(Build.testcasesOutputDir.in(Test).value, "Scala3doc testcases", "scala3doc/output/testcases", "master")
15701588
}.value,

scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,13 @@ trait ClassLikeSupport:
6969

7070
val fullExtra =
7171
if (signatureOnly) baseExtra
72-
else baseExtra.plus(CompositeMemberExtension(
73-
classDef.extractMembers,
74-
classDef.getParents.map(_.dokkaType.asSignature),
75-
supertypes,
76-
Nil))
72+
else
73+
baseExtra.plus(CompositeMemberExtension(
74+
classDef.extractPatchedMembers,
75+
classDef.getParents.map(_.dokkaType.asSignature),
76+
supertypes,
77+
Nil))
78+
end if
7779

7880
new DClass(
7981
dri,
@@ -203,6 +205,31 @@ trait ClassLikeSupport:
203205
inherited.flatMap(s => parseInheritedMember(s))
204206
}
205207

208+
/** Extracts members while taking Dotty logic for patching the stdlib into account. */
209+
def extractPatchedMembers: Seq[Member] = {
210+
val ownMembers = c.extractMembers
211+
def extractPatchMembers(sym: Symbol) = {
212+
// NOTE for some reason scala.language$.experimental$ class doesn't show up here, so we manually add the name
213+
val ownMemberDRIs = ownMembers.iterator.map(_.name).toSet + "experimental$"
214+
sym.tree.asInstanceOf[ClassDef]
215+
.membersToDocument.filterNot(m => ownMemberDRIs.contains(m.symbol.name))
216+
.flatMap(parseMember)
217+
}
218+
c.symbol.show match {
219+
case "scala.Predef$" =>
220+
ownMembers ++
221+
extractPatchMembers(qctx.reflect.Symbol.requiredClass("scala.runtime.stdLibPatches.Predef$"))
222+
case "scala.language$" =>
223+
ownMembers ++
224+
extractPatchMembers(qctx.reflect.Symbol.requiredModule("scala.runtime.stdLibPatches.language").moduleClass)
225+
case "scala.language$.experimental$" =>
226+
ownMembers ++
227+
extractPatchMembers(qctx.reflect.Symbol.requiredModule("scala.runtime.stdLibPatches.language.experimental").moduleClass)
228+
case _ => ownMembers
229+
}
230+
231+
}
232+
206233
def getParents: List[Tree] =
207234
for
208235
parentTree <- c.parents if isValidPos(parentTree.pos) // We assume here that order is correct
@@ -417,4 +444,3 @@ trait ClassLikeSupport:
417444
valDef.symbol.source
418445
))
419446
)
420-

scala3doc/src/dotty/dokka/tasty/TastyParser.scala

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,22 @@ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends Do
3030
private val topLevels = Seq.newBuilder[Documentable]
3131

3232
def processCompilationUnit(using q: Quotes)(root: q.reflect.Tree): Unit =
33-
val parser = new TastyParser(q, this)
34-
35-
def driFor(link: String): Option[DRI] =
36-
val symOps = new SymOps[q.type](q)
37-
import symOps._
38-
Try(QueryParser(link).readQuery()).toOption.flatMap(q =>
39-
MemberLookup.lookupOpt(q, None).map{ case (sym, _) => sym.dri}
40-
)
33+
// NOTE we avoid documenting definitions in the magical stdLibPatches directory;
34+
// the symbols there are "patched" through dark Dotty magic onto other stdlib
35+
// definitions, so if we documented their origin, we'd get defs with duplicate DRIs
36+
if !root.symbol.show.startsWith("scala.runtime.stdLibPatches") then
37+
val parser = new TastyParser(q, this)
38+
39+
def driFor(link: String): Option[DRI] =
40+
val symOps = new SymOps[q.type](q)
41+
import symOps._
42+
Try(QueryParser(link).readQuery()).toOption.flatMap(q =>
43+
MemberLookup.lookupOpt(q, None).map{ case (sym, _) => sym.dri}
44+
)
4145

42-
ctx.staticSiteContext.foreach(_.memberLinkResolver = driFor)
43-
topLevels ++= parser.parseRootTree(root.asInstanceOf[parser.qctx.reflect.Tree])
46+
ctx.staticSiteContext.foreach(_.memberLinkResolver = driFor)
47+
topLevels ++= parser.parseRootTree(root.asInstanceOf[parser.qctx.reflect.Tree])
48+
end processCompilationUnit
4449

4550
def result(): List[DPackage] =
4651
topLevels.clear()

scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@ class Converter(val repr: Repr) extends BaseConverter {
3131
block match {
3232
case Title(text, level) =>
3333
val content = convertInline(text)
34+
// NOTE: these aren't strictly necessary, but if you inline them, incremental compilation will break
35+
val jContent = content.asJava : java.util.List[_ <: dkkd.DocTag]
36+
val jAtt = kt.emptyMap[String, String]
3437
emit(level match {
35-
case 1 => dkkd.H1(content.asJava, kt.emptyMap)
36-
case 2 => dkkd.H2(content.asJava, kt.emptyMap)
37-
case 3 => dkkd.H3(content.asJava, kt.emptyMap)
38-
case 4 => dkkd.H4(content.asJava, kt.emptyMap)
39-
case 5 => dkkd.H5(content.asJava, kt.emptyMap)
40-
case 6 => dkkd.H6(content.asJava, kt.emptyMap)
38+
case 1 => dkkd.H1(jContent, jAtt)
39+
case 2 => dkkd.H2(jContent, jAtt)
40+
case 3 => dkkd.H3(jContent, jAtt)
41+
case 4 => dkkd.H4(jContent, jAtt)
42+
case 5 => dkkd.H5(jContent, jAtt)
43+
case 6 => dkkd.H6(jContent, jAtt)
4144
})
4245

4346
case Paragraph(text) =>

0 commit comments

Comments
 (0)