Skip to content

Commit 123eb4e

Browse files
committed
Add links to Awsome Error Messages
Error messages should support including links for further reading that can be picked up by tools like IDEs. Examples are links to the language specification, or the "changes in Dotty" pages.
1 parent af528a1 commit 123eb4e

File tree

6 files changed

+77
-12
lines changed

6 files changed

+77
-12
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ class ConsoleReporter(
3434
true
3535
}
3636

37-
if (didPrint && ctx.shouldExplain(m))
37+
if (didPrint && ctx.shouldExplain(m)) {
3838
printMessage(explanation(m.contained()))
39+
if (m.contained().links.nonEmpty) printMessage(documentationLinks(m.contained()))
40+
}
3941
else if (didPrint && m.contained().explanation.nonEmpty)
4042
printMessage("\nlonger explanation available when compiling with `-explain`")
4143
}

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ package reporting
44

55
import core.Contexts.Context
66
import core.Decorators._
7-
import printing.Highlighting.{Blue, Red}
7+
import printing.Highlighting.{Blue, Red, Yellow}
88
import printing.SyntaxHighlighting
99
import diagnostic.{ErrorMessageID, Message, MessageContainer, NoExplanation}
1010
import diagnostic.messages._
1111
import util.SourcePosition
12-
import util.Chars.{ LF, CR, FF, SU }
13-
import scala.annotation.switch
12+
import util.Chars.{CR, FF, LF, SU}
1413

14+
import scala.annotation.switch
1515
import scala.collection.mutable
1616

1717
trait MessageRendering {
@@ -130,13 +130,29 @@ trait MessageRendering {
130130
val sb = new StringBuilder(
131131
hl"""|
132132
|${Blue("Explanation")}
133-
|${Blue("===========")}"""
133+
|${Blue("===========")}
134+
|"""
134135
)
135-
sb.append('\n').append(m.explanation)
136-
if (m.explanation.lastOption != Some('\n')) sb.append('\n')
136+
sb.append(m.explanation)
137+
if (!m.explanation.endsWith("\n")) sb.append('\n')
137138
sb.toString
138139
}
139140

141+
/** Documentation links rendered under "Further reading" header */
142+
def documentationLinks(m: Message)(implicit ctx: Context): String = {
143+
val sb = new StringBuilder(
144+
hl"""|
145+
|${Blue("Further reading")}
146+
|${Blue("===============")}
147+
|"""
148+
)
149+
for (link <- m.links) {
150+
sb.append(link.text).append(hl" ${Yellow(link.url)}\n")
151+
}
152+
sb.append('\n').toString
153+
}
154+
155+
140156
/** The whole message rendered from `msg` */
141157
def messageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(implicit ctx: Context): String = {
142158
val sb = mutable.StringBuilder.newBuilder

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ object Message {
1717
new NoExplanation(str)
1818
}
1919

20+
sealed trait DocumentationLink {
21+
def text: String
22+
def url: String
23+
}
24+
25+
object DocumentationLink {
26+
case class LanguageSpec(text: String = "Language Specification", suffix: String) extends DocumentationLink {
27+
val url = s"https://www.scala-lang.org/files/archive/spec/2.13/$suffix"
28+
}
29+
case class TourUrl(text: String, suffix: String) extends DocumentationLink {
30+
val url = s"http://docs.scala-lang.org/overviews/$suffix"
31+
}
32+
case class DottyDocs(text: String = "Dotty documentation", suffix: String) extends DocumentationLink {
33+
val url = s"http://dotty.epfl.ch/docs/$suffix"
34+
}
35+
case class FullUrl(text: String, url: String) extends DocumentationLink
36+
}
37+
2038
/** A `Message` contains all semantic information necessary to easily
2139
* comprehend what caused the message to be logged. Each message can be turned
2240
* into a `MessageContainer` which contains the log level and can later be
@@ -56,6 +74,8 @@ abstract class Message(val errorId: ErrorMessageID) { self =>
5674
*/
5775
def explanation: String
5876

77+
def links: List[DocumentationLink] = Nil
78+
5979
/** The implicit `Context` in messages is a large thing that we don't want
6080
* persisted. This method gets around that by duplicating the message
6181
* without the implicit context being passed along.
@@ -64,6 +84,8 @@ abstract class Message(val errorId: ErrorMessageID) { self =>
6484
val msg = self.msg
6585
val kind = self.kind
6686
val explanation = self.explanation
87+
private val persistedLinks = self.links
88+
override def links = persistedLinks
6789
}
6890
}
6991

compiler/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ object MessageContainer {
1515
implicit class MessageContext(val c: Context) extends AnyVal {
1616
def shouldExplain(cont: MessageContainer): Boolean = {
1717
implicit val ctx = c
18-
cont.contained().explanation match {
19-
case "" => false
20-
case _ => ctx.settings.explain.value
21-
}
18+
cont.contained().explanation.nonEmpty && c.settings.explain.value
2219
}
2320
}
2421
}

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import dotty.tools.dotc.ast.Trees
2323
import dotty.tools.dotc.ast.untpd.Modifiers
2424
import dotty.tools.dotc.core.Flags.{FlagSet, Mutable}
2525
import dotty.tools.dotc.core.SymDenotations.SymDenotation
26+
import dotty.tools.dotc.reporting.diagnostic.DocumentationLink.{DottyDocs, LanguageSpec}
27+
2628
import scala.util.control.NonFatal
2729

2830
object messages {
@@ -137,6 +139,10 @@ object messages {
137139
|It is recommended to use the ${"NonFatal"} extractor to catch all exceptions as it
138140
|correctly handles transfer functions like ${"return"}."""
139141
}
142+
143+
override def links: List[DocumentationLink] = List(
144+
LanguageSpec(suffix = "06-expressions.html#try-expressions")
145+
)
140146
}
141147

142148
case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context)
@@ -164,6 +170,10 @@ object messages {
164170
hl"""|Dotty introduces intersection types - `&' types. These replace the
165171
|use of the ${"with"} keyword. There are a few differences in
166172
|semantics between intersection types and using `${"with"}'."""
173+
174+
override def links = List(
175+
DottyDocs(suffix = "reference/intersection-types.html")
176+
)
167177
}
168178

169179
case class CaseClassMissingParamList(cdef: untpd.TypeDef)(implicit ctx: Context)
@@ -176,6 +186,10 @@ object messages {
176186
hl"""|${cdef.name} must have at least one parameter list, if you would rather
177187
|have a singleton representation of ${cdef.name}, use a "${"case object"}".
178188
|Or, add an explicit `()' as a parameter list to ${cdef.name}."""
189+
190+
override def links = List(
191+
LanguageSpec(suffix = "05-classes-and-objects.html#case-classes")
192+
)
179193
}
180194

181195
case class AnonymousFunctionMissingParamType(param: untpd.ValDef,
@@ -204,6 +218,10 @@ object messages {
204218
|Make sure you give it a type of what you expect to match and help the type inference system:
205219
|
206220
|${"val f: Seq[Int] => Option[List[Int]] = { case xs @ List(1, 2, 3) => Some(xs) }"} """
221+
222+
override def links = List(
223+
LanguageSpec(suffix = "06-expressions.html#anonymous-functions")
224+
)
207225
}
208226

209227
case class WildcardOnTypeArgumentNotAllowedOnNew()(implicit ctx: Context)
@@ -410,7 +428,12 @@ object messages {
410428
|
411429
|$code2
412430
|"""
431+
413432
}
433+
434+
override def links = List(
435+
DottyDocs(suffix = "reference/trait-parameters.html")
436+
)
414437
}
415438

416439
case class TopLevelImplicitClass(cdef: untpd.TypeDef)(implicit ctx: Context)
@@ -1706,6 +1729,10 @@ object messages {
17061729
|In this instance, the modifier combination is not supported
17071730
"""
17081731
}
1732+
1733+
override def links: List[DocumentationLink] = List(
1734+
LanguageSpec("Please see the official Scala Language Specification section on modifiers",
1735+
"05-classes-and-objects.html#modifiers"))
17091736
}
17101737

17111738
case class ImplicitFunctionTypeNeedsNonEmptyParameterList()(implicit ctx: Context)

sbt-bridge/src/xsbt/DelegatingReporter.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter
4747

4848
val sb = new StringBuilder()
4949
sb.append(messageAndPos(cont.contained(), cont.pos, diagnosticLevel(cont)))
50-
if (ctx.shouldExplain(cont) && cont.contained().explanation.nonEmpty) {
50+
if (ctx.shouldExplain(cont)) {
5151
sb.append(explanation(cont.contained()))
52+
if (cont.contained().links.nonEmpty) sb.append(documentationLinks(cont.contained()))
5253
}
5354

5455
delegate.log(position, sb.toString(), severity)

0 commit comments

Comments
 (0)