From e24289ac19a21c6b3794d02e8fe42766224f173c Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 19 Sep 2016 19:26:16 +0200 Subject: Make relevant parts of compiler conform to new error handling --- src/dotty/tools/dotc/parsing/Parsers.scala | 23 ++-- src/dotty/tools/dotc/printing/Formatting.scala | 5 +- .../tools/dotc/reporting/ConsoleReporter.scala | 25 ++-- .../dotc/reporting/FancyConsoleReporter.scala | 18 +-- .../dotc/reporting/HideNonSensicalMessages.scala | 4 +- src/dotty/tools/dotc/reporting/Reporter.scala | 59 ++++----- src/dotty/tools/dotc/reporting/StoreReporter.scala | 6 +- .../tools/dotc/reporting/ThrowingReporter.scala | 4 +- .../dotc/reporting/UniqueMessagePositions.scala | 4 +- .../tools/dotc/reporting/diagnostic/Message.scala | 98 +++++++-------- .../reporting/diagnostic/MessageContainer.scala | 76 ++++++++++++ .../dotc/reporting/diagnostic/MessageCreator.scala | 52 -------- .../tools/dotc/reporting/diagnostic/messages.scala | 134 ++++++++++++++++++--- .../tools/dotc/reporting/diagnostic/parser.scala | 8 -- .../tools/dotc/reporting/diagnostic/syntax.scala | 61 ---------- .../tools/dotc/reporting/diagnostic/tpe.scala | 55 --------- src/dotty/tools/dotc/typer/ErrorReporting.scala | 3 +- src/dotty/tools/dotc/typer/Typer.scala | 12 +- test/test/CompilerTest.scala | 4 +- test/test/OtherEntryPointsTest.scala | 4 +- 20 files changed, 317 insertions(+), 338 deletions(-) create mode 100644 src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala delete mode 100644 src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala delete mode 100644 src/dotty/tools/dotc/reporting/diagnostic/parser.scala delete mode 100644 src/dotty/tools/dotc/reporting/diagnostic/syntax.scala delete mode 100644 src/dotty/tools/dotc/reporting/diagnostic/tpe.scala diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 1b451ced7..dcd596298 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -27,9 +27,8 @@ import rewrite.Rewrites.patch object Parsers { import ast.untpd._ - import reporting.diagnostic.MessageCreator - import MessageCreator._ - import reporting.diagnostic.syntax._ + import reporting.diagnostic.Message + import reporting.diagnostic.messages._ case class OpInfo(operand: Tree, operator: Name, offset: Offset) @@ -100,17 +99,17 @@ object Parsers { /** Issue an error at given offset if beyond last error offset * and update lastErrorOffset. */ - def syntaxError(expl: MessageCreator, offset: Int = in.offset): Unit = + def syntaxError(msg: Message, offset: Int = in.offset): Unit = if (offset > lastErrorOffset) { - syntaxError(expl, Position(offset)) + syntaxError(msg, Position(offset)) lastErrorOffset = in.offset } /** Unconditionally issue an error at given position, without * updating lastErrorOffset. */ - def syntaxError(expl: MessageCreator, pos: Position): Unit = - ctx.explainError(expl, source atPos pos) + def syntaxError(msg: Message, pos: Position): Unit = + ctx.error(msg, source atPos pos) } @@ -216,20 +215,20 @@ object Parsers { } } - def warning(msg: MessageCreator, offset: Int = in.offset) = - ctx.explainWarning(msg, source atPos Position(offset)) + def warning(msg: Message, offset: Int = in.offset) = + ctx.warning(msg, source atPos Position(offset)) - def deprecationWarning(msg: String, offset: Int = in.offset) = + def deprecationWarning(msg: Message, offset: Int = in.offset) = ctx.deprecationWarning(msg, source atPos Position(offset)) /** Issue an error at current offset taht input is incomplete */ - def incompleteInputError(msg: String) = + def incompleteInputError(msg: Message) = ctx.incompleteInputError(msg, source atPos Position(in.offset)) /** If at end of file, issue an incompleteInputError. * Otherwise issue a syntax error and skip to next safe point. */ - def syntaxErrorOrIncomplete(msg: String) = + def syntaxErrorOrIncomplete(msg: Message) = if (in.token == EOF) incompleteInputError(msg) else { syntaxError(msg) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index f1bb57bd5..b39d5683e 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -8,7 +8,7 @@ import collection.Map import Decorators._ import scala.annotation.switch import scala.util.control.NonFatal -import reporting.diagnostic.Message +import reporting.diagnostic.MessageContainer object Formatting { @@ -67,6 +67,7 @@ object Formatting { */ class ErrorMessageFormatter(sc: StringContext) extends StringFormatter(sc) { override protected def showArg(arg: Any)(implicit ctx: Context): String = { + import MessageContainer._ def isSensical(arg: Any): Boolean = arg match { case tpe: Type => tpe.exists && !tpe.isErroneous @@ -76,7 +77,7 @@ object Formatting { } val str = super.showArg(arg) if (isSensical(arg)) str - else Message.nonSensicalStartTag + str + Message.nonSensicalEndTag + else nonSensicalStartTag + str + nonSensicalEndTag } } diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 1ae6a1135..92eea9fa9 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -9,6 +9,7 @@ import Reporter._ import java.io.{ BufferedReader, IOException, PrintWriter } import scala.reflect.internal.util._ import diagnostic.Message +import diagnostic.MessageContainer import diagnostic.messages._ /** @@ -16,11 +17,11 @@ import diagnostic.messages._ * console. */ class ConsoleReporter( - reader: BufferedReader = Console.in, - writer: PrintWriter = new PrintWriter(Console.err, true)) - extends Reporter with UniqueMessagePositions with HideNonSensicalMessages { + reader: BufferedReader = Console.in, + writer: PrintWriter = new PrintWriter(Console.err, true) +) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages { - import Message._ + import MessageContainer._ /** maximal number of error messages to be printed */ protected def ErrorLimit = 100 @@ -35,9 +36,9 @@ class ConsoleReporter( def printMessage(msg: String): Unit = { writer.print(msg + "\n"); writer.flush() } /** Prints the message with the given position indication. */ - def printMessageAndPos(msg: String, pos: SourcePosition, kind: String = "")(implicit ctx: Context): Unit = { + def printMessageAndPos(msg: Message, pos: SourcePosition, kind: String)(implicit ctx: Context): Unit = { val posStr = if (pos.exists) s"$pos: " else "" - printMessage(s"${posStr}$kind: $msg") + printMessage(s"${posStr}${kind}: ${msg.msg}") if (pos.exists) { printSourceLine(pos) printColumnMarker(pos) @@ -52,21 +53,21 @@ class ConsoleReporter( |${m.explanation}""".stripMargin ) - override def doReport(m: Message)(implicit ctx: Context): Unit = { + override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { m match { case m: Error => - printMessageAndPos(m.message, m.pos, m.kind) + printMessageAndPos(m.contained, m.pos, m.kind) if (ctx.settings.prompt.value) displayPrompt() case m: ConditionalWarning if !m.enablingOption.value => case m: MigrationWarning => - printMessageAndPos(m.message, m.pos, m.kind) + printMessageAndPos(m.contained, m.pos, m.kind) case m: Warning => - printMessageAndPos(m.message, m.pos, m.kind) + printMessageAndPos(m.contained, m.pos, m.kind) case _ => - printMessageAndPos(m.message, m.pos, m.kind) + printMessageAndPos(m.contained, m.pos, m.kind) } - if (ctx.shouldExplain(m)) printExplanation(m) + if (ctx.shouldExplain(m)) printExplanation(m.contained) } def displayPrompt(): Unit = { diff --git a/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala index d69396f5f..725e69ff8 100644 --- a/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala @@ -73,15 +73,15 @@ class FancyConsoleReporter( }).show else "" /** Prints the message with the given position indication. */ - override def printMessageAndPos(msg: String, pos: SourcePosition, kind: String = "")(implicit ctx: Context): Unit = { + override def printMessageAndPos(msg: Message, pos: SourcePosition, kind: String)(implicit ctx: Context): Unit = { printMessage(posStr(pos, kind)) if (pos.exists) { val (src, offset) = sourceLine(pos) val marker = columnMarker(pos, offset) - val err = errorMsg(pos, msg, offset) + val err = errorMsg(pos, msg.msg, offset) printMessage(List(src, marker, err).mkString("\n")) - } else printMessage(msg) + } else printMessage(msg.msg) } override def printExplanation(m: Message)(implicit ctx: Context): Unit = { @@ -90,16 +90,4 @@ class FancyConsoleReporter( |${Blue("===========")}""".stripMargin) printMessage(m.explanation) } - - - //override def summary(implicit ctx: Context): String = { - // val b = new mutable.ListBuffer[String] - // if (warningCount > 0) - // b += countString(warningCount, Yellow("warning").show) + " found" - // if (errorCount > 0) - // b += countString(errorCount, Red("error").show) + " found" - // for ((settingName, count) <- unreportedWarnings) - // b += s"there were $count ${settingName.tail} ${Yellow("warning(s)").show}; re-run with $settingName for details" - // b.mkString("\n") - //} } diff --git a/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala b/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala index 140777275..ba1ab9b33 100644 --- a/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala +++ b/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala @@ -3,7 +3,7 @@ package dotc package reporting import core.Contexts.Context -import diagnostic.Message +import diagnostic.MessageContainer /** * This trait implements `isHidden` so that we avoid reporting non-sensical messages. @@ -12,7 +12,7 @@ trait HideNonSensicalMessages extends Reporter { /** Hides non-sensical messages, unless we haven't reported any error yet or * `-Yshow-suppressed-errors` is set. */ - override def isHidden(m: Message)(implicit ctx: Context): Boolean = + override def isHidden(m: MessageContainer)(implicit ctx: Context): Boolean = super.isHidden(m) || { m.isNonSensical && hasErrors && // if there are no errors yet, report even if diagnostic is non-sensical diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index 538464daa..b969fa878 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -12,13 +12,13 @@ import core.Mode import dotty.tools.dotc.core.Symbols.Symbol import diagnostic.messages._ import diagnostic._ -import MessageCreator._ +import Message._ object Reporter { /** Convert a SimpleReporter into a real Reporter */ def fromSimpleReporter(simple: interfaces.SimpleReporter): Reporter = new Reporter with UniqueMessagePositions with HideNonSensicalMessages { - override def doReport(m: Message)(implicit ctx: Context): Unit = m match { + override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = m match { case m: ConditionalWarning if !m.enablingOption.value => case _ => simple.report(m) @@ -37,17 +37,17 @@ trait Reporting { this: Context => def echo(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = reporter.report(new Info(msg, pos, "Info")) - def deprecationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new DeprecationWarning(msg, pos, "Deprecation Warning")) + def deprecationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + reporter.report(msg.deprecationWarning(pos)) - def migrationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new MigrationWarning(msg, pos, "Migration Warning")) + def migrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + reporter.report(msg.migrationWarning(pos)) - def uncheckedWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new UncheckedWarning(msg, pos, "Unchecked Warning")) + def uncheckedWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + reporter.report(msg.uncheckedWarning(pos)) - def featureWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new FeatureWarning(msg, pos, "Feature Warning")) + def featureWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + reporter.report(msg.featureWarning(pos)) def featureWarning(feature: String, featureDescription: String, isScala2Feature: Boolean, featureUseSite: Symbol, required: Boolean, pos: SourcePosition): Unit = { @@ -72,32 +72,24 @@ trait Reporting { this: Context => else reporter.report(new FeatureWarning(msg, pos)) } - def warning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new Warning(msg, pos)) - - def explainWarning(msg: => MessageCreator, pos: SourcePosition = NoSourcePosition): Unit = + def warning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = reporter.report(msg.warning(pos)) - def strictWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = + def strictWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = if (this.settings.strict.value) error(msg, pos) - else warning(msg + "\n(This would be an error under strict mode)", pos) - - def error(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = { - // println("*** ERROR: " + msg) // !!! DEBUG - reporter.report(new Error(msg, pos)) - } + else warning(msg.mapMsg(_ + "\n(This would be an error under strict mode)"), pos) - def explainError(msg: => MessageCreator, pos: SourcePosition = NoSourcePosition): Unit = + def error(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = reporter.report(msg.error(pos)) - def errorOrMigrationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = + def errorOrMigrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = if (ctx.scala2Mode) migrationWarning(msg, pos) else error(msg, pos) - def restrictionError(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - error(s"Implementation restriction: $msg", pos) + def restrictionError(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + error(msg.mapMsg(m => s"Implementation restriction: $m"), pos) - def incompleteInputError(msg: String, pos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Unit = - reporter.incomplete(new Error(msg, pos))(ctx) + def incompleteInputError(msg: Message, pos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Unit = + reporter.incomplete(msg.error(pos))(ctx) /** Log msg if settings.log contains the current phase. * See [[config.CompilerCommand#explainAdvanced]] for the exact meaning of @@ -184,7 +176,7 @@ trait Reporting { this: Context => abstract class Reporter extends interfaces.ReporterResult { /** Report a diagnostic */ - def doReport(d: Message)(implicit ctx: Context): Unit + def doReport(d: MessageContainer)(implicit ctx: Context): Unit /** Whether very long lines can be truncated. This exists so important * debugging information (like printing the classpath) is not rendered @@ -199,7 +191,7 @@ abstract class Reporter extends interfaces.ReporterResult { finally _truncationOK = saved } - type ErrorHandler = Message => Context => Unit + type ErrorHandler = MessageContainer => Context => Unit private var incompleteHandler: ErrorHandler = d => c => report(d)(c) def withIncompleteHandler[T](handler: ErrorHandler)(op: => T): T = { val saved = incompleteHandler @@ -228,7 +220,7 @@ abstract class Reporter extends interfaces.ReporterResult { override def default(key: String) = 0 } - def report(d: Message)(implicit ctx: Context): Unit = + def report(d: MessageContainer)(implicit ctx: Context): Unit = if (!isHidden(d)) { doReport(d)(ctx.addMode(Mode.Printing)) d match { @@ -242,12 +234,11 @@ abstract class Reporter extends interfaces.ReporterResult { } } - def incomplete(d: Message)(implicit ctx: Context): Unit = + def incomplete(d: MessageContainer)(implicit ctx: Context): Unit = incompleteHandler(d)(ctx) - /** Summary of warnings and errors */ - def summary/*(implicit ctx: Context)*/: String = { + def summary: String = { val b = new mutable.ListBuffer[String] if (warningCount > 0) b += countString(warningCount, "warning") + " found" @@ -275,7 +266,7 @@ abstract class Reporter extends interfaces.ReporterResult { } /** Should this diagnostic not be reported at all? */ - def isHidden(m: Message)(implicit ctx: Context): Boolean = ctx.mode.is(Mode.Printing) + def isHidden(m: MessageContainer)(implicit ctx: Context): Boolean = ctx.mode.is(Mode.Printing) /** Does this reporter contain not yet reported errors or warnings? */ def hasPending: Boolean = false diff --git a/src/dotty/tools/dotc/reporting/StoreReporter.scala b/src/dotty/tools/dotc/reporting/StoreReporter.scala index fa4fd991f..3744a35dd 100644 --- a/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -6,7 +6,7 @@ import core.Contexts.Context import collection.mutable import Reporter.{Error, Warning} import config.Printers.typr -import diagnostic.Message +import diagnostic.MessageContainer import diagnostic.messages._ /** @@ -14,9 +14,9 @@ import diagnostic.messages._ */ class StoreReporter(outer: Reporter) extends Reporter { - private var infos: mutable.ListBuffer[Message] = null + private var infos: mutable.ListBuffer[MessageContainer] = null - def doReport(m: Message)(implicit ctx: Context): Unit = { + def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { typr.println(s">>>> StoredError: ${m.message}") // !!! DEBUG if (infos == null) infos = new mutable.ListBuffer infos += m diff --git a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala index f4e3b472d..d8e03ab66 100644 --- a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala +++ b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala @@ -4,7 +4,7 @@ package reporting import core.Contexts.Context import collection.mutable -import diagnostic.Message +import diagnostic.MessageContainer import diagnostic.messages.Error import Reporter._ @@ -13,7 +13,7 @@ import Reporter._ * info to the underlying reporter. */ class ThrowingReporter(reportInfo: Reporter) extends Reporter { - def doReport(m: Message)(implicit ctx: Context): Unit = m match { + def doReport(m: MessageContainer)(implicit ctx: Context): Unit = m match { case _: Error => throw m case _ => reportInfo.doReport(m) } diff --git a/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala b/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala index 1ef8b3447..c5ff8cb6b 100644 --- a/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala +++ b/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala @@ -5,7 +5,7 @@ package reporting import scala.collection.mutable import util.{SourcePosition, SourceFile} import core.Contexts.Context -import diagnostic.Message +import diagnostic.MessageContainer /** * This trait implements `isHidden` so that multiple messages per position @@ -18,7 +18,7 @@ trait UniqueMessagePositions extends Reporter { /** Logs a position and returns true if it was already logged. * @note Two positions are considered identical for logging if they have the same point. */ - override def isHidden(m: Message)(implicit ctx: Context): Boolean = + override def isHidden(m: MessageContainer)(implicit ctx: Context): Boolean = super.isHidden(m) || { m.pos.exists && { positions get (ctx.source, m.pos.point) match { diff --git a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index b3c19820f..443dc4de8 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -3,66 +3,60 @@ package dotc package reporting package diagnostic -import util.SourcePosition +import util.{SourcePosition, NoSourcePosition} import core.Contexts.Context -import java.util.Optional - object Message { - val nonSensicalStartTag = "" - val nonSensicalEndTag = "" - - implicit class MessageContext(val c: Context) extends AnyVal { - def shouldExplain(msg: Message): Boolean = { - implicit val ctx: Context = c - msg.explanation match { - case "" => false - case _ => ctx.settings.explain.value - } - } - } + implicit def toNoExplanation(str: String): Message = + new NoExplanation(str) } -class Message( - msgFn: => String, - val pos: SourcePosition, - val level: Int, - val kind: String, - val explanation: String -) extends Exception with interfaces.Diagnostic { - import Message._ - private var myMsg: String = null - private var myIsNonSensical: Boolean = false +abstract class Message(val errorId: String) { self => + import messages._ + + def msg: String + def kind: String + def explanation: String - override def position: Optional[interfaces.SourcePosition] = - if (pos.exists && pos.source.exists) Optional.of(pos) else Optional.empty() + def container(c: String) = + if (kind == "") c + else s"$kind $c" - /** The message to report */ - def message: String = { - if (myMsg == null) { - myMsg = msgFn - if (myMsg.contains(nonSensicalStartTag)) { - myIsNonSensical = true - // myMsg might be composed of several d"..." invocations -> nested - // nonsensical tags possible - myMsg = - myMsg - .replaceAllLiterally(nonSensicalStartTag, "") - .replaceAllLiterally(nonSensicalEndTag, "") - } - } - myMsg + def mapMsg(f: String => String) = new Message(errorId) { + val msg = f(self.msg) + val kind = self.kind + val explanation = self.explanation } - /** A message is non-sensical if it contains references to - * tags. Such tags are inserted by the error diagnostic framework if a - * message contains references to internally generated error types. Normally - * we want to suppress error messages referring to types like this because - * they look weird and are normally follow-up errors to something that was - * diagnosed before. - */ - def isNonSensical = { message; myIsNonSensical } + def error(pos: SourcePosition) = + new Error(self, pos, container("Error"), explanation) + + def warning(pos: SourcePosition) = + new Warning(self, pos, container("Warning"), explanation) + + def info(pos: SourcePosition) = + new Info(self, pos, container("Info"), explanation) + + def featureWarning(pos: SourcePosition) = + new FeatureWarning(self, pos, container("Feature Warning"), explanation) + + def uncheckedWarning(pos: SourcePosition) = + new UncheckedWarning(self, pos, container("Unchecked Warning"), explanation) + + def deprecationWarning(pos: SourcePosition) = + new DeprecationWarning(self, pos, container("Deprecation Warning"), explanation) + + def migrationWarning(pos: SourcePosition) = + new MigrationWarning(self, pos, container("Migration Warning"), explanation) +} + +class NoExplanation(val msg: String) extends Message("") { + val explanation = "" + val kind = "" +} - override def toString = s"$getClass at $pos: $message" - override def getMessage() = message +object NoExplanation { + def unapply(m: Message): Option[Message] = + if (m.explanation == "") Some(m) + else None } diff --git a/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala new file mode 100644 index 000000000..d15c1d2f1 --- /dev/null +++ b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala @@ -0,0 +1,76 @@ +package dotty.tools +package dotc +package reporting +package diagnostic + +import util.SourcePosition +import core.Contexts.Context + +import java.util.Optional + +object MessageContainer { + val nonSensicalStartTag = "" + val nonSensicalEndTag = "" + + implicit class MessageContext(val c: Context) extends AnyVal { + def shouldExplain(cont: MessageContainer): Boolean = { + implicit val ctx: Context = c + cont.explanation match { + case "" => false + case _ => ctx.settings.explain.value + } + } + } +} + +class MessageContainer( + msgFn: => Message, + val pos: SourcePosition, + val level: Int, + val kind: String, + val explanation: String +) extends Exception with interfaces.Diagnostic { + import MessageContainer._ + private var myMsg: String = null + private var myIsNonSensical: Boolean = false + private var myContained: Message = null + + override def position: Optional[interfaces.SourcePosition] = + if (pos.exists && pos.source.exists) Optional.of(pos) else Optional.empty() + + /** The message to report */ + def message: String = { + if (myMsg == null) { + myMsg = msgFn.msg + if (myMsg.contains(nonSensicalStartTag)) { + myIsNonSensical = true + // myMsg might be composed of several d"..." invocations -> nested + // nonsensical tags possible + myMsg = + myMsg + .replaceAllLiterally(nonSensicalStartTag, "") + .replaceAllLiterally(nonSensicalEndTag, "") + } + } + myMsg + } + + def contained: Message = { + if (myContained == null) + myContained = msgFn + + myContained + } + + /** A message is non-sensical if it contains references to + * tags. Such tags are inserted by the error diagnostic framework if a + * message contains references to internally generated error types. Normally + * we want to suppress error messages referring to types like this because + * they look weird and are normally follow-up errors to something that was + * diagnosed before. + */ + def isNonSensical = { message; myIsNonSensical } + + override def toString = s"$getClass at $pos: ${message}" + override def getMessage() = message +} diff --git a/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala deleted file mode 100644 index 99ccca4cc..000000000 --- a/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala +++ /dev/null @@ -1,52 +0,0 @@ -package dotty.tools -package dotc -package reporting -package diagnostic - -import util.{SourcePosition, NoSourcePosition} -import core.Contexts.Context - -object MessageCreator { - implicit def toNoExplanation(str: String): MessageCreator = - new NoExplanation(str) -} - -trait MessageCreator { - import messages._ - - def msg: String - def kind: String - def explanation: String - - def error(pos: SourcePosition) = - new Error(msg, pos, kind, explanation) - - def warning(pos: SourcePosition) = - new Warning(msg, pos, kind, explanation) - - def info(pos: SourcePosition) = - new Info(msg, pos, kind, explanation) - - def featureWarnign(pos: SourcePosition) = - new FeatureWarning(msg, pos, kind, explanation) - - def uncheckedWarning(pos: SourcePosition) = - new UncheckedWarning(msg, pos, kind, explanation) - - def deprecationWarning(pos: SourcePosition) = - new DeprecationWarning(msg, pos, kind, explanation) - - def migrationWarning(pos: SourcePosition) = - new MigrationWarning(msg, pos, kind, explanation) -} - -class NoExplanation(val msg: String) extends MessageCreator { - val explanation = "" - val kind = "" -} - -object NoExplanation { - def unapply(m: MessageCreator): Option[MessageCreator] = - if (m.explanation == "") Some(m) - else None -} diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 382e9a295..4b7502287 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -3,37 +3,40 @@ package dotc package reporting package diagnostic -import dotc.core.Contexts.Context +import dotc.core._ +import Contexts.Context, Decorators._, Symbols._, Names._ import util.{SourceFile, NoSource} import util.{SourcePosition, NoSourcePosition} import config.Settings.Setting import interfaces.Diagnostic.{ERROR, WARNING, INFO} +import dotc.printing.SyntaxHighlighting._ object messages { + /** Concrete messages to be consumed by the reporter ---------------------- */ class Error( - msgFn: => String, + msgFn: => Message, pos: SourcePosition, - kind: String = "Error", + kind: String, explanation: String = "" - ) extends Message(msgFn, pos, ERROR, kind, explanation) + ) extends MessageContainer(msgFn, pos, ERROR, kind, explanation) class Warning( - msgFn: => String, + msgFn: => Message, pos: SourcePosition, - kind: String = "Warning", + kind: String, explanation: String = "" - ) extends Message(msgFn, pos, WARNING, kind, explanation) + ) extends MessageContainer(msgFn, pos, WARNING, kind, explanation) class Info( - msgFn: => String, + msgFn: => Message, pos: SourcePosition, - kind: String = "Info", + kind: String, explanation: String = "" - ) extends Message(msgFn, pos, INFO, kind, explanation) + ) extends MessageContainer(msgFn, pos, INFO, kind, explanation) abstract class ConditionalWarning( - msgFn: => String, + msgFn: => Message, pos: SourcePosition, kind: String, explanation: String = "" @@ -42,7 +45,7 @@ object messages { } class FeatureWarning( - msgFn: => String, + msgFn: => Message, pos: SourcePosition, kind: String = "Feature Warning", explanation: String = "" @@ -51,7 +54,7 @@ object messages { } class UncheckedWarning( - msgFn: => String, + msgFn: => Message, pos: SourcePosition, kind: String = "Unchecked Warning", explanation: String = "" @@ -60,7 +63,7 @@ object messages { } class DeprecationWarning( - msgFn: => String, + msgFn: => Message, pos: SourcePosition, kind: String = "Deprecation Warning", explanation: String = "" @@ -69,7 +72,7 @@ object messages { } class MigrationWarning( - msgFn: => String, + msgFn: => Message, pos: SourcePosition, kind: String = "Migration Warning", explanation: String = "" @@ -77,4 +80,105 @@ object messages { def enablingOption(implicit ctx: Context) = ctx.settings.migration } + /** Messages ---------------------------------------------------------------- + * + * The role of messages is to provide the necessary details for a simple to + * understand diagnostic event. Each message can be turned into a message + * container (one of the above) by calling the appropriate method on them. + * For instance: + * + * ```scala + * EmptyCatchBlock(tree).error // res: Error + * EmptyCatchBlock(tree).warning // res: Warning + * ``` + */ + import dotc.ast.Trees._ + import dotc.ast.untpd + + /** Syntax Errors --------------------------------------------------------- */ + abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree, errNo: String)(implicit ctx: Context) + extends Message(errNo) { + val explanation = { + val tryString = tryBody match { + case Block(Nil, untpd.EmptyTree) => "{}" + case _ => tryBody.show + } + + val code1 = + s"""|try $tryString catch { + | case t: Throwable => ??? + |}""".stripMargin + + val code2 = + s"""|try $tryString finally { + | // perform your cleanup here! + |}""".stripMargin + + hl"""|A ${"try"} expression should be followed by some mechanism to handle any exceptions + |thrown. Typically a ${"catch"} expression follows the ${"try"} and pattern matches + |on any expected exceptions. For example: + | + |$code1 + | + |It is also possible to follow a ${"try"} immediately by a ${"finally"} - letting the + |exception propagate - but still allowing for some clean up in ${"finally"}: + | + |$code2""".stripMargin + } + } + + class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context) + extends EmptyCatchOrFinallyBlock(tryBody, "E001") { + val kind = "Syntax" + val msg = + hl"""|The ${"catch"} block does not contain a valid expression, try + |adding a case like - `${"case e: Exception =>"}` to the block""".stripMargin + } + + case class EmptyCatchAndFinallyBlock(tryBody: untpd.Tree)(implicit ctx: Context) + extends EmptyCatchOrFinallyBlock(tryBody, "E002") { + val kind = "Syntax" + val msg = + hl"""|A ${"try"} without ${"catch"} or ${"finally"} is equivalent to putting + |its body in a block; no exceptions are handled.""".stripMargin + } + + /** Type Errors ----------------------------------------------------------- */ + class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context) + extends Message("E003") { + val kind = "Naming" + val msg = em"duplicate pattern variable: `${bind.name}`" + + val explanation = { + val pat = tree.pat.show + val guard = tree.guard match { + case untpd.EmptyTree => "" + case guard => s"if ${guard.show}" + } + + val body = tree.body match { + case Block(Nil, untpd.EmptyTree) => "" + case body => s" ${body.show}" + } + + val caseDef = s"case $pat$guard => $body" + + hl"""|For each ${"case"} bound variable names have to be unique. In: + | + |$caseDef + | + |`${bind.name}` is not unique. Rename one of the bound variables!""".stripMargin + } + } + + class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context) + extends Message("E004") { + val kind = "Missing identifier" + val msg = em"not found: $treeKind$name" + + val explanation = { + hl"""|An identifier for `${name.show}` is missing. This means that something + |has either been misspelt or you're forgetting an import""".stripMargin + } + } } diff --git a/src/dotty/tools/dotc/reporting/diagnostic/parser.scala b/src/dotty/tools/dotc/reporting/diagnostic/parser.scala deleted file mode 100644 index b73e353eb..000000000 --- a/src/dotty/tools/dotc/reporting/diagnostic/parser.scala +++ /dev/null @@ -1,8 +0,0 @@ -package dotty.tools -package dotc -package reporting -package diagnostic - -object parser { - -} diff --git a/src/dotty/tools/dotc/reporting/diagnostic/syntax.scala b/src/dotty/tools/dotc/reporting/diagnostic/syntax.scala deleted file mode 100644 index b9a662c7d..000000000 --- a/src/dotty/tools/dotc/reporting/diagnostic/syntax.scala +++ /dev/null @@ -1,61 +0,0 @@ -package dotty.tools -package dotc -package reporting -package diagnostic - -import dotc.core._ -import Contexts.Context, Decorators._, Symbols._ -import dotc.printing.SyntaxHighlighting._ -import util.{SourcePosition, NoSourcePosition} - -object syntax { - import dotc.ast.Trees._ - import dotc.ast.untpd - - abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree)(implicit ctx: Context) extends MessageCreator { - val explanation = { - val tryString = tryBody match { - case Block(Nil, untpd.EmptyTree) => "{}" - case _ => tryBody.show - } - - val code1 = - s"""|try $tryString catch { - | case t: Throwable => ??? - |}""".stripMargin - - val code2 = - s"""|try $tryString finally { - | // perform your cleanup here! - |}""".stripMargin - - hl"""|A ${"try"} expression should be followed by some mechanism to handle any exceptions - |thrown. Typically a ${"catch"} expression follows the ${"try"} and pattern matches - |on any expected exceptions. For example: - | - |$code1 - | - |It is also possible to follow a ${"try"} immediately by a ${"finally"} - letting the - |exception propagate - but still allowing for some clean up in ${"finally"}: - | - |$code2 - """.stripMargin - } - } - - class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context) - extends EmptyCatchOrFinallyBlock(tryBody) { - val kind = "Syntax" - val msg = - hl"""|The ${"catch"} block does not contain a valid expression, try - |adding a case like - `${"case e: Exception =>"}` to the block""".stripMargin - } - - case class EmptyCatchAndFinallyBlock(tryBody: untpd.Tree)(implicit ctx: Context) - extends EmptyCatchOrFinallyBlock(tryBody) { - val kind = "Syntax" - val msg = - hl"""|A ${"try"} without ${"catch"} or ${"finally"} is equivalent to putting - |its body in a block; no exceptions are handled.""".stripMargin - } -} diff --git a/src/dotty/tools/dotc/reporting/diagnostic/tpe.scala b/src/dotty/tools/dotc/reporting/diagnostic/tpe.scala deleted file mode 100644 index ee221f80d..000000000 --- a/src/dotty/tools/dotc/reporting/diagnostic/tpe.scala +++ /dev/null @@ -1,55 +0,0 @@ -package dotty.tools -package dotc -package reporting -package diagnostic - -import dotc.core._ -import Contexts.Context, Decorators._, Symbols._, Names._ -import dotc.printing.SyntaxHighlighting._ -import util.{SourcePosition, NoSourcePosition} - -object tpe { - import dotc.ast.Trees._ - import dotc.ast.untpd - - class DuplicateBind( - bind: untpd.Bind, - tree: untpd.CaseDef - )(implicit ctx: Context) extends MessageCreator { - val kind = "Naming" - - val msg = - em"duplicate pattern variable: `${bind.name}`" - - val explanation = { - val pat = tree.pat.show - val guard = tree.guard match { - case untpd.EmptyTree => "" - case guard => s"if ${guard.show}" - } - - val body = tree.body match { - case Block(Nil, untpd.EmptyTree) => "" - case body => s" ${body.show}" - } - - val caseDef = s"case $pat$guard => $body" - - hl"""|For each ${"case"} bound variable names have to be unique. In: - | - |$caseDef - | - |`${bind.name}` is not unique. Rename one of the bound variables!""".stripMargin - } - } - - class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context) extends MessageCreator { - val kind = "Missing identifier" - val msg = em"not found: $treeKind$name" - - val explanation = { - hl"""|An identifier for `${name.show}` is missing. This means that something - |has either been misspelt or you're forgetting an import""".stripMargin - } - } -} diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index 43c093510..8b740c3dc 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -11,6 +11,7 @@ import util.Positions._ import printing.{Showable, RefinedPrinter} import scala.collection.mutable import java.util.regex.Matcher.quoteReplacement +import reporting.diagnostic.Message object ErrorReporting { @@ -19,7 +20,7 @@ object ErrorReporting { 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 = { + def errorType(msg: => Message, pos: Position)(implicit ctx: Context): ErrorType = { ctx.error(msg, pos) ErrorType } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index b29254b89..0cc04613a 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -65,8 +65,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit import tpd.{cpy => _, _} import untpd.cpy import Dynamic.isDynamicMethod - import reporting.diagnostic.MessageCreator - import reporting.diagnostic.tpe._ + import reporting.diagnostic.Message + import reporting.diagnostic.messages._ /** A temporary data item valid for a single typed ident: * The set of all root import symbols that have been @@ -99,7 +99,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit /** Method is necessary because error messages need to bind to * to typedIdent's context which is lost in nested calls to findRef */ - def error(msg: => MessageCreator, pos: Position) = ctx.explainError(msg, pos) + def error(msg: => Message, pos: Position) = ctx.error(msg, pos) /** Is this import a root import that has been shadowed by an explicit * import in the same program? @@ -772,10 +772,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit TypeTree(pt) case _ => if (!mt.isDependent) EmptyTree - else throw new Error(i"internal error: cannot turn dependent method type $mt into closure, position = ${tree.pos}, raw type = ${mt.toString}") // !!! DEBUG. Eventually, convert to an error? + else throw new java.lang.Error(i"internal error: cannot turn dependent method type $mt into closure, position = ${tree.pos}, raw type = ${mt.toString}") // !!! DEBUG. Eventually, convert to an error? } case tp => - throw new Error(i"internal error: closing over non-method $tp, pos = ${tree.pos}") + throw new java.lang.Error(i"internal error: closing over non-method $tp, pos = ${tree.pos}") } else typed(tree.tpt) //println(i"typing closure $tree : ${meth1.tpe.widen}") @@ -848,7 +848,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit super.transform(trt.withType(elimWildcardSym(trt.tpe))) match { case b: Bind => if (ctx.scope.lookup(b.name) == NoSymbol) ctx.enter(b.symbol) - else ctx.explainError(new DuplicateBind(b, tree), b.pos) + else ctx.error(new DuplicateBind(b, tree), b.pos) b.symbol.info = elimWildcardSym(b.symbol.info) b case t => t diff --git a/test/test/CompilerTest.scala b/test/test/CompilerTest.scala index 942948f08..8cf6b2feb 100644 --- a/test/test/CompilerTest.scala +++ b/test/test/CompilerTest.scala @@ -5,7 +5,7 @@ import dotty.partest.DPConfig import dotty.tools.dotc.{Main, Bench, Driver} import dotty.tools.dotc.interfaces.Diagnostic.ERROR import dotty.tools.dotc.reporting._ -import diagnostic.Message +import diagnostic.MessageContainer import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.config.CompilerCommand import dotty.tools.io.PlainFile @@ -238,7 +238,7 @@ abstract class CompilerTest { val storeReporter = new Reporter with UniqueMessagePositions with HideNonSensicalMessages { private val consoleReporter = new ConsoleReporter() private val innerStoreReporter = new StoreReporter(consoleReporter) - def doReport(m: Message)(implicit ctx: Context): Unit = { + def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { if (m.level == ERROR) { innerStoreReporter.flush() consoleReporter.doReport(m) diff --git a/test/test/OtherEntryPointsTest.scala b/test/test/OtherEntryPointsTest.scala index 824034055..abaa043c0 100644 --- a/test/test/OtherEntryPointsTest.scala +++ b/test/test/OtherEntryPointsTest.scala @@ -5,7 +5,7 @@ import org.junit.Assert._ import dotty.tools.dotc.Main import dotty.tools.dotc.interfaces.{CompilerCallback, SourceFile} import dotty.tools.dotc.reporting._ -import dotty.tools.dotc.reporting.diagnostic.Message +import dotty.tools.dotc.reporting.diagnostic.MessageContainer import dotty.tools.dotc.core.Contexts._ import java.io.File import scala.collection.mutable.ListBuffer @@ -51,7 +51,7 @@ class OtherEntryPointsTest { private class CustomReporter extends Reporter with UniqueMessagePositions with HideNonSensicalMessages { - def doReport(m: Message)(implicit ctx: Context): Unit = { + def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { } } -- cgit v1.2.3