Skip to content

Commit aa3e581

Browse files
Backport "Reintroduce reflective REPL pprint call." to 3.8.0 (#24404)
Backports #24353 to the 3.8.0-RC1. PR submitted by the release tooling.
2 parents d5013ad + 47f920a commit aa3e581

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)