summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/typechecker/TypeStrings.scala
blob: cb1f1f45688a86db9f1b41f9472e8cd1856566e3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author  Paul Phillips
 */

package scala.tools.nsc
package typechecker

import java.lang.{ reflect => r }
import r.TypeVariable
import scala.reflect.NameTransformer
import NameTransformer._
import scala.reflect.runtime.{universe => ru}
import scala.reflect.{ClassTag, classTag}

/** A more principled system for turning types into strings.
 */
trait StructuredTypeStrings extends DestructureTypes {
  val global: Global
  import global._

  case class LabelAndType(label: String, typeName: String) { }
  object LabelAndType {
    val empty = LabelAndType("", "")
  }
  case class Grouping(ldelim: String, mdelim: String, rdelim: String, labels: Boolean) {
    def join(elems: String*): String = (
      if (elems.isEmpty) ""
      else elems.mkString(ldelim, mdelim, rdelim)
    )
  }
  val NoGrouping      = Grouping("", "", "", labels = false)
  val ListGrouping    = Grouping("(", ", ", ")", labels = false)
  val ProductGrouping = Grouping("(", ", ", ")", labels = true)
  val BlockGrouping   = Grouping(" { ", "; ", "}", labels = false)

  private def str(level: Int)(body: => String): String = "  " * level + body
  private def block(level: Int, grouping: Grouping)(name: String, nodes: List[TypeNode]): String = {
    val l1 = str(level)(name + grouping.ldelim)
    val l2 = nodes.map(_ show level + 1)
    val l3 = str(level)(grouping.rdelim)

    l1 +: l2 :+ l3 mkString "\n"
  }
  private def maybeBlock(level: Int, grouping: Grouping)(name: String, nodes: List[TypeNode]): String = {
    val threshold = 70

    val try1 = str(level)(name + grouping.join(nodes map (_.show(0, grouping.labels)): _*))
    if (try1.length < threshold) try1
    else block(level, grouping)(name, nodes)
  }
  private def shortClass(x: Any) = {
    if (settings.debug) {
      val name   = (x.getClass.getName split '.').last
      val str    = if (TypeStrings.isAnonClass(x.getClass)) name else (name split '$').last

      " // " + str
    }
    else ""
  }

  sealed abstract class TypeNode {
    def grouping: Grouping
    def nodes: List[TypeNode]

    def show(indent: Int, showLabel: Boolean): String = maybeBlock(indent, grouping)(mkPrefix(showLabel), nodes)
    def show(indent: Int): String = show(indent, showLabel = true)
    def show(): String = show(0)

    def withLabel(l: String): this.type = modifyNameInfo(_.copy(label = l))
    def withType(t: String): this.type  = modifyNameInfo(_.copy(typeName = t))

    def label       = nameInfo.label
    def typeName    = nameInfo.typeName

    protected def mkPrefix(showLabel: Boolean) = {
      val pre = if (showLabel && label != "") label + " = " else ""
      pre + typeName
    }
    override def toString = show() // + "(toString)"
    private var nameInfo: LabelAndType = LabelAndType.empty
    private def modifyNameInfo(f: LabelAndType => LabelAndType): this.type = {
      nameInfo = f(nameInfo)
      this
    }
  }
  case class TypeAtom[T](atom: T) extends TypeNode {
    def grouping = NoGrouping
    def nodes = Nil
    override protected def mkPrefix(showLabel: Boolean) =
      super.mkPrefix(showLabel) + atom + shortClass(atom)
  }
  case class TypeProduct(nodes: List[TypeNode]) extends TypeNode {
    def grouping: Grouping = ProductGrouping
    def emptyTypeName = ""
    override def typeName = if (nodes.isEmpty) emptyTypeName else super.typeName
  }

  /** For a NullaryMethod, in = TypeEmpty; for MethodType(Nil, _) in = TypeNil */
  class NullaryFunction(out: TypeNode) extends TypeProduct(List(out)) {
    override def typeName = "NullaryMethodType"
  }
  class MonoFunction(in: TypeNode, out: TypeNode) extends TypeProduct(List(in, out)) {
    override def typeName = "MethodType"
  }
  class PolyFunction(in: TypeNode, out: TypeNode) extends TypeProduct(List(in, out)) {
    override def typeName = "PolyType"
  }

  class TypeList(nodes: List[TypeNode]) extends TypeProduct(nodes) {
    override def grouping = ListGrouping
    override def emptyTypeName = "Nil"
    override def typeName = "List"
  }

  object TypeEmpty extends TypeNode {
    override def grouping = NoGrouping
    override def nodes = Nil
    override def label = ""
    override def typeName = ""
    override def show(indent: Int, showLabel: Boolean) = ""
  }

  object intoNodes extends DestructureType[TypeNode] {
    def withLabel(node: TypeNode, label: String): TypeNode   = node withLabel label
    def withType(node: TypeNode, typeName: String): TypeNode = node withType typeName

    def wrapEmpty                             = TypeEmpty
    def wrapSequence(nodes: List[TypeNode])   = new TypeList(nodes)
    def wrapProduct(nodes: List[TypeNode])    = new TypeProduct(nodes)
    def wrapPoly(in: TypeNode, out: TypeNode) = new PolyFunction(in, out)
    def wrapMono(in: TypeNode, out: TypeNode) = if (in == wrapEmpty) new NullaryFunction(out) else new MonoFunction(in, out)
    def wrapAtom[U](value: U)                 = new TypeAtom(value)
  }

  def show(tp: Type): String = intoNodes(tp).show()
}


/** Logic for turning a type into a String.  The goal is to be
 *  able to take some arbitrary object 'x' and obtain the most precise
 *  String for which an injection of x.asInstanceOf[String] will
 *  be valid from both the JVM's and scala's perspectives.
 *
 *  "definition" is when you want strings like
 */
trait TypeStrings {
  private type JClass = java.lang.Class[_]
  private val ObjectClass = classOf[java.lang.Object]
  private val primitives = Set[String]("byte", "char", "short", "int", "long", "float", "double", "boolean", "void")
  private val primitiveMap = (primitives.toList map { x =>
    val key = x match {
      case "int"  => "Integer"
      case "char" => "Character"
      case s      => s.capitalize
    }
    val value = x match {
      case "void" => "Unit"
      case s      => s.capitalize
    }

    ("java.lang." + key) -> ("scala." + value)
  }).toMap

  def isAnonClass(cl: Class[_]) = {
    val xs = cl.getName.reverse takeWhile (_ != '$')
    xs.nonEmpty && xs.forall(_.isDigit)
  }

  def scalaName(s: String): String = {
    if (s endsWith MODULE_SUFFIX_STRING) s.init + ".type"
    else if (s == "void") "scala.Unit"
    else if (primitives(s)) "scala." + s.capitalize
    else primitiveMap.getOrElse(s, NameTransformer.decode(s))
  }
  // Trying to put humpty dumpty back together again.
  def scalaName(clazz: JClass): String = {
    val name      = clazz.getName
    val enclClass = clazz.getEnclosingClass
    def enclPre   = enclClass.getName + MODULE_SUFFIX_STRING
    def enclMatch = name startsWith enclPre

    scalaName(
      if (enclClass == null || isAnonClass(clazz) || !enclMatch) name
      else enclClass.getName + "." + (name stripPrefix enclPre)
    )
  }
  def anyClass(x: Any): JClass = if (x == null) null else x.getClass

  private def brackets(tps: String*): String =
    if (tps.isEmpty) ""
    else tps.mkString("[", ", ", "]")

  private def tvarString(tvar: TypeVariable[_]): String = tvarString(tvar.getBounds.toList)
  private def tvarString(bounds: List[AnyRef]): String = {
    val xs = bounds filterNot (_ == ObjectClass) collect { case x: JClass => x }
    if (xs.isEmpty) "_"
    else scalaName(xs.head)
  }
  private def tparamString(clazz: JClass): String = {
    brackets(clazz.getTypeParameters map tvarString: _*)
  }

  private def tparamString[T: ru.TypeTag] : String = {
    import ru._ // get TypeRefTag in scope so that pattern match works (TypeRef is an abstract type)
    def typeArguments: List[ru.Type] = ru.typeOf[T] match { case ru.TypeRef(_, _, args) => args; case _ => Nil }
    brackets(typeArguments map (jc => tvarString(List(jc))): _*)
  }

  /** Going for an overabundance of caution right now.  Later these types
   *  can be a lot more precise, but right now the tags have a habit of
   *  introducing material which is not syntactically valid as scala source.
   *  When this happens it breaks the repl.  It would be nice if we mandated
   *  that tag toString methods (or some other method, since it's bad
   *  practice to rely on toString for correctness) generated the VALID string
   *  representation of the type.
   */
  def fromValue(value: Any): String                          = if (value == null) "Null" else fromClazz(anyClass(value))
  def fromClazz(clazz: JClass): String                       = scalaName(clazz) + tparamString(clazz)
  def fromTag[T: ru.TypeTag : ClassTag] : String             = scalaName(classTag[T].runtimeClass) + tparamString[T]

  /** Reducing fully qualified noise for some common packages.
   */
  def quieter(tpe: String, alsoStrip: String*): String = {
    val transforms = List(
      "scala.collection.immutable." -> "immutable.",
      "scala.collection.mutable." -> "mutable.",
      "scala.collection.generic." -> "generic.",
      "java.lang." -> "jl.",
      "scala.runtime." -> "runtime."
    ) ++ (alsoStrip map (_ -> ""))

    transforms.foldLeft(tpe) {
      case (res, (k, v)) => res.replaceAll(k, v)
    }
  }
}

object TypeStrings extends TypeStrings { }