Skip to content

REPL: make truncation by characters configurable #16167

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ private sealed trait VerboseSettings:
val VprofileSortedBy = ChoiceSetting("-Vprofile-sorted-by", "key", "Show metrics about sources and internal representations sorted by given column name", List("name", "path", "lines", "tokens", "tasty", "complexity"), "")
val VprofileDetails = IntSetting("-Vprofile-details", "Show metrics about sources and internal representations of the most complex methods", 0)
val VreplMaxPrintElements: Setting[Int] = IntSetting("-Vrepl-max-print-elements", "Number of elements to be printed before output is truncated.", 1000)
val VreplMaxPrintCharacters: Setting[Int] = IntSetting("-Vrepl-max-print-characters", "Number of characters to be printed before output is truncated.", 50000)

/** -W "Warnings" settings
*/
Expand Down
40 changes: 17 additions & 23 deletions compiler/src/dotty/tools/repl/Rendering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,10 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):

import Rendering._

private val MaxStringElements: Int = 1000 // no need to mkString billions of elements

private var myClassLoader: AbstractFileClassLoader = _

/** (value, maxElements) => String */
private var myReplStringOf: (Object, Int) => String = _

/** info to add if output got truncated */
private val infoOutputGotTruncated = " ... large output truncated, print value to show all"
/** (value, maxElements, maxCharacters) => String */
private var myReplStringOf: (Object, Int, Int) => String = _

/** Class loader used to load compiled code */
private[repl] def classLoader()(using Context) =
Expand All @@ -62,7 +57,7 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
// `ScalaRunTime.replStringOf`. Probe for new API without extraneous newlines.
// For old API, try to clean up extraneous newlines by stripping suffix and maybe prefix newline.
val scalaRuntime = Class.forName("scala.runtime.ScalaRunTime", true, myClassLoader)
val renderer = "stringOf" // was: replStringOf
val renderer = "stringOf"
def stringOfMaybeTruncated(value: Object, maxElements: Int): String = {
try {
val meth = scalaRuntime.getMethod(renderer, classOf[Object], classOf[Int], classOf[Boolean])
Expand All @@ -75,38 +70,37 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
}
}

(value: Object, maxElements: Int) => {
(value: Object, maxElements: Int, maxCharacters: Int) => {
// `ScalaRuntime.stringOf` may truncate the output, in which case we want to indicate that fact to the user
// In order to figure out if it did get truncated, we invoke it twice - once with the `maxElements` that we
// want to print, and once without a limit. If the first is shorter, truncation did occur.
val maybeTruncated = stringOfMaybeTruncated(value, maxElements)
val notTruncated = stringOfMaybeTruncated(value, Int.MaxValue)
if (maybeTruncated.length == notTruncated.length) maybeTruncated
else maybeTruncated + infoOutputGotTruncated
val maybeTruncatedByElementCount = stringOfMaybeTruncated(value, maxElements)
val maybeTruncated = truncate(maybeTruncatedByElementCount, maxCharacters)

// our string representation may have been truncated by element and/or character count
// if so, append an info string - but only once
if (notTruncated.length == maybeTruncated.length) maybeTruncated
else s"$maybeTruncated ... large output truncated, print value to show all"
}

}
myClassLoader
}

/** Used to elide long output in replStringOf.
*
* TODO: Perhaps implement setting scala.repl.maxprintstring as in Scala 2, but
* then this bug will surface, so perhaps better not?
* https://github.com/scala/bug/issues/12337
*/
private[repl] def truncate(str: String): String =
private[repl] def truncate(str: String, maxPrintCharacters: Int)(using ctx: Context): String =
val ncp = str.codePointCount(0, str.length) // to not cut inside code point
if ncp <= MaxStringElements then str
else str.substring(0, str.offsetByCodePoints(0, MaxStringElements - 1)) + infoOutputGotTruncated
if ncp <= maxPrintCharacters then str
else str.substring(0, str.offsetByCodePoints(0, maxPrintCharacters - 1))

/** Return a String representation of a value we got from `classLoader()`. */
private[repl] def replStringOf(value: Object)(using Context): String =
assert(myReplStringOf != null,
"replStringOf should only be called on values creating using `classLoader()`, but `classLoader()` has not been called so far")
val maxPrintElements = ctx.settings.VreplMaxPrintElements.valueIn(ctx.settingsState)
val res = myReplStringOf(value, maxPrintElements)
if res == null then "null // non-null reference has null-valued toString" else truncate(res)
val maxPrintCharacters = ctx.settings.VreplMaxPrintCharacters.valueIn(ctx.settingsState)
val res = myReplStringOf(value, maxPrintElements, maxPrintCharacters)
if res == null then "null // non-null reference has null-valued toString" else res

/** Load the value of the symbol using reflection.
*
Expand Down
14 changes: 0 additions & 14 deletions compiler/test-resources/repl/i11377

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
scala> Seq(1,2,3)
val res0: Seq[Int] = List(1, 2, 3)

scala>:settings -Vrepl-max-print-elements:2

scala>:settings -Vrepl-max-print-characters:50

scala> Seq(1,2,3)
val res1: Seq[Int] = List(1, 2) ... large output truncated, print value to show all

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
scala> 1.to(10).mkString
val res0: String = 12345678910

scala>:settings -Vrepl-max-print-characters:10

scala> 1.to(10).mkString
val res1: String = 123456789 ... large output truncated, print value to show all