summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
blob: be576289f6c0ad4718b30f3fe18c6947f3874550 (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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
/* NSC -- new Scala compiler
 * Copyright 2005-2010 LAMP/EPFL
 * @author  Paul Phillips
 */

package scala.tools.nsc
package typechecker

import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.util.control.ControlThrowable
import scala.util.control.Exception.ultimately
import symtab.Flags._
import PartialFunction._

/** An interface to enable higher configurability of diagnostic messages
 *  regarding type errors.  This is barely a beginning as error messages are
 *  distributed far and wide across the codebase.  The plan is to partition
 *  error messages into some broad groups and provide some mechanism for
 *  being more or less verbose on a selective basis.  Possible groups include
 *  such examples as
 *
 *    arity errors
 *    kind errors
 *    variance errors
 *    ambiguity errors
 *    volatility/stability errors
 *    implementation restrictions
 *
 *  And more, and there is plenty of overlap, so it'll be a process.
 *
 *  @author Paul Phillips
 *  @version 1.0
 */
trait TypeDiagnostics {
  self: Analyzer =>

  import global._
  import definitions._
  import global.typer.infer

  /** It can be quite difficult to know which of the many functions called "error"
   *  is being called at any given point in the compiler.  To alleviate this I am
   *  renaming such functions inside this trait based on where it originated.
   */
  def inferError(pos: Position, msg: String) = infer.error(pos, msg)

  /** The common situation of making sure nothing is erroneous could be
   *  nicer if Symbols, Types, and Trees all implemented some common interface
   *  in which isErroneous and similar would be placed.
   */
  def noErroneousTypes(tps: Type*)    = tps forall (x => !x.isErroneous)
  def noErroneousSyms(syms: Symbol*)  = syms forall (x => !x.isErroneous)
  def noErroneousTrees(trees: Tree*)  = trees forall (x => !x.isErroneous)

  /** A map of Positions to addendums - if an error involves a position in
   *  the map, the addendum should also be printed.
   */
  private var addendums = mutable.Map[Position, () => String]()

  def setAddendum(pos: Position, msg: () => String) =
    if (pos != NoPosition)
      addendums(pos) = msg

  def withAddendum(pos: Position) = (_: String) + addendums.getOrElse(pos, () => "")()

  def decodeWithNamespace(name: Name): String = {
    val prefix = if (name.isTypeName) "type " else "value "
    prefix + name.decode
  }

  /** Does the positioned line assigned to t1 precede that of t2?
   */
  def linePrecedes(t1: Tree, t2: Tree) = t1.pos.isDefined && t1.pos.isDefined && t1.pos.line < t2.pos.line

  def notAMember(sel: Tree, qual: Tree, name: Name) = {
    def decoded = decodeWithNamespace(name)

    def msg: String = name match {
      case nme.CONSTRUCTOR    => qual.tpe.widen+" does not have a constructor"
      case _                  =>
        def memberOf = if (qual.tpe.typeSymbol.isTypeParameterOrSkolem) "type parameter " else ""
        def possibleCause =
          if (linePrecedes(qual, sel))
            "\npossible cause: maybe a semicolon is missing before `"+decoded+"'?"
          else
            ""

        decoded+" is not a member of "+ memberOf + qual.tpe.widen + possibleCause
    }
    inferError(sel.pos, withAddendum(qual.pos)(msg))
  }

  /** Only prints the parameter names if they're not synthetic,
   *  since "x$1: Int" does not offer any more information than "Int".
   */
  private def methodTypeErrorString(tp: Type) = tp match {
    case mt @ MethodType(params, resultType)  =>
      def forString =
        if (params exists (_.isSynthetic)) params map (_.tpe)
        else params map (_.defString)

       forString.mkString("(", ",", ")") + resultType
    case x                                    => x.toString
  }

  def alternatives(tree: Tree): List[Type] = tree.tpe match {
    case OverloadedType(pre, alternatives)  => alternatives map pre.memberType
    case _                                  => Nil
  }
  def alternativesString(tree: Tree) =
    alternatives(tree) map (x => "  " + methodTypeErrorString(x)) mkString ("", " <and>\n", "\n")

  def missingParameterTypeError(fun: Tree, vparam: ValDef) = {
    val suffix = if (vparam.mods.isSynthetic) " for expanded function "+fun else ""

    inferError(vparam.pos, "missing parameter type" + suffix)
    ErrorType
  }

  def treeSymTypeMsg(tree: Tree): String = {
    val sym               = tree.symbol
    def hasParams         = tree.tpe.paramSectionCount > 0
    def preResultString   = if (hasParams) ": " else " of type "

    def nullMessage       = "expression of type " + tree.tpe
    def overloadedMessage = "overloaded method " + sym + " with alternatives:\n" + alternativesString(tree)
    def moduleMessage     = "" + sym
    def defaultMessage    = moduleMessage + preResultString + tree.tpe
    def applyMessage      = defaultMessage + tree.symbol.locationString

    if (sym == null) nullMessage
    else if (sym.isOverloaded) overloadedMessage
    else if (sym.isModule) moduleMessage
    else if (sym.name == nme.apply) applyMessage
    else defaultMessage
  }

  def applyErrorMsg(tree: Tree, msg: String, argtpes: List[Type], pt: Type) = {
    def asParams(xs: List[Any]) = xs.mkString("(", ", ", ")")

    def resType   = if (pt isWildcard) "" else " with expected result type " + pt
    def allTypes  = (alternatives(tree) flatMap (_.paramTypes)) ++ argtpes :+ pt

    withDisambiguation(allTypes: _*) {
      treeSymTypeMsg(tree) + msg + asParams(argtpes) + resType
    }
  }

  def disambiguate(ss: List[String]) = ss match {
    case Nil      => Nil
    case s :: ss  => s :: (ss map { case `s` => "(some other)"+s ; case x => x })
  }

  // todo: use also for other error messages
  def existentialContext(tp: Type) = tp.existentialSkolems match {
    case Nil  => ""
    case xs   => " where " + (disambiguate(xs map (_.existentialToString)) mkString ", ")
  }

  def foundReqMsg(found: Type, req: Type): String =
    withDisambiguation(found, req) {
      ";\n found   : " + found.toLongString + existentialContext(found) +
       "\n required: " + req + existentialContext(req)
    }

  /** If two given types contain different type variables with the same name
   *  differentiate the names by including owner information.  Also, if the
   *  type error is because of a conflict between two identically named
   *  classes and one is in package scala, fully qualify the name so one
   *  need not deduce why "java.util.Iterator" and "Iterator" don't match.
   *  Another disambiguation performed is to address the confusion present
   *  in the following snippet:
   *    def f[Int](x: Int) = x + 5.
   */
  def withDisambiguation[T](types: Type*)(op: => T): T = {
    object SymExtractor {
      def unapply(x: Any) = x match {
        case t @ TypeRef(_, sym, _)   => Some(t -> sym)
        case t @ ConstantType(value)  => Some(t -> t.underlying.typeSymbol)
        case _                        => None
      }
    }
    val typerefs =
      for (tp <- types.toList ; SymExtractor(t, sym) <- tp) yield
        t -> sym

    val savedNames    = typerefs map { case (_, sym) => sym -> sym.name } toMap
    def restoreNames  = savedNames foreach { case (sym, name) => sym.name = name }

    def isAlreadyAltered(sym: Symbol) = sym.name != savedNames(sym)

    def modifyName(sym: Symbol)(f: String => String): Unit =
      sym.name = newTypeName(f(sym.name.toString))

    def scalaQualify(sym: Symbol) =
      if (sym.owner.isScalaPackageClass)
        modifyName(sym)("scala." + _)

    def explainName(sym: Symbol) = {
      scalaQualify(sym)

      if (!isAlreadyAltered(sym))
        modifyName(sym)(_ + "(in " + sym.owner + ")")
    }

    ultimately(restoreNames) {
      for ((t1, sym1) <- typerefs ; (t2, sym2) <- typerefs ; if sym1 != sym2 && (sym1 isLess sym2)) {

        if (t1.toString == t2.toString) {   // type variable collisions
          List(sym1, sym2) foreach explainName
          if (sym1.owner == sym2.owner)
            sym2.name = newTypeName("(some other)"+sym2.name)
        }
        else if (sym1.name == sym2.name) {  // symbol name collisions
          List(sym1, sym2) foreach { x =>
            if (x.owner.isScalaPackageClass)
              modifyName(x)("scala." + _)
            else if (x.isTypeParameterOrSkolem)
              explainName(x)
          }
        }
      }

      // performing the actual operation
      op
    }
  }

  trait TyperDiagnostics {
    self: Typer =>

    private def contextError(pos: Position, msg: String) = context.error(pos, msg)
    private def contextError(pos: Position, err: Throwable) = context.error(pos, err)

    def symWasOverloaded(sym: Symbol) = sym.owner.isClass && sym.owner.info.member(sym.name).isOverloaded
    def cyclicAdjective(sym: Symbol)  = if (symWasOverloaded(sym)) "overloaded" else "recursive"

    /** Returns Some(msg) if the given tree is untyped apparently due
     *  to a cyclic reference, and None otherwise.
     */
    def cyclicReferenceMessage(sym: Symbol, tree: Tree) = condOpt(tree) {
      case ValDef(_, _, tpt, _) if tpt.tpe == null        => "recursive "+sym+" needs type"
      case DefDef(_, _, _, _, tpt, _) if tpt.tpe == null  => List(cyclicAdjective(sym), sym, "needs result type") mkString " "
    }

    /** Report a type error.
     *
     *  @param pos0   The position where to report the error
     *  @param ex     The exception that caused the error
     */
    def reportTypeError(pos: Position, ex: TypeError) {
      if (ex.pos == NoPosition) ex.pos = pos
      if (!context.reportGeneralErrors) throw ex
      if (settings.debug.value) ex.printStackTrace()

      ex match {
        case CyclicReference(sym, info: TypeCompleter) =>
          contextError(ex.pos, cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage())

          if (sym == ObjectClass)
            throw new FatalError("cannot redefine root "+sym)
        case _ =>
          contextError(ex.pos, ex)
      }
    }
  }
}