@@ -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