Skip to content

Commit

Permalink
Properly display value of value class in repl (#15545)
Browse files Browse the repository at this point in the history
* fix repl to properly display value of value class after it's creation
  • Loading branch information
rochala authored Jul 1, 2022
1 parent f157978 commit 41678e6
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 18 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class ReplPrinter(_ctx: Context) extends RefinedPrinter(_ctx) {
if (sym.is(Method)) {
sym.info match {
case tp: ExprType => ":" ~~ toText(tp.resType)
case _ => toText(sym.info)
case info => toText(info)
}
}
else if (sym.isType && sym.info.isTypeAlias) toText(sym.info)
Expand Down
42 changes: 25 additions & 17 deletions compiler/src/dotty/tools/repl/Rendering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import dotc.core.Symbols.{Symbol, defn}
import dotc.core.StdNames.{nme, str}
import dotc.printing.ReplPrinter
import dotc.reporting.Diagnostic
import dotc.transform.ValueClasses

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

import Rendering._

Expand Down Expand Up @@ -80,43 +81,53 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) {
* 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): String =
val showTruncated = " ... large output truncated, print value to show all"
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)) + showTruncated
}

/** Return a String representation of a value we got from `classLoader()`. */
private[repl] def replStringOf(value: Object)(using Context): String = {
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 res = myReplStringOf(value)
if res == null then "null // non-null reference has null-valued toString" else truncate(res)
}

/** Load the value of the symbol using reflection.
*
* Calling this method evaluates the expression using reflection
*/
private def valueOf(sym: Symbol)(using Context): Option[String] = {
private def valueOf(sym: Symbol)(using Context): Option[String] =
val objectName = sym.owner.fullName.encode.toString.stripSuffix("$")
val resObj: Class[?] = Class.forName(objectName, true, classLoader())
val value =
resObj
.getDeclaredMethods.find(_.getName == sym.name.encode.toString)
.map(_.invoke(null))
val string = value.map(replStringOf(_))
val symValue = resObj
.getDeclaredMethods.find(_.getName == sym.name.encode.toString)
.flatMap(result => rewrapValueClass(sym.info.classSymbol, result.invoke(null)))
val valueString = symValue.map(replStringOf)

if (!sym.is(Flags.Method) && sym.info == defn.UnitType)
None
else
string.map { s =>
valueString.map { s =>
if (s.startsWith(REPL_WRAPPER_NAME_PREFIX))
s.drop(REPL_WRAPPER_NAME_PREFIX.length).dropWhile(c => c.isDigit || c == '$')
else
s
}
}

/** Rewrap value class to their Wrapper class
*
* @param sym Value Class symbol
* @param value underlying value
*/
private def rewrapValueClass(sym: Symbol, value: Object)(using Context): Option[Object] =
if ValueClasses.isDerivedValueClass(sym) then
val valueClassName = sym.flatName.encode.toString
val valueClass = Class.forName(valueClassName, true, classLoader())
valueClass.getConstructors.headOption.map(_.newInstance(value))
else
Some(value)

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

}

object Rendering {
object Rendering:
final val REPL_WRAPPER_NAME_PREFIX = str.REPL_SESSION_LINE

extension (s: Symbol)
Expand All @@ -182,5 +192,3 @@ object Rendering {
val text = printer.dclText(s)
text.mkString(ctx.settings.pageWidth.value, ctx.settings.printLines.value)
}

}
144 changes: 144 additions & 0 deletions compiler/test-resources/repl/i15493
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
scala> class NInt(val x: Int) extends AnyVal
// defined class NInt

scala> NInt(23)
val res0: NInt = NInt@17

scala> res0.toString
val res1: String = NInt@17

scala> 23
val res2: Int = 23

scala> class NBoolean(val x: Boolean) extends AnyVal
// defined class NBoolean

scala> NBoolean(true)
val res3: NBoolean = NBoolean@4cf

scala> res3.toString
val res4: String = NBoolean@4cf

scala> true
val res5: Boolean = true

scala> class NByte(val x: Byte) extends AnyVal
// defined class NByte

scala> NByte(1)
val res6: NByte = NByte@1

scala> res6.toString
val res7: String = NByte@1

scala> val res8: Byte = 1
val res8: Byte = 1

scala> class NShort(val x: Short) extends AnyVal
// defined class NShort

scala> NShort(1)
val res9: NShort = NShort@1

scala> res9.toString
val res10: String = NShort@1

scala> val res11: Short = 1
val res11: Short = 1

scala> class NLong(val x: Long) extends AnyVal
// defined class NLong

scala> NLong(1)
val res12: NLong = NLong@1

scala> res12.toString
val res13: String = NLong@1

scala> 1L
val res14: Long = 1

scala> class NFloat(val x: Float) extends AnyVal
// defined class NFloat

scala> NFloat(1L)
val res15: NFloat = NFloat@3f800000

scala> res15.toString
val res16: String = NFloat@3f800000

scala> 1.0F
val res17: Float = 1.0

scala> class NDouble(val x: Double) extends AnyVal
// defined class NDouble

scala> NDouble(1D)
val res18: NDouble = NDouble@3ff00000

scala> res18.toString
val res19: String = NDouble@3ff00000

scala> 1.0D
val res20: Double = 1.0

scala> class NChar(val x: Char) extends AnyVal
// defined class NChar

scala> NChar('a')
val res21: NChar = NChar@61

scala> res21.toString
val res22: String = NChar@61

scala> 'a'
val res23: Char = a

scala> class NString(val x: String) extends AnyVal
// defined class NString

scala> NString("test")
val res24: NString = NString@364492

scala> res24.toString
val res25: String = NString@364492

scala> "test"
val res26: String = test

scala> class CustomToString(val x: Int) extends AnyVal { override def toString(): String = s"Test$x" }
// defined class CustomToString

scala> CustomToString(23)
val res27: CustomToString = Test23

scala> res27.toString
val res28: String = Test23

scala> class `<>`(x: Int) extends AnyVal
// defined class <>

scala> `<>`(23)
val res29: <> = less$greater@17

scala> res29.toString
val res30: String = less$greater@17

scala> class `🤪`(x: Int) extends AnyVal
// defined class 🤪

scala> `🤪`(23)
val res31: 🤪 = uD83E$uDD2A@17

scala> res31.toString
val res32: String = uD83E$uDD2A@17

scala> object Outer { class Foo(x: Int) extends AnyVal }
// defined object Outer

scala> Outer.Foo(23)
val res33: Outer.Foo = Outer$Foo@17

scala> res33.toString
val res34: String = Outer$Foo@17

0 comments on commit 41678e6

Please sign in to comment.