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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
|
/* NSC -- new Scala compiler
* Copyright 2005-2011 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, context }
/** 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)
/** For errors which are artifacts of the implementation: such messages
* indicate that the restriction may be lifted in the future.
*/
def restrictionWarning(pos: Position, unit: CompilationUnit, msg: String): Unit =
unit.warning(pos, "Implementation restriction: " + msg)
def restrictionError(pos: Position, unit: CompilationUnit, msg: String): Unit =
unit.error(pos, "Implementation restriction: " + msg)
/** A map of Positions to addendums - if an error involves a position in
* the map, the addendum should also be printed.
*/
private var addendums = perRunCaches.newMap[Position, () => String]()
def setAddendum(pos: Position, msg: () => String) =
if (pos != NoPosition)
addendums(pos) = msg
def withAddendum(pos: Position) = (_: String) + addendums.getOrElse(pos, () => "")()
def decodeWithKind(name: Name, owner: Symbol): String = {
val prefix = (
if (name.isTypeName) "type "
else if (owner.isPackageClass) "object "
else "value "
)
prefix + name.decode
}
/** Does the positioned line assigned to t1 precede that of t2?
*/
def posPrecedes(p1: Position, p2: Position) = p1.isDefined && p2.isDefined && p1.line < p2.line
def linePrecedes(t1: Tree, t2: Tree) = posPrecedes(t1.pos, t2.pos)
private object DealiasedType extends TypeMap {
def apply(tp: Type): Type = tp match {
// Avoid "explaining" that String is really java.lang.String,
// while still dealiasing types from non-default namespaces.
case TypeRef(pre, sym, args) if sym.isAliasType && !sym.isInDefaultNamespace =>
mapOver(tp.dealias)
case _ =>
mapOver(tp)
}
}
/** An explanatory note to be added to error messages
* when there's a problem with abstract var defs */
def abstractVarMessage(sym: Symbol): String =
if (underlyingSymbol(sym).isVariable)
"\n(Note that variables need to be initialized to be defined)"
else ""
/** 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")
/** The symbol which the given accessor represents (possibly in part).
* This is used for error messages, where we want to speak in terms
* of the actual declaration or definition, not in terms of the generated setters
* and getters.
*/
def underlyingSymbol(member: Symbol): Symbol =
if (!member.hasAccessorFlag) member
else if (!member.isDeferred) member.accessed
else {
val getter = if (member.isSetter) member.getter(member.owner) else member
val flags = if (getter.setter(member.owner) != NoSymbol) DEFERRED | MUTABLE else DEFERRED
getter.owner.newValue(getter.name.toTermName, getter.pos, flags) setInfo getter.tpe.resultType
}
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 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.skolemsExceptMethodTypeParams match {
case Nil => ""
case xs => " where " + (disambiguate(xs map (_.existentialToString)) mkString ", ")
}
def varianceWord(sym: Symbol): String =
if (sym.variance == 1) "covariant"
else if (sym.variance == -1) "contravariant"
else "invariant"
def explainAlias(tp: Type) = {
// Don't automatically normalize standard aliases; they still will be
// expanded if necessary to disambiguate simple identifiers.
if ((tp eq tp.normalize) || tp.typeSymbolDirect.isInDefaultNamespace) ""
else {
// A sanity check against expansion being identical to original.
val s = "" + DealiasedType(tp)
if (s == "" + tp) ""
else "\n (which expands to) " + s
}
}
/** Look through the base types of the found type for any which
* might have been valid subtypes if given conformant type arguments.
* Examine those for situations where the type error would have been
* eliminated if the variance were different. In such cases, append
* an additional explanatory message.
*
* TODO: handle type aliases better.
*/
def explainVariance(found: Type, req: Type): String = {
found.baseTypeSeq.toList foreach { tp =>
if (tp.typeSymbol isSubClass req.typeSymbol) {
val foundArgs = tp.typeArgs
val reqArgs = req.typeArgs
val params = req.typeConstructor.typeParams
if (foundArgs.nonEmpty && foundArgs.length == reqArgs.length) {
val relationships = (foundArgs, reqArgs, params).zipped map {
case (arg, reqArg, param) =>
def mkMsg(isSubtype: Boolean) = {
val op = if (isSubtype) "<:" else ">:"
val suggest = if (isSubtype) "+" else "-"
val reqsym = req.typeSymbol
def isJava = reqsym.isJavaDefined
def isScala = reqsym hasTransOwner ScalaPackageClass
val explainFound = "%s %s %s%s, but ".format(
arg, op, reqArg,
// If the message involves a type from the base type sequence rather than the
// actual found type, we need to explain why we're talking about it. Less brute
// force measures than comparing normalized Strings were producing error messages
// like "and java.util.ArrayList[String] <: java.util.ArrayList[String]" but there
// should be a cleaner way to do this.
if (found.normalize.toString == tp.normalize.toString) ""
else " (and %s <: %s)".format(found, tp)
)
val explainDef = {
val prepend = if (isJava) "Java-defined " else ""
"%s%s is %s in %s.".format(prepend, reqsym, varianceWord(param), param)
}
// Don't suggest they change the class declaration if it's somewhere
// under scala.* or defined in a java class, because attempting either
// would be fruitless.
val suggestChange = "\nYou may wish to " + (
if (isScala || isJava)
"investigate a wildcard type such as `_ %s %s`. (SLS 3.2.10)".format(op, reqArg)
else
"define %s as %s%s instead. (SLS 4.5)".format(param.name, suggest, param.name)
)
Some("Note: " + explainFound + explainDef + suggestChange)
}
// In these cases the arg is OK and needs no explanation.
val conforms = (
(arg =:= reqArg)
|| ((arg <:< reqArg) && param.isCovariant)
|| ((reqArg <:< arg) && param.isContravariant)
)
val invariant = param.variance == 0
if (conforms) Some("")
else if ((arg <:< reqArg) && invariant) mkMsg(true) // covariant relationship
else if ((reqArg <:< arg) && invariant) mkMsg(false) // contravariant relationship
else None // we assume in other cases our ham-fisted advice will merely serve to confuse
}
val messages = relationships.flatten
// the condition verifies no type argument came back None
if (messages.size == foundArgs.size)
return messages filterNot (_ == "") mkString ("\n", "\n", "")
}
}
}
"" // no elaborable variance situation found
}
// TODO - figure out how to avoid doing any work at all
// when the message will never be seen. I though context.reportErrors
// being false would do that, but if I return "<suppressed>" under
// that condition, I see it.
def foundReqMsg(found: Type, req: Type): String = {
def baseMessage = (
";\n found : " + found.toLongString + existentialContext(found) + explainAlias(found) +
"\n required: " + req + existentialContext(req) + explainAlias(req)
)
withDisambiguation(Nil, found, req)(baseMessage) + explainVariance(found, req)
}
case class TypeDiag(tp: Type, sym: Symbol) extends Ordered[TypeDiag] {
// save the name because it will be mutated until it has been
// distinguished from the other types in the same error message
private val savedName = sym.name
def restoreName() = sym.name = savedName
def isAltered = sym.name != savedName
def modifyName(f: String => String) = sym setName newTypeName(f(sym.name.toString))
/** Prepend java.lang, scala., or Predef. if this type originated
* in one of those.
*/
def qualifyDefaultNamespaces() = {
val intersect = Set(trueOwner, aliasOwner) intersect UnqualifiedOwners
if (intersect.nonEmpty) preQualify()
}
// functions to manipulate the name
def preQualify() = modifyName(trueOwner.fullName + "." + _)
def postQualify() = modifyName(_ + "(in " + trueOwner + ")")
def typeQualify() = if (sym.isTypeParameterOrSkolem) postQualify()
def nameQualify() = if (trueOwner.isPackageClass) preQualify() else postQualify()
def trueOwner = tp.typeSymbol.effectiveOwner
def aliasOwner = tp.typeSymbolDirect.effectiveOwner
def sym_==(other: TypeDiag) = tp.typeSymbol == other.tp.typeSymbol
def owner_==(other: TypeDiag) = trueOwner == other.trueOwner
def string_==(other: TypeDiag) = tp.toString == other.tp.toString
def name_==(other: TypeDiag) = sym.name == other.sym.name
def compare(other: TypeDiag) =
if (this == other) 0
else if (sym isLess other.sym) -1
else 1
override def toString = {
"""
|tp = %s
|tp.typeSymbol = %s
|tp.typeSymbol.owner = %s
|tp.typeSymbolDirect = %s
|tp.typeSymbolDirect.owner = %s
""".stripMargin.format(
tp, tp.typeSymbol, tp.typeSymbol.owner, tp.typeSymbolDirect, tp.typeSymbolDirect.owner
)
}
}
/** This is tricky stuff - we need to traverse types deeply to
* explain name ambiguities, which may occur anywhere. However
* when lub explosions come through it knocks us into an n^2
* disaster, see SI-5580. This is trying to perform the initial
* filtering of possibly ambiguous types in a sufficiently
* aggressive way that the state space won't explode.
*/
private def typeDiags(locals: List[Symbol], types0: Type*): List[TypeDiag] = {
val types = types0.toList
// If two different type diag instances are seen for a given
// key (either the string representation of a type, or the simple
// name of a symbol) then keep them for disambiguation.
val strings = mutable.Map[String, Set[TypeDiag]]() withDefaultValue Set()
val names = mutable.Map[Name, Set[TypeDiag]]() withDefaultValue Set()
def record(t: Type, sym: Symbol) = {
val diag = TypeDiag(t, sym)
strings("" + t) += diag
names(sym.name) += diag
}
for (tpe <- types ; t <- tpe) {
t match {
case ConstantType(_) => record(t, t.underlying.typeSymbol)
case TypeRef(_, sym, _) => record(t, sym)
case _ => ()
}
}
val collisions = strings.values ++ names.values filter (_.size > 1)
collisions.flatten.toList
}
/** The distinct pairs from an ordered list. */
private def pairs[T <: Ordered[T]](xs: Seq[T]): Seq[(T, T)] = {
for (el1 <- xs ; el2 <- xs ; if el1 < el2) yield
((el1, el2))
}
/** Given any number of types, alters the name information in the symbols
* until they can be distinguished from one another: then executes the given
* code. The names are restored and the result is returned.
*/
def withDisambiguation[T](locals: List[Symbol], types: Type*)(op: => T): T = {
val typeRefs = typeDiags(locals, types: _*)
val toCheck = pairs(typeRefs) filterNot { case (td1, td2) => td1 sym_== td2 }
ultimately(typeRefs foreach (_.restoreName())) {
for ((td1, td2) <- toCheck) {
val tds = List(td1, td2)
// If the types print identically, qualify them:
// a) If the dealiased owner is a package, the full path
// b) Otherwise, append (in <owner>)
if (td1 string_== td2)
tds foreach (_.nameQualify())
// If they have the same simple name, and either of them is in the
// scala package or predef, qualify with scala so it is not confusing why
// e.g. java.util.Iterator and Iterator are different types.
if (td1 name_== td2)
tds foreach (_.qualifyDefaultNamespaces())
// If they still print identically:
// a) If they are type parameters with different owners, append (in <owner>)
// b) Failing that, the best we can do is append "(some other)" to the latter.
if (td1 string_== td2) {
if (td1 owner_== td2)
td2.modifyName("(some other)" + _)
else
tds foreach (_.typeQualify())
}
}
// performing the actual operation
op
}
}
trait TyperDiagnostics {
self: Typer =>
private def contextError(context0: Analyzer#Context, pos: Position, msg: String) = context0.error(pos, msg)
private def contextError(context0: Analyzer#Context, pos: Position, err: Throwable) = context0.error(pos, err)
private def contextWarning(pos: Position, msg: String) = context.unit.warning(pos, msg)
def permanentlyHiddenWarning(pos: Position, hidden: Name, defn: Symbol) =
contextWarning(pos, "imported `%s' is permanently hidden by definition of %s".format(hidden, defn.fullLocationString))
object checkDead {
private var expr: Symbol = NoSymbol
private def exprOK = expr != Object_synchronized
private def treeOK(tree: Tree) = tree.tpe != null && tree.tpe.typeSymbol == NothingClass
def updateExpr(fn: Tree) = {
if (fn.symbol != null && fn.symbol.isMethod && !fn.symbol.isConstructor)
checkDead.expr = fn.symbol
}
def apply(tree: Tree): Tree = {
// Error suppression will squash some of these warnings unless we circumvent it.
// It is presumed if you are using a -Y option you would really like to hear
// the warnings you've requested.
if (settings.warnDeadCode.value && context.unit.exists && treeOK(tree) && exprOK)
context.warning(tree.pos, "dead code following this construct", true)
tree
}
// The checkDead call from typedArg is more selective.
def inMode(mode: Int, tree: Tree): Tree = {
val modeOK = (mode & (EXPRmode | BYVALmode | POLYmode)) == (EXPRmode | BYVALmode)
if (modeOK) apply(tree)
else tree
}
}
private def symWasOverloaded(sym: Symbol) = sym.owner.isClass && sym.owner.info.member(sym.name).isOverloaded
private 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 " "
case Import(expr, selectors) =>
( "encountered unrecoverable cycle resolving import." +
"\nNote: this is often due in part to a class depending on a definition nested within its companion." +
"\nIf applicable, you may wish to try moving some members into another object."
)
}
/** Report a type error.
*
* @param pos0 The position where to report the error
* @param ex The exception that caused the error
*/
def reportTypeError(context0: Context, pos: Position, ex: TypeError) {
if (ex.pos == NoPosition) ex.pos = pos
// TODO: should be replaced by throwErrors
// but it seems that throwErrors excludes some of the errors that should actually be
// buffered, causing TypeErrors to fly around again. This needs some more investigation.
if (!context0.reportErrors) throw ex
if (settings.debug.value) ex.printStackTrace()
ex match {
case CyclicReference(sym, info: TypeCompleter) =>
val pos = info.tree match {
case Import(expr, _) => expr.pos
case _ => ex.pos
}
contextError(context0, pos, cyclicReferenceMessage(sym, info.tree) getOrElse ex.getMessage())
if (sym == ObjectClass)
throw new FatalError("cannot redefine root "+sym)
case _ =>
contextError(context0, ex.pos, ex)
}
}
}
}
|