@@ -27,9 +27,43 @@ 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 is contains 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 interruption instrumentation, it is unlikely that we can get rid of reflection here.
46+ val cl = classLoader()
47+ val pprintCls = Class .forName(" pprint.PPrinter$BlackWhite$" , false , cl)
48+ val fansiStrCls = Class .forName(" fansi.Str" , false , cl)
49+ val BlackWhite = pprintCls.getField(" MODULE$" ).get(null )
50+ val BlackWhite_apply = pprintCls.getMethod(" apply" ,
51+ classOf [Any ], // value
52+ classOf [Int ], // width
53+ classOf [Int ], // height
54+ classOf [Int ], // indentation
55+ classOf [Int ], // initialOffset
56+ classOf [Boolean ], // escape Unicode
57+ classOf [Boolean ], // show field names
58+ )
59+ val FansiStr_plainText = fansiStrCls.getMethod(" plainText" )
60+ val fansiStr = BlackWhite_apply .invoke(
61+ BlackWhite , value, width, height, 2 , initialOffset, false , true
62+ )
63+ FansiStr_plainText .invoke(fansiStr).asInstanceOf [String ]
64+ catch
65+ case ex : ClassNotFoundException => fallback()
66+ case ex : NoSuchMethodException => fallback()
3367 }
3468
3569
0 commit comments