diff --git a/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala b/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala index 57a38126cfd4..ea3afef27fae 100644 --- a/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala @@ -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) diff --git a/compiler/src/dotty/tools/repl/Rendering.scala b/compiler/src/dotty/tools/repl/Rendering.scala index 98944c9ab48c..608ca23c5fec 100644 --- a/compiler/src/dotty/tools/repl/Rendering.scala +++ b/compiler/src/dotty/tools/repl/Rendering.scala @@ -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 @@ -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._ @@ -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) @@ -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) @@ -182,5 +192,3 @@ object Rendering { val text = printer.dclText(s) text.mkString(ctx.settings.pageWidth.value, ctx.settings.printLines.value) } - -} diff --git a/compiler/test-resources/repl/i15493 b/compiler/test-resources/repl/i15493 new file mode 100644 index 000000000000..f543f5c1d0f7 --- /dev/null +++ b/compiler/test-resources/repl/i15493 @@ -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 +