From 47f920a37097bf4c9c925f6af4b171af4e085b05 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 12 Nov 2025 15:01:29 +0100 Subject: [PATCH] Reintroduce reflective REPL pprint call. (#24353) The class name changed, but the reflective call is still necessary for non-JDK classes to be formatted correctly. [Cherry-picked 0ff17345404c74f1a2a267c776b83ee5c9549b5f] --- repl/src/dotty/tools/repl/Rendering.scala | 41 +++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/repl/src/dotty/tools/repl/Rendering.scala b/repl/src/dotty/tools/repl/Rendering.scala index 3f62654fafae..3eed0b332c7e 100644 --- a/repl/src/dotty/tools/repl/Rendering.scala +++ b/repl/src/dotty/tools/repl/Rendering.scala @@ -27,9 +27,44 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None): var myClassLoader: AbstractFileClassLoader = uninitialized private def pprintRender(value: Any, width: Int, height: Int, initialOffset: Int)(using Context): String = { - pprint.PPrinter.BlackWhite - .apply(value, width = width, height = height, initialOffset = initialOffset) - .plainText + def fallback() = + // might as well be `println` in this case, but JDK classes e.g. `Float` are correctly handled. + pprint.PPrinter.BlackWhite + .apply(value, width = width, height = height, initialOffset = initialOffset) + .plainText + + try + // normally, if we used vanilla JDK and layered classloaders, we wouldnt need reflection. + // however PPrint works by runtime type testing to deconstruct values. This is + // sensitive to which classloader instantiates the object under test, i.e. + // `value` is constructed inside the repl classloader. Testing for + // `value.isInstanceOf[scala.Product]` in this classloader fails (JDK AppClassLoader), + // because repl classloader has two layers where it can redefine `scala.Product`: + // - `new URLClassLoader` constructed with contents of the `-classpath` setting + // - `AbstractFileClassLoader` also might instrument the library code to support interrupt. + // Due the possible interruption instrumentation, it is unlikely that we can get + // rid of reflection here. + val cl = classLoader() + val pprintCls = Class.forName("pprint.PPrinter$BlackWhite$", false, cl) + val fansiStrCls = Class.forName("fansi.Str", false, cl) + val BlackWhite = pprintCls.getField("MODULE$").get(null) + val BlackWhite_apply = pprintCls.getMethod("apply", + classOf[Any], // value + classOf[Int], // width + classOf[Int], // height + classOf[Int], // indentation + classOf[Int], // initialOffset + classOf[Boolean], // escape Unicode + classOf[Boolean], // show field names + ) + val FansiStr_plainText = fansiStrCls.getMethod("plainText") + val fansiStr = BlackWhite_apply.invoke( + BlackWhite, value, width, height, 2, initialOffset, false, true + ) + FansiStr_plainText.invoke(fansiStr).asInstanceOf[String] + catch + case ex: ClassNotFoundException => fallback() + case ex: NoSuchMethodException => fallback() }