aboutsummaryrefslogtreecommitdiff
path: root/src/dotty/tools/dotc/typer/ErrorReporting.scala
blob: 8f9b01fe67d4becfe32a8e1ee1624de22d88c27e (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
package dotty.tools
package dotc
package typer

import ast._
import core._
import Trees._
import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._
import Applications._, Implicits._
import util.Positions._
import printing.Showable
import printing.Disambiguation.disambiguated

object ErrorReporting {

  import tpd._

  def errorTree(tree: untpd.Tree, msg: => String)(implicit ctx: Context): tpd.Tree =
    tree withType errorType(msg, tree.pos)

  def errorType(msg: => String, pos: Position)(implicit ctx: Context): ErrorType = {
    ctx.error(msg, pos)
    ErrorType
  }

  def cyclicErrorMsg(ex: CyclicReference)(implicit ctx: Context) = {
    val cycleSym = ex.denot.symbol
    def errorMsg(msg: String, cx: Context): String =
      if (cx.mode is Mode.InferringReturnType) {
        cx.tree match {
          case tree: Trees.ValOrDefDef[_] =>
            val treeSym = ctx.symOfContextTree(tree)
            if (treeSym.exists && treeSym.name == cycleSym.name && treeSym.owner == cycleSym.owner) {
              val result = if (cycleSym.isSourceMethod) " result" else ""
              d"overloaded or recursive $cycleSym needs$result type"
            }
            else errorMsg(msg, cx.outer)
          case _ =>
            errorMsg(msg, cx.outer)
        }
      } else msg
    errorMsg(ex.show, ctx)
  }

  class Errors(implicit ctx: Context) {

    def expectedTypeStr(tp: Type): String = tp match {
      case tp: FunProto =>
        val result = tp.resultType match {
          case tp: WildcardType => ""
          case tp => d"and expected result type $tp"
        }
        d"arguments (${tp.typedArgs.tpes}%, %)$result"
      case _ =>
        d"expected type $tp"
    }

    def anonymousTypeMemberStr(tpe: Type) = {
      val kind = tpe match {
          case _: TypeBounds => "type with bounds"
          case _: PolyType | _: MethodType => "method"
          case _ => "value of type"
        }
        d"$kind $tpe"
    }

    def overloadedAltsStr(alts: List[SingleDenotation]) =
      d"overloaded alternatives of ${denotStr(alts.head)} with types\n" +
      d" ${alts map (_.info)}%\n %"

    def denotStr(denot: Denotation): String =
      if (denot.isOverloaded) overloadedAltsStr(denot.alternatives)
      else if (denot.symbol.exists) denot.symbol.showLocated
      else anonymousTypeMemberStr(denot.info)

    def refStr(tp: Type): String = tp match {
      case tp: NamedType => denotStr(tp.denot)
      case _ => anonymousTypeMemberStr(tp)
    }

    def exprStr(tree: Tree): String = refStr(tree.tpe)

    def patternConstrStr(tree: Tree): String = ???

    def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = {
      errorTree(tree, typeMismatchStr(tree.tpe, pt) + implicitFailure.postscript)
    }

    def typeMismatchStr(found: Type, expected: Type) = disambiguated { implicit ctx =>
      val (typerStateStr, explanationStr) =
        if (ctx.settings.explaintypes.value)
          ("\n" + ctx.typerState.show, "\n" + TypeComparer.explained((found <:< expected)(_)))
        else
          ("", "")
      def infoStr = found match { // DEBUG
          case tp: TypeRef => s"with info ${tp.info} / ${tp.prefix.toString} / ${tp.prefix.dealias.toString}"
          case _ => ""
        }
      d"""type mismatch:
           | found   : $found
           | required: $expected""".stripMargin + typerStateStr + explanationStr
    }
  }

  def err(implicit ctx: Context): Errors = new Errors

  /** The d string interpolator works like the i string interpolator, but marks nonsensical errors
   *  using `<nonsensical>...</nonsensical>` tags.
   *  Note: Instead of these tags, it would be nicer to return a data structure containing the message string
   *  and a boolean indicating whether the message is sensical, but then we cannot use string operations
   *  like concatenation, stripMargin etc on the values returned by d"...", and in the current error
   *  message composition methods, this is crucial.
   */
  implicit class DiagnosticString(val sc: StringContext) extends AnyVal {
    import DiagnosticString._
    def d(args: Any*)(implicit ctx: Context): String = {
      def isSensical(arg: Any): Boolean = arg match {
        case l: Seq[_] => l.forall(isSensical(_))
        case tpe: Type if tpe.isErroneous => false
        case NoType => false
        case sym: Symbol if sym.isCompleted =>
          sym.info != ErrorType && sym.info != TypeAlias(ErrorType) && sym.info != NoType
        case _ => true
      }

      val s = new InfoString(sc).i(args : _*)
      if (args.forall(isSensical(_))) s else nonSensicalStartTag + s + nonSensicalEndTag
    }
  }

  object DiagnosticString {
    final val nonSensicalStartTag = "<nonsensical>"
    final val nonSensicalEndTag = "</nonsensical>"
  }

}