@@ -137,236 +137,6 @@ object Formatting {
137137 }
138138 }
139139
140- /** The `em` string interpolator works like the `i` string interpolator, but marks nonsensical errors
141- * using `<nonsensical>...</nonsensical>` tags.
142- * Note: Instead of these tags, it would be nicer to return a data structure containing the message string
143- * and a boolean indicating whether the message is sensical, but then we cannot use string operations
144- * like concatenation, stripMargin etc on the values returned by em"...", and in the current error
145- * message composition methods, this is crucial.
146- */
147- def forErrorMessages (op : Context ?=> String )(using Context ): String = op(using errorMessageCtx)
148-
149- private class ErrorMessagePrinter (_ctx : Context ) extends RefinedPrinter (_ctx):
150- override def toText (tp : Type ): Text = wrapNonSensical(tp, super .toText(tp))
151- override def toText (sym : Symbol ): Text = wrapNonSensical(sym, super .toText(sym))
152-
153- private def wrapNonSensical (arg : Any , text : Text )(using Context ): Text = {
154- import Message ._
155- def isSensical (arg : Any ): Boolean = arg match {
156- case tpe : Type =>
157- tpe.exists && ! tpe.isErroneous
158- case sym : Symbol if sym.isCompleted =>
159- sym.info match {
160- case _ : ErrorType | TypeAlias (_ : ErrorType ) | NoType => false
161- case _ => true
162- }
163- case _ => true
164- }
165-
166- if (isSensical(arg)) text
167- else nonSensicalStartTag ~ text ~ nonSensicalEndTag
168- }
169-
170- private type Recorded = Symbol | ParamRef | SkolemType
171-
172- private case class SeenKey (str : String , isType : Boolean )
173- private class Seen extends mutable.HashMap [SeenKey , List [Recorded ]] {
174-
175- override def default (key : SeenKey ) = Nil
176-
177- def record (str : String , isType : Boolean , entry : Recorded )(using Context ): String = {
178-
179- /** If `e1` is an alias of another class of the same name, return the other
180- * class symbol instead. This normalization avoids recording e.g. scala.List
181- * and scala.collection.immutable.List as two different types
182- */
183- def followAlias (e1 : Recorded ): Recorded = e1 match {
184- case e1 : Symbol if e1.isAliasType =>
185- val underlying = e1.typeRef.underlyingClassRef(refinementOK = false ).typeSymbol
186- if (underlying.name == e1.name) underlying else e1
187- case _ => e1
188- }
189- val key = SeenKey (str, isType)
190- val existing = apply(key)
191- lazy val dealiased = followAlias(entry)
192-
193- // alts: The alternatives in `existing` that are equal, or follow (an alias of) `entry`
194- var alts = existing.dropWhile(alt => dealiased ne followAlias(alt))
195- if (alts.isEmpty) {
196- alts = entry :: existing
197- update(key, alts)
198- }
199- val suffix = alts.length match {
200- case 1 => " "
201- case n => n.toString.toCharArray.map {
202- case '0' => '⁰'
203- case '1' => '¹'
204- case '2' => '²'
205- case '3' => '³'
206- case '4' => '⁴'
207- case '5' => '⁵'
208- case '6' => '⁶'
209- case '7' => '⁷'
210- case '8' => '⁸'
211- case '9' => '⁹'
212- }.mkString
213- }
214- str + suffix
215- }
216- }
217-
218- private class ExplainingPrinter (seen : Seen )(_ctx : Context ) extends ErrorMessagePrinter (_ctx) {
219-
220- /** True if printer should a source module instead of its module class */
221- private def useSourceModule (sym : Symbol ): Boolean =
222- sym.is(ModuleClass , butNot = Package ) && sym.sourceModule.exists && ! _ctx.settings.YdebugNames .value
223-
224- override def simpleNameString (sym : Symbol ): String =
225- if (useSourceModule(sym)) simpleNameString(sym.sourceModule)
226- else seen.record(super .simpleNameString(sym), sym.isType, sym)
227-
228- override def ParamRefNameString (param : ParamRef ): String =
229- seen.record(super .ParamRefNameString (param), param.isInstanceOf [TypeParamRef ], param)
230-
231- override def toTextRef (tp : SingletonType ): Text = tp match {
232- case tp : SkolemType => seen.record(tp.repr.toString, isType = true , tp)
233- case _ => super .toTextRef(tp)
234- }
235-
236- override def toText (tp : Type ): Text = tp match {
237- case tp : TypeRef if useSourceModule(tp.symbol) => Str (" object " ) ~ super .toText(tp)
238- case _ => super .toText(tp)
239- }
240- }
241-
242- /** Create explanation for single `Recorded` type or symbol */
243- def explanation (entry : AnyRef )(using Context ): String = {
244- def boundStr (bound : Type , default : ClassSymbol , cmp : String ) =
245- if (bound.isRef(default)) " " else i " $cmp $bound"
246-
247- def boundsStr (bounds : TypeBounds ): String = {
248- val lo = boundStr(bounds.lo, defn.NothingClass , " >:" )
249- val hi = boundStr(bounds.hi, defn.AnyClass , " <:" )
250- if (lo.isEmpty) hi
251- else if (hi.isEmpty) lo
252- else s " $lo and $hi"
253- }
254-
255- def addendum (cat : String , info : Type ): String = info match {
256- case bounds @ TypeBounds (lo, hi) if bounds ne TypeBounds .empty =>
257- if (lo eq hi) i " which is an alias of $lo"
258- else i " with $cat ${boundsStr(bounds)}"
259- case _ =>
260- " "
261- }
262-
263- entry match {
264- case param : TypeParamRef =>
265- s " is a type variable ${addendum(" constraint" , TypeComparer .bounds(param))}"
266- case param : TermParamRef =>
267- s " is a reference to a value parameter "
268- case sym : Symbol =>
269- val info =
270- if (ctx.gadt.contains(sym))
271- sym.info & ctx.gadt.fullBounds(sym)
272- else
273- sym.info
274- s " is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum(" bounds" , info)}"
275- case tp : SkolemType =>
276- s " is an unknown value of type ${tp.widen.show}"
277- }
278- }
279-
280- /** Turns a `Seen` into a `String` to produce an explanation for types on the
281- * form `where: T is...`
282- *
283- * @return string disambiguating types
284- */
285- private def explanations (seen : Seen )(using Context ): String = {
286- def needsExplanation (entry : Recorded ) = entry match {
287- case param : TypeParamRef => ctx.typerState.constraint.contains(param)
288- case param : ParamRef => false
289- case skolem : SkolemType => true
290- case sym : Symbol =>
291- ctx.gadt.contains(sym) && ctx.gadt.fullBounds(sym) != TypeBounds .empty
292- }
293-
294- val toExplain : List [(String , Recorded )] = seen.toList.flatMap { kvs =>
295- val res : List [(String , Recorded )] = kvs match {
296- case (key, entry :: Nil ) =>
297- if (needsExplanation(entry)) (key.str, entry) :: Nil else Nil
298- case (key, entries) =>
299- for (alt <- entries) yield {
300- val tickedString = seen.record(key.str, key.isType, alt)
301- (tickedString, alt)
302- }
303- }
304- res // help the inferrencer out
305- }.sortBy(_._1)
306-
307- def columnar (parts : List [(String , String )]): List [String ] = {
308- lazy val maxLen = parts.map(_._1.length).max
309- parts.map {
310- case (leader, trailer) =>
311- val variable = hl(leader)
312- s """ $variable${" " * (maxLen - leader.length)} $trailer"""
313- }
314- }
315-
316- val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) }
317- val explainLines = columnar(explainParts)
318- if (explainLines.isEmpty) " " else i " where: $explainLines% \n % \n "
319- }
320-
321- private def errorMessageCtx (using Context ): Context =
322- val ctx1 = ctx.property(MessageLimiter ) match
323- case Some (_ : ErrorMessageLimiter ) => ctx
324- case _ => ctx.fresh.setProperty(MessageLimiter , ErrorMessageLimiter ())
325- ctx1.printer match
326- case _ : ErrorMessagePrinter => ctx1
327- case _ => ctx1.fresh.setPrinterFn(ctx => ErrorMessagePrinter (ctx))
328-
329- /** Context with correct printer set for explanations */
330- private def explainCtx (seen : Seen )(using Context ): Context =
331- val ectx = errorMessageCtx
332- ectx.printer match
333- case dp : ExplainingPrinter =>
334- ectx // re-use outer printer and defer explanation to it
335- case _ =>
336- ectx.fresh.setPrinterFn(ctx => new ExplainingPrinter (seen)(ctx))
337-
338- /** Entrypoint for explanation string interpolator:
339- *
340- * ```
341- * ex"disambiguate $tpe1 and $tpe2"
342- * ```
343- */
344- def explained (op : Context ?=> String )(using Context ): String = {
345- val seen = new Seen
346- val msg = op(using explainCtx(seen))
347- val addendum = explanations(seen)
348- if (addendum.isEmpty) msg else msg ++ " \n\n " ++ addendum
349- }
350-
351- /** When getting a type mismatch it is useful to disambiguate placeholders like:
352- *
353- * ```
354- * found: List[Int]
355- * required: List[T]
356- * where: T is a type in the initializer of value s which is an alias of
357- * String
358- * ```
359- *
360- * @return the `where` section as well as the printing context for the
361- * placeholders - `("T is a...", printCtx)`
362- */
363- def disambiguateTypes (args : Type * )(using Context ): (String , Context ) = {
364- val seen = new Seen
365- val printCtx = explainCtx(seen)
366- args.foreach(_.show(using printCtx)) // showing each member will put it into `seen`
367- (explanations(seen), printCtx)
368- }
369-
370140 /** This method will produce a colored type diff from the given arguments.
371141 * The idea is to do this for known cases that are useful and then fall back
372142 * on regular syntax highlighting for the cases which are unhandled.
@@ -378,16 +148,13 @@ object Formatting {
378148 * @return the (found, expected, changePercentage) with coloring to
379149 * highlight the difference
380150 */
381- def typeDiff (found : Type , expected : Type )(using Context ): (String , String ) = {
382- val fnd = wrapNonSensical(found, found.toText(ctx.printer)).show
383- val exp = wrapNonSensical(expected, expected.toText(ctx.printer)).show
384-
385- DiffUtil .mkColoredTypeDiff(fnd, exp) match {
386- case _ if ctx.settings.color.value == " never" => (fnd, exp)
387- case (fnd, exp, change) if change < 0.5 => (fnd, exp)
151+ def typeDiff (found : Type , expected : Type )(using Context ): (String , String ) =
152+ val fnd = found.show
153+ val exp = expected.show
154+ DiffUtil .mkColoredTypeDiff(fnd, exp) match
155+ case (fnd1, exp1, change)
156+ if change < 0.5 && ctx.settings.color.value != " never" => (fnd1, exp1)
388157 case _ => (fnd, exp)
389- }
390- }
391158
392159 /** Explicit syntax highlighting */
393160 def hl (s : String )(using Context ): String =
0 commit comments