From b767dab981dc1108e648cc80b161d0aed9dea882 Mon Sep 17 00:00:00 2001 From: rochala Date: Tue, 28 Jun 2022 20:58:02 +0200 Subject: [PATCH 1/5] fix repl to properly display value of value class after it's creation --- .../tools/dotc/printing/ReplPrinter.scala | 2 +- compiler/src/dotty/tools/repl/Rendering.scala | 10 +- compiler/test-resources/repl/i15493 | 107 ++++++++++++++++++ 3 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 compiler/test-resources/repl/i15493 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..1af7a037c168 100644 --- a/compiler/src/dotty/tools/repl/Rendering.scala +++ b/compiler/src/dotty/tools/repl/Rendering.scala @@ -106,11 +106,17 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) { resObj .getDeclaredMethods.find(_.getName == sym.name.encode.toString) .map(_.invoke(null)) - val string = value.map(replStringOf(_)) + + val resultSym = sym.info.classSymbol + val valueString = if (resultSym.isValueClass && !resultSym.isPrimitiveValueClass) then + value.map(value => s"${sym.info.classSymbol.name}@${value.hashCode.toHexString}") + else + value.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 diff --git a/compiler/test-resources/repl/i15493 b/compiler/test-resources/repl/i15493 new file mode 100644 index 000000000000..b1d7c04eea75 --- /dev/null +++ b/compiler/test-resources/repl/i15493 @@ -0,0 +1,107 @@ +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 From 5715012d2c8adc1e3b29244571a67e749aceb341 Mon Sep 17 00:00:00 2001 From: rochala Date: Wed, 29 Jun 2022 12:30:03 +0200 Subject: [PATCH 2/5] use toString to display value of value class in repl --- compiler/src/dotty/tools/repl/Rendering.scala | 52 +++++++++++-------- compiler/test-resources/repl/i15493 | 9 ++++ 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/repl/Rendering.scala b/compiler/src/dotty/tools/repl/Rendering.scala index 1af7a037c168..52df1a878325 100644 --- a/compiler/src/dotty/tools/repl/Rendering.scala +++ b/compiler/src/dotty/tools/repl/Rendering.scala @@ -23,7 +23,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,38 +80,32 @@ 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 resultSym = sym.info.classSymbol - val valueString = if (resultSym.isValueClass && !resultSym.isPrimitiveValueClass) then - value.map(value => s"${sym.info.classSymbol.name}@${value.hashCode.toHexString}") - else - value.map(replStringOf(_)) + val value = resObj + .getDeclaredMethods.find(_.getName == sym.name.encode.toString) + .map(_.invoke(null)) + .flatMap(rewrapValueClass(sym.info.classSymbol, _)) + + val valueString = value.map(replStringOf) if (!sym.is(Flags.Method) && sym.info == defn.UnitType) None @@ -122,7 +116,26 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None) { else s } - } + + /** Rewrap value classes to their's Wrappers and evaluate their `toString` value. + * + * @param sym Value Class symbol + * @param value underlying value + */ + def rewrapValueClass(sym: Symbol, value: Object)(using Context): Option[Object] = + if (sym.isValueClass && !sym.isPrimitiveValueClass) then + val valueClassName = sym.flatName.encode.toString + val valueClass = Class.forName(valueClassName, true, classLoader()) + + for { + constructor <- valueClass.getConstructors.headOption + toStringMethod <- valueClass.getMethods.find(_.getName == nme.toString_.toString) + } yield { + val instance = constructor.newInstance(value) + toStringMethod.invoke(instance) + } + else + Some(value) def renderTypeDef(d: Denotation)(using Context): Diagnostic = infoDiagnostic("// defined " ++ d.symbol.showUser, d) @@ -177,9 +190,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) @@ -188,5 +200,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 index b1d7c04eea75..0901fbd23909 100644 --- a/compiler/test-resources/repl/i15493 +++ b/compiler/test-resources/repl/i15493 @@ -105,3 +105,12 @@ 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 From 81d4712576d62f7d0c6b35cf2d07420860cdae84 Mon Sep 17 00:00:00 2001 From: rochala Date: Wed, 29 Jun 2022 13:53:32 +0200 Subject: [PATCH 3/5] remove toString call --- compiler/src/dotty/tools/repl/Rendering.scala | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/repl/Rendering.scala b/compiler/src/dotty/tools/repl/Rendering.scala index 52df1a878325..0e1979cc84de 100644 --- a/compiler/src/dotty/tools/repl/Rendering.scala +++ b/compiler/src/dotty/tools/repl/Rendering.scala @@ -100,12 +100,10 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None): 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 + val symValue = resObj .getDeclaredMethods.find(_.getName == sym.name.encode.toString) - .map(_.invoke(null)) - .flatMap(rewrapValueClass(sym.info.classSymbol, _)) - - val valueString = value.map(replStringOf) + .flatMap(result => rewrapValueClass(sym.info.classSymbol, result.invoke(null))) + val valueString = symValue.map(replStringOf) if (!sym.is(Flags.Method) && sym.info == defn.UnitType) None @@ -117,23 +115,16 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None): s } - /** Rewrap value classes to their's Wrappers and evaluate their `toString` value. + /** Rewrap value class to their Wrapper class * * @param sym Value Class symbol * @param value underlying value */ - def rewrapValueClass(sym: Symbol, value: Object)(using Context): Option[Object] = + private def rewrapValueClass(sym: Symbol, value: Object)(using Context): Option[Object] = if (sym.isValueClass && !sym.isPrimitiveValueClass) then val valueClassName = sym.flatName.encode.toString val valueClass = Class.forName(valueClassName, true, classLoader()) - - for { - constructor <- valueClass.getConstructors.headOption - toStringMethod <- valueClass.getMethods.find(_.getName == nme.toString_.toString) - } yield { - val instance = constructor.newInstance(value) - toStringMethod.invoke(instance) - } + valueClass.getConstructors.headOption.map(_.newInstance(value)) else Some(value) From 8b135ad0145edbdc0f578d48d6f6092149e61e64 Mon Sep 17 00:00:00 2001 From: rochala Date: Thu, 30 Jun 2022 11:09:03 +0200 Subject: [PATCH 4/5] change value class check to existing method, add tests for special chars --- compiler/src/dotty/tools/repl/Rendering.scala | 3 ++- compiler/test-resources/repl/i15493 | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/repl/Rendering.scala b/compiler/src/dotty/tools/repl/Rendering.scala index 0e1979cc84de..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 @@ -121,7 +122,7 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None): * @param value underlying value */ private def rewrapValueClass(sym: Symbol, value: Object)(using Context): Option[Object] = - if (sym.isValueClass && !sym.isPrimitiveValueClass) then + if ValueClasses.isDerivedValueClass(sym) then val valueClassName = sym.flatName.encode.toString val valueClass = Class.forName(valueClassName, true, classLoader()) valueClass.getConstructors.headOption.map(_.newInstance(value)) diff --git a/compiler/test-resources/repl/i15493 b/compiler/test-resources/repl/i15493 index 0901fbd23909..7476b63374fd 100644 --- a/compiler/test-resources/repl/i15493 +++ b/compiler/test-resources/repl/i15493 @@ -114,3 +114,21 @@ 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 From 23a89e589366f12839c492dd181f9a6ff6ee2774 Mon Sep 17 00:00:00 2001 From: rochala Date: Fri, 1 Jul 2022 16:17:44 +0200 Subject: [PATCH 5/5] add test --- compiler/test-resources/repl/i15493 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compiler/test-resources/repl/i15493 b/compiler/test-resources/repl/i15493 index 7476b63374fd..f543f5c1d0f7 100644 --- a/compiler/test-resources/repl/i15493 +++ b/compiler/test-resources/repl/i15493 @@ -132,3 +132,13 @@ 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 +