Skip to content

Commit 54360d0

Browse files
authored
Merge pull request #9005 from rethab/8677/output-lines-order
sort output of definitions in repl, fixes #8677
2 parents 0a62671 + 34371fa commit 54360d0

File tree

9 files changed

+124
-72
lines changed

9 files changed

+124
-72
lines changed

compiler/src/dotty/tools/dotc/reporting/Message.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ object Message {
2424
* consumed by a subclass of `Reporter`. However, the error position is only
2525
* part of `Diagnostic`, not `Message`.
2626
*
27-
* NOTE: you should not be persisting Most messages take an implicit
28-
* `Context` and these contexts weigh in at about 4mb per instance, as such
29-
* persisting these will result in a memory leak.
27+
* NOTE: you should not persist a message directly, because most messages take
28+
* an implicit `Context` and these contexts weigh in at about 4mb per instance.
29+
* Therefore, persisting these will result in a memory leak.
3030
*
3131
* Instead use the `persist` method to create an instance that does not keep a
3232
* reference to these contexts.

compiler/src/dotty/tools/dotc/util/Spans.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import language.implicitConversions
55
/** The offsets part of a full position, consisting of 2 or 3 entries:
66
* - start: the start offset of the span, in characters from start of file
77
* - end : the end offset of the span
8-
* - point: if given, the offset where a sing;le `^` would be logically placed
8+
* - point: if given, the offset where a single `^` would be logically placed
99
*
10-
& Spans are encoded according to the following format in little endian:
10+
* Spans are encoded according to the following format in little endian:
1111
* Start: unsigned 26 Bits (works for source files up to 64M)
1212
* End: unsigned 26 Bits
1313
* Point: unsigned 12 Bits relative to start
@@ -30,8 +30,8 @@ object Spans {
3030

3131
/** A span indicates a range between a start offset and an end offset.
3232
* Spans can be synthetic or source-derived. A source-derived span
33-
* has in addition a point lies somewhere between start and end. The point
34-
* is roughly where the ^ would go if an error was diagnosed at that position.
33+
* has in addition a point. The point lies somewhere between start and end. The point
34+
* is roughly where the `^` would go if an error was diagnosed at that position.
3535
* All quantities are encoded opaquely in a Long.
3636
*/
3737
class Span(val coords: Long) extends AnyVal {

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

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ import java.io.{ StringWriter, PrintWriter }
55
import java.lang.{ ClassLoader, ExceptionInInitializerError }
66
import java.lang.reflect.InvocationTargetException
77

8+
import dotc.ast.tpd
89
import dotc.core.Contexts.Context
910
import dotc.core.Denotations.Denotation
1011
import dotc.core.Flags
11-
import dotc.core.Symbols.Symbol
12+
import dotc.core.Flags._
13+
import dotc.core.Symbols.{Symbol, defn}
1214
import dotc.core.StdNames.str
15+
import dotc.core.NameOps.NameDecorator
16+
import dotc.printing.ReplPrinter
17+
import dotc.reporting.{MessageRendering, Message, Diagnostic}
18+
import dotc.util.SourcePosition
1319

1420
/** This rendering object uses `ClassLoader`s to accomplish crossing the 4th
1521
* wall (i.e. fetching back values from the compiled class files put into a
@@ -21,8 +27,15 @@ import dotc.core.StdNames.str
2127
*/
2228
private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) {
2329

30+
import Rendering._
31+
2432
private val MaxStringElements: Int = 1000 // no need to mkString billions of elements
2533

34+
/** A `MessageRenderer` for the REPL without file positions */
35+
private val messageRenderer = new MessageRendering {
36+
override def posStr(pos: SourcePosition, diagnosticLevel: String, message: Message)(implicit ctx: Context): String = ""
37+
}
38+
2639
private var myClassLoader: ClassLoader = _
2740

2841
private var myReplStringOf: Object => String = _
@@ -63,7 +76,6 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) {
6376
* Calling this method evaluates the expression using reflection
6477
*/
6578
private def valueOf(sym: Symbol)(implicit ctx: Context): Option[String] = {
66-
val defn = ctx.definitions
6779
val objectName = sym.owner.fullName.encode.toString.stripSuffix("$")
6880
val resObj: Class[?] = Class.forName(objectName, true, classLoader())
6981
val value =
@@ -82,19 +94,33 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) {
8294
}
8395
}
8496

97+
/** Formats errors using the `messageRenderer` */
98+
def formatError(dia: Diagnostic)(implicit state: State): Diagnostic =
99+
new Diagnostic(
100+
messageRenderer.messageAndPos(dia.msg, dia.pos, messageRenderer.diagnosticLevel(dia))(state.context),
101+
dia.pos,
102+
dia.level
103+
)
104+
105+
def renderTypeDef(d: Denotation)(implicit ctx: Context): Diagnostic =
106+
infoDiagnostic("// defined " ++ d.symbol.showUser, d)
107+
108+
def renderTypeAlias(d: Denotation)(implicit ctx: Context): Diagnostic =
109+
infoDiagnostic("// defined alias " ++ d.symbol.showUser, d)
110+
85111
/** Render method definition result */
86-
def renderMethod(d: Denotation)(implicit ctx: Context): String =
87-
d.symbol.showUser
112+
def renderMethod(d: Denotation)(implicit ctx: Context): Diagnostic =
113+
infoDiagnostic(d.symbol.showUser, d)
88114

89115
/** Render value definition result */
90-
def renderVal(d: Denotation)(implicit ctx: Context): Option[String] = {
116+
def renderVal(d: Denotation)(implicit ctx: Context): Option[Diagnostic] = {
91117
val dcl = d.symbol.showUser
92118

93119
try {
94-
if (d.symbol.is(Flags.Lazy)) Some(dcl)
95-
else valueOf(d.symbol).map(value => s"$dcl = $value")
120+
if (d.symbol.is(Flags.Lazy)) Some(infoDiagnostic(dcl, d))
121+
else valueOf(d.symbol).map(value => infoDiagnostic(s"$dcl = $value", d))
96122
}
97-
catch { case ex: InvocationTargetException => Some(renderError(ex)) }
123+
catch { case ex: InvocationTargetException => Some(infoDiagnostic(renderError(ex), d)) }
98124
}
99125

100126
/** Render the stack trace of the underlying exception */
@@ -108,4 +134,19 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) {
108134
cause.printStackTrace(pw)
109135
sw.toString
110136
}
137+
138+
private def infoDiagnostic(msg: String, d: Denotation)(implicit ctx: Context): Diagnostic =
139+
new Diagnostic.Info(msg, d.symbol.sourcePos)
140+
141+
}
142+
143+
object Rendering {
144+
145+
implicit class ShowUser(val s: Symbol) extends AnyVal {
146+
def showUser(implicit ctx: Context): String = {
147+
val printer = new ReplPrinter(ctx)
148+
val text = printer.dclText(s)
149+
text.mkString(ctx.settings.pageWidth.value, ctx.settings.printLines.value)
150+
}
151+
}
111152
}

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

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ class ReplDriver(settings: Array[String],
139139
// TODO: i5069
140140
final def bind(name: String, value: Any)(implicit state: State): State = state
141141

142+
// redirecting the output allows us to test `println` in scripted tests
142143
private def withRedirectedOutput(op: => State): State = {
143144
val savedOut = System.out
144145
val savedErr = System.err
@@ -238,19 +239,34 @@ class ReplDriver(settings: Array[String],
238239
allImports += (newState.objectIndex -> newImports)
239240
val newStateWithImports = newState.copy(imports = allImports)
240241

241-
val warnings = newState.context.reporter.removeBufferedMessages(newState.context)
242-
displayErrors(warnings)(newState) // display warnings
243-
implicit val ctx = newState.context
244-
if (!ctx.settings.XreplDisableDisplay.value)
245-
displayDefinitions(unit.tpdTree, newestWrapper)(newStateWithImports)
246-
else
247-
newStateWithImports
242+
val warnings = newState.context.reporter
243+
.removeBufferedMessages(newState.context)
244+
.map(rendering.formatError)
245+
246+
implicit val ctx: Context = newState.context
247+
val (updatedState, definitions) =
248+
if (!ctx.settings.XreplDisableDisplay.value)
249+
renderDefinitions(unit.tpdTree, newestWrapper)(newStateWithImports)
250+
else
251+
(newStateWithImports, Seq.empty)
252+
253+
// output is printed in the order it was put in. warnings should be
254+
// shown before infos (eg. typedefs) for the same line. column
255+
// ordering is mostly to make tests deterministic
256+
implicit val diagnosticOrdering: Ordering[Diagnostic] =
257+
Ordering[(Int, Int, Int)].on(d => (d.pos.line, -d.level, d.pos.column))
258+
259+
(definitions ++ warnings)
260+
.sorted
261+
.map(_.msg)
262+
.foreach(out.println)
263+
264+
updatedState
248265
}
249266
)
250267
}
251268

252-
/** Display definitions from `tree` */
253-
private def displayDefinitions(tree: tpd.Tree, newestWrapper: Name)(implicit state: State): State = {
269+
private def renderDefinitions(tree: tpd.Tree, newestWrapper: Name)(implicit state: State): (State, Seq[Diagnostic]) = {
254270
implicit val ctx = state.context
255271

256272
def resAndUnit(denot: Denotation) = {
@@ -264,7 +280,7 @@ class ReplDriver(settings: Array[String],
264280
name.startsWith(str.REPL_RES_PREFIX) && hasValidNumber && sym.info == defn.UnitType
265281
}
266282

267-
def displayMembers(symbol: Symbol) = if (tree.symbol.info.exists) {
283+
def extractAndFormatMembers(symbol: Symbol): (State, Seq[Diagnostic]) = if (tree.symbol.info.exists) {
268284
val info = symbol.info
269285
val defs =
270286
info.bounds.hi.finalResultType
@@ -274,51 +290,47 @@ class ReplDriver(settings: Array[String],
274290
denot.symbol.owner == defn.ObjectClass ||
275291
denot.symbol.isConstructor
276292
}
277-
.sortBy(_.name)
278293

279294
val vals =
280295
info.fields
281296
.filterNot(_.symbol.isOneOf(ParamAccessor | Private | Synthetic | Artifact | Module))
282297
.filter(_.symbol.name.is(SimpleNameKind))
283-
.sortBy(_.name)
284298

285299
val typeAliases =
286-
info.bounds.hi.typeMembers.filter(_.symbol.info.isTypeAlias).sortBy(_.name)
300+
info.bounds.hi.typeMembers.filter(_.symbol.info.isTypeAlias)
287301

288-
(
289-
typeAliases.map("// defined alias " + _.symbol.showUser) ++
302+
val formattedMembers =
303+
typeAliases.map(rendering.renderTypeAlias) ++
290304
defs.map(rendering.renderMethod) ++
291-
vals.map(rendering.renderVal).flatten
292-
).foreach(str => out.println(SyntaxHighlighting.highlight(str)))
305+
vals.flatMap(rendering.renderVal)
293306

294-
state.copy(valIndex = state.valIndex - vals.count(resAndUnit))
307+
(state.copy(valIndex = state.valIndex - vals.count(resAndUnit)), formattedMembers)
295308
}
296-
else state
309+
else (state, Seq.empty)
297310

298311
def isSyntheticCompanion(sym: Symbol) =
299312
sym.is(Module) && sym.is(Synthetic)
300313

301-
def displayTypeDefs(sym: Symbol) = sym.info.memberClasses
314+
def typeDefs(sym: Symbol): Seq[Diagnostic] = sym.info.memberClasses
302315
.collect {
303316
case x if !isSyntheticCompanion(x.symbol) && !x.symbol.name.isReplWrapperName =>
304-
x.symbol
317+
rendering.renderTypeDef(x)
305318
}
306-
.foreach { sym =>
307-
out.println(SyntaxHighlighting.highlight("// defined " + sym.showUser))
308-
}
309-
310319

311320
ctx.atPhase(ctx.typerPhase.next) {
312321
// Display members of wrapped module:
313322
tree.symbol.info.memberClasses
314323
.find(_.symbol.name == newestWrapper.moduleClassName)
315324
.map { wrapperModule =>
316-
displayTypeDefs(wrapperModule.symbol)
317-
displayMembers(wrapperModule.symbol)
325+
val formattedTypeDefs = typeDefs(wrapperModule.symbol)
326+
val (newState, formattedMembers) = extractAndFormatMembers(wrapperModule.symbol)
327+
val highlighted = (formattedTypeDefs ++ formattedMembers)
328+
.map(d => new Diagnostic(d.msg.mapMsg(SyntaxHighlighting.highlight), d.pos, d.level))
329+
(newState, highlighted)
318330
}
319331
.getOrElse {
320332
// user defined a trait/class/object, so no module needed
321-
state
333+
(state, Seq.empty)
322334
}
323335
}
324336
}
@@ -378,18 +390,9 @@ class ReplDriver(settings: Array[String],
378390
state
379391
}
380392

381-
/** A `MessageRenderer` without file positions */
382-
private val messageRenderer = new MessageRendering {
383-
override def posStr(pos: SourcePosition, diagnosticLevel: String, message: Message)(implicit ctx: Context): String = ""
384-
}
385-
386-
/** Render messages using the `MessageRendering` trait */
387-
private def renderMessage(dia: Diagnostic): Context => String =
388-
messageRenderer.messageAndPos(dia.msg, dia.pos, messageRenderer.diagnosticLevel(dia))(_)
389-
390-
/** Output errors to `out` */
393+
/** shows all errors nicely formatted */
391394
private def displayErrors(errs: Seq[Diagnostic])(implicit state: State): State = {
392-
errs.map(renderMessage(_)(state.context)).foreach(out.println)
395+
errs.map(rendering.formatError).map(_.msg).foreach(out.println)
393396
state
394397
}
395398
}
Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
package dotty.tools
22

3-
import dotc.core.Contexts.Context
4-
import dotc.core.Symbols.Symbol
5-
import dotc.printing.ReplPrinter
63
import dotc.reporting.{HideNonSensicalMessages, StoreReporter, UniqueMessagePositions}
74

85
package object repl {
96
/** Create empty outer store reporter */
107
private[repl] def newStoreReporter: StoreReporter =
118
new StoreReporter(null)
129
with UniqueMessagePositions with HideNonSensicalMessages
13-
14-
private[repl] implicit class ShowUser(val s: Symbol) extends AnyVal {
15-
def showUser(implicit ctx: Context): String = {
16-
val printer = new ReplPrinter(ctx)
17-
val text = printer.dclText(s)
18-
text.mkString(ctx.settings.pageWidth.value, ctx.settings.printLines.value)
19-
}
20-
}
2110
}

compiler/test-resources/repl/top-level-block

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ scala> f + g
1717
val res3: Int = 10
1818

1919
scala> { val x = 3; 4; val y = 5 }
20-
val res4: Int = 4
2120
val x: Int = 3
21+
val res4: Int = 4
2222
val y: Int = 5

compiler/test/dotty/tools/repl/LoadTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ class LoadTests extends ReplTest {
4242
file = """|@main def helloWorld = println("Hello, World!")
4343
|@main def helloTo(name: String) = println(s"Hello, $name!")
4444
|""".stripMargin,
45-
defs = """|def helloTo(name: String): Unit
46-
|def helloWorld: Unit
45+
defs = """|def helloWorld: Unit
46+
|def helloTo(name: String): Unit
4747
|
4848
|
4949
|""".stripMargin,

compiler/test/dotty/tools/repl/ReplCompilerTests.scala

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ class ReplCompilerTests extends ReplTest {
5151

5252
val expected = List(
5353
"def foo: Int",
54-
"val res0: Int = 2",
55-
"val res1: Int = 20",
5654
"val x: Int = 10",
57-
"var y: Int = 5"
55+
"val res0: Int = 2",
56+
"var y: Int = 5",
57+
"val res1: Int = 20"
5858
)
5959

6060
assertEquals(expected, lines())
@@ -84,6 +84,25 @@ class ReplCompilerTests extends ReplTest {
8484
assertEquals("x: Int = 10", storedOutput().trim)
8585
}
8686

87+
@Test def i8677 = fromInitialState { implicit state =>
88+
run {
89+
"""|sealed trait T1
90+
|case class X() extends T1
91+
|case class Y() extends T1
92+
| case object O extends T1
93+
""".stripMargin
94+
}
95+
96+
val expected = List(
97+
"// defined trait T1",
98+
"// defined case class X",
99+
"// defined case class Y",
100+
"// defined case object O"
101+
)
102+
103+
assertEquals(expected, lines())
104+
}
105+
87106
// FIXME: Tests are not run in isolation, the classloader is corrupted after the first exception
88107
@Ignore @Test def i3305: Unit = {
89108
fromInitialState { implicit s =>

language-server/test/dotty/tools/languageserver/WorksheetTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class WorksheetTest {
9696
@Test def patternMatching1: Unit = {
9797
ws"""${m1}val (foo, bar) = (1, 2)${m2}""".withSource
9898
.run(m1,
99-
((m1 to m2), s"val bar: Int = 2${nl}val foo: Int = 1"))
99+
((m1 to m2), s"val foo: Int = 1${nl}val bar: Int = 2"))
100100
}
101101

102102
@Test def evaluationException: Unit = {

0 commit comments

Comments
 (0)