diff options
author | Felix Mulder <felix.mulder@gmail.com> | 2016-09-14 17:52:11 +0200 |
---|---|---|
committer | Felix Mulder <felix.mulder@gmail.com> | 2016-10-10 13:25:32 +0200 |
commit | 281dbb553b20ca16dd9e749acdfe39129e20fa28 (patch) | |
tree | a21e8339e443fddd3ff3b9f80435d59f92708d6e /src/dotty | |
parent | 3bb27d468cb06894c72b9f2cfba854ad49d68a74 (diff) | |
download | dotty-281dbb553b20ca16dd9e749acdfe39129e20fa28.tar.gz dotty-281dbb553b20ca16dd9e749acdfe39129e20fa28.tar.bz2 dotty-281dbb553b20ca16dd9e749acdfe39129e20fa28.zip |
Add initial structure for improved explanations of error messages
Diffstat (limited to 'src/dotty')
-rw-r--r-- | src/dotty/tools/dotc/config/ScalaSettings.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/parsing/Parsers.scala | 23 | ||||
-rw-r--r-- | src/dotty/tools/dotc/reporting/Examples.scala | 67 | ||||
-rw-r--r-- | src/dotty/tools/dotc/reporting/Reporter.scala | 13 |
4 files changed, 91 insertions, 13 deletions
diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index ff17a9939..a4daefcba 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -23,6 +23,7 @@ class ScalaSettings extends Settings.SettingGroup { val migration = BooleanSetting("-migration", "Emit warning and location for migration issues from Scala 2.") val encoding = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding) val explaintypes = BooleanSetting("-explaintypes", "Explain type errors in more detail.") + val explainerrors = BooleanSetting("-explain", "Explain errors in more detail.") val feature = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.") val g = ChoiceSetting("-g", "level", "Set level of generated debugging info.", List("none", "source", "line", "vars", "notailcalls"), "vars") val help = BooleanSetting("-help", "Print a synopsis of standard options") diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 9aadf0c61..e4378d82b 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -17,6 +17,7 @@ import ast.Trees._ import Decorators._ import StdNames._ import util.Positions._ +import reporting.ErrorExplanations._ import Constants._ import ScriptParsers._ import Comments._ @@ -97,17 +98,17 @@ object Parsers { /** Issue an error at given offset if beyond last error offset * and update lastErrorOffset. */ - def syntaxError(msg: String, offset: Int = in.offset): Unit = + def syntaxError(expl: Explanation, offset: Int = in.offset): Unit = if (offset > lastErrorOffset) { - syntaxError(msg, Position(offset)) + syntaxError(expl, Position(offset)) lastErrorOffset = in.offset } /** Unconditionally issue an error at given position, without * updating lastErrorOffset. */ - def syntaxError(msg: String, pos: Position): Unit = - ctx.error(msg, source atPos pos) + def syntaxError(expl: Explanation, pos: Position): Unit = + ctx.explainError(expl, source atPos pos) } @@ -213,8 +214,8 @@ object Parsers { } } - def warning(msg: String, offset: Int = in.offset) = - ctx.warning(msg, source atPos Position(offset)) + def warning(msg: Explanation, offset: Int = in.offset) = + ctx.explainWarning(msg, source atPos Position(offset)) def deprecationWarning(msg: String, offset: Int = in.offset) = ctx.deprecationWarning(msg, source atPos Position(offset)) @@ -1013,19 +1014,15 @@ object Parsers { } else EmptyTree handler match { - case Block(Nil, EmptyTree) => syntaxError( - "`catch` block does not contain a valid expression, try adding a case like - `case e: Exception =>` to the block", - handler.pos - ) + case Block(Nil, EmptyTree) => + syntaxError(EmptyCatchBlock(body), handler.pos) case _ => } val finalizer = if (in.token == FINALLY) { accept(FINALLY); expr() } else { - if (handler.isEmpty) - warning("A try without `catch` or `finally` is equivalent to putting its body in a block; no exceptions are handled.") - + if (handler.isEmpty) warning(EmptyCatchAndFinallyBlock(body)) EmptyTree } ParsedTry(body, handler, finalizer) diff --git a/src/dotty/tools/dotc/reporting/Examples.scala b/src/dotty/tools/dotc/reporting/Examples.scala new file mode 100644 index 000000000..d2dbd70eb --- /dev/null +++ b/src/dotty/tools/dotc/reporting/Examples.scala @@ -0,0 +1,67 @@ +package dotty.tools +package dotc +package reporting + +import dotc.core.Contexts.Context + +object ErrorExplanations { + import dotc.ast.Trees._ + import dotc.ast.untpd + + implicit def stringToExplanation(s: String) = NoExplanation(s) + + implicit class ShouldExplainCtx(val c: Context) extends AnyVal { + def shouldExplain(expl: Explanation): Boolean = { + implicit val ctx = c + expl match { + case _: NoExplanation => false + case expl if ctx.settings.explainerrors.value => true + case _ => false + } + } + } + + trait Explanation { + def msg: String + def explanation: String + } + + case class NoExplanation(msg: String) extends Explanation { + val explanation = "" + } + + abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree)(implicit ctx: Context) extends Explanation { + val explanation = { + val tryString = tryBody match { + case Block(Nil, untpd.EmptyTree) => "{}" + case _ => tryBody.show + } + s"""|Explanation: + |============ + |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: + | + |try $tryString catch { + | case t: Throwable => ??? + |} + | + |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`: + | + |try $tryString finally { + | // perform your cleanup here! + |}""".stripMargin + } + } + + case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context) extends EmptyCatchOrFinallyBlock(tryBody) { + val msg = + "`catch` block does not contain a valid expression, try adding a case like - `case e: Exception =>` to the block" + } + + case class EmptyCatchAndFinallyBlock(tryBody: untpd.Tree)(implicit ctx: Context) extends EmptyCatchOrFinallyBlock(tryBody) { + val msg = + "A try without `catch` or `finally` is equivalent to putting its body in a block; no exceptions are handled." + } +} diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index 75113d823..eb9a10a59 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -13,6 +13,7 @@ import java.lang.System.currentTimeMillis import core.Mode import interfaces.Diagnostic.{ERROR, WARNING, INFO} import dotty.tools.dotc.core.Symbols.Symbol +import ErrorExplanations._ object Reporter { class Error(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, ERROR) @@ -95,6 +96,12 @@ trait Reporting { this: Context => def warning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = reporter.report(new Warning(msg, pos)) + def explainWarning(expl: => Explanation, pos: SourcePosition = NoSourcePosition): Unit = { + warning(expl.msg, pos) + if (this.shouldExplain(expl)) + reporter.report(new Info(expl.explanation, NoSourcePosition)) + } + def strictWarning(msg: => String, 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) @@ -104,6 +111,12 @@ trait Reporting { this: Context => reporter.report(new Error(msg, pos)) } + def explainError(expl: => Explanation, pos: SourcePosition = NoSourcePosition): Unit = { + error(expl.msg, pos) + if (this.shouldExplain(expl)) + reporter.report(new Info(expl.explanation, NoSourcePosition)) + } + def errorOrMigrationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = if (ctx.scala2Mode) migrationWarning(msg, pos) else error(msg, pos) |