Skip to content

Commit 76ca743

Browse files
committed
Add :doc command to REPL
This command is used to display the documentation associated to the given expression. For instance: ```scala scala> /** A class */ class A scala> /** An object */ object O { /** A def */ def foo = 0 } scala> :doc new A /** A class */ scala> :doc O /** An object */ scala> :doc O.foo /** A def */ ```
1 parent 27e85b2 commit 76ca743

File tree

4 files changed

+204
-1
lines changed

4 files changed

+204
-1
lines changed

compiler/src/dotty/tools/repl/ParseResult.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ object TypeOf {
6060
val command = ":type"
6161
}
6262

63+
/**
64+
* A command that is used to display the documentation associated with
65+
* the given expression.
66+
*/
67+
case class DocOf(expr: String) extends Command
68+
object DocOf {
69+
val command = ":doc"
70+
}
71+
6372
/** `:imports` lists the imports that have been explicitly imported during the
6473
* session
6574
*/
@@ -89,6 +98,7 @@ case object Help extends Command {
8998
|:load <path> interpret lines in a file
9099
|:quit exit the interpreter
91100
|:type <expression> evaluate the type of the given expression
101+
|:doc <expression> print the documentation for the given expresssion
92102
|:imports show import history
93103
|:reset reset the repl to its initial state, forgetting all session entries
94104
""".stripMargin
@@ -117,6 +127,7 @@ object ParseResult {
117127
case Imports.command => Imports
118128
case Load.command => Load(arg)
119129
case TypeOf.command => TypeOf(arg)
130+
case DocOf.command => DocOf(arg)
120131
case _ => UnknownCommand(cmd)
121132
}
122133
case _ => {

compiler/src/dotty/tools/repl/ReplCompiler.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import dotc.ast.{ untpd, tpd }
66
import dotc.{ Run, CompilationUnit, Compiler }
77
import dotc.core.Decorators._, dotc.core.Flags._, dotc.core.Phases, Phases.Phase
88
import dotc.core.Names._, dotc.core.Contexts._, dotc.core.StdNames._
9+
import dotc.core.Symbols._, dotc.core.Comments._
910
import dotc.core.Constants.Constant
1011
import dotc.util.SourceFile
1112
import dotc.typer.{ ImportInfo, FrontEnd }
@@ -151,6 +152,45 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {
151152
runCompilationUnit(unit, defs.state)
152153
}
153154

155+
def docOf(expr: String)(implicit state: State): Result[String] = {
156+
implicit val ctx: Context = state.run.runContext
157+
158+
def pickSymbol(symbol: Symbol): Symbol = {
159+
if (symbol.is(Module, butNot = ModuleClass)) symbol.moduleClass
160+
if (symbol.isConstructor) symbol.owner
161+
else symbol
162+
}
163+
164+
def extractSymbol(tree: tpd.Tree): Symbol = {
165+
tree match {
166+
case tpd.closureDef(defdef) => defdef.rhs.symbol
167+
case _ => tree.symbol
168+
}
169+
}
170+
171+
typeCheck(expr).map {
172+
case v @ ValDef(_, _, Block(stats, _)) if stats.nonEmpty =>
173+
val stat = stats.last.asInstanceOf[tpd.Tree]
174+
if (stat.tpe.isError) stat.tpe.show
175+
else {
176+
val symbol = pickSymbol(extractSymbol(stat))
177+
val doc =
178+
for { docCtx <- ctx.docCtx
179+
doc <- docCtx.docstrings.get(symbol) } yield doc.raw
180+
181+
doc.getOrElse(s"// No doc for ${symbol.show}")
182+
}
183+
184+
case _ =>
185+
"""Couldn't display the documentation for your expression, so sorry :(
186+
|
187+
|Please report this to my masters at github.com/lampepfl/dotty
188+
""".stripMargin
189+
}
190+
191+
192+
}
193+
154194
def typeOf(expr: String)(implicit state: State): Result[String] =
155195
typeCheck(expr).map { tree =>
156196
import dotc.ast.Trees._

compiler/src/dotty/tools/repl/ReplDriver.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class ReplDriver(settings: Array[String],
9191

9292
/** Create a fresh and initialized context with IDE mode enabled */
9393
private[this] def initialCtx = {
94-
val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive)
94+
val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive).addMode(Mode.ReadComments)
9595
val ictx = setup(settings, rootCtx)._2.fresh
9696
ictx.base.initialize()(ictx)
9797
ictx
@@ -370,6 +370,13 @@ class ReplDriver(settings: Array[String],
370370
state.withHistory(s"${TypeOf.command} $expr")
371371
}
372372

373+
case DocOf(expr) =>
374+
compiler.docOf(expr).fold(
375+
displayErrors,
376+
res => out.println(SyntaxHighlighting(res))
377+
)
378+
state.withHistory(s"${DocOf.command} $expr")
379+
373380
case Quit =>
374381
// end of the world!
375382
state
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package dotty.tools
2+
package repl
3+
4+
import org.junit.Test
5+
import org.junit.Assert.assertEquals
6+
7+
import ReplTest._
8+
9+
class DocTests extends ReplTest {
10+
@Test def docOfDef =
11+
fromInitialState { implicit s => compile("/** doc */ def foo = 0") }
12+
.andThen { implicit s =>
13+
compiler.docOf("foo")
14+
.fold(onErrors, assertEquals("/** doc */", _))
15+
}
16+
17+
@Test def docOfVal =
18+
fromInitialState { implicit s => compile("/** doc */ val foo = 0") }
19+
.andThen { implicit s =>
20+
compiler.docOf("foo")
21+
.fold(onErrors, assertEquals("/** doc */", _))
22+
}
23+
24+
@Test def docOfObject =
25+
fromInitialState { implicit s => compile("/** doc */ object Foo") }
26+
.andThen { implicit s =>
27+
compiler.docOf("Foo")
28+
.fold(onErrors, assertEquals("/** doc */", _))
29+
}
30+
31+
@Test def docOfClass =
32+
fromInitialState { implicit s => compile("/** doc */ class Foo") }
33+
.andThen { implicit s =>
34+
compiler.docOf("new Foo")
35+
.fold(onErrors, assertEquals("/** doc */", _))
36+
}
37+
38+
@Test def docOfTrait =
39+
fromInitialState { implicit s => compile("/** doc */ trait Foo") }
40+
.andThen { implicit s =>
41+
compiler.docOf("new Foo")
42+
.fold(onErrors, assertEquals("/** doc */", _))
43+
}
44+
45+
@Test def docOfDefInObject =
46+
fromInitialState { implicit s => compile("object O { /** doc */ def foo = 0 }") }
47+
.andThen { implicit s =>
48+
compiler.docOf("O.foo")
49+
.fold(onErrors, assertEquals("/** doc */", _))
50+
}
51+
52+
@Test def docOfValInObject =
53+
fromInitialState { implicit s => compile("object O { /** doc */ val foo = 0 }") }
54+
.andThen { implicit s =>
55+
compiler.docOf("O.foo")
56+
.fold(onErrors, assertEquals("/** doc */", _))
57+
}
58+
59+
@Test def docOfObjectInObject =
60+
fromInitialState { implicit s => compile("object O { /** doc */ object Foo }") }
61+
.andThen { implicit s =>
62+
compiler.docOf("O.Foo")
63+
.fold(onErrors, assertEquals("/** doc */", _))
64+
}
65+
66+
@Test def docOfClassInObject =
67+
fromInitialState { implicit s => compile("object O { /** doc */ class Foo }") }
68+
.andThen { implicit s =>
69+
compiler.docOf("new O.Foo")
70+
.fold(onErrors, assertEquals("/** doc */", _))
71+
}
72+
73+
@Test def docOfTraitInObject =
74+
fromInitialState { implicit s => compile("object O { /** doc */ trait Foo }") }
75+
.andThen { implicit s =>
76+
compiler.docOf("new O.Foo")
77+
.fold(onErrors, assertEquals("/** doc */", _))
78+
}
79+
80+
@Test def docOfDetInClass =
81+
fromInitialState { implicit s => compile("class C { /** doc */ def foo = 0 }") }
82+
.andThen { implicit s => compile("val c = new C") }
83+
.andThen { implicit s =>
84+
compiler.docOf("c.foo")
85+
.fold(onErrors, assertEquals("/** doc */", _))
86+
}
87+
88+
@Test def docOfVatInClass =
89+
fromInitialState { implicit s => compile("class C { /** doc */ val foo = 0 }") }
90+
.andThen { implicit s => compile("val c = new C") }
91+
.andThen { implicit s =>
92+
compiler.docOf("c.foo")
93+
.fold(onErrors, assertEquals("/** doc */", _))
94+
}
95+
96+
@Test def docOfObjectInClass =
97+
fromInitialState { implicit s => compile("class C { /** doc */ object Foo }") }
98+
.andThen { implicit s => compile("val c = new C") }
99+
.andThen { implicit s =>
100+
compiler.docOf("c.Foo")
101+
.fold(onErrors, assertEquals("/** doc */", _))
102+
}
103+
104+
@Test def docOfClassInClass =
105+
fromInitialState { implicit s => compile("class C { /** doc */ class Foo }") }
106+
.andThen { implicit s => compile("val c = new C") }
107+
.andThen { implicit s =>
108+
compiler.docOf("new c.Foo")
109+
.fold(onErrors, assertEquals("/** doc */", _))
110+
}
111+
112+
@Test def docOfTraitInClass =
113+
fromInitialState { implicit s => compile("class C { /** doc */ trait Foo }") }
114+
.andThen { implicit s => compile("val c = new C") }
115+
.andThen { implicit s =>
116+
compiler.docOf("new c.Foo")
117+
.fold(onErrors, assertEquals("/** doc */", _))
118+
}
119+
120+
@Test def docOfOverloadedDef =
121+
fromInitialState { implicit s =>
122+
compile("""object O {
123+
|/** doc0 */ def foo(x: Int) = x
124+
|/** doc1 */ def foo(x: String) = x
125+
|}""".stripMargin)
126+
}
127+
.andThen { implicit s =>
128+
compiler.docOf("O.foo(_: Int)")
129+
.fold(onErrors, assertEquals("/** doc0 */", _))
130+
s
131+
}
132+
.andThen { implicit s =>
133+
compiler.docOf("O.foo(_: String)")
134+
.fold(onErrors, assertEquals("/** doc1 */", _))
135+
}
136+
137+
@Test def docOfInherited =
138+
fromInitialState { implicit s => compile("class C { /** doc */ def foo = 0 }") }
139+
.andThen { implicit s => compile("object O extends C") }
140+
.andThen { implicit s =>
141+
compiler.docOf("O.foo")
142+
.fold(onErrors, assertEquals("/** doc */", _))
143+
}
144+
145+
}

0 commit comments

Comments
 (0)