Skip to content

Commit fcf0efe

Browse files
committed
Merge pull request #1151 from felixmulder/topic/wip-docstrings
Add support for raw docstrings in ASTs
2 parents 1c56d8c + 60379c2 commit fcf0efe

File tree

11 files changed

+605
-46
lines changed

11 files changed

+605
-46
lines changed

src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ object Trees {
2929
/** The total number of created tree nodes, maintained if Stats.enabled */
3030
@sharable var ntrees = 0
3131

32+
/** Attachment key for trees with documentation strings attached */
33+
val DocComment = new Attachment.Key[String]
34+
3235
/** Modifiers and annotations for definitions
3336
* @param flags The set flags
3437
* @param privateWithin If a private or protected has is followed by a
@@ -321,6 +324,8 @@ object Trees {
321324
private[ast] def rawMods: Modifiers[T] =
322325
if (myMods == null) genericEmptyModifiers else myMods
323326

327+
def rawComment: Option[String] = getAttachment(DocComment)
328+
324329
def withMods(mods: Modifiers[Untyped]): ThisTree[Untyped] = {
325330
val tree = if (myMods == null || (myMods == mods)) this else clone.asInstanceOf[MemberDef[Untyped]]
326331
tree.setMods(mods)
@@ -329,6 +334,11 @@ object Trees {
329334

330335
def withFlags(flags: FlagSet): ThisTree[Untyped] = withMods(Modifiers(flags))
331336

337+
def setComment(comment: Option[String]): ThisTree[Untyped] = {
338+
comment.map(putAttachment(DocComment, _))
339+
asInstanceOf[ThisTree[Untyped]]
340+
}
341+
332342
protected def setMods(mods: Modifiers[T @uncheckedVariance]) = myMods = mods
333343

334344
override def envelope: Position = rawMods.pos.union(pos).union(initialPos)

src/dotty/tools/dotc/config/Printers.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ object Printers {
1313
}
1414

1515
val default: Printer = new Printer
16+
val dottydoc: Printer = noPrinter
1617
val core: Printer = noPrinter
1718
val typr: Printer = noPrinter
1819
val constr: Printer = noPrinter

src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ class ScalaSettings extends Settings.SettingGroup {
159159
val YprintSyms = BooleanSetting("-Yprint-syms", "when printing trees print info in symbols instead of corresponding info in trees.")
160160
val YtestPickler = BooleanSetting("-Ytest-pickler", "self-test for pickling functionality; should be used with -Ystop-after:pickler")
161161
val YcheckReentrant = BooleanSetting("-Ycheck-reentrant", "check that compiled program does not contain vars that can be accessed from a global root.")
162+
val YkeepComments = BooleanSetting("-Ykeep-comments", "Keep comments when scanning source files.")
162163
def stop = YstopAfter
163164

164165
/** Area-specific debug output.

src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,14 @@ object Contexts {
550550
def squashed(p: Phase): Phase = {
551551
allPhases.find(_.period.containsPhaseId(p.id)).getOrElse(NoPhase)
552552
}
553+
554+
val _docstrings: mutable.Map[Symbol, String] =
555+
mutable.Map.empty
556+
557+
def docstring(sym: Symbol): Option[String] = _docstrings.get(sym)
558+
559+
def addDocstring(sym: Symbol, doc: Option[String]): Unit =
560+
doc.map(d => _docstrings += (sym -> d))
553561
}
554562

555563
/** The essential mutable state of a context base, collected into a common class */

src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,13 +1752,13 @@ object Parsers {
17521752
*/
17531753
def defOrDcl(start: Int, mods: Modifiers): Tree = in.token match {
17541754
case VAL =>
1755-
patDefOrDcl(posMods(start, mods))
1755+
patDefOrDcl(posMods(start, mods), in.getDocString(start))
17561756
case VAR =>
1757-
patDefOrDcl(posMods(start, addFlag(mods, Mutable)))
1757+
patDefOrDcl(posMods(start, addFlag(mods, Mutable)), in.getDocString(start))
17581758
case DEF =>
1759-
defDefOrDcl(posMods(start, mods))
1759+
defDefOrDcl(posMods(start, mods), in.getDocString(start))
17601760
case TYPE =>
1761-
typeDefOrDcl(posMods(start, mods))
1761+
typeDefOrDcl(posMods(start, mods), in.getDocString(start))
17621762
case _ =>
17631763
tmplDef(start, mods)
17641764
}
@@ -1768,7 +1768,7 @@ object Parsers {
17681768
* ValDcl ::= Id {`,' Id} `:' Type
17691769
* VarDcl ::= Id {`,' Id} `:' Type
17701770
*/
1771-
def patDefOrDcl(mods: Modifiers): Tree = {
1771+
def patDefOrDcl(mods: Modifiers, docstring: Option[String] = None): Tree = {
17721772
val lhs = commaSeparated(pattern2)
17731773
val tpt = typedOpt()
17741774
val rhs =
@@ -1782,8 +1782,10 @@ object Parsers {
17821782
}
17831783
} else EmptyTree
17841784
lhs match {
1785-
case (id @ Ident(name: TermName)) :: Nil => cpy.ValDef(id)(name, tpt, rhs).withMods(mods)
1786-
case _ => PatDef(mods, lhs, tpt, rhs)
1785+
case (id @ Ident(name: TermName)) :: Nil => {
1786+
cpy.ValDef(id)(name, tpt, rhs).withMods(mods).setComment(docstring)
1787+
} case _ =>
1788+
PatDef(mods, lhs, tpt, rhs)
17871789
}
17881790
}
17891791

@@ -1792,7 +1794,7 @@ object Parsers {
17921794
* DefDcl ::= DefSig `:' Type
17931795
* DefSig ::= id [DefTypeParamClause] ParamClauses
17941796
*/
1795-
def defDefOrDcl(mods: Modifiers): Tree = atPos(tokenRange) {
1797+
def defDefOrDcl(mods: Modifiers, docstring: Option[String] = None): Tree = atPos(tokenRange) {
17961798
def scala2ProcedureSyntax(resultTypeStr: String) = {
17971799
val toInsert =
17981800
if (in.token == LBRACE) s"$resultTypeStr ="
@@ -1833,7 +1835,7 @@ object Parsers {
18331835
accept(EQUALS)
18341836
expr()
18351837
}
1836-
DefDef(name, tparams, vparamss, tpt, rhs).withMods(mods1)
1838+
DefDef(name, tparams, vparamss, tpt, rhs).withMods(mods1).setComment(docstring)
18371839
}
18381840
}
18391841

@@ -1867,15 +1869,15 @@ object Parsers {
18671869
/** TypeDef ::= type Id [TypeParamClause] `=' Type
18681870
* TypeDcl ::= type Id [TypeParamClause] TypeBounds
18691871
*/
1870-
def typeDefOrDcl(mods: Modifiers): Tree = {
1872+
def typeDefOrDcl(mods: Modifiers, docstring: Option[String] = None): Tree = {
18711873
newLinesOpt()
18721874
atPos(tokenRange) {
18731875
val name = ident().toTypeName
18741876
val tparams = typeParamClauseOpt(ParamOwner.Type)
18751877
in.token match {
18761878
case EQUALS =>
18771879
in.nextToken()
1878-
TypeDef(name, tparams, typ()).withMods(mods)
1880+
TypeDef(name, tparams, typ()).withMods(mods).setComment(docstring)
18791881
case SUPERTYPE | SUBTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF =>
18801882
TypeDef(name, tparams, typeBounds()).withMods(mods)
18811883
case _ =>
@@ -1888,35 +1890,40 @@ object Parsers {
18881890
/** TmplDef ::= ([`case'] `class' | `trait') ClassDef
18891891
* | [`case'] `object' ObjectDef
18901892
*/
1891-
def tmplDef(start: Int, mods: Modifiers): Tree = in.token match {
1892-
case TRAIT =>
1893-
classDef(posMods(start, addFlag(mods, Trait)))
1894-
case CLASS =>
1895-
classDef(posMods(start, mods))
1896-
case CASECLASS =>
1897-
classDef(posMods(start, mods | Case))
1898-
case OBJECT =>
1899-
objectDef(posMods(start, mods | Module))
1900-
case CASEOBJECT =>
1901-
objectDef(posMods(start, mods | Case | Module))
1902-
case _ =>
1903-
syntaxErrorOrIncomplete("expected start of definition")
1904-
EmptyTree
1893+
def tmplDef(start: Int, mods: Modifiers): Tree = {
1894+
val docstring = in.getDocString(start)
1895+
in.token match {
1896+
case TRAIT =>
1897+
classDef(posMods(start, addFlag(mods, Trait)), docstring)
1898+
case CLASS =>
1899+
classDef(posMods(start, mods), docstring)
1900+
case CASECLASS =>
1901+
classDef(posMods(start, mods | Case), docstring)
1902+
case OBJECT =>
1903+
objectDef(posMods(start, mods | Module), docstring)
1904+
case CASEOBJECT =>
1905+
objectDef(posMods(start, mods | Case | Module), docstring)
1906+
case _ =>
1907+
syntaxErrorOrIncomplete("expected start of definition")
1908+
EmptyTree
1909+
}
19051910
}
19061911

19071912
/** ClassDef ::= Id [ClsTypeParamClause]
19081913
* [ConstrMods] ClsParamClauses TemplateOpt
19091914
*/
1910-
def classDef(mods: Modifiers): TypeDef = atPos(tokenRange) {
1915+
def classDef(mods: Modifiers, docstring: Option[String]): TypeDef = atPos(tokenRange) {
19111916
val name = ident().toTypeName
19121917
val constr = atPos(in.offset) {
19131918
val tparams = typeParamClauseOpt(ParamOwner.Class)
19141919
val cmods = constrModsOpt()
19151920
val vparamss = paramClauses(name, mods is Case)
1921+
19161922
makeConstructor(tparams, vparamss).withMods(cmods)
19171923
}
19181924
val templ = templateOpt(constr)
1919-
TypeDef(name, templ).withMods(mods)
1925+
1926+
TypeDef(name, templ).withMods(mods).setComment(docstring)
19201927
}
19211928

19221929
/** ConstrMods ::= AccessModifier
@@ -1932,10 +1939,11 @@ object Parsers {
19321939

19331940
/** ObjectDef ::= Id TemplateOpt
19341941
*/
1935-
def objectDef(mods: Modifiers): ModuleDef = {
1942+
def objectDef(mods: Modifiers, docstring: Option[String] = None): ModuleDef = {
19361943
val name = ident()
19371944
val template = templateOpt(emptyConstructor())
1938-
ModuleDef(name, template).withMods(mods)
1945+
1946+
ModuleDef(name, template).withMods(mods).setComment(docstring)
19391947
}
19401948

19411949
/* -------- TEMPLATES ------------------------------------------- */
@@ -2160,7 +2168,8 @@ object Parsers {
21602168
if (in.token == PACKAGE) {
21612169
in.nextToken()
21622170
if (in.token == OBJECT) {
2163-
ts += objectDef(atPos(start, in.skipToken()) { Modifiers(Package) })
2171+
val docstring = in.getDocString(start)
2172+
ts += objectDef(atPos(start, in.skipToken()) { Modifiers(Package) }, docstring)
21642173
if (in.token != EOF) {
21652174
acceptStatSep()
21662175
ts ++= topStatSeq()

src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,39 @@ object Scanners {
175175
}
176176

177177
class Scanner(source: SourceFile, override val startFrom: Offset = 0)(implicit ctx: Context) extends ScannerCommon(source)(ctx) {
178-
var keepComments = false
178+
val keepComments = ctx.settings.YkeepComments.value
179179

180-
/** All comments in the reverse order of their position in the source.
181-
* set only when `keepComments` is true.
180+
/** All doc comments as encountered, each list contains doc comments from
181+
* the same block level. Starting with the deepest level and going upward
182182
*/
183-
var revComments: List[Comment] = Nil
183+
private[this] var docsPerBlockStack: List[List[Comment]] = List(List())
184+
185+
/** Adds level of nesting to docstrings */
186+
def enterBlock(): Unit =
187+
docsPerBlockStack = Nil ::: docsPerBlockStack
188+
189+
/** Removes level of nesting for docstrings */
190+
def exitBlock(): Unit = docsPerBlockStack = docsPerBlockStack match {
191+
case x :: xs => List(List())
192+
case _ => docsPerBlockStack.tail
193+
}
194+
195+
/** Returns the closest docstring preceding the position supplied */
196+
def getDocString(pos: Int): Option[String] = {
197+
def closest(c: Comment, docstrings: List[Comment]): Comment = docstrings match {
198+
case x :: xs if (c.pos.end < x.pos.end && x.pos.end <= pos) => closest(x, xs)
199+
case Nil => c
200+
}
201+
202+
docsPerBlockStack match {
203+
case (list @ (x :: xs)) :: _ => {
204+
val c = closest(x, xs)
205+
docsPerBlockStack = list.dropWhile(_ != c).tail :: docsPerBlockStack.tail
206+
Some(c.chrs)
207+
}
208+
case _ => None
209+
}
210+
}
184211

185212
/** A buffer for comments */
186213
val commentBuf = new StringBuilder
@@ -487,13 +514,13 @@ object Scanners {
487514
case ',' =>
488515
nextChar(); token = COMMA
489516
case '(' =>
490-
nextChar(); token = LPAREN
517+
enterBlock(); nextChar(); token = LPAREN
491518
case '{' =>
492-
nextChar(); token = LBRACE
519+
enterBlock(); nextChar(); token = LBRACE
493520
case ')' =>
494-
nextChar(); token = RPAREN
521+
exitBlock(); nextChar(); token = RPAREN
495522
case '}' =>
496-
nextChar(); token = RBRACE
523+
exitBlock(); nextChar(); token = RBRACE
497524
case '[' =>
498525
nextChar(); token = LBRACKET
499526
case ']' =>
@@ -558,9 +585,12 @@ object Scanners {
558585
def finishComment(): Boolean = {
559586
if (keepComments) {
560587
val pos = Position(start, charOffset, start)
561-
nextChar()
562-
revComments = Comment(pos, flushBuf(commentBuf)) :: revComments
588+
val comment = Comment(pos, flushBuf(commentBuf))
589+
590+
if (comment.isDocComment)
591+
docsPerBlockStack = (docsPerBlockStack.head :+ comment) :: docsPerBlockStack.tail
563592
}
593+
564594
true
565595
}
566596
nextChar()

src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,19 +401,29 @@ class Namer { typer: Typer =>
401401
val pkg = createPackageSymbol(pcl.pid)
402402
index(pcl.stats)(ctx.fresh.setOwner(pkg.moduleClass))
403403
invalidateCompanions(pkg, Trees.flatten(pcl.stats map expanded))
404+
setDocstring(pkg, stat)
404405
ctx
405406
case imp: Import =>
406407
importContext(createSymbol(imp), imp.selectors)
407408
case mdef: DefTree =>
408-
enterSymbol(createSymbol(mdef))
409+
val sym = enterSymbol(createSymbol(mdef))
410+
setDocstring(sym, stat)
409411
ctx
410412
case stats: Thicket =>
411-
for (tree <- stats.toList) enterSymbol(createSymbol(tree))
413+
for (tree <- stats.toList) {
414+
val sym = enterSymbol(createSymbol(tree))
415+
setDocstring(sym, stat)
416+
}
412417
ctx
413418
case _ =>
414419
ctx
415420
}
416421

422+
def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context) = tree match {
423+
case t: MemberDef => ctx.base.addDocstring(sym, t.rawComment)
424+
case _ => ()
425+
}
426+
417427
/** Create top-level symbols for statements and enter them into symbol table */
418428
def index(stats: List[Tree])(implicit ctx: Context): Context = {
419429

@@ -859,7 +869,7 @@ class Namer { typer: Typer =>
859869
WildcardType
860870
}
861871
paramFn(typedAheadType(mdef.tpt, tptProto).tpe)
862-
}
872+
}
863873

864874
/** The type signature of a DefDef with given symbol */
865875
def defDefSig(ddef: DefDef, sym: Symbol)(implicit ctx: Context) = {

src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
586586
case _ => false
587587
}
588588

589-
/** The funcion body to be returned in the closure. Can become a TypedSplice
589+
/** The function body to be returned in the closure. Can become a TypedSplice
590590
* of a typed expression if this is necessary to infer a parameter type.
591591
*/
592592
var fnBody = tree.body

0 commit comments

Comments
 (0)