summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/Reporting.scala
blob: a8fd3bec763d0f7d6cb751d4b40a85df1ba686fc (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
/* NSC -- new Scala compiler
 * Copyright 2005-2014 LAMP/EPFL, Typesafe Inc.
 * @author  Adriaan Moors
 */

package scala
package tools
package nsc

import reporters.{ Reporter, ConsoleReporter }
import scala.collection.{ mutable, immutable }

/** Provides delegates to the reporter doing the actual work.
 * PerRunReporting implements per-Run stateful info tracking and reporting
 *
 * TODO: make reporting configurable
 */
trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions with CompilationUnits with scala.reflect.api.Symbols =>
  def settings: Settings

  // == currentRun.reporting
  def currentReporting: PerRunReporting

  def supplementTyperState(errorMessage: String): String

  // not deprecated yet, but a method called "error" imported into
  // nearly every trait really must go.  For now using globalError.
  def error(msg: String) = globalError(msg)

  override def deprecationWarning(pos: Position, msg: String) = currentReporting.deprecationWarning(pos, msg)
  override def supplementErrorMessage(errorMessage: String)   = currentReporting.supplementErrorMessage(errorMessage)

  // a new instance of this class is created for every Run (access the current instance via `currentReporting`)
  class PerRunReporting {
    // NOTE: scala.reflect.macros.Parsers#parse relies on everything related to reporting going through this def...
    // TODO: can we rework this to avoid the indirection/fragility?
    def reporter = Reporting.this.reporter

    /** Collects for certain classes of warnings during this run. */
    private class ConditionalWarning(what: String, option: Settings#BooleanSetting) {
      val warnings = mutable.LinkedHashMap[Position, String]()
      def warn(pos: Position, msg: String) =
        if (option) reporter.warning(pos, msg)
        else if (!(warnings contains pos)) warnings += ((pos, msg))
      def summarize() =
        if (warnings.nonEmpty && (option.isDefault || settings.fatalWarnings)) {
          val numWarnings  = warnings.size
          val warningEvent = // TODO use scala.reflect.internal.util.StringOps.countElementsAsString(numWarnings, s"$what warning")
            if (numWarnings > 1) s"were $numWarnings $what warnings"
            else s"was one $what warning"

          reporter.warning(NoPosition, s"there $warningEvent; re-run with ${option.name} for details")
        }
    }

    // This change broke sbt; I gave it the thrilling name of uncheckedWarnings0 so
    // as to recover uncheckedWarnings for its ever-fragile compiler interface.
    private val _deprecationWarnings    = new ConditionalWarning("deprecation", settings.deprecation)
    private val _uncheckedWarnings      = new ConditionalWarning("unchecked", settings.unchecked)
    private val _featureWarnings        = new ConditionalWarning("feature", settings.feature)
    private val _inlinerWarnings        = new ConditionalWarning("inliner", settings.YinlinerWarnings)
    private val _allConditionalWarnings = List(_deprecationWarnings, _uncheckedWarnings, _featureWarnings, _inlinerWarnings)

    def deprecationWarning(pos: Position, msg: String): Unit = _deprecationWarnings.warn(pos, msg)
    def uncheckedWarning(pos: Position, msg: String): Unit   = _uncheckedWarnings.warn(pos, msg)
    def featureWarning(pos: Position, msg: String): Unit     = _featureWarnings.warn(pos, msg)
    def inlinerWarning(pos: Position, msg: String): Unit     = _inlinerWarnings.warn(pos, msg)

    def deprecationWarnings = _deprecationWarnings.warnings.toList
    def uncheckedWarnings   = _uncheckedWarnings.warnings.toList
    def featureWarnings     = _featureWarnings.warnings.toList
    def inlinerWarnings     = _inlinerWarnings.warnings.toList

    def allConditionalWarnings = _allConditionalWarnings flatMap (_.warnings)

    private[this] var reportedFeature = Set[Symbol]()
    def featureWarning(pos: Position, featureName: String, featureDesc: String, featureTrait: Symbol, construct: => String = "", required: Boolean): Unit = {
      val req     = if (required) "needs to" else "should"
      val fqname  = "scala.language." + featureName
      val explain = (
        if (reportedFeature contains featureTrait) "" else
        s"""|
            |This can be achieved by adding the import clause 'import $fqname'
            |or by setting the compiler option -language:$featureName.
            |See the Scala docs for value $fqname for a discussion
            |why the feature $req be explicitly enabled.""".stripMargin
      )
      reportedFeature += featureTrait

      val msg = s"$featureDesc $req be enabled\nby making the implicit value $fqname visible.$explain" replace ("#", construct)
      if (required) reporter.error(pos, msg)
      else featureWarning(pos, msg)
    }

    /** Has any macro expansion used a fallback during this run? */
    var seenMacroExpansionsFallingBack = false

    def summarizeErrors(): Unit = if (!reporter.hasErrors) {
      _allConditionalWarnings foreach (_.summarize())

      if (seenMacroExpansionsFallingBack)
        reporter.warning(NoPosition, "some macros could not be expanded and code fell back to overridden methods;"+
                "\nrecompiling with generated classfiles on the classpath might help.")

      // todo: migrationWarnings

      if (settings.fatalWarnings && reporter.hasWarnings)
        reporter.error(NoPosition, "No warnings can be incurred under -Xfatal-warnings.")
    }

    // for repl
    private[this] var incompleteHandler: (Position, String) => Unit = null
    def withIncompleteHandler[T](handler: (Position, String) => Unit)(thunk: => T) = {
      val saved = incompleteHandler
      incompleteHandler = handler
      try thunk
      finally incompleteHandler = saved
    }

    def incompleteHandled = incompleteHandler != null
    def incompleteInputError(pos: Position, msg: String): Unit =
      if (incompleteHandled) incompleteHandler(pos, msg)
      else reporter.error(pos, msg)

    /** Have we already supplemented the error message of a compiler crash? */
    private[this] var supplementedError = false
    def supplementErrorMessage(errorMessage: String): String =
      if (supplementedError) errorMessage
      else {
        supplementedError = true
        supplementTyperState(errorMessage)
      }
  }
}