Skip to content

Commit e02c785

Browse files
authored
Merge pull request #4789 from dotty-staging/topic/dottydoc-from-tasty
Support `-from-tasty` in Dottydoc
2 parents fe99358 + 0cd20b4 commit e02c785

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+608
-455
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ class Compiler {
5959
protected def transformPhases: List[List[Phase]] =
6060
List(new FirstTransform, // Some transformations to put trees into a canonical form
6161
new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars
62-
new ElimPackagePrefixes) :: // Eliminate references to package prefixes in Select nodes
62+
new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes
63+
new CookComments) :: // Cook the comments: expand variables, doc, etc.
6364
List(new CheckStatic, // Check restrictions that apply to @static members
6465
new ElimRepeated, // Rewrite vararg parameters and arguments
6566
new ExpandSAMs, // Expand single abstract method closures to anonymous classes

compiler/src/dotty/tools/dotc/core/Comments.scala

Lines changed: 64 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package dotc
33
package core
44

55
import ast.{ untpd, tpd }
6-
import Decorators._, Symbols._, Contexts._, Flags.EmptyFlags
6+
import Decorators._, Symbols._, Contexts._
77
import util.SourceFile
88
import util.Positions._
99
import util.CommentParsing._
@@ -33,41 +33,58 @@ object Comments {
3333
def docstring(sym: Symbol): Option[Comment] = _docstrings.get(sym)
3434

3535
def addDocstring(sym: Symbol, doc: Option[Comment]): Unit =
36-
doc.map(d => _docstrings.update(sym, d))
36+
doc.foreach(d => _docstrings.update(sym, d))
3737
}
3838

39-
/** A `Comment` contains the unformatted docstring as well as a position
40-
*
41-
* The `Comment` contains functionality to create versions of itself without
42-
* `@usecase` sections as well as functionality to map the `raw` docstring
43-
*/
44-
abstract case class Comment(pos: Position, raw: String) { self =>
45-
def isExpanded: Boolean
39+
/**
40+
* A `Comment` contains the unformatted docstring, it's position and potentially more
41+
* information that is populated when the comment is "cooked".
42+
*
43+
* @param pos The position of this `Comment`.
44+
* @param raw The raw comment, as seen in the source code, without any expansion.
45+
* @param expanded If this comment has been expanded, it's expansion, otherwise `None`.
46+
* @param usecases The usecases for this comment.
47+
*/
48+
final case class Comment(pos: Position, raw: String, expanded: Option[String], usecases: List[UseCase]) {
4649

47-
def usecases: List[UseCase]
50+
/** Has this comment been cooked or expanded? */
51+
def isExpanded: Boolean = expanded.isDefined
4852

49-
val isDocComment = raw.startsWith("/**")
53+
/** The body of this comment, without the `@usecase` and `@define` sections, after expansion. */
54+
lazy val expandedBody: Option[String] =
55+
expanded.map(removeSections(_, "@usecase", "@define"))
5056

51-
def expand(f: String => String): Comment = new Comment(pos, f(raw)) {
52-
val isExpanded = true
53-
val usecases = self.usecases
54-
}
57+
val isDocComment = Comment.isDocComment(raw)
5558

56-
def withUsecases(implicit ctx: Context): Comment = new Comment(pos, stripUsecases) {
57-
val isExpanded = self.isExpanded
58-
val usecases = parseUsecases
59+
/**
60+
* Expands this comment by giving its content to `f`, and then parsing the `@usecase` sections.
61+
* Typically, `f` will take care of expanding the variables.
62+
*
63+
* @param f The expansion function.
64+
* @return The expanded comment, with the `usecases` populated.
65+
*/
66+
def expand(f: String => String)(implicit ctx: Context): Comment = {
67+
val expandedComment = f(raw)
68+
val useCases = Comment.parseUsecases(expandedComment, pos)
69+
Comment(pos, raw, Some(expandedComment), useCases)
5970
}
71+
}
72+
73+
object Comment {
6074

61-
private[this] lazy val stripUsecases: String =
62-
removeSections(raw, "@usecase", "@define")
75+
def isDocComment(comment: String): Boolean = comment.startsWith("/**")
6376

64-
private[this] def parseUsecases(implicit ctx: Context): List[UseCase] =
65-
if (!raw.startsWith("/**"))
66-
List.empty[UseCase]
67-
else
68-
tagIndex(raw)
69-
.filter { startsWithTag(raw, _, "@usecase") }
70-
.map { case (start, end) => decomposeUseCase(start, end) }
77+
def apply(pos: Position, raw: String): Comment =
78+
Comment(pos, raw, None, Nil)
79+
80+
private def parseUsecases(expandedComment: String, pos: Position)(implicit ctx: Context): List[UseCase] =
81+
if (!isDocComment(expandedComment)) {
82+
Nil
83+
} else {
84+
tagIndex(expandedComment)
85+
.filter { startsWithTag(expandedComment, _, "@usecase") }
86+
.map { case (start, end) => decomposeUseCase(expandedComment, pos, start, end) }
87+
}
7188

7289
/** Turns a usecase section into a UseCase, with code changed to:
7390
* {{{
@@ -77,7 +94,7 @@ object Comments {
7794
* def foo: A = ???
7895
* }}}
7996
*/
80-
private[this] def decomposeUseCase(start: Int, end: Int)(implicit ctx: Context): UseCase = {
97+
private[this] def decomposeUseCase(body: String, pos: Position, start: Int, end: Int)(implicit ctx: Context): UseCase = {
8198
def subPos(start: Int, end: Int) =
8299
if (pos == NoPosition) NoPosition
83100
else {
@@ -86,49 +103,34 @@ object Comments {
86103
pos withStart start1 withPoint start1 withEnd end1
87104
}
88105

89-
val codeStart = skipWhitespace(raw, start + "@usecase".length)
90-
val codeEnd = skipToEol(raw, codeStart)
91-
val code = raw.substring(codeStart, codeEnd) + " = ???"
92-
val codePos = subPos(codeStart, codeEnd)
93-
val commentStart = skipLineLead(raw, codeEnd + 1) min end
94-
val commentStr = "/** " + raw.substring(commentStart, end) + "*/"
95-
val commentPos = subPos(commentStart, end)
106+
val codeStart = skipWhitespace(body, start + "@usecase".length)
107+
val codeEnd = skipToEol(body, codeStart)
108+
val code = body.substring(codeStart, codeEnd) + " = ???"
109+
val codePos = subPos(codeStart, codeEnd)
96110

97-
UseCase(Comment(commentPos, commentStr), code, codePos)
111+
UseCase(code, codePos)
98112
}
99113
}
100114

101-
object Comment {
102-
def apply(pos: Position, raw: String, expanded: Boolean = false, usc: List[UseCase] = Nil): Comment =
103-
new Comment(pos, raw) {
104-
val isExpanded = expanded
105-
val usecases = usc
106-
}
107-
}
108-
109-
abstract case class UseCase(comment: Comment, code: String, codePos: Position) {
110-
/** Set by typer */
111-
var tpdCode: tpd.DefDef = _
112-
113-
def untpdCode: untpd.Tree
115+
final case class UseCase(code: String, codePos: Position, untpdCode: untpd.Tree, tpdCode: Option[tpd.DefDef]) {
116+
def typed(tpdCode: tpd.DefDef): UseCase = copy(tpdCode = Some(tpdCode))
114117
}
115118

116119
object UseCase {
117-
def apply(comment: Comment, code: String, codePos: Position)(implicit ctx: Context) =
118-
new UseCase(comment, code, codePos) {
119-
val untpdCode = {
120-
val tree = new Parser(new SourceFile("<usecase>", code)).localDef(codePos.start)
121-
122-
tree match {
123-
case tree: untpd.DefDef =>
124-
val newName = ctx.freshNames.newName(tree.name, NameKinds.DocArtifactName)
125-
untpd.DefDef(newName, tree.tparams, tree.vparamss, tree.tpt, tree.rhs)
126-
case _ =>
127-
ctx.error(ProperDefinitionNotFound(), codePos)
128-
tree
129-
}
120+
def apply(code: String, codePos: Position)(implicit ctx: Context): UseCase = {
121+
val tree = {
122+
val tree = new Parser(new SourceFile("<usecase>", code)).localDef(codePos.start)
123+
tree match {
124+
case tree: untpd.DefDef =>
125+
val newName = ctx.freshNames.newName(tree.name, NameKinds.DocArtifactName)
126+
tree.copy(name = newName)
127+
case _ =>
128+
ctx.error(ProperDefinitionNotFound(), codePos)
129+
tree
130130
}
131131
}
132+
UseCase(code, codePos, tree, None)
133+
}
132134
}
133135

134136
/**

compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class CommentPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr]
2323
buf.writeAddr(addr)
2424
buf.writeNat(length)
2525
buf.writeBytes(bytes, length)
26-
buf.writeByte(if (cmt.isExpanded) 1 else 0)
26+
buf.writeLongInt(cmt.pos.coords)
2727
case other =>
2828
()
2929
}

compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package dotty.tools.dotc.core.tasty
33
import dotty.tools.dotc.core.Comments.Comment
44
import dotty.tools.dotc.core.Symbols.Symbol
55
import dotty.tools.dotc.core.tasty.TastyBuffer.Addr
6-
import dotty.tools.dotc.util.Positions
6+
import dotty.tools.dotc.util.Positions.Position
77

88
import scala.collection.mutable.HashMap
99

@@ -19,9 +19,9 @@ class CommentUnpickler(reader: TastyReader) {
1919
val length = readNat()
2020
if (length > 0) {
2121
val bytes = readBytes(length)
22-
val expanded = readByte() == 1
22+
val position = new Position(readLongInt())
2323
val rawComment = new String(bytes, Charset.forName("UTF-8"))
24-
comments(addr) = Comment(Positions.NoPosition, rawComment, expanded = expanded)
24+
comments(addr) = Comment(position, rawComment)
2525
}
2626
}
2727
comments.toMap

compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,7 @@ Standard Section: "Positions" Assoc*
235235
236236
Standard Section: "Comments" Comment*
237237
238-
Comment = Length Bytes Byte // Raw comment's bytes encoded as UTF-8, plus a byte indicating
239-
// whether the comment is expanded (1) or not expanded (0)
238+
Comment = Length Bytes LongInt // Raw comment's bytes encoded as UTF-8, followed by the comment's coordinates.
240239
241240
242241
**************************************************************************************/

compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class ReadTastyTreesFromClasses extends FrontEnd {
3333
}
3434

3535
def alreadyLoaded(): None.type = {
36-
ctx.warning(s"sclass $className cannot be unpickled because it is already loaded")
36+
ctx.warning(s"class $className cannot be unpickled because it is already loaded")
3737
None
3838
}
3939

@@ -72,5 +72,7 @@ class ReadTastyTreesFromClasses extends FrontEnd {
7272
case _ =>
7373
cannotUnpickle(s"no class file was found")
7474
}
75+
case unit =>
76+
Some(unit)
7577
}
7678
}

compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class InteractiveCompiler extends Compiler {
1313
// This could be improved by reporting errors back to the IDE
1414
// after each phase group instead of waiting for the pipeline to finish.
1515
override def phases: List[List[Phase]] = List(
16-
List(new FrontEnd)
16+
List(new FrontEnd),
17+
List(new transform.CookComments)
1718
)
1819
}

compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
3333
private val myInitCtx: Context = {
3434
val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive).addMode(Mode.ReadComments)
3535
rootCtx.setSetting(rootCtx.settings.YretainTrees, true)
36+
rootCtx.setSetting(rootCtx.settings.YcookComments, true)
3637
val ctx = setup(settings.toArray, rootCtx)._2
3738
ctx.initialize()(ctx)
3839
ctx
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dotty.tools.dotc.transform
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.core.Contexts.Context
5+
import dotty.tools.dotc.typer.Docstrings
6+
7+
class CookComments extends MegaPhase.MiniPhase {
8+
override def phaseName = "cookComments"
9+
10+
override def transformTypeDef(tree: tpd.TypeDef)(implicit ctx: Context): tpd.Tree = {
11+
if (ctx.settings.YcookComments.value && tree.isClassDef) {
12+
val cls = tree.symbol
13+
val cookingCtx = ctx.localContext(tree, cls).setNewScope
14+
val template = tree.rhs.asInstanceOf[tpd.Template]
15+
val owner = template.self.symbol.orElse(cls)
16+
17+
template.body.foreach { stat =>
18+
Docstrings.cookComment(stat.symbol, owner)(cookingCtx)
19+
}
20+
21+
Docstrings.cookComment(cls, cls)(cookingCtx)
22+
}
23+
24+
tree
25+
26+
}
27+
28+
}

compiler/src/dotty/tools/dotc/typer/Docstrings.scala

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,67 @@ package typer
44

55
import core._
66
import Contexts._, Symbols._, Decorators._, Comments._
7-
import util.Positions._
87
import ast.tpd
98

10-
trait Docstrings { self: Typer =>
9+
object Docstrings {
1110

12-
/** The Docstrings typer will handle the expansion of `@define` and
13-
* `@inheritdoc` if there is a `DocContext` present as a property in the
14-
* supplied `ctx`.
15-
*
16-
* It will also type any `@usecase` available in function definitions.
17-
*/
18-
def cookComments(syms: List[Symbol], owner: Symbol)(implicit ctx: Context): Unit =
19-
ctx.docCtx.foreach { docbase =>
20-
val relevantSyms = syms.filter(docbase.docstring(_).exists(!_.isExpanded))
21-
relevantSyms.foreach { sym =>
22-
expandParentDocs(sym)
23-
val usecases = docbase.docstring(sym).map(_.usecases).getOrElse(Nil)
24-
25-
usecases.foreach { usecase =>
26-
enterSymbol(createSymbol(usecase.untpdCode))
11+
/**
12+
* Expands or cooks the documentation for `sym` in class `owner`.
13+
* The expanded comment will directly replace the original comment in the doc context.
14+
*
15+
* The expansion registers `@define` sections, and will replace `@inheritdoc` and variable
16+
* occurrences in the comments.
17+
*
18+
* If the doc comments contain `@usecase` sections, they will be typed.
19+
*
20+
* @param sym The symbol for which the comment is being cooked.
21+
* @param owner The class for which comments are being cooked.
22+
*/
23+
def cookComment(sym: Symbol, owner: Symbol)(implicit ctx: Context): Option[Comment] = {
24+
ctx.docCtx.flatMap { docCtx =>
25+
expand(sym, owner)(ctx, docCtx)
26+
}
27+
}
2728

28-
typedStats(usecase.untpdCode :: Nil, owner) match {
29-
case List(df: tpd.DefDef) => usecase.tpdCode = df
30-
case _ => ctx.error("`@usecase` was not a valid definition", usecase.codePos)
29+
private def expand(sym: Symbol, owner: Symbol)(implicit ctx: Context, docCtx: ContextDocstrings): Option[Comment] = {
30+
docCtx.docstring(sym).flatMap {
31+
case cmt if cmt.isExpanded =>
32+
Some(cmt)
33+
case _ =>
34+
expandComment(sym).map { expanded =>
35+
val typedUsecases = expanded.usecases.map { usecase =>
36+
ctx.typer.enterSymbol(ctx.typer.createSymbol(usecase.untpdCode))
37+
ctx.typer.typedStats(usecase.untpdCode :: Nil, owner) match {
38+
case List(df: tpd.DefDef) =>
39+
usecase.typed(df)
40+
case _ =>
41+
ctx.error("`@usecase` was not a valid definition", usecase.codePos)
42+
usecase
43+
}
3144
}
45+
46+
val commentWithUsecases = expanded.copy(usecases = typedUsecases)
47+
docCtx.addDocstring(sym, Some(commentWithUsecases))
48+
commentWithUsecases
3249
}
33-
}
3450
}
51+
}
3552

36-
private def expandParentDocs(sym: Symbol)(implicit ctx: Context): Unit =
37-
ctx.docCtx.foreach { docCtx =>
38-
docCtx.docstring(sym).foreach { cmt =>
39-
def expandDoc(owner: Symbol): Unit = if (!cmt.isExpanded) {
40-
val tplExp = docCtx.templateExpander
41-
tplExp.defineVariables(sym)
42-
43-
val newCmt = cmt
44-
.expand(tplExp.expandedDocComment(sym, owner, _))
45-
.withUsecases
53+
private def expandComment(sym: Symbol, owner: Symbol, comment: Comment)(implicit ctx: Context, docCtx: ContextDocstrings): Comment = {
54+
val tplExp = docCtx.templateExpander
55+
tplExp.defineVariables(sym)
56+
val newComment = comment.expand(tplExp.expandedDocComment(sym, owner, _))
57+
docCtx.addDocstring(sym, Some(newComment))
58+
newComment
59+
}
4660

47-
docCtx.addDocstring(sym, Some(newCmt))
48-
}
49-
50-
if (sym ne NoSymbol) {
51-
expandParentDocs(sym.owner)
52-
expandDoc(sym.owner)
53-
}
54-
}
61+
private def expandComment(sym: Symbol)(implicit ctx: Context, docCtx: ContextDocstrings): Option[Comment] = {
62+
if (sym eq NoSymbol) None
63+
else {
64+
for {
65+
cmt <- docCtx.docstring(sym) if !cmt.isExpanded
66+
_ = expandComment(sym.owner)
67+
} yield expandComment(sym, sym.owner, cmt)
5568
}
69+
}
5670
}

0 commit comments

Comments
 (0)