Skip to content

Commit 41678e6

Browse files
authored
Properly display value of value class in repl (#15545)
* fix repl to properly display value of value class after it's creation
1 parent f157978 commit 41678e6

File tree

3 files changed

+170
-18
lines changed

3 files changed

+170
-18
lines changed

compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class ReplPrinter(_ctx: Context) extends RefinedPrinter(_ctx) {
3737
if (sym.is(Method)) {
3838
sym.info match {
3939
case tp: ExprType => ":" ~~ toText(tp.resType)
40-
case _ => toText(sym.info)
40+
case info => toText(info)
4141
}
4242
}
4343
else if (sym.isType && sym.info.isTypeAlias) toText(sym.info)

compiler/src/dotty/tools/repl/Rendering.scala

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import dotc.core.Symbols.{Symbol, defn}
1414
import dotc.core.StdNames.{nme, str}
1515
import dotc.printing.ReplPrinter
1616
import dotc.reporting.Diagnostic
17+
import dotc.transform.ValueClasses
1718

1819
/** This rendering object uses `ClassLoader`s to accomplish crossing the 4th
1920
* wall (i.e. fetching back values from the compiled class files put into a
@@ -23,7 +24,7 @@ import dotc.reporting.Diagnostic
2324
* `ReplDriver#resetToInitial` is called, the accompanying instance of
2425
* `Rendering` is no longer valid.
2526
*/
26-
private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) {
27+
private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
2728

2829
import Rendering._
2930

@@ -80,43 +81,53 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) {
8081
* then this bug will surface, so perhaps better not?
8182
* https://github.com/scala/bug/issues/12337
8283
*/
83-
private[repl] def truncate(str: String): String = {
84+
private[repl] def truncate(str: String): String =
8485
val showTruncated = " ... large output truncated, print value to show all"
8586
val ncp = str.codePointCount(0, str.length) // to not cut inside code point
8687
if ncp <= MaxStringElements then str
8788
else str.substring(0, str.offsetByCodePoints(0, MaxStringElements - 1)) + showTruncated
88-
}
8989

9090
/** Return a String representation of a value we got from `classLoader()`. */
91-
private[repl] def replStringOf(value: Object)(using Context): String = {
91+
private[repl] def replStringOf(value: Object)(using Context): String =
9292
assert(myReplStringOf != null,
9393
"replStringOf should only be called on values creating using `classLoader()`, but `classLoader()` has not been called so far")
9494
val res = myReplStringOf(value)
9595
if res == null then "null // non-null reference has null-valued toString" else truncate(res)
96-
}
9796

9897
/** Load the value of the symbol using reflection.
9998
*
10099
* Calling this method evaluates the expression using reflection
101100
*/
102-
private def valueOf(sym: Symbol)(using Context): Option[String] = {
101+
private def valueOf(sym: Symbol)(using Context): Option[String] =
103102
val objectName = sym.owner.fullName.encode.toString.stripSuffix("$")
104103
val resObj: Class[?] = Class.forName(objectName, true, classLoader())
105-
val value =
106-
resObj
107-
.getDeclaredMethods.find(_.getName == sym.name.encode.toString)
108-
.map(_.invoke(null))
109-
val string = value.map(replStringOf(_))
104+
val symValue = resObj
105+
.getDeclaredMethods.find(_.getName == sym.name.encode.toString)
106+
.flatMap(result => rewrapValueClass(sym.info.classSymbol, result.invoke(null)))
107+
val valueString = symValue.map(replStringOf)
108+
110109
if (!sym.is(Flags.Method) && sym.info == defn.UnitType)
111110
None
112111
else
113-
string.map { s =>
112+
valueString.map { s =>
114113
if (s.startsWith(REPL_WRAPPER_NAME_PREFIX))
115114
s.drop(REPL_WRAPPER_NAME_PREFIX.length).dropWhile(c => c.isDigit || c == '$')
116115
else
117116
s
118117
}
119-
}
118+
119+
/** Rewrap value class to their Wrapper class
120+
*
121+
* @param sym Value Class symbol
122+
* @param value underlying value
123+
*/
124+
private def rewrapValueClass(sym: Symbol, value: Object)(using Context): Option[Object] =
125+
if ValueClasses.isDerivedValueClass(sym) then
126+
val valueClassName = sym.flatName.encode.toString
127+
val valueClass = Class.forName(valueClassName, true, classLoader())
128+
valueClass.getConstructors.headOption.map(_.newInstance(value))
129+
else
130+
Some(value)
120131

121132
def renderTypeDef(d: Denotation)(using Context): Diagnostic =
122133
infoDiagnostic("// defined " ++ d.symbol.showUser, d)
@@ -171,9 +182,8 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) {
171182
private def infoDiagnostic(msg: String, d: Denotation)(using Context): Diagnostic =
172183
new Diagnostic.Info(msg, d.symbol.sourcePos)
173184

174-
}
175185

176-
object Rendering {
186+
object Rendering:
177187
final val REPL_WRAPPER_NAME_PREFIX = str.REPL_SESSION_LINE
178188

179189
extension (s: Symbol)
@@ -182,5 +192,3 @@ object Rendering {
182192
val text = printer.dclText(s)
183193
text.mkString(ctx.settings.pageWidth.value, ctx.settings.printLines.value)
184194
}
185-
186-
}

compiler/test-resources/repl/i15493

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
scala> class NInt(val x: Int) extends AnyVal
2+
// defined class NInt
3+
4+
scala> NInt(23)
5+
val res0: NInt = NInt@17
6+
7+
scala> res0.toString
8+
val res1: String = NInt@17
9+
10+
scala> 23
11+
val res2: Int = 23
12+
13+
scala> class NBoolean(val x: Boolean) extends AnyVal
14+
// defined class NBoolean
15+
16+
scala> NBoolean(true)
17+
val res3: NBoolean = NBoolean@4cf
18+
19+
scala> res3.toString
20+
val res4: String = NBoolean@4cf
21+
22+
scala> true
23+
val res5: Boolean = true
24+
25+
scala> class NByte(val x: Byte) extends AnyVal
26+
// defined class NByte
27+
28+
scala> NByte(1)
29+
val res6: NByte = NByte@1
30+
31+
scala> res6.toString
32+
val res7: String = NByte@1
33+
34+
scala> val res8: Byte = 1
35+
val res8: Byte = 1
36+
37+
scala> class NShort(val x: Short) extends AnyVal
38+
// defined class NShort
39+
40+
scala> NShort(1)
41+
val res9: NShort = NShort@1
42+
43+
scala> res9.toString
44+
val res10: String = NShort@1
45+
46+
scala> val res11: Short = 1
47+
val res11: Short = 1
48+
49+
scala> class NLong(val x: Long) extends AnyVal
50+
// defined class NLong
51+
52+
scala> NLong(1)
53+
val res12: NLong = NLong@1
54+
55+
scala> res12.toString
56+
val res13: String = NLong@1
57+
58+
scala> 1L
59+
val res14: Long = 1
60+
61+
scala> class NFloat(val x: Float) extends AnyVal
62+
// defined class NFloat
63+
64+
scala> NFloat(1L)
65+
val res15: NFloat = NFloat@3f800000
66+
67+
scala> res15.toString
68+
val res16: String = NFloat@3f800000
69+
70+
scala> 1.0F
71+
val res17: Float = 1.0
72+
73+
scala> class NDouble(val x: Double) extends AnyVal
74+
// defined class NDouble
75+
76+
scala> NDouble(1D)
77+
val res18: NDouble = NDouble@3ff00000
78+
79+
scala> res18.toString
80+
val res19: String = NDouble@3ff00000
81+
82+
scala> 1.0D
83+
val res20: Double = 1.0
84+
85+
scala> class NChar(val x: Char) extends AnyVal
86+
// defined class NChar
87+
88+
scala> NChar('a')
89+
val res21: NChar = NChar@61
90+
91+
scala> res21.toString
92+
val res22: String = NChar@61
93+
94+
scala> 'a'
95+
val res23: Char = a
96+
97+
scala> class NString(val x: String) extends AnyVal
98+
// defined class NString
99+
100+
scala> NString("test")
101+
val res24: NString = NString@364492
102+
103+
scala> res24.toString
104+
val res25: String = NString@364492
105+
106+
scala> "test"
107+
val res26: String = test
108+
109+
scala> class CustomToString(val x: Int) extends AnyVal { override def toString(): String = s"Test$x" }
110+
// defined class CustomToString
111+
112+
scala> CustomToString(23)
113+
val res27: CustomToString = Test23
114+
115+
scala> res27.toString
116+
val res28: String = Test23
117+
118+
scala> class `<>`(x: Int) extends AnyVal
119+
// defined class <>
120+
121+
scala> `<>`(23)
122+
val res29: <> = less$greater@17
123+
124+
scala> res29.toString
125+
val res30: String = less$greater@17
126+
127+
scala> class `🤪`(x: Int) extends AnyVal
128+
// defined class 🤪
129+
130+
scala> `🤪`(23)
131+
val res31: 🤪 = uD83E$uDD2A@17
132+
133+
scala> res31.toString
134+
val res32: String = uD83E$uDD2A@17
135+
136+
scala> object Outer { class Foo(x: Int) extends AnyVal }
137+
// defined object Outer
138+
139+
scala> Outer.Foo(23)
140+
val res33: Outer.Foo = Outer$Foo@17
141+
142+
scala> res33.toString
143+
val res34: String = Outer$Foo@17
144+

0 commit comments

Comments
 (0)