@@ -27,7 +27,7 @@ import scala.annotation.threadUnsafe
27
27
* they will be evaluated in the Message context, which makes formatting safer
28
28
* and more robust.
29
29
* - For common messages, or messages that might require explanation, prefer defining
30
- * a new Message class in messages and use that instead. The advantage is that these
30
+ * a new ` Message` class in file ` messages.scala` and use that instead. The advantage is that these
31
31
* messages have unique IDs that can be referenced elsewhere.
32
32
*/
33
33
object Message :
@@ -45,18 +45,33 @@ object Message:
45
45
46
46
private case class SeenKey (str : String , isType : Boolean )
47
47
48
- private class Seen (disambiguate : Boolean ) extends collection.mutable.HashMap [SeenKey , List [Recorded ]]:
49
- override def default (key : SeenKey ) = Nil
48
+ /** A class that records printed items of one of the types in `Recorded`,
49
+ * adds superscripts for disambiguations, and can explain recorded symbols
50
+ * in ` where` clause
51
+ */
52
+ private class Seen (disambiguate : Boolean ):
53
+
54
+ val seen = new collection.mutable.HashMap [SeenKey , List [Recorded ]]:
55
+ override def default (key : SeenKey ) = Nil
50
56
51
57
var nonSensical = false
58
+
59
+ /** If false, stop all recordings */
52
60
private var recordOK = disambiguate
53
61
54
62
/** Clear all entries and stop further entries to be added */
55
63
def disable () =
56
- clear()
64
+ seen. clear()
57
65
recordOK = false
58
66
59
- def record (str : String , isType : Boolean , entry : Recorded )(using Context ): String = {
67
+ /** Record an entry `entry` with given String representation `str` and a
68
+ * type/term namespace identified by `isType`.
69
+ * If the entry was not yet recorded, allocate the next superscript corresponding
70
+ * to the same string in the same name space. The first recording is the string proper
71
+ * and following recordings get consecutive superscripts starting with 2.
72
+ * @return The possibly superscripted version of `str`.
73
+ */
74
+ def record (str : String , isType : Boolean , entry : Recorded )(using Context ): String =
60
75
if ! recordOK then return str
61
76
// println(s"recording $str, $isType, $entry")
62
77
@@ -71,15 +86,15 @@ object Message:
71
86
case _ => e1
72
87
}
73
88
val key = SeenKey (str, isType)
74
- val existing = apply (key)
89
+ val existing = seen (key)
75
90
lazy val dealiased = followAlias(entry)
76
91
77
92
// alts: The alternatives in `existing` that are equal, or follow (an alias of) `entry`
78
93
var alts = existing.dropWhile(alt => dealiased ne followAlias(alt))
79
- if ( alts.isEmpty) {
94
+ if alts.isEmpty then
80
95
alts = entry :: existing
81
- update (key, alts)
82
- }
96
+ seen (key) = alts
97
+
83
98
val suffix = alts.length match {
84
99
case 1 => " "
85
100
case n => n.toString.toCharArray.map {
@@ -96,88 +111,86 @@ object Message:
96
111
}.mkString
97
112
}
98
113
str + suffix
99
- }
100
- end Seen
114
+ end record
115
+
116
+ /** Create explanation for single `Recorded` type or symbol */
117
+ private def explanation (entry : AnyRef )(using Context ): String =
118
+ def boundStr (bound : Type , default : ClassSymbol , cmp : String ) =
119
+ if (bound.isRef(default)) " " else i " $cmp $bound"
120
+
121
+ def boundsStr (bounds : TypeBounds ): String = {
122
+ val lo = boundStr(bounds.lo, defn.NothingClass , " >:" )
123
+ val hi = boundStr(bounds.hi, defn.AnyClass , " <:" )
124
+ if (lo.isEmpty) hi
125
+ else if (hi.isEmpty) lo
126
+ else s " $lo and $hi"
127
+ }
101
128
102
- /** Create explanation for single `Recorded` type or symbol */
103
- def explanation (entry : AnyRef )(using Context ): String =
104
- def boundStr (bound : Type , default : ClassSymbol , cmp : String ) =
105
- if (bound.isRef(default)) " " else i " $cmp $bound"
106
-
107
- def boundsStr (bounds : TypeBounds ): String = {
108
- val lo = boundStr(bounds.lo, defn.NothingClass , " >:" )
109
- val hi = boundStr(bounds.hi, defn.AnyClass , " <:" )
110
- if (lo.isEmpty) hi
111
- else if (hi.isEmpty) lo
112
- else s " $lo and $hi"
113
- }
114
-
115
- def addendum (cat : String , info : Type ): String = info match {
116
- case bounds @ TypeBounds (lo, hi) if bounds ne TypeBounds .empty =>
117
- if (lo eq hi) i " which is an alias of $lo"
118
- else i " with $cat ${boundsStr(bounds)}"
119
- case _ =>
120
- " "
121
- }
122
-
123
- entry match {
124
- case param : TypeParamRef =>
125
- s " is a type variable ${addendum(" constraint" , TypeComparer .bounds(param))}"
126
- case param : TermParamRef =>
127
- s " is a reference to a value parameter "
128
- case sym : Symbol =>
129
- val info =
130
- if (ctx.gadt.contains(sym))
131
- sym.info & ctx.gadt.fullBounds(sym)
132
- else
133
- sym.info
134
- s " is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum(" bounds" , info)}"
135
- case tp : SkolemType =>
136
- s " is an unknown value of type ${tp.widen.show}"
137
- }
138
- end explanation
139
-
140
- /** Turns a `Seen` into a `String` to produce an explanation for types on the
141
- * form `where: T is...`
142
- *
143
- * @return string disambiguating types
144
- */
145
- private def explanations (seen : Seen )(using Context ): String =
146
- def needsExplanation (entry : Recorded ) = entry match {
147
- case param : TypeParamRef => ctx.typerState.constraint.contains(param)
148
- case param : ParamRef => false
149
- case skolem : SkolemType => true
150
- case sym : Symbol =>
151
- ctx.gadt.contains(sym) && ctx.gadt.fullBounds(sym) != TypeBounds .empty
152
- }
153
-
154
- val toExplain : List [(String , Recorded )] = seen.toList.flatMap { kvs =>
155
- val res : List [(String , Recorded )] = kvs match {
156
- case (key, entry :: Nil ) =>
157
- if (needsExplanation(entry)) (key.str, entry) :: Nil else Nil
158
- case (key, entries) =>
159
- for (alt <- entries) yield {
160
- val tickedString = seen.record(key.str, key.isType, alt)
161
- (tickedString, alt)
162
- }
129
+ def addendum (cat : String , info : Type ): String = info match {
130
+ case bounds @ TypeBounds (lo, hi) if bounds ne TypeBounds .empty =>
131
+ if (lo eq hi) i " which is an alias of $lo"
132
+ else i " with $cat ${boundsStr(bounds)}"
133
+ case _ =>
134
+ " "
135
+ }
136
+
137
+ entry match {
138
+ case param : TypeParamRef =>
139
+ s " is a type variable ${addendum(" constraint" , TypeComparer .bounds(param))}"
140
+ case param : TermParamRef =>
141
+ s " is a reference to a value parameter "
142
+ case sym : Symbol =>
143
+ val info =
144
+ if (ctx.gadt.contains(sym))
145
+ sym.info & ctx.gadt.fullBounds(sym)
146
+ else
147
+ sym.info
148
+ s " is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum(" bounds" , info)}"
149
+ case tp : SkolemType =>
150
+ s " is an unknown value of type ${tp.widen.show}"
151
+ }
152
+ end explanation
153
+
154
+ /** Produce a where clause with explanations for recorded iterms.
155
+ */
156
+ def explanations (using Context ): String =
157
+ def needsExplanation (entry : Recorded ) = entry match {
158
+ case param : TypeParamRef => ctx.typerState.constraint.contains(param)
159
+ case param : ParamRef => false
160
+ case skolem : SkolemType => true
161
+ case sym : Symbol =>
162
+ ctx.gadt.contains(sym) && ctx.gadt.fullBounds(sym) != TypeBounds .empty
163
163
}
164
- res // help the inferrencer out
165
- }.sortBy(_._1)
166
-
167
- def columnar (parts : List [(String , String )]): List [String ] = {
168
- lazy val maxLen = parts.map(_._1.length).max
169
- parts.map {
170
- case (leader, trailer) =>
171
- val variable = hl(leader)
172
- s """ $variable${" " * (maxLen - leader.length)} $trailer"""
164
+
165
+ val toExplain : List [(String , Recorded )] = seen.toList.flatMap { kvs =>
166
+ val res : List [(String , Recorded )] = kvs match {
167
+ case (key, entry :: Nil ) =>
168
+ if (needsExplanation(entry)) (key.str, entry) :: Nil else Nil
169
+ case (key, entries) =>
170
+ for (alt <- entries) yield {
171
+ val tickedString = record(key.str, key.isType, alt)
172
+ (tickedString, alt)
173
+ }
174
+ }
175
+ res // help the inferrencer out
176
+ }.sortBy(_._1)
177
+
178
+ def columnar (parts : List [(String , String )]): List [String ] = {
179
+ lazy val maxLen = parts.map(_._1.length).max
180
+ parts.map {
181
+ case (leader, trailer) =>
182
+ val variable = hl(leader)
183
+ s """ $variable${" " * (maxLen - leader.length)} $trailer"""
184
+ }
173
185
}
174
- }
175
186
176
- val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) }
177
- val explainLines = columnar(explainParts)
178
- if (explainLines.isEmpty) " " else i " where: $explainLines% \n % \n "
179
- end explanations
187
+ val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) }
188
+ val explainLines = columnar(explainParts)
189
+ if (explainLines.isEmpty) " " else i " where: $explainLines% \n % \n "
190
+ end explanations
191
+ end Seen
180
192
193
+ /** Printer to be used when formatting messages */
181
194
private class Printer (val seen : Seen , _ctx : Context ) extends RefinedPrinter (_ctx):
182
195
183
196
/** True if printer should a show source module instead of its module class */
@@ -284,7 +297,7 @@ abstract class Message(val errorId: ErrorMessageID)(using Context) { self =>
284
297
else ctx.printer match
285
298
case msgPrinter : Message .Printer =>
286
299
myIsNonSensical = msgPrinter.seen.nonSensical
287
- val addendum = explanations( msgPrinter.seen)
300
+ val addendum = msgPrinter.seen.explanations
288
301
msgPrinter.seen.disable()
289
302
// Clear entries and stop futher recording so that messages containing the current
290
303
// one don't repeat the explanations or use explanations from the msgPostscript.
0 commit comments