Skip to content

Commit 5b347bf

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 bdf3ef4 commit 5b347bf

File tree

4 files changed

+218
-1
lines changed

4 files changed

+218
-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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package dotty.tools.repl
33
import dotty.tools.backend.jvm.GenBCode
44
import dotty.tools.dotc.ast.Trees._
55
import dotty.tools.dotc.ast.{tpd, untpd}
6+
import dotty.tools.dotc.core.Comments.CommentsContext
67
import dotty.tools.dotc.core.Contexts._
78
import dotty.tools.dotc.core.Decorators._
89
import dotty.tools.dotc.core.Flags._
910
import dotty.tools.dotc.core.Names._
1011
import dotty.tools.dotc.core.Phases
1112
import dotty.tools.dotc.core.Phases.Phase
1213
import dotty.tools.dotc.core.StdNames._
14+
import dotty.tools.dotc.core.Symbols._
1315
import dotty.tools.dotc.reporting.diagnostic.messages
1416
import dotty.tools.dotc.typer.{FrontEnd, ImportInfo}
1517
import dotty.tools.dotc.util.Positions._
@@ -164,6 +166,43 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {
164166
}
165167
}
166168

169+
def docOf(expr: String)(implicit state: State): Result[String] = {
170+
implicit val ctx: Context = state.context
171+
172+
def pickSymbol(symbol: Symbol): Symbol = {
173+
if (symbol.is(Module, butNot = ModuleClass)) symbol.moduleClass
174+
if (symbol.isConstructor) symbol.owner
175+
else symbol
176+
}
177+
178+
def extractSymbol(tree: tpd.Tree): Symbol = {
179+
tree match {
180+
case tpd.closureDef(defdef) => defdef.rhs.symbol
181+
case _ => tree.symbol
182+
}
183+
}
184+
185+
typeCheck(expr).map {
186+
case v @ ValDef(_, _, Block(stats, _)) if stats.nonEmpty =>
187+
val stat = stats.last.asInstanceOf[tpd.Tree]
188+
if (stat.tpe.isError) stat.tpe.show
189+
else {
190+
val symbol = pickSymbol(extractSymbol(stat))
191+
val doc =
192+
for { docCtx <- ctx.docCtx
193+
doc <- docCtx.docstrings.get(symbol) } yield doc.raw
194+
195+
doc.getOrElse(s"// No doc for ${symbol.show}")
196+
}
197+
198+
case _ =>
199+
"""Couldn't display the documentation for your expression, so sorry :(
200+
|
201+
|Please report this to my masters at github.com/lampepfl/dotty
202+
""".stripMargin
203+
}
204+
}
205+
167206
final def typeCheck(expr: String, errorsAllowed: Boolean = false)(implicit state: State): Result[tpd.ValDef] = {
168207

169208
def wrapped(expr: String, sourceFile: SourceFile, state: State)(implicit ctx: Context): Result[untpd.PackageDef] = {

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

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

6464
/** Create a fresh and initialized context with IDE mode enabled */
6565
private[this] def initialCtx = {
66-
val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive)
66+
val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive).addMode(Mode.ReadComments)
6767
val ictx = setup(settings, rootCtx)._2
6868
ictx.base.initialize()(ictx)
6969
ictx
@@ -338,6 +338,13 @@ class ReplDriver(settings: Array[String],
338338
)
339339
state
340340

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

0 commit comments

Comments
 (0)