@@ -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 */
3333object 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