Skip to content

Commit d704ede

Browse files
committed
Refactor Message.Seen class
- Use aggregation instead of inheritance - Encapsulate explanations inside Seen
1 parent 40d29d6 commit d704ede

File tree

1 file changed

+100
-87
lines changed

1 file changed

+100
-87
lines changed

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

Lines changed: 100 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import scala.annotation.threadUnsafe
2727
* they will be evaluated in the Message context, which makes formatting safer
2828
* and more robust.
2929
* - 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
3131
* messages have unique IDs that can be referenced elsewhere.
3232
*/
3333
object Message:
@@ -45,18 +45,33 @@ object Message:
4545

4646
private case class SeenKey(str: String, isType: Boolean)
4747

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
5056

5157
var nonSensical = false
58+
59+
/** If false, stop all recordings */
5260
private var recordOK = disambiguate
5361

5462
/** Clear all entries and stop further entries to be added */
5563
def disable() =
56-
clear()
64+
seen.clear()
5765
recordOK = false
5866

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 =
6075
if !recordOK then return str
6176
//println(s"recording $str, $isType, $entry")
6277

@@ -71,15 +86,15 @@ object Message:
7186
case _ => e1
7287
}
7388
val key = SeenKey(str, isType)
74-
val existing = apply(key)
89+
val existing = seen(key)
7590
lazy val dealiased = followAlias(entry)
7691

7792
// alts: The alternatives in `existing` that are equal, or follow (an alias of) `entry`
7893
var alts = existing.dropWhile(alt => dealiased ne followAlias(alt))
79-
if (alts.isEmpty) {
94+
if alts.isEmpty then
8095
alts = entry :: existing
81-
update(key, alts)
82-
}
96+
seen(key) = alts
97+
8398
val suffix = alts.length match {
8499
case 1 => ""
85100
case n => n.toString.toCharArray.map {
@@ -96,88 +111,86 @@ object Message:
96111
}.mkString
97112
}
98113
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+
}
101128

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
163163
}
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+
}
173185
}
174-
}
175186

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
180192

193+
/** Printer to be used when formatting messages */
181194
private class Printer(val seen: Seen, _ctx: Context) extends RefinedPrinter(_ctx):
182195

183196
/** 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 =>
284297
else ctx.printer match
285298
case msgPrinter: Message.Printer =>
286299
myIsNonSensical = msgPrinter.seen.nonSensical
287-
val addendum = explanations(msgPrinter.seen)
300+
val addendum = msgPrinter.seen.explanations
288301
msgPrinter.seen.disable()
289302
// Clear entries and stop futher recording so that messages containing the current
290303
// one don't repeat the explanations or use explanations from the msgPostscript.

0 commit comments

Comments
 (0)