Skip to content

Commit ba06bf0

Browse files
authored
Merge pull request #1761 from dotty-staging/topic/product-show
[REPL] Add show capability to common types
2 parents 19bc03c + 35e8fcb commit ba06bf0

File tree

5 files changed

+206
-5
lines changed

5 files changed

+206
-5
lines changed

compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -683,9 +683,36 @@ class CompilingInterpreter(
683683
code.print(resultExtractors.mkString(""))
684684
}
685685

686+
private val ListReg = """^.*List\[(\w+)\]$""".r
687+
private val MapReg = """^.*Map\[(\w+),[ ]*(\w+)\]$""".r
688+
private val LitReg = """^.*\((.+)\)$""".r
689+
686690
private def resultExtractor(req: Request, varName: Name): String = {
687691
val prettyName = varName.decode
688-
val varType = string2code(req.typeOf(varName))
692+
// FIXME: `varType` is prettified to abbreviate common types where
693+
// appropriate, and to also prettify literal types
694+
//
695+
// This should be rewritten to use the actual types once we have a
696+
// semantic representation available to the REPL
697+
val varType = string2code(req.typeOf(varName)) match {
698+
// Extract List's paremeter from full path
699+
case ListReg(param) => s"List[$param]"
700+
// Extract Map's paremeters from full path
701+
case MapReg(k, v) => s"Map[$k, $v]"
702+
// Extract literal type from literal type representation. Example:
703+
//
704+
// ```
705+
// scala> val x: 42 = 42
706+
// val x: Int(42) = 42
707+
// scala> val y: "hello" = "hello"
708+
// val y: String("hello") = "hello"
709+
// ```
710+
case LitReg(lit) => lit
711+
// When the type is a singleton value like None, don't show `None$`
712+
// instead show `None.type`.
713+
case x if x.lastOption == Some('$') => x.init + ".type"
714+
case x => x
715+
}
689716
val fullPath = req.fullPath(varName)
690717

691718
val varOrVal = statement match {
@@ -695,8 +722,10 @@ class CompilingInterpreter(
695722

696723
s""" + "$varOrVal $prettyName: $varType = " + {
697724
| if ($fullPath.asInstanceOf[AnyRef] != null) {
698-
| (if ($fullPath.toString().contains('\\n')) "\\n" else "") +
699-
| $fullPath.toString() + "\\n"
725+
| (if ($fullPath.toString().contains('\\n')) "\\n" else "") + {
726+
| import dotty.Show._
727+
| $fullPath.show /*toString()*/ + "\\n"
728+
| }
700729
| } else {
701730
| "null\\n"
702731
| }

library/src/dotty/Show.scala

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package dotty
2+
3+
import scala.annotation.implicitNotFound
4+
5+
@implicitNotFound("No member of type class Show could be found for ${T}")
6+
trait Show[-T] {
7+
def show(t: T): String
8+
}
9+
10+
/** Ideally show would only contain `defaultShow` and the pimped generic class,
11+
* but since we can't change the current stdlib, we're stuck with providing
12+
* default instances in this object
13+
*/
14+
object Show {
15+
private[this] val defaultShow: Show[Any] = new Show[Any] {
16+
def show(x: Any) = x.toString
17+
}
18+
19+
/** This class implements pimping of all types to provide a show method.
20+
* Currently it is quite permissive, if there's no instance of `Show[T]` for
21+
* any `T`, we default to `T#toString`.
22+
*/
23+
implicit class ShowValue[V](val v: V) extends AnyVal {
24+
def show(implicit ev: Show[V] = defaultShow): String =
25+
ev.show(v)
26+
}
27+
28+
implicit val stringShow: Show[String] = new Show[String] {
29+
// From 2.12 spec, `charEscapeSeq`:
30+
// ‘\‘ (‘b‘ | ‘t‘ | ‘n‘ | ‘f‘ | ‘r‘ | ‘"‘ | ‘'‘ | ‘\‘)
31+
def show(str: String) =
32+
"\"" + {
33+
val sb = new StringBuilder
34+
str.foreach {
35+
case '\b' => sb.append("\\b")
36+
case '\t' => sb.append("\\t")
37+
case '\n' => sb.append("\\n")
38+
case '\f' => sb.append("\\f")
39+
case '\r' => sb.append("\\r")
40+
case '\'' => sb.append("\\'")
41+
case '\"' => sb.append("\\\"")
42+
case c => sb.append(c)
43+
}
44+
sb.toString
45+
} + "\""
46+
}
47+
48+
implicit val intShow: Show[Int] = new Show[Int] {
49+
def show(i: Int) = i.toString
50+
}
51+
52+
implicit val floatShow: Show[Float] = new Show[Float] {
53+
def show(f: Float) = f + "f"
54+
}
55+
56+
implicit val doubleShow: Show[Double] = new Show[Double] {
57+
def show(d: Double) = d.toString
58+
}
59+
60+
implicit val charShow: Show[Char] = new Show[Char] {
61+
def show(c: Char) = "'" + (c match {
62+
case '\b' => "\\b"
63+
case '\t' => "\\t"
64+
case '\n' => "\\n"
65+
case '\f' => "\\f"
66+
case '\r' => "\\r"
67+
case '\'' => "\\'"
68+
case '\"' => "\\\""
69+
case c => c
70+
}) + "'"
71+
}
72+
73+
implicit def showList[T](implicit st: Show[T]): Show[List[T]] = new Show[List[T]] {
74+
def show(xs: List[T]) =
75+
if (xs.isEmpty) "Nil"
76+
else "List(" + xs.map(_.show).mkString(", ") + ")"
77+
}
78+
79+
implicit val showNil: Show[Nil.type] = new Show[Nil.type] {
80+
def show(xs: Nil.type) = "Nil"
81+
}
82+
83+
implicit def showOption[T](implicit st: Show[T]): Show[Option[T]] = new Show[Option[T]] {
84+
def show(ot: Option[T]): String = ot match {
85+
case Some(t) => "Some("+ st.show(t) + ")"
86+
case none => "None"
87+
}
88+
}
89+
90+
implicit val showNone: Show[None.type] = new Show[None.type] {
91+
def show(n: None.type) = "None"
92+
}
93+
94+
implicit def showMap[K,V](implicit sk: Show[K], sv: Show[V]): Show[Map[K,V]] = new Show[Map[K,V]] {
95+
def show(m: Map[K, V]) =
96+
"Map(" + m.map { case (k, v) => sk.show(k) + " -> " + sv.show(v) } .mkString (", ") + ")"
97+
}
98+
99+
implicit def showMapOfNothing: Show[Map[Nothing,Nothing]] = new Show[Map[Nothing,Nothing]] {
100+
def show(m: Map[Nothing, Nothing]) = m.toString
101+
}
102+
}

library/test/dotty/ShowTests.scala

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package dotty
2+
3+
import org.junit.Test
4+
import org.junit.Assert._
5+
6+
class ShowTests {
7+
import Show._
8+
9+
@Test def showString = {
10+
assertEquals("\"\\thello world!\"", "\thello world!".show)
11+
assertEquals("\"\\nhello world!\"", "\nhello world!".show)
12+
assertEquals("\"\\rhello world!\"", "\rhello world!".show)
13+
assertEquals("\"\\b\\t\\n\\f\\r\\\'\\\"\"", "\b\t\n\f\r\'\"".show)
14+
}
15+
16+
@Test def showFloat = {
17+
assertEquals("1.0f", 1.0f.show)
18+
assertEquals("1.0f", 1.0F.show)
19+
}
20+
21+
@Test def showDouble = {
22+
assertEquals("1.0", 1.0d.show)
23+
assertEquals("1.0", 1.0.show)
24+
}
25+
26+
@Test def showChar = {
27+
assertEquals("'\\b'", '\b'.show)
28+
assertEquals("'\\t'", '\t'.show)
29+
assertEquals("'\\n'", '\n'.show)
30+
assertEquals("'\\f'", '\f'.show)
31+
assertEquals("'\\r'", '\r'.show)
32+
assertEquals("'\\''", '\''.show)
33+
assertEquals("'\\\"'", '\"'.show)
34+
}
35+
36+
@Test def showCar = {
37+
case class Car(model: String, manufacturer: String, year: Int)
38+
implicit val showCar = new Show[Car] {
39+
def show(c: Car) =
40+
"Car(" + c.model.show + ", " + c.manufacturer.show + ", " + c.year.show + ")"
41+
}
42+
43+
case class Shop(xs: List[Car], name: String)
44+
implicit val showShop = new Show[Shop] {
45+
def show(sh: Shop) =
46+
"Shop(" + sh.xs.show + ", " + sh.name.show + ")"
47+
}
48+
49+
assertEquals("Car(\"Mustang\", \"Ford\", 1967)", Car("Mustang", "Ford", 1967).show)
50+
}
51+
52+
@Test def showOptions = {
53+
assertEquals("None", None.show)
54+
assertEquals("None", (None: Option[String]).show)
55+
assertEquals("Some(\"hello opt\")", Some("hello opt").show)
56+
}
57+
58+
@Test def showMaps = {
59+
val mp = scala.collection.immutable.Map("str1" -> "val1", "str2" -> "val2")
60+
assertEquals("Map(\"str1\" -> \"val1\", \"str2\" -> \"val2\")", mp.show)
61+
}
62+
63+
@Test def withoutShow = {
64+
case class Car(model: String, manufacturer: String, year: Int)
65+
66+
assertEquals("Car(Mustang,Ford,1967)", Car("Mustang", "Ford", 1967).show)
67+
assertEquals("Map()", Map().show)
68+
}
69+
}

project/Build.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,8 @@ object DottyBuild extends Build {
375375
settings(
376376
libraryDependencies ++= Seq(
377377
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
378-
"org.scala-lang" % "scala-library" % scalaVersion.value
378+
"org.scala-lang" % "scala-library" % scalaVersion.value,
379+
"com.novocode" % "junit-interface" % "0.11" % "test"
379380
)
380381
).
381382
settings(publishing)

tests/repl/import.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ val res0: scala.collection.mutable.ListBuffer[Int] = ListBuffer(22)
77
scala> buf ++= List(1, 2, 3)
88
val res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(22, 1, 2, 3)
99
scala> buf.toList
10-
val res2: scala.collection.immutable.List[Int] = List(22, 1, 2, 3)
10+
val res2: List[Int] = List(22, 1, 2, 3)
1111
scala> :quit

0 commit comments

Comments
 (0)