Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ object SyntaxHighlighting {
val CommentColor: String = Console.BLUE
val KeywordColor: String = Console.YELLOW
val ValDefColor: String = Console.CYAN
val LiteralColor: String = Console.RED
val LiteralColor: String = Console.GREEN
val StringColor: String = Console.GREEN
val TypeColor: String = Console.MAGENTA
val AnnotationColor: String = Console.MAGENTA
Expand Down Expand Up @@ -80,6 +80,9 @@ object SyntaxHighlighting {
case IDENTIFIER if name == nme.??? =>
highlightRange(start, end, Console.RED_B)

case IDENTIFIER if name.head.isUpper && name.exists(!_.isUpper) =>
highlightRange(start, end, KeywordColor)

case _ =>
}
}
Expand Down
53 changes: 1 addition & 52 deletions compiler/src/dotty/tools/repl/Rendering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):

var myClassLoader: AbstractFileClassLoader = uninitialized

/** (value, maxElements, maxCharacters) => String */
var myReplStringOf: (Object, Int, Int) => String = uninitialized

/** Class loader used to load compiled code */
private[repl] def classLoader()(using Context) =
Expand All @@ -46,45 +44,6 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
}

myClassLoader = new AbstractFileClassLoader(ctx.settings.outputDir.value, parent)
myReplStringOf = {
// We need to use the ScalaRunTime class coming from the scala-library
// on the user classpath, and not the one available in the current
// classloader, so we use reflection instead of simply calling
// `ScalaRunTime.stringOf`. Also probe for new stringOf that does string quoting, etc.
val scalaRuntime = Class.forName("scala.runtime.ScalaRunTime", true, myClassLoader)
val renderer = "stringOf"
val stringOfInvoker: (Object, Int) => String =
def richStringOf: (Object, Int) => String =
val method = scalaRuntime.getMethod(renderer, classOf[Object], classOf[Int], classOf[Boolean])
val richly = java.lang.Boolean.TRUE // add a repl option for enriched output
(value, maxElements) => method.invoke(null, value, maxElements, richly).asInstanceOf[String]
def poorStringOf: (Object, Int) => String =
try
val method = scalaRuntime.getMethod(renderer, classOf[Object], classOf[Int])
(value, maxElements) => method.invoke(null, value, maxElements).asInstanceOf[String]
catch case _: NoSuchMethodException => (value, maxElements) => String.valueOf(value).take(maxElements)
try richStringOf
catch case _: NoSuchMethodException => poorStringOf
def stringOfMaybeTruncated(value: Object, maxElements: Int): String = stringOfInvoker(value, maxElements)

// require value != null
// `ScalaRuntime.stringOf` returns null iff value.toString == null, let caller handle that.
// `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.
// Note that `stringOf` has new API in flight to handle truncation, see stringOfMaybeTruncated.
(value: Object, maxElements: Int, maxCharacters: Int) =>
stringOfMaybeTruncated(value, Int.MaxValue) match
case null => null
case notTruncated =>
val maybeTruncated =
val maybeTruncatedByElementCount = stringOfMaybeTruncated(value, maxElements)
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 then maybeTruncated
else s"$maybeTruncated ... large output truncated, print value to show all"
}
myClassLoader
}

Expand All @@ -95,17 +54,7 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):

/** Return a String representation of a value we got from `classLoader()`. */
private[repl] def replStringOf(sym: Symbol, 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 maxPrintCharacters = ctx.settings.VreplMaxPrintCharacters.valueIn(ctx.settingsState)
// stringOf returns null if value.toString returns null. Show some text as a fallback.
def fallback = s"""null // result of "${sym.name}.toString" is null"""
if value == null then "null" else
myReplStringOf(value, maxPrintElements, maxPrintCharacters) match
case null => fallback
case res => res
end if
dotty.shaded.pprint.PPrinter.BlackWhite.apply(value).plainText

/** Load the value of the symbol using reflection.
*
Expand Down
4 changes: 2 additions & 2 deletions compiler/test-resources/repl/19184
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
scala> def o(s: String) = "o"; def oo(s: String) = "oo"; val o = "o"; val oo = "oo"
def o(s: String): String
def oo(s: String): String
val o: String = o
val oo: String = oo
val o: String = "o"
val oo: String = "oo"
2 changes: 1 addition & 1 deletion compiler/test-resources/repl/defaultClassloader
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
scala> val d: Long = (new java.sql.Date(100L)).getTime
val d: Long = 100
val d: Long = 100L
2 changes: 1 addition & 1 deletion compiler/test-resources/repl/i13181
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
scala> scala.compiletime.codeOf(1+2)
val res0: String = 1 + 2
val res0: String = "1 + 2"
2 changes: 1 addition & 1 deletion compiler/test-resources/repl/i1369
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
scala> print("foo")
foo
scala> "Hello"
val res0: String = Hello
val res0: String = "Hello"
34 changes: 17 additions & 17 deletions compiler/test-resources/repl/i15493
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ scala> NInt(23)
val res0: NInt = NInt@17

scala> res0.toString
val res1: String = NInt@17
val res1: String = "rs$line$1$NInt@17"
Copy link
Contributor Author

@lihaoyi lihaoyi Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what's happening here before this PR, but there must be some super sketchy stdout-regexing happening to mangle the .toString so it looks different when returned or println-ed.

scala> res0.toString
val res1: String = NInt@17
                                                                                                                          
scala> println(res0.toString)
rs$line$1$NInt@17

The new behavior is probably better: we special case returning because it uses pprint, println is just println, and if someone wants pprint themselves they can use dotty.shaded.pprint.log


scala> 23
val res2: Int = 23
Expand All @@ -17,7 +17,7 @@ scala> NBoolean(true)
val res3: NBoolean = NBoolean@4cf

scala> res3.toString
val res4: String = NBoolean@4cf
val res4: String = "rs$line$5$NBoolean@4cf"

scala> true
val res5: Boolean = true
Expand All @@ -29,7 +29,7 @@ scala> NByte(1)
val res6: NByte = NByte@1

scala> res6.toString
val res7: String = NByte@1
val res7: String = "rs$line$9$NByte@1"

scala> val res8: Byte = 1
val res8: Byte = 1
Expand All @@ -41,7 +41,7 @@ scala> NShort(1)
val res9: NShort = NShort@1

scala> res9.toString
val res10: String = NShort@1
val res10: String = "rs$line$13$NShort@1"

scala> val res11: Short = 1
val res11: Short = 1
Expand All @@ -53,10 +53,10 @@ scala> NLong(1)
val res12: NLong = NLong@1

scala> res12.toString
val res13: String = NLong@1
val res13: String = "rs$line$17$NLong@1"

scala> 1L
val res14: Long = 1
val res14: Long = 1L

scala> class NFloat(val x: Float) extends AnyVal
// defined class NFloat
Expand All @@ -65,10 +65,10 @@ scala> NFloat(1L)
val res15: NFloat = NFloat@3f800000

scala> res15.toString
val res16: String = NFloat@3f800000
val res16: String = "rs$line$21$NFloat@3f800000"

scala> 1.0F
val res17: Float = 1.0
val res17: Float = 1.0F

scala> class NDouble(val x: Double) extends AnyVal
// defined class NDouble
Expand All @@ -77,7 +77,7 @@ scala> NDouble(1D)
val res18: NDouble = NDouble@3ff00000

scala> res18.toString
val res19: String = NDouble@3ff00000
val res19: String = "rs$line$25$NDouble@3ff00000"

scala> 1.0D
val res20: Double = 1.0
Expand All @@ -89,10 +89,10 @@ scala> NChar('a')
val res21: NChar = NChar@61

scala> res21.toString
val res22: String = NChar@61
val res22: String = "rs$line$29$NChar@61"

scala> 'a'
val res23: Char = a
val res23: Char = 'a'

scala> class NString(val x: String) extends AnyVal
// defined class NString
Expand All @@ -101,10 +101,10 @@ scala> NString("test")
val res24: NString = NString@364492

scala> res24.toString
val res25: String = NString@364492
val res25: String = "rs$line$33$NString@364492"

scala> "test"
val res26: String = test
val res26: String = "test"

scala> class CustomToString(val x: Int) extends AnyVal { override def toString(): String = s"Test$x" }
// defined class CustomToString
Expand All @@ -113,7 +113,7 @@ scala> CustomToString(23)
val res27: CustomToString = Test23

scala> res27.toString
val res28: String = Test23
val res28: String = "Test23"

scala> class `<>`(x: Int) extends AnyVal
// defined class <>
Expand All @@ -122,7 +122,7 @@ scala> `<>`(23)
val res29: <> = less$greater@17

scala> res29.toString
val res30: String = less$greater@17
val res30: String = "rs$line$40$$less$greater@17"

scala> class `🤪`(x: Int) extends AnyVal
// defined class 🤪
Expand All @@ -131,7 +131,7 @@ scala> `🤪`(23)
val res31: 🤪 = uD83E$uDD2A@17

scala> res31.toString
val res32: String = uD83E$uDD2A@17
val res32: String = "rs$line$43$$uD83E$uDD2A@17"

scala> object Outer { class Foo(x: Int) extends AnyVal }
// defined object Outer
Expand All @@ -140,7 +140,7 @@ scala> Outer.Foo(23)
val res33: Outer.Foo = Outer$Foo@17

scala> res33.toString
val res34: String = Outer$Foo@17
val res34: String = "rs$line$46$Outer$Foo@17"

scala> Vector.unapplySeq(Vector(2))
val res35: scala.collection.SeqFactory.UnapplySeqWrapper[Int] = scala.collection.SeqFactory$UnapplySeqWrapper@df507bfd
Expand Down
2 changes: 1 addition & 1 deletion compiler/test-resources/repl/i18383
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ scala> class Foo { import scala.util.*; println("foo") }
// defined class Foo

scala> { import scala.util.*; "foo" }
val res0: String = foo
val res0: String = "foo"
2 changes: 1 addition & 1 deletion compiler/test-resources/repl/i3388
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
scala> val foo = "1"; foo.toInt
val foo: String = 1
val foo: String = "1"
val res0: Int = 1
10 changes: 5 additions & 5 deletions compiler/test-resources/repl/jar-multiple
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ Added 'compiler/test-resources/jars/mylibrary.jar' to classpath.
scala> import mylibrary.Utils

scala> Utils.greet("Alice")
val res0: String = Hello, Alice!
val res0: String = "Hello, Alice!"

scala>:jar compiler/test-resources/jars/mylibrary2.jar
Added 'compiler/test-resources/jars/mylibrary2.jar' to classpath.

scala> import mylibrary2.Utils2

scala> Utils2.greet("Alice")
val res1: String = Greetings, Alice!
val res1: String = "Greetings, Alice!"

scala> Utils.greet("Alice")
val res2: String = Hello, Alice!
val res2: String = "Hello, Alice!"

scala> import mylibrary.Utils.greet

scala> greet("Tom")
val res3: String = Hello, Tom!
val res3: String = "Hello, Tom!"

scala> Utils.greet("Alice")
val res4: String = Hello, Alice!
val res4: String = "Hello, Alice!"

scala> z
val res5: Int = 1
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ 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
val res1: Seq[Int] = List(1, 2, 3)

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
scala> 1.to(10).mkString
val res0: String = 12345678910
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
val res1: String = "12345678910"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old -Vrepl-max-print-elements and -Vrepl-max-print-characters:10 don't work with PPrint. Instead, we can control the max width and height before truncation. As a first pass I'd say we can do that in a follow up, but if people really want I can add those -Vrepl-width and -Vrepl-height in this PR

Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ val res0: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
scala>:settings -Vrepl-max-print-elements:20

scala> 1.to(300).toList
val res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) ... large output truncated, print value to show all
val res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300)

Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class SyntaxHighlightingTests extends DottyTest {
test("val foo = 123", "<K|val> <V|foo> = <L|123>")
test(
"val foo: List[List[Int]] = List(List(1))",
"<K|val> <V|foo>: <T|List>[<T|List>[<T|Int>]] = List(List(<L|1>))"
"<K|val> <V|foo>: <T|List>[<T|List>[<T|Int>]] = <K|List>(<K|List>(<L|1>))"
)

test("var", "<K|var>")
Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotty/tools/repl/LoadTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class LoadTests extends ReplTest {
|def helloWorld: String
|""".stripMargin,
runCode = "helloWorld",
output = """|val res0: String = Hello, World!
output = """|val res0: String = "Hello, World!"
|""".stripMargin
)

Expand Down
4 changes: 1 addition & 3 deletions compiler/test/dotty/tools/repl/ReplCompilerTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ class ReplCompilerTests extends ReplTest:

@Test def testSingletonPrint = initially {
run("""val a = "hello"; val x: a.type = a""")
assertMultiLineEquals("val a: String = hello\nval x: a.type = hello", storedOutput().trim)
assertMultiLineEquals("val a: String = \"hello\"\nval x: a.type = \"hello\"", storedOutput().trim)
}

@Test def i6574 = initially {
Expand Down Expand Up @@ -445,7 +445,6 @@ class ReplCompilerTests extends ReplTest:
.andThen:
val last = lines().last
assertTrue(last, last.startsWith("val tpolecat: Object = null"))
assertTrue(last, last.endsWith("""// result of "tpolecat.toString" is null"""))

@Test def `i17333 print toplevel object with null toString`: Unit =
initially:
Expand All @@ -454,7 +453,6 @@ class ReplCompilerTests extends ReplTest:
run("tpolecat")
val last = lines().last
assertTrue(last, last.startsWith("val res0: tpolecat.type = null"))
assertTrue(last, last.endsWith("""// result of "res0.toString" is null"""))

@Test def `i21431 filter out best effort options`: Unit =
initially:
Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotty/tools/repl/ShadowingTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class ShadowingTests extends ReplTest(options = ShadowingTests.options):
testScript(name = "<shadow-subdir-x>",
"""|scala> val (x, y) = (42, "foo")
|val x: Int = 42
|val y: String = foo
|val y: String = "foo"
|
|scala> if (true) x else y
|val res0: Int | String = 42
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class WorksheetTest {
case _ => "odd"
}${m2}"""
.run(m1,
((m1 to m2), "val res0: String = odd"))
((m1 to m2), "val res0: String = \"odd\""))
}

@Test def evaluationException: Unit = {
Expand Down
Loading
Loading