@@ -18,9 +18,8 @@ object Formatting {
1818 object ShownDef :
1919 /** Represents a value that has been "shown" and can be consumed by StringFormatter.
2020 * Not just a string because it may be a Seq that StringFormatter will intersperse with the trailing separator.
21- * Also, it's not a `String | Seq[String]` because then we'd need a Context to call `Showable#show`. We could
22- * make Context a requirement for a Show instance but then we'd have lots of instances instead of just one ShowAny
23- * instance. We could also try to make `Show#show` require the Context, but then that breaks the Conversion. */
21+ * It may also be a CtxShown, which allows the Show instance to finish showing the value with the string
22+ * interpolator's correct context, that is with non-sensical tagging, message limiting, explanations, etc. */
2423 opaque type Shown = Any
2524 object Shown :
2625 given [A : Show ]: Conversion [A , Shown ] = Show [A ].show(_)
@@ -29,6 +28,14 @@ object Formatting {
2928 /** Show a value T by returning a "shown" result. */
3029 def show (x : T ): Shown
3130
31+ trait CtxShow :
32+ def run (using Context ): Shown
33+
34+ extension (s : Shown )
35+ def ctxShow (using Context ): Shown = s match
36+ case cs : CtxShow => cs.run
37+ case _ => s
38+
3239 /** The base implementation, passing the argument to StringFormatter which will try to `.show` it. */
3340 object ShowAny extends Show [Any ]:
3441 def show (x : Any ): Shown = x
@@ -37,11 +44,7 @@ object Formatting {
3744 given Show [Product ] = ShowAny
3845
3946 class ShowImplicits2 extends ShowImplicits3 :
40- given Show [ParamInfo ] with
41- def show (x : ParamInfo ) = x match
42- case x : Symbol => Show [x.type ].show(x)
43- case x : LambdaParam => Show [x.type ].show(x)
44- case _ => ShowAny
47+ given Show [ParamInfo ] = ShowAny
4548
4649 class ShowImplicits1 extends ShowImplicits2 :
4750 given Show [ImplicitRef ] = ShowAny
@@ -52,10 +55,10 @@ object Formatting {
5255 inline def apply [A ](using inline z : Show [A ]): Show [A ] = z
5356
5457 given [X : Show ]: Show [Seq [X ]] with
55- def show (x : Seq [X ]) = x.map(Show [ X ].show)
58+ def show (x : Seq [X ]) = (ctx ?=> x.map(show1)) : CtxShow
5659
5760 given [A : Show , B : Show ]: Show [(A , B )] with
58- def show (x : (A , B )) = (Show [ A ].show( x._1), Show [ B ].show (x._2))
61+ def show (x : (A , B )) = (ctx ?=> (show1( x._1), show1 (x._2))) : CtxShow
5962
6063 given [X : Show ]: Show [X | Null ] with
6164 def show (x : X | Null ) = if x == null then " null" else Show [X ].show(x.nn)
@@ -67,10 +70,10 @@ object Formatting {
6770 def show (x : TypeComparer .ApproxState ) = TypeComparer .ApproxState .Repr .show(x)
6871
6972 given Show [Showable ] = ShowAny
70- given Show [Shown ] = ShowAny
7173 given Show [Int ] = ShowAny
7274 given Show [Char ] = ShowAny
7375 given Show [Boolean ] = ShowAny
76+ given Show [Integer ] = ShowAny
7477 given Show [String ] = ShowAny
7578 given Show [Class [? ]] = ShowAny
7679 given Show [Throwable ] = ShowAny
@@ -84,6 +87,11 @@ object Formatting {
8487 given Show [util.SourceFile ] = ShowAny
8588 given Show [util.Spans .Span ] = ShowAny
8689 given Show [tasty.TreeUnpickler # OwnerTree ] = ShowAny
90+
91+ private def show1 [A : Show ](x : A )(using Context ) = show2(Show [A ].show(x).ctxShow)
92+ private def show2 (x : Shown )(using Context ): String = x match
93+ case seq : Seq [? ] => seq.map(show2).mkString(" [" , " , " , " ]" )
94+ case res => res.tryToShow
8795 end Show
8896 end ShownDef
8997 export ShownDef .{ Show , Shown }
@@ -100,15 +108,14 @@ object Formatting {
100108 class StringFormatter (protected val sc : StringContext ) {
101109 protected def showArg (arg : Any )(using Context ): String = arg.tryToShow
102110
103- private def treatArg (arg : Shown , suffix : String )(using Context ): (Any , String ) = arg match {
104- case arg : Seq [? ] if suffix.nonEmpty && suffix.head == '%' =>
105- val (rawsep, rest) = suffix.tail.span(_ != '%' )
106- val sep = StringContext .processEscapes(rawsep)
107- if (rest.nonEmpty) (arg.map(showArg).mkString(sep), rest.tail)
108- else (arg, suffix)
111+ private def treatArg (arg : Shown , suffix : String )(using Context ): (String , String ) = arg.ctxShow match {
112+ case arg : Seq [? ] if suffix.indexOf('%' ) == 0 && suffix.indexOf('%' , 1 ) != - 1 =>
113+ val end = suffix.indexOf('%' , 1 )
114+ val sep = StringContext .processEscapes(suffix.substring(1 , end))
115+ (arg.mkString(sep), suffix.substring(end + 1 ))
109116 case arg : Seq [? ] =>
110117 (arg.map(showArg).mkString(" [" , " , " , " ]" ), suffix)
111- case _ =>
118+ case arg =>
112119 (showArg(arg), suffix)
113120 }
114121
@@ -134,11 +141,13 @@ object Formatting {
134141 * like concatenation, stripMargin etc on the values returned by em"...", and in the current error
135142 * message composition methods, this is crucial.
136143 */
137- class ErrorMessageFormatter (sc : StringContext ) extends StringFormatter (sc):
138- override protected def showArg (arg : Any )(using Context ): String =
139- wrapNonSensical(arg, super .showArg(arg)(using errorMessageCtx))
144+ def forErrorMessages (op : Context ?=> String )(using Context ): String = op(using errorMessageCtx)
140145
141- private def wrapNonSensical (arg : Any , str : String )(using Context ): String = {
146+ private class ErrorMessagePrinter (_ctx : Context ) extends RefinedPrinter (_ctx):
147+ override def toText (tp : Type ): Text = wrapNonSensical(tp, super .toText(tp))
148+ override def toText (sym : Symbol ): Text = wrapNonSensical(sym, super .toText(sym))
149+
150+ private def wrapNonSensical (arg : Any , text : Text )(using Context ): Text = {
142151 import Message ._
143152 def isSensical (arg : Any ): Boolean = arg match {
144153 case tpe : Type =>
@@ -151,8 +160,8 @@ object Formatting {
151160 case _ => true
152161 }
153162
154- if (isSensical(arg)) str
155- else nonSensicalStartTag + str + nonSensicalEndTag
163+ if (isSensical(arg)) text
164+ else nonSensicalStartTag ~ text ~ nonSensicalEndTag
156165 }
157166
158167 private type Recorded = Symbol | ParamRef | SkolemType
@@ -203,7 +212,7 @@ object Formatting {
203212 }
204213 }
205214
206- private class ExplainingPrinter (seen : Seen )(_ctx : Context ) extends RefinedPrinter (_ctx) {
215+ private class ExplainingPrinter (seen : Seen )(_ctx : Context ) extends ErrorMessagePrinter (_ctx) {
207216
208217 /** True if printer should a source module instead of its module class */
209218 private def useSourceModule (sym : Symbol ): Boolean =
@@ -307,18 +316,22 @@ object Formatting {
307316 }
308317
309318 private def errorMessageCtx (using Context ): Context =
310- ctx.property(MessageLimiter ) match
311- case Some (_ : ErrorMessageLimiter ) => ctx
312- case _ => ctx.fresh.setProperty(MessageLimiter , ErrorMessageLimiter ())
319+ ctx.withProperty(MessageLimiter )(ErrorMessageLimiter ()).withPrinter(new ErrorMessagePrinter (_))
313320
314321 /** Context with correct printer set for explanations */
315322 private def explainCtx (seen : Seen )(using Context ): Context =
316- val ectx = errorMessageCtx
317- ectx.printer match
318- case dp : ExplainingPrinter =>
319- ectx // re-use outer printer and defer explanation to it
320- case _ =>
321- ectx.fresh.setPrinterFn(ctx => new ExplainingPrinter (seen)(ctx))
323+ errorMessageCtx.withPrinter(new ExplainingPrinter (seen)(_))
324+
325+ extension (ctx : Context )
326+ private def withProperty [V , T <: V ](key : util.Property .Key [V ])(mk : => T )(using reflect.TypeTest [V , T ]) =
327+ ctx.property(key) match
328+ case Some (_ : T ) => ctx
329+ case _ => ctx.fresh.setProperty(key, mk)
330+
331+ private def withPrinter [T <: Printer ](mk : Context => T )(using reflect.TypeTest [Printer , T ]) =
332+ ctx.printer match
333+ case _ : T => ctx // re-use outer printer and defer to it
334+ case _ => ctx.fresh.setPrinterFn(mk(_))
322335
323336 /** Entrypoint for explanation string interpolator:
324337 *
@@ -364,8 +377,8 @@ object Formatting {
364377 * highlight the difference
365378 */
366379 def typeDiff (found : Type , expected : Type )(using Context ): (String , String ) = {
367- val fnd = wrapNonSensical(found, found.show)
368- val exp = wrapNonSensical(expected, expected.show)
380+ val fnd = wrapNonSensical(found, found.toText(ctx.printer)).show
381+ val exp = wrapNonSensical(expected, expected.toText(ctx.printer)).show
369382
370383 DiffUtil .mkColoredTypeDiff(fnd, exp) match {
371384 case _ if ctx.settings.color.value == " never" => (fnd, exp)
0 commit comments