Skip to content

Commit 8a7c84c

Browse files
authored
Merge pull request #15810 from dwijnand/fix-Show-infra
Fix aspects of the Show setup
2 parents f0be00d + 63068a9 commit 8a7c84c

File tree

6 files changed

+152
-66
lines changed

6 files changed

+152
-66
lines changed

compiler/src/dotty/tools/dotc/core/Decorators.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,13 +293,13 @@ object Decorators {
293293
* error messages after the first one if some of their arguments are "non-sensical".
294294
*/
295295
def em(args: Shown*)(using Context): String =
296-
new ErrorMessageFormatter(sc).assemble(args)
296+
forErrorMessages(new StringFormatter(sc).assemble(args))
297297

298298
/** Formatting with added explanations: Like `em`, but add explanations to
299299
* give more info about type variables and to disambiguate where needed.
300300
*/
301301
def ex(args: Shown*)(using Context): String =
302-
explained(em(args: _*))
302+
explained(new StringFormatter(sc).assemble(args))
303303

304304
extension [T <: AnyRef](arr: Array[T])
305305
def binarySearch(x: T | Null): Int = java.util.Arrays.binarySearch(arr.asInstanceOf[Array[Object | Null]], x)

compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ object OrderingConstraint {
2929
empty
3030
else
3131
val result = new OrderingConstraint(boundsMap, lowerMap, upperMap)
32-
ctx.run.nn.recordConstraintSize(result, result.boundsMap.size)
32+
if ctx.run != null then ctx.run.nn.recordConstraintSize(result, result.boundsMap.size)
3333
result
3434

3535
/** A lens for updating a single entry array in one of the three constraint maps */

compiler/src/dotty/tools/dotc/printing/Formatting.scala

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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 CtxShow, 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,12 @@ 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]) = new CtxShow:
59+
def run(using Context) = x.map(show1)
5660

5761
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))
62+
def show(x: (A, B)) = new CtxShow:
63+
def run(using Context) = (show1(x._1), show1(x._2))
5964

6065
given [X: Show]: Show[X | Null] with
6166
def show(x: X | Null) = if x == null then "null" else Show[X].show(x.nn)
@@ -71,6 +76,7 @@ object Formatting {
7176
given Show[Int] = ShowAny
7277
given Show[Char] = ShowAny
7378
given Show[Boolean] = ShowAny
79+
given Show[Integer] = ShowAny
7480
given Show[String] = ShowAny
7581
given Show[Class[?]] = ShowAny
7682
given Show[Throwable] = ShowAny
@@ -84,6 +90,11 @@ object Formatting {
8490
given Show[util.SourceFile] = ShowAny
8591
given Show[util.Spans.Span] = ShowAny
8692
given Show[tasty.TreeUnpickler#OwnerTree] = ShowAny
93+
94+
private def show1[A: Show](x: A)(using Context) = show2(Show[A].show(x).ctxShow)
95+
private def show2(x: Shown)(using Context): String = x match
96+
case seq: Seq[?] => seq.map(show2).mkString("[", ", ", "]")
97+
case res => res.tryToShow
8798
end Show
8899
end ShownDef
89100
export ShownDef.{ Show, Shown }
@@ -100,15 +111,14 @@ object Formatting {
100111
class StringFormatter(protected val sc: StringContext) {
101112
protected def showArg(arg: Any)(using Context): String = arg.tryToShow
102113

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)
114+
private def treatArg(arg: Shown, suffix: String)(using Context): (String, String) = arg.ctxShow match {
115+
case arg: Seq[?] if suffix.indexOf('%') == 0 && suffix.indexOf('%', 1) != -1 =>
116+
val end = suffix.indexOf('%', 1)
117+
val sep = StringContext.processEscapes(suffix.substring(1, end))
118+
(arg.mkString(sep), suffix.substring(end + 1))
109119
case arg: Seq[?] =>
110120
(arg.map(showArg).mkString("[", ", ", "]"), suffix)
111-
case _ =>
121+
case arg =>
112122
(showArg(arg), suffix)
113123
}
114124

@@ -134,11 +144,13 @@ object Formatting {
134144
* like concatenation, stripMargin etc on the values returned by em"...", and in the current error
135145
* message composition methods, this is crucial.
136146
*/
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))
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))
140152

141-
private def wrapNonSensical(arg: Any, str: String)(using Context): String = {
153+
private def wrapNonSensical(arg: Any, text: Text)(using Context): Text = {
142154
import Message._
143155
def isSensical(arg: Any): Boolean = arg match {
144156
case tpe: Type =>
@@ -151,8 +163,8 @@ object Formatting {
151163
case _ => true
152164
}
153165

154-
if (isSensical(arg)) str
155-
else nonSensicalStartTag + str + nonSensicalEndTag
166+
if (isSensical(arg)) text
167+
else nonSensicalStartTag ~ text ~ nonSensicalEndTag
156168
}
157169

158170
private type Recorded = Symbol | ParamRef | SkolemType
@@ -203,7 +215,7 @@ object Formatting {
203215
}
204216
}
205217

206-
private class ExplainingPrinter(seen: Seen)(_ctx: Context) extends RefinedPrinter(_ctx) {
218+
private class ExplainingPrinter(seen: Seen)(_ctx: Context) extends ErrorMessagePrinter(_ctx) {
207219

208220
/** True if printer should a source module instead of its module class */
209221
private def useSourceModule(sym: Symbol): Boolean =
@@ -307,9 +319,12 @@ object Formatting {
307319
}
308320

309321
private def errorMessageCtx(using Context): Context =
310-
ctx.property(MessageLimiter) match
322+
val ctx1 = ctx.property(MessageLimiter) match
311323
case Some(_: ErrorMessageLimiter) => ctx
312324
case _ => ctx.fresh.setProperty(MessageLimiter, ErrorMessageLimiter())
325+
ctx1.printer match
326+
case _: ErrorMessagePrinter => ctx1
327+
case _ => ctx1.fresh.setPrinterFn(ctx => ErrorMessagePrinter(ctx))
313328

314329
/** Context with correct printer set for explanations */
315330
private def explainCtx(seen: Seen)(using Context): Context =
@@ -364,8 +379,8 @@ object Formatting {
364379
* highlight the difference
365380
*/
366381
def typeDiff(found: Type, expected: Type)(using Context): (String, String) = {
367-
val fnd = wrapNonSensical(found, found.show)
368-
val exp = wrapNonSensical(expected, expected.show)
382+
val fnd = wrapNonSensical(found, found.toText(ctx.printer)).show
383+
val exp = wrapNonSensical(expected, expected.toText(ctx.printer)).show
369384

370385
DiffUtil.mkColoredTypeDiff(fnd, exp) match {
371386
case _ if ctx.settings.color.value == "never" => (fnd, exp)

compiler/src/dotty/tools/dotc/printing/MessageLimiter.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ class ErrorMessageLimiter extends MessageLimiter:
5050

5151
override def recurseLimit =
5252
val freeFraction: Double = ((sizeLimit - textLength) max 0).toDouble / sizeLimit
53+
// 10'000 - 0 / 10'0000 = 100% free
54+
// 10'000 - 200 / 10'0000 = 98% free * 50 = 49
55+
// 10'000 - 1'000 / 10'0000 = 90% free * 50 = 45
56+
// 10'000 - 2'000 / 10'0000 = 80% free * 50 = 40
57+
// every 200 characters consumes a "recurseCount"
58+
// which, additionally, is lowered from 100 to 50 here
5359
(initialRecurseLimit * freeFraction).toInt
5460

5561

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package dotty.tools
2+
package dotc
3+
4+
import core.*, Contexts.*, Decorators.*, Denotations.*, Flags.*, Names.*, StdNames.*, SymDenotations.*, Symbols.*, Types.*
5+
import config.Printers.*
6+
import printing.Formatting.Show
7+
8+
import org.junit.Test
9+
import org.junit.Assert.*
10+
11+
class StringFormatterTest extends AbstractStringFormatterTest:
12+
@Test def string = check("foo", i"${"foo"}")
13+
@Test def integer = check("1", i"${Int.box(1)}")
14+
@Test def type1 = check("Any", i"${defn.AnyType}")
15+
@Test def symbol = check("class Any", i"${defn.AnyClass}")
16+
@Test def paramInfo = check("class Any", i"${defn.AnyClass: ParamInfo}")
17+
@Test def seq = check("[Any, String]", i"${Seq(defn.AnyType, defn.StringType)}")
18+
@Test def seqSep = check("Any; String", i"${Seq(defn.AnyType, defn.StringType)}%; %")
19+
@Test def tuple = check("(1,Any)", i"${(1, defn.AnyType)}")
20+
@Test def seqOfTup = check("(1,Any), (2,String)", i"${Seq(1 -> defn.AnyType, 2 -> defn.StringType)}%, %")
21+
@Test def flags1 = check("final", i"$Final")
22+
@Test def flagsSeq = check("<static>, final", i"${Seq(JavaStatic, Final)}%, %")
23+
@Test def flagsTup = check("(<static>,final)", i"${(JavaStatic, Final)}")
24+
@Test def seqOfTup2 = check("(final,given), (private,lazy)", i"${Seq((Final, Given), (Private, Lazy))}%, %")
25+
26+
class StorePrinter extends Printer:
27+
var string: String = "<never set>"
28+
override def println(msg: => String) = string = msg
29+
30+
@Test def testShowing: Unit =
31+
val store = StorePrinter()
32+
(JavaStatic | Final).showing(i"flags=$result", store)
33+
assertEquals("flags=final <static>", store.string)
34+
35+
@Test def testShowingWithOriginalType: Unit =
36+
val store = StorePrinter()
37+
(JavaStatic | Final).showing(i"flags=${if result.is(Private) then result &~ Private else result | Private}", store)
38+
assertEquals("flags=private final <static>", store.string)
39+
end StringFormatterTest
40+
41+
class EmStringFormatterTest extends AbstractStringFormatterTest:
42+
@Test def seq = check("[Any, String]", em"${Seq(defn.AnyType, defn.StringType)}")
43+
@Test def seqSeq = check("Any; String", em"${Seq(defn.AnyType, defn.StringType)}%; %")
44+
@Test def ellipsis = assert(em"$Big".contains("..."))
45+
@Test def err = check("<nonsensical>type Err</nonsensical>", em"$Err")
46+
@Test def ambig = check("Foo vs Foo", em"$Foo vs $Foo")
47+
@Test def cstrd = check("Foo; Bar", em"$mkCstrd%; %")
48+
@Test def seqErr = check("[class Any, <nonsensical>type Err</nonsensical>]", em"${Seq(defn.AnyClass, Err)}")
49+
@Test def seqSeqErr = check("class Any; <nonsensical>type Err</nonsensical>", em"${Seq(defn.AnyClass, Err)}%; %")
50+
@Test def tupleErr = check("(1,<nonsensical>type Err</nonsensical>)", em"${(1, Err)}")
51+
@Test def tupleAmb = check("(Foo,Foo)", em"${(Foo, Foo)}")
52+
@Test def tupleFlags = check("(Foo,abstract)", em"${(Foo, Abstract)}")
53+
@Test def seqOfTupleFlags = check("[(Foo,abstract)]", em"${Seq((Foo, Abstract))}")
54+
end EmStringFormatterTest
55+
56+
class ExStringFormatterTest extends AbstractStringFormatterTest:
57+
@Test def seq = check("[Any, String]", ex"${Seq(defn.AnyType, defn.StringType)}")
58+
@Test def seqSeq = check("Any; String", ex"${Seq(defn.AnyType, defn.StringType)}%; %")
59+
@Test def ellipsis = assert(ex"$Big".contains("..."))
60+
@Test def err = check("<nonsensical>type Err</nonsensical>", ex"$Err")
61+
@Test def ambig = check("""Foo vs Foo²
62+
|
63+
|where: Foo is a type
64+
| Foo² is a type
65+
|""".stripMargin, ex"$Foo vs $Foo")
66+
@Test def cstrd = check("""Foo; Bar
67+
|
68+
|where: Bar is a type variable with constraint <: String
69+
| Foo is a type variable with constraint <: Int
70+
|""".stripMargin, ex"$mkCstrd%; %")
71+
@Test def seqErr = check("[class Any, <nonsensical>type Err</nonsensical>]", ex"${Seq(defn.AnyClass, Err)}")
72+
@Test def seqSeqErr = check("class Any; <nonsensical>type Err</nonsensical>", ex"${Seq(defn.AnyClass, Err)}%; %")
73+
@Test def tupleErr = check("(1,<nonsensical>type Err</nonsensical>)", ex"${(1, Err)}")
74+
@Test def tupleAmb = check("""(Foo,Foo²)
75+
|
76+
|where: Foo is a type
77+
| Foo² is a type
78+
|""".stripMargin, ex"${(Foo, Foo)}")
79+
end ExStringFormatterTest
80+
81+
abstract class AbstractStringFormatterTest extends DottyTest:
82+
override def initializeCtx(fc: FreshContext) = super.initializeCtx(fc.setSetting(fc.settings.color, "never"))
83+
84+
def Foo = newSymbol(defn.RootClass, typeName("Foo"), EmptyFlags, TypeBounds.empty).typeRef
85+
def Err = newErrorSymbol(defn.RootClass, typeName("Err"), "")
86+
def Big = (1 to 120).foldLeft(defn.StringType)((tp, i) => RefinedType(tp, typeName("A" * 69 + i), TypeAlias(defn.IntType)))
87+
88+
def mkCstrd =
89+
val names = List(typeName("Foo"), typeName("Bar"))
90+
val infos = List(TypeBounds.upper(defn.IntType), TypeBounds.upper(defn.StringType))
91+
val tl = PolyType(names)(_ => infos, _ => defn.AnyType)
92+
TypeComparer.addToConstraint(tl, Nil)
93+
tl.paramRefs
94+
95+
def ckSub(obtained: String, snippet: String) = assert(obtained.contains(snippet))
96+
def check(expected: String, obtained: String) = assertEquals(expected, obtained)

compiler/test/dotty/tools/dotc/printing/PrinterTests.scala

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,14 @@ package dotty.tools
22
package dotc
33
package printing
44

5-
import ast.{ Trees, tpd }
6-
import core.Names._
7-
import core.Symbols._
8-
import core.Decorators._
9-
import dotty.tools.dotc.core.Contexts.Context
5+
import core.*, Contexts.*, Decorators.*, Names.*, Symbols.*
6+
import ast.tpd.*
107

11-
import org.junit.Assert.assertEquals
128
import org.junit.Test
9+
import org.junit.Assert.*
1310

1411
class PrinterTests extends DottyTest {
15-
16-
private def newContext = {
17-
initialCtx.setSetting(ctx.settings.color, "never")
18-
}
19-
ctx = newContext
20-
21-
import tpd._
12+
override def initializeCtx(fc: FreshContext) = super.initializeCtx(fc.setSetting(fc.settings.color, "never"))
2213

2314
@Test
2415
def packageObject: Unit = {
@@ -47,30 +38,8 @@ class PrinterTests extends DottyTest {
4738

4839
checkCompile("typer", source) { (tree, context) =>
4940
given Context = context
50-
val bar @ Trees.DefDef(_, _, _, _) = tree.find(tree => tree.symbol.name == termName("bar2")).get: @unchecked
41+
val bar @ DefDef(_, _, _, _) = tree.find(tree => tree.symbol.name == termName("bar2")).get: @unchecked
5142
assertEquals("Int & (Boolean | String)", bar.tpt.show)
5243
}
5344
}
54-
55-
@Test def string: Unit = assertEquals("foo", i"${"foo"}")
56-
57-
import core.Flags._
58-
@Test def flagsSingle: Unit = assertEquals("final", i"$Final")
59-
@Test def flagsSeq: Unit = assertEquals("<static>, final", i"${Seq(JavaStatic, Final)}%, %")
60-
@Test def flagsTuple: Unit = assertEquals("(<static>,final)", i"${(JavaStatic, Final)}")
61-
@Test def flagsSeqOfTuple: Unit = assertEquals("(final,given), (private,lazy)", i"${Seq((Final, Given), (Private, Lazy))}%, %")
62-
63-
class StorePrinter extends config.Printers.Printer:
64-
var string: String = "<never set>"
65-
override def println(msg: => String) = string = msg
66-
67-
@Test def testShowing: Unit =
68-
val store = StorePrinter()
69-
(JavaStatic | Final).showing(i"flags=$result", store)
70-
assertEquals("flags=final <static>", store.string)
71-
72-
@Test def TestShowingWithOriginalType: Unit =
73-
val store = StorePrinter()
74-
(JavaStatic | Final).showing(i"flags=${if result.is(Private) then result &~ Private else result | Private}", store)
75-
assertEquals("flags=private final <static>", store.string)
7645
}

0 commit comments

Comments
 (0)