-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathFormatting.scala
214 lines (179 loc) · 8.98 KB
/
Formatting.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
package dotty.tools
package dotc
package printing
import scala.collection.mutable
import core.*
import Texts.*, Types.*, Flags.*, Symbols.*, Contexts.*
import Decorators.*
import reporting.Message
import util.{Chars, DiffUtil, SimpleIdentitySet}
import Highlighting.*
object Formatting {
object ShownDef:
/** Represents a value that has been "shown" and can be consumed by StringFormatter.
* Not just a string because it may be a Seq that StringFormatter will intersperse with the trailing separator.
* It may also be a CtxShow, which allows the Show instance to finish showing the value with the string
* interpolator's correct context, that is with non-sensical tagging, message limiting, explanations, etc. */
opaque type Shown = Any
object Shown:
given [A: Show]: Conversion[A, Shown] = Show[A].show(_)
extension (s: Shown)
def runCtxShow(using Context): Shown = s match
case cs: CtxShow => cs.run
case _ => s
def toStr(x: Shown)(using Context): String = x match
case seq: Seq[?] => seq.map(toStr).mkString("[", ", ", "]")
case res => res.tryToShow
import Shown.runCtxShow
sealed abstract class Show[-T]:
/** Show a value T by returning a "shown" result. */
def show(x: T): Shown
trait CtxShow:
def run(using Context): Shown
private inline def CtxShow(inline x: Context ?=> Shown) =
class InlinedCtxShow extends CtxShow { def run(using Context) = x(using ctx) }
new InlinedCtxShow
private def toStr[A: Show](x: A)(using Context): String = Shown.toStr(toShown(x))
private def toShown[A: Show](x: A)(using Context): Shown = Show[A].show(x).runCtxShow
/** The base implementation, passing the argument to StringFormatter which will try to `.show` it. */
object ShowAny extends Show[Any]:
def show(x: Any): Shown = x
class ShowImplicits4:
given [X: Show]: Show[X | Null] with
def show(x: X | Null) = if x == null then "null" else CtxShow(toStr(x.nn))
class ShowImplicits3 extends ShowImplicits4:
given Show[Product] = ShowAny
class ShowImplicits2 extends ShowImplicits3:
given Show[ParamInfo] = ShowAny
class ShowImplicits1 extends ShowImplicits2:
given Show[ImplicitRef] = ShowAny
given Show[Names.Designator] = ShowAny
given Show[util.SrcPos] = ShowAny
object Show extends ShowImplicits1:
inline def apply[A](using inline z: Show[A]): Show[A] = z
given [X: Show]: Show[Option[X]] with
def show(x: Option[X]) =
CtxShow(x.map(toStr))
end given
given [X: Show]: Show[Seq[X]] with
def show(x: Seq[X]) = CtxShow(x.map(toStr))
given Show[Seq[Nothing]] with
def show(x: Seq[Nothing]) = CtxShow(x)
given [K: Show, V: Show]: Show[Map[K, V]] with
def show(x: Map[K, V]) =
CtxShow(x.map((k, v) => s"${toStr(k)} => ${toStr(v)}"))
given [H: Show, T <: Tuple: Show]: Show[H *: T] with
def show(x: H *: T) =
CtxShow(toStr(x.head) *: toShown(x.tail).asInstanceOf[Tuple])
given [X <: AnyRef: Show]: Show[SimpleIdentitySet[X]] with
def show(x: SimpleIdentitySet[X]) = summon[Show[List[X]]].show(x.toList)
given Show[FlagSet] with
def show(x: FlagSet) = x.flagsString
given Show[TypeComparer.ApproxState] with
def show(x: TypeComparer.ApproxState) = TypeComparer.ApproxState.Repr.show(x)
given Show[ast.TreeInfo.PurityLevel] with
def show(x: ast.TreeInfo.PurityLevel) = x match
case ast.TreeInfo.Path => "PurityLevel.Path"
case ast.TreeInfo.Pure => "PurityLevel.Pure"
case ast.TreeInfo.Idempotent => "PurityLevel.Idempotent"
case ast.TreeInfo.Impure => "PurityLevel.Impure"
case ast.TreeInfo.PurePath => "PurityLevel.PurePath"
case ast.TreeInfo.IdempotentPath => "PurityLevel.IdempotentPath"
case _ => s"PurityLevel(${x.x.toBinaryString})"
given Show[Atoms] with
def show(x: Atoms) = x match
case Atoms.Unknown => "Unknown"
case Atoms.Range(lo, hi) => CtxShow(s"Range(${toStr(lo.toList)}, ${toStr(hi.toList)})")
end given
given Show[ast.untpd.Modifiers] with
def show(x: ast.untpd.Modifiers) =
CtxShow(s"Modifiers(${toStr(x.flags)}, ${toStr(x.privateWithin)}, ${toStr(x.annotations)}, ${toStr(x.mods)})")
given Show[ast.untpd.Mod] with
def show(x: ast.untpd.Mod) = CtxShow(s"Mod(${toStr(x.flags)})")
given Show[Showable] = ShowAny
given Show[Shown] = ShowAny
given Show[Int] = ShowAny
given Show[Char] = ShowAny
given Show[Boolean] = ShowAny
given Show[Integer] = ShowAny
given Show[Long] = ShowAny
given Show[String] = ShowAny
given Show[Class[?]] = ShowAny
given Show[Throwable] = ShowAny
given Show[StringBuffer] = ShowAny
given Show[CompilationUnit] = ShowAny
given Show[Phases.Phase] = ShowAny
given Show[TyperState] = ShowAny
given Show[Unit] = ShowAny
given Show[config.ScalaVersion] = ShowAny
given Show[io.AbstractFile] = ShowAny
given Show[parsing.Scanners.Scanner] = ShowAny
given Show[util.SourceFile] = ShowAny
given Show[util.Spans.Span] = ShowAny
given Show[tasty.TreeUnpickler#OwnerTree] = ShowAny
given Show[typer.ForceDegree.Value] = ShowAny
end Show
end ShownDef
export ShownDef.{ Show, Shown }
/** General purpose string formatter, with the following features:
*
* 1. Invokes the `show` extension method on the interpolated arguments.
* 2. Sequences can be formatted using the desired separator between two `%` signs,
* eg `i"myList = (${myList}%, %)"`
* 3. Safe handling of multi-line margins. Left margins are stripped on the parts
* of the string context *before* inserting the arguments. That way, we guard
* against accidentally treating an interpolated value as a margin.
*/
class StringFormatter(protected val sc: StringContext) {
protected def showArg(arg: Any)(using Context): String = arg.tryToShow
private def treatArg(arg: Shown, suffix: String)(using Context): (String, String) = arg.runCtxShow match {
case arg: Seq[?] if suffix.indexOf('%') == 0 && suffix.indexOf('%', 1) != -1 =>
val end = suffix.indexOf('%', 1)
val sep = StringContext.processEscapes(suffix.substring(1, end).nn)
(arg.mkString(sep), suffix.substring(end + 1).nn)
case arg: Seq[?] =>
(arg.map(showArg).mkString("[", ", ", "]"), suffix)
case arg =>
(showArg(arg), suffix)
}
def assemble(args: Seq[Shown])(using Context): String = {
// compatible with CharArrayReader (not StringOps)
inline def isLineBreak(c: Char) = c == Chars.LF || c == Chars.FF
def stripTrailingPart(s: String) = {
val (pre, post) = s.span(c => !isLineBreak(c))
pre ++ post.stripMargin
}
val (prefix, suffixes) = sc.parts.toList match {
case head :: tail => (head.stripMargin, tail map stripTrailingPart)
case Nil => ("", Nil)
}
val (args1, suffixes1) = args.lazyZip(suffixes).map(treatArg(_, _)).unzip
new StringContext(prefix :: suffixes1.toList*).s(args1*)
}
}
/** This method will produce a colored type diff from the given arguments.
* The idea is to do this for known cases that are useful and then fall back
* on regular syntax highlighting for the cases which are unhandled.
*
* Please note that if used in combination with `disambiguateTypes` the
* correct `Context` for printing should also be passed when calling the
* method.
*
* @return the (found, expected, changePercentage) with coloring to
* highlight the difference
*/
def typeDiff(found: Type, expected: Type)(using Context): (String, String) =
val fnd = found.show
val exp = expected.show
DiffUtil.mkColoredTypeDiff(fnd, exp) match
case (fnd1, exp1, change)
if change < 0.5 && ctx.settings.color.value != "never" => (fnd1, exp1)
case _ => (fnd, exp)
/** Explicit syntax highlighting */
def hl(s: String)(using Context): String =
SyntaxHighlighting.highlight(s)
/** Explicitly highlight a string with the same formatting as used for keywords */
def hlAsKeyword(str: String)(using Context): String =
if str.isEmpty || ctx.settings.color.value == "never" then str
else s"${SyntaxHighlighting.KeywordColor}$str${SyntaxHighlighting.NoColor}"
}