-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Message.scala
147 lines (124 loc) · 5.24 KB
/
Message.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package dotty.tools
package dotc
package reporting
import util.SourcePosition
object Message {
val nonSensicalStartTag: String = "<nonsensical>"
val nonSensicalEndTag: String = "</nonsensical>"
/** This implicit conversion provides a fallback for error messages that have
* not yet been ported to the new scheme. Comment out this `implicit def` to
* see where old errors still exist
*/
implicit def toNoExplanation(str: => String): Message =
NoExplanation(str)
}
/** A `Message` contains all semantic information necessary to easily
* comprehend what caused the message to be logged. Each message can be turned
* into a `Diagnostic` which contains the log level and can later be
* consumed by a subclass of `Reporter`. However, the error position is only
* part of `Diagnostic`, not `Message`.
*
* NOTE: you should not persist a message directly, because most messages take
* an implicit `Context` and these contexts weigh in at about 4mb per instance.
* Therefore, persisting these will result in a memory leak.
*
* Instead use the `persist` method to create an instance that does not keep a
* reference to these contexts.
*
* @param errorId a unique id identifying the message, this will later be
* used to reference documentation online
*/
abstract class Message(val errorId: ErrorMessageID) { self =>
import Message._
/** The `msg` contains the diagnostic message e.g:
*
* > expected: String
* > found: Int
*
* This message will be placed underneath the position given by the enclosing
* `Diagnostic`. The message is given in raw form, with possible embedded
* <nonsensical> tags.
*/
protected def msg: String
/** The kind of the error message, e.g. "Syntax" or "Type Mismatch".
* This will be printed as "$kind Error", "$kind Warning", etc, on the first
* line of the message.
*/
def kind: String
/** The explanation should provide a detailed description of why the error
* occurred and use examples from the user's own code to illustrate how to
* avoid these errors. It might contain embedded <nonsensical> tags.
*/
protected def explain: String
/** A message suffix that can be added for certain subclasses */
protected def msgSuffix: String = ""
/** Does this message have an explanation?
* This is normally the same as `explain.nonEmpty` but can be overridden
* if we need a way to return `true` without actually calling the
* `explain` method.
*/
def canExplain: Boolean = explain.nonEmpty
private var myMsg: String | Null = null
private var myIsNonSensical: Boolean = false
private def dropNonSensical(msg: String): String =
if msg.contains(nonSensicalStartTag) then
myIsNonSensical = true
// myMsg might be composed of several d"..." invocations -> nested
// nonsensical tags possible
msg
.replace(nonSensicalStartTag, "")
.replace(nonSensicalEndTag, "")
else msg
/** The message with potential embedded <nonsensical> tags */
def rawMessage = message
/** The message to report. <nonsensical> tags are filtered out */
lazy val message: String = dropNonSensical(msg + msgSuffix)
/** The explanation to report. <nonsensical> tags are filtered out */
lazy val explanation: String = dropNonSensical(explain)
/** A message is non-sensical if it contains references to <nonsensical>
* tags. Such tags are inserted by the error diagnostic framework if a
* message contains references to internally generated error types. Normally
* we want to suppress error messages referring to types like this because
* they look weird and are normally follow-up errors to something that was
* diagnosed before.
*/
def isNonSensical: Boolean = { message; myIsNonSensical }
/** The implicit `Context` in messages is a large thing that we don't want
* persisted. This method gets around that by duplicating the message,
* forcing its `msg` and `explanation` vals and dropping the implicit context
* that was captured in the original message.
*/
def persist: Message = new Message(errorId) {
val kind = self.kind
val msg = self.msg
val explain = self.explain
override val canExplain = self.canExplain
}
def append(suffix: => String): Message = mapMsg(_ ++ suffix)
def mapMsg(f: String => String): Message = new Message(errorId):
val kind = self.kind
def msg = f(self.msg)
def explain = self.explain
override def canExplain = self.canExplain
def appendExplanation(suffix: => String): Message = new Message(errorId):
val kind = self.kind
def msg = self.msg
def explain = self.explain ++ suffix
override def canExplain = true
override def toString = msg
}
/** The fallback `Message` containing no explanation and having no `kind` */
class NoExplanation(msgFn: => String) extends Message(ErrorMessageID.NoExplanationID) {
def msg: String = msgFn
def explain: String = ""
val kind: String = ""
override def toString(): String = msg
}
/** The extractor for `NoExplanation` can be used to check whether any error
* lacks an explanation
*/
object NoExplanation {
def unapply(m: Message): Option[Message] =
if (m.explanation == "") Some(m)
else None
}