Skip to content

Commit 23b7ce1

Browse files
committed
Reintroduce reflective REPL pprint call.
The class name changed, but the reflective call is still necessary for deeply nested objects to be formatted correctly. Includes a comment describing why the reflection is needed.
1 parent 32bfd5f commit 23b7ce1

File tree

1 file changed

+38
-3
lines changed

1 file changed

+38
-3
lines changed

repl/src/dotty/tools/repl/Rendering.scala

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,44 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
2727
var myClassLoader: AbstractFileClassLoader = uninitialized
2828

2929
private def pprintRender(value: Any, width: Int, height: Int, initialOffset: Int)(using Context): String = {
30-
pprint.PPrinter.BlackWhite
31-
.apply(value, width = width, height = height, initialOffset = initialOffset)
32-
.plainText
30+
def fallback() =
31+
// might as well be `println` in this case, but JDK classes e.g. `Float` are correctly handled.
32+
pprint.PPrinter.BlackWhite
33+
.apply(value, width = width, height = height, initialOffset = initialOffset)
34+
.plainText
35+
36+
try
37+
// normally, if we used vanilla JDK and layered classloaders, we wouldnt need reflection.
38+
// however PPrint works by runtime type testing to deconstruct values. This is
39+
// sensitive to which classloader instantiates the object under test, i.e.
40+
// `value` is constructed inside the repl classloader. Testing for
41+
// `value.isInstanceOf[scala.Product]` in this classloader fails (JDK AppClassLoader),
42+
// because repl classloader has two layers where it can redefine `scala.Product`:
43+
// - `new URLClassLoader` constructed with contents of the `-classpath` setting
44+
// - `AbstractFileClassLoader` also might instrument the library code to support interrupt.
45+
// Due the possible interruption instrumentation, it is unlikely that we can get
46+
// rid of reflection here.
47+
val cl = classLoader()
48+
val pprintCls = Class.forName("pprint.PPrinter$BlackWhite$", false, cl)
49+
val fansiStrCls = Class.forName("fansi.Str", false, cl)
50+
val BlackWhite = pprintCls.getField("MODULE$").get(null)
51+
val BlackWhite_apply = pprintCls.getMethod("apply",
52+
classOf[Any], // value
53+
classOf[Int], // width
54+
classOf[Int], // height
55+
classOf[Int], // indentation
56+
classOf[Int], // initialOffset
57+
classOf[Boolean], // escape Unicode
58+
classOf[Boolean], // show field names
59+
)
60+
val FansiStr_plainText = fansiStrCls.getMethod("plainText")
61+
val fansiStr = BlackWhite_apply.invoke(
62+
BlackWhite, value, width, height, 2, initialOffset, false, true
63+
)
64+
FansiStr_plainText.invoke(fansiStr).asInstanceOf[String]
65+
catch
66+
case ex: ClassNotFoundException => fallback()
67+
case ex: NoSuchMethodException => fallback()
3368
}
3469

3570

0 commit comments

Comments
 (0)