From 281dbb553b20ca16dd9e749acdfe39129e20fa28 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 14 Sep 2016 17:52:11 +0200 Subject: [PATCH 01/53] Add initial structure for improved explanations of error messages --- .../tools/dotc/config/ScalaSettings.scala | 1 + src/dotty/tools/dotc/parsing/Parsers.scala | 23 +++---- src/dotty/tools/dotc/reporting/Examples.scala | 67 +++++++++++++++++++ src/dotty/tools/dotc/reporting/Reporter.scala | 13 ++++ 4 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 src/dotty/tools/dotc/reporting/Examples.scala diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index ff17a993991a..a4daefcba200 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 9aadf0c616ae..e4378d82b108 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 000000000000..d2dbd70eb347 --- /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 75113d823880..eb9a10a59739 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) From a6997587fec8e73f28ca3145339ee971aeaa94b2 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 15 Sep 2016 11:51:02 +0200 Subject: [PATCH 02/53] Add highlighter string interpolator --- .../tools/dotc/printing/SyntaxHighlighting.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 83c4289765a2..932b980dba31 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -5,9 +5,25 @@ package printing import parsing.Tokens._ import scala.annotation.switch import scala.collection.mutable.StringBuilder +import core.Contexts.Context /** This object provides functions for syntax highlighting in the REPL */ object SyntaxHighlighting { + + implicit class SyntaxFormatting(val sc: StringContext) extends AnyVal { + def hl(args: Any*)(implicit ctx: Context): String = + new SyntaxFormatter(sc).assemble(args) + } + + private class SyntaxFormatter(sc: StringContext) extends Formatting.StringFormatter(sc) { + override def assemble(args: Seq[Any])(implicit ctx: Context): String = super.assemble { + args.map { + case str: String => new String(apply(str).toArray) + case x => x + } + } + } + val NoColor = Console.RESET val CommentColor = Console.GREEN val KeywordColor = Console.CYAN From d2c9e190070a87f75f5398c90758134269752a90 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 15 Sep 2016 11:51:40 +0200 Subject: [PATCH 03/53] Return iterable from highlighting function instead of vector --- src/dotty/tools/dotc/printing/SyntaxHighlighting.scala | 4 ++-- src/dotty/tools/dotc/repl/AmmoniteReader.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 932b980dba31..c933dd0b9763 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -51,7 +51,7 @@ object SyntaxHighlighting { private val typeEnders = '{' :: '}' :: ')' :: '(' :: '=' :: ' ' :: ',' :: '.' :: '\n' :: Nil - def apply(chars: Iterable[Char]): Vector[Char] = { + def apply(chars: Iterable[Char]): Iterable[Char] = { var prev: Char = 0 var remaining = chars.toStream val newBuf = new StringBuilder @@ -281,6 +281,6 @@ object SyntaxHighlighting { prev = curr } - newBuf.toVector + newBuf.toIterable } } diff --git a/src/dotty/tools/dotc/repl/AmmoniteReader.scala b/src/dotty/tools/dotc/repl/AmmoniteReader.scala index 614654a28b78..435269081dde 100644 --- a/src/dotty/tools/dotc/repl/AmmoniteReader.scala +++ b/src/dotty/tools/dotc/repl/AmmoniteReader.scala @@ -61,7 +61,7 @@ class AmmoniteReader(val interpreter: Interpreter)(implicit ctx: Context) extend if (ctx.useColors) SyntaxHighlighting(buffer) else buffer - val ansiBuffer = Ansi.Str.parse(coloredBuffer) + val ansiBuffer = Ansi.Str.parse(coloredBuffer.toVector) val (newBuffer, cursorOffset) = SelectionFilter.mangleBuffer( selectionFilter, ansiBuffer, cursor, Ansi.Reversed.On ) From 6495210d685fcf89e8ee453e8999ee8883f20683 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 15 Sep 2016 11:52:22 +0200 Subject: [PATCH 04/53] Add highlighting for `???` --- src/dotty/tools/dotc/printing/SyntaxHighlighting.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index c933dd0b9763..51dee349ba4e 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -36,6 +36,7 @@ object SyntaxHighlighting { private def typeDef(str: String) = TypeColor + str + NoColor private def literal(str: String) = LiteralColor + str + NoColor private def annotation(str: String) = AnnotationColor + str + NoColor + private val tripleQs = Console.RED_B + "???" + NoColor private val keywords: Seq[String] = for { index <- IF to INLINE // All alpha keywords @@ -123,7 +124,11 @@ object SyntaxHighlighting { case '`' => appendTo('`', _ == '`', none) case _ => { - if (n.isUpper && keywordStart) + if (n == '?' && remaining.take(2).mkString == "??") { + takeChars(2) + newBuf append tripleQs + prev = '?' + } else if (n.isUpper && keywordStart) appendWhile(n, !typeEnders.contains(_), typeDef) else if (numberStart(n)) appendWhile(n, { x => x.isDigit || x == '.' || x == '\u0000'}, literal) From e4a7db12c2e24b668f146c75469c1e61b7455456 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 15 Sep 2016 15:38:40 +0200 Subject: [PATCH 05/53] Simplify "hl" interpolator --- .../tools/dotc/printing/SyntaxHighlighting.scala | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 51dee349ba4e..6c7e65a0bfb6 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -11,17 +11,8 @@ import core.Contexts.Context object SyntaxHighlighting { implicit class SyntaxFormatting(val sc: StringContext) extends AnyVal { - def hl(args: Any*)(implicit ctx: Context): String = - new SyntaxFormatter(sc).assemble(args) - } - - private class SyntaxFormatter(sc: StringContext) extends Formatting.StringFormatter(sc) { - override def assemble(args: Seq[Any])(implicit ctx: Context): String = super.assemble { - args.map { - case str: String => new String(apply(str).toArray) - case x => x - } - } + def hl(args: Any*): String = + sc.s(args.map(x => new String(apply(x.toString).toArray)): _*) } val NoColor = Console.RESET From 66f7f7d23264374f9fbc581538fa1f47de16433c Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 15 Sep 2016 15:39:12 +0200 Subject: [PATCH 06/53] Use highlighting in explanation classes --- src/dotty/tools/dotc/reporting/Examples.scala | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/dotty/tools/dotc/reporting/Examples.scala b/src/dotty/tools/dotc/reporting/Examples.scala index d2dbd70eb347..9759d39e8cec 100644 --- a/src/dotty/tools/dotc/reporting/Examples.scala +++ b/src/dotty/tools/dotc/reporting/Examples.scala @@ -3,6 +3,7 @@ package dotc package reporting import dotc.core.Contexts.Context +import dotc.printing.SyntaxHighlighting._ object ErrorExplanations { import dotc.ast.Trees._ @@ -36,32 +37,40 @@ object ErrorExplanations { 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 + + val code1 = + s"""|try $tryString catch { + | case t: Throwable => ??? + |}""".stripMargin + + val code2 = + s"""|try $tryString finally { + | // perform your cleanup here! + |}""".stripMargin + + hl"""|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: + | + |$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 } } 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" + hl"""The ${"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." + hl"""A ${"try"} without ${"catch"} or ${"finally"} is equivalent to putting its body in a block; no exceptions are handled.""" } } From c9a8bb8d783d288ae2c165b8eefb3ca47c372c4e Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 15 Sep 2016 18:04:03 +0200 Subject: [PATCH 07/53] Add more examples to Typer --- src/dotty/tools/dotc/reporting/Examples.scala | 32 ++++++++++++++++++- src/dotty/tools/dotc/typer/Typer.scala | 24 ++++++++------ 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/reporting/Examples.scala b/src/dotty/tools/dotc/reporting/Examples.scala index 9759d39e8cec..652620e2991b 100644 --- a/src/dotty/tools/dotc/reporting/Examples.scala +++ b/src/dotty/tools/dotc/reporting/Examples.scala @@ -2,8 +2,10 @@ package dotty.tools package dotc package reporting -import dotc.core.Contexts.Context +import dotc.core._ +import Contexts.Context, Decorators._, Symbols._ import dotc.printing.SyntaxHighlighting._ +import util.{SourcePosition, NoSourcePosition} object ErrorExplanations { import dotc.ast.Trees._ @@ -73,4 +75,32 @@ object ErrorExplanations { val msg = hl"""A ${"try"} without ${"catch"} or ${"finally"} is equivalent to putting its body in a block; no exceptions are handled.""" } + + case class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context) extends Explanation { + 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"""|Explanation + |=========== + |For each ${"case"} bound variable names have to be unique. In: + | + |$caseDef + | + |`${bind.name}` is not unique. Rename one of the binds!""".stripMargin + } + } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 3e3bb32f5571..19297f9ec29c 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -37,6 +37,8 @@ import rewrite.Rewrites.patch import NavigateAST._ import transform.SymUtils._ import language.implicitConversions +import printing.SyntaxHighlighting._ +import reporting.ErrorExplanations._ object Typer { @@ -141,9 +143,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit * imported by * or defined in */ - def bindingString(prec: Int, whereFound: Context, qualifier: String = "")(implicit ctx: Context) = - if (prec == wildImport || prec == namedImport) ex"imported$qualifier by ${whereFound.importInfo}" - else ex"defined$qualifier in ${whereFound.owner}" + def bindingString(prec: Int, whereFound: Context, qualifier: String = "") = + if (prec == wildImport || prec == namedImport) { + ex"""imported$qualifier by ${hl"${whereFound.importInfo.toString}"}""" + } else + ex"""defined$qualifier in ${hl"${whereFound.owner.toString}"}""" /** Check that any previously found result from an inner context * does properly shadow the new one from an outer context. @@ -166,9 +170,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit else { if (!scala2pkg && !previous.isError && !found.isError) { error( - ex"""reference to $name is ambiguous; - |it is both ${bindingString(newPrec, ctx, "")} - |and ${bindingString(prevPrec, prevCtx, " subsequently")}""", + ex"""|reference to `$name` is ambiguous + |it is both ${bindingString(newPrec, ctx, "")} + |and ${bindingString(prevPrec, prevCtx, " subsequently")}""", tree.pos) } previous @@ -181,7 +185,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def checkUnambiguous(found: Type) = { val other = namedImportRef(site, selectors.tail) if (other.exists && found.exists && (found != other)) - error(em"reference to $name is ambiguous; it is imported twice in ${ctx.tree}", + error(em"reference to `$name` is ambiguous; it is imported twice in ${ctx.tree}", tree.pos) found } @@ -839,11 +843,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit mapOver(t) } } - override def transform(tree: Tree)(implicit ctx: Context) = - super.transform(tree.withType(elimWildcardSym(tree.tpe))) match { + override def transform(trt: Tree)(implicit ctx: Context) = + super.transform(trt.withType(elimWildcardSym(trt.tpe))) match { case b: Bind => if (ctx.scope.lookup(b.name) == NoSymbol) ctx.enter(b.symbol) - else ctx.error(em"duplicate pattern variable: ${b.name}", b.pos) + else ctx.explainError(DuplicateBind(b, tree), b.pos) b.symbol.info = elimWildcardSym(b.symbol.info) b case t => t From 1946f94d34a929055c2f0d2dd231c5f5e634c536 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 15 Sep 2016 19:20:11 +0200 Subject: [PATCH 08/53] Change layout of ErrorMessages --- .../tools/dotc/parsing/JavaParsers.scala | 1 + .../tools/dotc/parsing/MarkupParsers.scala | 1 + src/dotty/tools/dotc/parsing/Parsers.scala | 9 +- .../tools/dotc/reporting/ErrorMessages.scala | 117 ++++++++++++++++++ src/dotty/tools/dotc/reporting/Examples.scala | 106 ---------------- src/dotty/tools/dotc/reporting/Reporter.scala | 18 +-- src/dotty/tools/dotc/typer/Typer.scala | 3 +- 7 files changed, 135 insertions(+), 120 deletions(-) create mode 100644 src/dotty/tools/dotc/reporting/ErrorMessages.scala delete mode 100644 src/dotty/tools/dotc/reporting/Examples.scala diff --git a/src/dotty/tools/dotc/parsing/JavaParsers.scala b/src/dotty/tools/dotc/parsing/JavaParsers.scala index ed7cf9e3f9cd..90eb908776e7 100644 --- a/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -30,6 +30,7 @@ import scala.reflect.internal.util.Collections._ object JavaParsers { import ast.untpd._ + import reporting.ErrorMessages.Syntax._ class JavaParser(source: SourceFile)(implicit ctx: Context) extends ParserCommon(source) { diff --git a/src/dotty/tools/dotc/parsing/MarkupParsers.scala b/src/dotty/tools/dotc/parsing/MarkupParsers.scala index f648b9e2cdeb..ea40eb568433 100644 --- a/src/dotty/tools/dotc/parsing/MarkupParsers.scala +++ b/src/dotty/tools/dotc/parsing/MarkupParsers.scala @@ -32,6 +32,7 @@ import Utility._ object MarkupParsers { import ast.untpd._ + import reporting.ErrorMessages.Syntax._ case object MissingEndTagControl extends ControlThrowable { override def getMessage = "start tag was here: " diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index e4378d82b108..48422650c34c 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -17,7 +17,7 @@ import ast.Trees._ import Decorators._ import StdNames._ import util.Positions._ -import reporting.ErrorExplanations._ +import reporting.ErrorMessages._ import Constants._ import ScriptParsers._ import Comments._ @@ -28,6 +28,7 @@ import rewrite.Rewrites.patch object Parsers { import ast.untpd._ + import reporting.ErrorMessages.Syntax._ case class OpInfo(operand: Tree, operator: Name, offset: Offset) @@ -98,7 +99,7 @@ object Parsers { /** Issue an error at given offset if beyond last error offset * and update lastErrorOffset. */ - def syntaxError(expl: Explanation, offset: Int = in.offset): Unit = + def syntaxError(expl: ErrorMessage, offset: Int = in.offset): Unit = if (offset > lastErrorOffset) { syntaxError(expl, Position(offset)) lastErrorOffset = in.offset @@ -107,7 +108,7 @@ object Parsers { /** Unconditionally issue an error at given position, without * updating lastErrorOffset. */ - def syntaxError(expl: Explanation, pos: Position): Unit = + def syntaxError(expl: ErrorMessage, pos: Position): Unit = ctx.explainError(expl, source atPos pos) } @@ -214,7 +215,7 @@ object Parsers { } } - def warning(msg: Explanation, offset: Int = in.offset) = + def warning(msg: ErrorMessage, offset: Int = in.offset) = ctx.explainWarning(msg, source atPos Position(offset)) def deprecationWarning(msg: String, offset: Int = in.offset) = diff --git a/src/dotty/tools/dotc/reporting/ErrorMessages.scala b/src/dotty/tools/dotc/reporting/ErrorMessages.scala new file mode 100644 index 000000000000..7186ccbe24de --- /dev/null +++ b/src/dotty/tools/dotc/reporting/ErrorMessages.scala @@ -0,0 +1,117 @@ +package dotty.tools +package dotc +package reporting + +import dotc.core._ +import Contexts.Context, Decorators._, Symbols._ +import dotc.printing.SyntaxHighlighting._ +import util.{SourcePosition, NoSourcePosition} + +object ErrorMessages { + import dotc.ast.Trees._ + import dotc.ast.untpd + + implicit class ShouldExplainCtx(val c: Context) extends AnyVal { + def shouldExplain(expl: ErrorMessage): Boolean = { + implicit val ctx = c + expl match { + case _: NoExplanation => false + case expl if ctx.settings.explainerrors.value => true + case _ => false + } + } + } + + trait ErrorMessage { + def kind: String + def msg: String + def explanation: String + } + + case class NoExplanation(msg: String)(implicit val kind: String) extends ErrorMessage { + val explanation = "" + } + + object Syntax { + implicit val kind: String = "Syntax" + implicit def stringToErrorMessage(s: String): ErrorMessage = NoExplanation(s) + + abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree)(implicit ctx: Context, val kind: String) extends ErrorMessage { + 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"""|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: + | + |$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 + } + } + + case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context, override val kind: String) + extends EmptyCatchOrFinallyBlock(tryBody) { + val msg = + hl"""The ${"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, override val kind: String) + extends EmptyCatchOrFinallyBlock(tryBody) { + val msg = + hl"""A ${"try"} without ${"catch"} or ${"finally"} is equivalent to putting its body in a block; no exceptions are handled.""" + } + } + + object Type { + implicit val kind: String = "Type" + implicit def stringToErrorMessage(s: String): ErrorMessage = NoExplanation(s) + + case class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context, val kind: String) extends ErrorMessage { + 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"""|Explanation + |=========== + |For each ${"case"} bound variable names have to be unique. In: + | + |$caseDef + | + |`${bind.name}` is not unique. Rename one of the binds!""".stripMargin + } + } + } +} diff --git a/src/dotty/tools/dotc/reporting/Examples.scala b/src/dotty/tools/dotc/reporting/Examples.scala deleted file mode 100644 index 652620e2991b..000000000000 --- a/src/dotty/tools/dotc/reporting/Examples.scala +++ /dev/null @@ -1,106 +0,0 @@ -package dotty.tools -package dotc -package reporting - -import dotc.core._ -import Contexts.Context, Decorators._, Symbols._ -import dotc.printing.SyntaxHighlighting._ -import util.{SourcePosition, NoSourcePosition} - -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 - } - - val code1 = - s"""|try $tryString catch { - | case t: Throwable => ??? - |}""".stripMargin - - val code2 = - s"""|try $tryString finally { - | // perform your cleanup here! - |}""".stripMargin - - hl"""|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: - | - |$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 - } - } - - case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context) extends EmptyCatchOrFinallyBlock(tryBody) { - val msg = - hl"""The ${"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 = - hl"""A ${"try"} without ${"catch"} or ${"finally"} is equivalent to putting its body in a block; no exceptions are handled.""" - } - - case class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context) extends Explanation { - 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"""|Explanation - |=========== - |For each ${"case"} bound variable names have to be unique. In: - | - |$caseDef - | - |`${bind.name}` is not unique. Rename one of the binds!""".stripMargin - } - } -} diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index eb9a10a59739..401938859c72 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -13,7 +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._ +import ErrorMessages._ object Reporter { class Error(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, ERROR) @@ -96,10 +96,10 @@ 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 explainWarning(err: => ErrorMessage, pos: SourcePosition = NoSourcePosition): Unit = { + warning(err.msg, pos) + if (this.shouldExplain(err)) + reporter.report(new Info(err.explanation, NoSourcePosition)) } def strictWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = @@ -111,10 +111,10 @@ 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 explainError(err: => ErrorMessage, pos: SourcePosition = NoSourcePosition): Unit = { + error(err.msg, pos) + if (this.shouldExplain(err)) + reporter.report(new Info(err.explanation, NoSourcePosition)) } def errorOrMigrationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 19297f9ec29c..6fddd928bde9 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -38,7 +38,7 @@ import NavigateAST._ import transform.SymUtils._ import language.implicitConversions import printing.SyntaxHighlighting._ -import reporting.ErrorExplanations._ +import reporting.ErrorMessages._ object Typer { @@ -66,6 +66,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit import tpd.{cpy => _, _} import untpd.cpy import Dynamic.isDynamicMethod + import reporting.ErrorMessages.Type._ /** A temporary data item valid for a single typed ident: * The set of all root import symbols that have been From 11cfc30baf214d5d69d0d5d79b981ad1c131d0e6 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 15 Sep 2016 19:20:31 +0200 Subject: [PATCH 09/53] Add missing star in docstring --- src/dotty/tools/dotc/core/TypeOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 5ba9a3351137..bee69ae691fe 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -36,7 +36,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. * Instead we produce an annotated type that marks the prefix as unsafe: * * (x: (C @ UnsafeNonvariant)#T)C#T - + * * We also set a global state flag `unsafeNonvariant` to the current run. * When typing a Select node, typer will check that flag, and if it * points to the current run will scan the result type of the select for From 15db5f6053badd49aae62a699aa59745288c932d Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 15 Sep 2016 19:20:53 +0200 Subject: [PATCH 10/53] Add smart comment formatting in ConsoleReporter --- .../dotc/reporting/ConsoleReporter.scala | 72 +++++++++++++++++-- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index deb772db5719..a681c1552223 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -8,6 +8,7 @@ import core.Contexts._ import Reporter._ import java.io.{ BufferedReader, IOException, PrintWriter } import scala.reflect.internal.util._ +import printing.SyntaxHighlighting._ /** * This class implements a Reporter that displays messages on a text @@ -21,6 +22,7 @@ class ConsoleReporter( /** maximal number of error messages to be printed */ protected def ErrorLimit = 100 +<<<<<<< HEAD def printPos(pos: SourcePosition): Unit = if (pos.exists) { printMessage(pos.lineContent.stripLineEnd) @@ -30,26 +32,82 @@ class ConsoleReporter( printPos(pos.outer) } } +======= + def sourceLine(pos: SourcePosition): (String, Int) = { + val lineNum = s"${pos.line}:" + (lineNum + hl"${pos.lineContent.stripLineEnd}", lineNum.length) + } + + def columnMarker(pos: SourcePosition, offset: Int) = + if (pos.startLine == pos.endLine) { + val whitespace = " " * (pos.column + offset) + val carets = + AnnotationColor + + ("^" * math.max(1, pos.endColumn - pos.startColumn)) + + NoColor + + whitespace + carets + } else { + " " * (pos.column + offset) + AnnotationColor + "^" + NoColor + } + + def errorMsg(pos: SourcePosition, msg: String, offset: Int)(implicit ctx: Context) = { + var hasLongLines = false + val leastWhitespace = msg.lines.foldLeft(Int.MaxValue) { (minPad, line) => + val lineLength = + line.replaceAll("\u001B\\[[;\\d]*m", "").length + val padding = + math.min(math.max(0, ctx.settings.pageWidth.value - offset - lineLength), offset + pos.startColumn) + + if (padding < minPad) padding + else minPad + } + + msg + .lines + .map { line => " " * leastWhitespace + line } + .mkString(sys.props("line.separator")) + } + + def posStr(pos: SourcePosition, kind: String)(implicit ctx: Context) = + if (pos.exists) { + val file = pos.source.file.toString + + val outer = if (pos.outer.exists) { + s"This location is in code that was inlined at ${pos.outer}:\n" + + printStr(pos.outer) + "\n" + "-" * ctx.settings.pageWidth.value + } else "" + + s"${Console.CYAN}$kind: $file " + + "-" * math.max(ctx.settings.pageWidth.value - file.length - kind.length - 1, 0) + + "\n" + outer + NoColor + } + else "" /** Prints the message. */ 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)(implicit ctx: Context): Unit = { - val posStr = if (pos.exists) s"$pos: " else "" - printMessage(posStr + msg) - printPos(pos) + def printMessageAndPos(msg: String, 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) + + printMessage(List(src, marker, err).mkString("\n")) + } else printMessage(msg) } override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { case d: Error => - printMessageAndPos(s"error: ${d.message}", d.pos) + printMessageAndPos(d.message, d.pos, "Error in") if (ctx.settings.prompt.value) displayPrompt() case d: ConditionalWarning if !d.enablingOption.value => case d: MigrationWarning => - printMessageAndPos(s"migration warning: ${d.message}", d.pos) + printMessageAndPos(d.message, d.pos, "Migration Warning in") case d: Warning => - printMessageAndPos(s"warning: ${d.message}", d.pos) + printMessageAndPos(d.message, d.pos, "Warning in") case _ => printMessageAndPos(d.message, d.pos) } From f8456fc71e0fba8c92eb2069a7abc872a59c2567 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 15 Sep 2016 22:28:30 +0200 Subject: [PATCH 11/53] Add error kind to diagnostic --- .../tools/dotc/interfaces/Diagnostic.java | 3 ++ .../dotc/reporting/ConsoleReporter.scala | 22 ++++---------- .../tools/dotc/reporting/Diagnostic.scala | 2 +- .../tools/dotc/reporting/ErrorMessages.scala | 2 +- src/dotty/tools/dotc/reporting/Reporter.scala | 30 +++++++++---------- 5 files changed, 26 insertions(+), 33 deletions(-) diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java index c46360afaa3d..ed1c37b1c2c2 100644 --- a/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java @@ -14,6 +14,9 @@ public interface Diagnostic { public static final int WARNING = 1; public static final int INFO = 0; + /** @return The kind of message being reported */ + String kind(); + /** @return The message to report */ String message(); diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index a681c1552223..5db549b7b606 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -22,17 +22,6 @@ class ConsoleReporter( /** maximal number of error messages to be printed */ protected def ErrorLimit = 100 -<<<<<<< HEAD - def printPos(pos: SourcePosition): Unit = - if (pos.exists) { - printMessage(pos.lineContent.stripLineEnd) - printMessage(" " * pos.column + "^") - if (pos.outer.exists) { - printMessage(s"\n... this location is in code that was inlined at ${pos.outer}:\n") - printPos(pos.outer) - } - } -======= def sourceLine(pos: SourcePosition): (String, Int) = { val lineNum = s"${pos.line}:" (lineNum + hl"${pos.lineContent.stripLineEnd}", lineNum.length) @@ -78,8 +67,9 @@ class ConsoleReporter( printStr(pos.outer) + "\n" + "-" * ctx.settings.pageWidth.value } else "" - s"${Console.CYAN}$kind: $file " + - "-" * math.max(ctx.settings.pageWidth.value - file.length - kind.length - 1, 0) + + val prefix = s"${Console.CYAN}-- $kind: $file " + prefix + + ("-" * math.max(ctx.settings.pageWidth.value - prefix.replaceAll("\u001B\\[[;\\d]*m", "").length, 0)) + "\n" + outer + NoColor } else "" @@ -101,13 +91,13 @@ class ConsoleReporter( override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { case d: Error => - printMessageAndPos(d.message, d.pos, "Error in") + printMessageAndPos(d.message, d.pos, d.kind) if (ctx.settings.prompt.value) displayPrompt() case d: ConditionalWarning if !d.enablingOption.value => case d: MigrationWarning => - printMessageAndPos(d.message, d.pos, "Migration Warning in") + printMessageAndPos(d.message, d.pos, d.kind) case d: Warning => - printMessageAndPos(d.message, d.pos, "Warning in") + printMessageAndPos(d.message, d.pos, d.kind) case _ => printMessageAndPos(d.message, d.pos) } diff --git a/src/dotty/tools/dotc/reporting/Diagnostic.scala b/src/dotty/tools/dotc/reporting/Diagnostic.scala index bcf55e993170..d08636802e53 100644 --- a/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/src/dotty/tools/dotc/reporting/Diagnostic.scala @@ -11,7 +11,7 @@ object Diagnostic { val nonSensicalEndTag = "" } -class Diagnostic(msgFn: => String, val pos: SourcePosition, val level: Int) +class Diagnostic(msgFn: => String, val pos: SourcePosition, val level: Int, val kind: String) extends Exception with interfaces.Diagnostic { import Diagnostic._ private var myMsg: String = null diff --git a/src/dotty/tools/dotc/reporting/ErrorMessages.scala b/src/dotty/tools/dotc/reporting/ErrorMessages.scala index 7186ccbe24de..2b4c0db7e9b3 100644 --- a/src/dotty/tools/dotc/reporting/ErrorMessages.scala +++ b/src/dotty/tools/dotc/reporting/ErrorMessages.scala @@ -110,7 +110,7 @@ object ErrorMessages { | |$caseDef | - |`${bind.name}` is not unique. Rename one of the binds!""".stripMargin + |`${bind.name}` is not unique. Rename one of the bound variables!""".stripMargin } } } diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index 401938859c72..db0922b2e51e 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -16,23 +16,23 @@ import dotty.tools.dotc.core.Symbols.Symbol import ErrorMessages._ object Reporter { - class Error(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, ERROR) - class Warning(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, WARNING) - class Info(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, INFO) + class Error(msgFn: => String, pos: SourcePosition, kind: String = "Error") extends Diagnostic(msgFn, pos, ERROR, kind) + class Warning(msgFn: => String, pos: SourcePosition, kind: String = "Warning") extends Diagnostic(msgFn, pos, WARNING, kind) + class Info(msgFn: => String, pos: SourcePosition, kind: String = "Info") extends Diagnostic(msgFn, pos, INFO, kind) - abstract class ConditionalWarning(msgFn: => String, pos: SourcePosition) extends Warning(msgFn, pos) { + abstract class ConditionalWarning(msgFn: => String, pos: SourcePosition, kind: String) extends Warning(msgFn, pos, kind) { def enablingOption(implicit ctx: Context): Setting[Boolean] } - class FeatureWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) { + class FeatureWarning(msgFn: => String, pos: SourcePosition, kind: String = "Feature Warning") extends ConditionalWarning(msgFn, pos, kind) { def enablingOption(implicit ctx: Context) = ctx.settings.feature } - class UncheckedWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) { + class UncheckedWarning(msgFn: => String, pos: SourcePosition, kind: String = "Unchecked Warning") extends ConditionalWarning(msgFn, pos, kind) { def enablingOption(implicit ctx: Context) = ctx.settings.unchecked } - class DeprecationWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) { + class DeprecationWarning(msgFn: => String, pos: SourcePosition, kind: String = "Deprecation Warning") extends ConditionalWarning(msgFn, pos, kind) { def enablingOption(implicit ctx: Context) = ctx.settings.deprecation } - class MigrationWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) { + class MigrationWarning(msgFn: => String, pos: SourcePosition, kind: String = "Migration Warning") extends ConditionalWarning(msgFn, pos, kind) { def enablingOption(implicit ctx: Context) = ctx.settings.migration } @@ -56,19 +56,19 @@ trait Reporting { this: Context => if (this.settings.verbose.value) this.echo(msg, pos) def echo(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new Info(msg, pos)) + reporter.report(new Info(msg, pos, "Info")) def deprecationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new DeprecationWarning(msg, pos)) + reporter.report(new DeprecationWarning(msg, pos, "Deprecation Warning")) def migrationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new MigrationWarning(msg, pos)) + reporter.report(new MigrationWarning(msg, pos, "Migration Warning")) def uncheckedWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new UncheckedWarning(msg, pos)) + reporter.report(new UncheckedWarning(msg, pos, "Unchecked Warning")) def featureWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new FeatureWarning(msg, pos)) + reporter.report(new FeatureWarning(msg, pos, "Feature Warning")) def featureWarning(feature: String, featureDescription: String, isScala2Feature: Boolean, featureUseSite: Symbol, required: Boolean, pos: SourcePosition): Unit = { @@ -97,7 +97,7 @@ trait Reporting { this: Context => reporter.report(new Warning(msg, pos)) def explainWarning(err: => ErrorMessage, pos: SourcePosition = NoSourcePosition): Unit = { - warning(err.msg, pos) + reporter.report(new Warning(err.msg, pos, s"${err.kind} warning")) if (this.shouldExplain(err)) reporter.report(new Info(err.explanation, NoSourcePosition)) } @@ -112,7 +112,7 @@ trait Reporting { this: Context => } def explainError(err: => ErrorMessage, pos: SourcePosition = NoSourcePosition): Unit = { - error(err.msg, pos) + reporter.report(new Error(err.msg, pos, s"${err.kind} error")) if (this.shouldExplain(err)) reporter.report(new Info(err.explanation, NoSourcePosition)) } From fb4f8ce66c406bfb6376396ea0521df063b883e9 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 16 Sep 2016 11:06:40 +0200 Subject: [PATCH 12/53] Rename `ConsoleReporter` => `FancyConsoleReporter` --- src/dotty/tools/dotc/Compiler.scala | 2 +- src/dotty/tools/dotc/core/Contexts.scala | 2 +- src/dotty/tools/dotc/repl/CompilingInterpreter.scala | 4 ++-- .../{ConsoleReporter.scala => FancyConsoleReporter.scala} | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename src/dotty/tools/dotc/reporting/{ConsoleReporter.scala => FancyConsoleReporter.scala} (99%) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 178cba7c4080..fb9ce83fca8b 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -8,7 +8,7 @@ import Symbols._ import Types._ import Scopes._ import typer.{FrontEnd, Typer, ImportInfo, RefChecks} -import reporting.{Reporter, ConsoleReporter} +import reporting.{Reporter, FancyConsoleReporter} import Phases.Phase import transform._ import transform.TreeTransforms.{TreeTransform, TreeTransformer} diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 5c9fdaf886ee..d1447a98f168 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -501,7 +501,7 @@ object Contexts { outer = NoContext period = InitialPeriod mode = Mode.None - typerState = new TyperState(new ConsoleReporter()) + typerState = new TyperState(new FancyConsoleReporter()) printerFn = new RefinedPrinter(_) owner = NoSymbol sstate = settings.defaultState diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala index 163bbea16606..ec2c167ce79d 100644 --- a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala +++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -16,7 +16,7 @@ import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer} //import ast.parser.SyntaxAnalyzer import io.{PlainFile, VirtualDirectory} import scala.reflect.io.{PlainDirectory, Directory} -import reporting.{ConsoleReporter, Reporter} +import reporting.{FancyConsoleReporter, Reporter} import core.Flags import util.{SourceFile, NameTransformer} import io.ClassPath @@ -117,7 +117,7 @@ class CompilingInterpreter( } } - private def newReporter = new ConsoleReporter(Console.in, out) { + private def newReporter = new FancyConsoleReporter(Console.in, out) { override def printMessage(msg: String) = { if (!delayOutput) { out.print(/*clean*/(msg) + "\n") diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala similarity index 99% rename from src/dotty/tools/dotc/reporting/ConsoleReporter.scala rename to src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala index 5db549b7b606..57f9998a2884 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala @@ -14,7 +14,7 @@ import printing.SyntaxHighlighting._ * This class implements a Reporter that displays messages on a text * console. */ -class ConsoleReporter( +class FancyConsoleReporter( reader: BufferedReader = Console.in, writer: PrintWriter = new PrintWriter(Console.err, true)) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages { From 12ac3054bf4288403babb172c125cdc98cfff012 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 16 Sep 2016 11:49:41 +0200 Subject: [PATCH 13/53] Add ability to choose between fancy and non-fancy output --- src/dotty/tools/dotc/Compiler.scala | 6 +- .../dotc/printing/SyntaxHighlighting.scala | 5 +- .../dotc/repl/CompilingInterpreter.scala | 38 ++++++---- .../dotc/reporting/ConsoleReporter.scala | 72 +++++++++++++++++++ .../dotc/reporting/FancyConsoleReporter.scala | 50 ++----------- 5 files changed, 110 insertions(+), 61 deletions(-) create mode 100644 src/dotty/tools/dotc/reporting/ConsoleReporter.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index fb9ce83fca8b..d6e3cc89b0b5 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -8,7 +8,7 @@ import Symbols._ import Types._ import Scopes._ import typer.{FrontEnd, Typer, ImportInfo, RefChecks} -import reporting.{Reporter, FancyConsoleReporter} +import reporting.{Reporter, ConsoleReporter, FancyConsoleReporter} import Phases.Phase import transform._ import transform.TreeTransforms.{TreeTransform, TreeTransformer} @@ -140,6 +140,10 @@ class Compiler { .setTyper(new Typer) .setMode(Mode.ImplicitsEnabled) .setTyperState(new MutableTyperState(ctx.typerState, ctx.typerState.reporter, isCommittable = true)) + .setReporter( + if (ctx.settings.color.value == "never") new ConsoleReporter() + else new FancyConsoleReporter() + ) ctx.initialize()(start) // re-initialize the base context with start def addImport(ctx: Context, refFn: () => TermRef) = ctx.fresh.setImportInfo(ImportInfo.rootImport(refFn)(ctx)) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 6c7e65a0bfb6..958e71086342 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -11,8 +11,9 @@ import core.Contexts.Context object SyntaxHighlighting { implicit class SyntaxFormatting(val sc: StringContext) extends AnyVal { - def hl(args: Any*): String = - sc.s(args.map(x => new String(apply(x.toString).toArray)): _*) + def hl(args: Any*)(implicit ctx: Context): String = + if (ctx.settings.color.value == "never") sc.s(args) + else sc.s(args.map(x => new String(apply(x.toString).toArray)): _*) } val NoColor = Console.RESET diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala index ec2c167ce79d..70b32a16ae33 100644 --- a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala +++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -16,7 +16,7 @@ import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer} //import ast.parser.SyntaxAnalyzer import io.{PlainFile, VirtualDirectory} import scala.reflect.io.{PlainDirectory, Directory} -import reporting.{FancyConsoleReporter, Reporter} +import reporting.{ConsoleReporter, FancyConsoleReporter, Reporter} import core.Flags import util.{SourceFile, NameTransformer} import io.ClassPath @@ -117,23 +117,31 @@ class CompilingInterpreter( } } - private def newReporter = new FancyConsoleReporter(Console.in, out) { - override def printMessage(msg: String) = { - if (!delayOutput) { - out.print(/*clean*/(msg) + "\n") - // Suppress clean for now for compiler messages - // Otherwise we will completely delete all references to - // line$object$ module classes. The previous interpreter did not - // have the project because the module class was written without the final `$' - // and therefore escaped the purge. We can turn this back on once - // we drop the final `$' from module classes. - out.flush() - } else { - previousOutput += (/*clean*/(msg) + "\n") - } + private def customPrintMessage(msg: String) = { + if (!delayOutput) { + out.print(/*clean*/(msg) + "\n") + // Suppress clean for now for compiler messages + // Otherwise we will completely delete all references to + // line$object$ module classes. The previous interpreter did not + // have the project because the module class was written without the final `$' + // and therefore escaped the purge. We can turn this back on once + // we drop the final `$' from module classes. + out.flush() + } else { + previousOutput += (/*clean*/(msg) + "\n") } } + private def newReporter(implicit ctx: Context) = + if (ctx.settings.color.value == "never") + new ConsoleReporter(Console.in, out) { + override def printMessage(msg: String) = customPrintMessage(msg) + } + else + new FancyConsoleReporter(Console.in, out) { + override def printMessage(msg: String) = customPrintMessage(msg) + } + /** the previous requests this interpreter has processed */ private val prevRequests = new ArrayBuffer[Request]() diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala new file mode 100644 index 000000000000..1d9423b70f97 --- /dev/null +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -0,0 +1,72 @@ +package dotty.tools +package dotc +package reporting + +import scala.collection.mutable +import util.SourcePosition +import core.Contexts._ +import Reporter._ +import java.io.{ BufferedReader, IOException, PrintWriter } +import scala.reflect.internal.util._ + +/** + * This class implements a Reporter that displays messages on a text + * console. + */ +class ConsoleReporter( + reader: BufferedReader = Console.in, + writer: PrintWriter = new PrintWriter(Console.err, true)) + extends Reporter with UniqueMessagePositions with HideNonSensicalMessages { + + /** maximal number of error messages to be printed */ + protected def ErrorLimit = 100 + + def printSourceLine(pos: SourcePosition) = + printMessage(pos.lineContent.stripLineEnd) + + def printColumnMarker(pos: SourcePosition) = + if (pos.exists) { printMessage(" " * pos.column + "^") } + + /** Prints the message. */ + 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 = { + val posStr = if (pos.exists) s"$pos: " else "" + printMessage(posStr + kind + msg) + if (pos.exists) { + printSourceLine(pos) + printColumnMarker(pos) + } + } + + override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { + case d: Error => + printMessageAndPos(d.message, d.pos, d.kind) + if (ctx.settings.prompt.value) displayPrompt() + case d: ConditionalWarning if !d.enablingOption.value => + case d: MigrationWarning => + printMessageAndPos(d.message, d.pos, d.kind) + case d: Warning => + printMessageAndPos(d.message, d.pos, d.kind) + case _ => + printMessageAndPos(d.message, d.pos, d.kind) + } + + def displayPrompt(): Unit = { + writer.print("\na)bort, s)tack, r)esume: ") + writer.flush() + if (reader != null) { + val response = reader.read().asInstanceOf[Char].toLower + if (response == 'a' || response == 's') { + Thread.dumpStack() + if (response == 'a') + sys.exit(1) + } + writer.print("\n") + writer.flush() + } + } + + override def flush()(implicit ctx: Context): Unit = { writer.flush() } +} diff --git a/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala index 57f9998a2884..80f15a4b0c27 100644 --- a/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala @@ -11,18 +11,15 @@ import scala.reflect.internal.util._ import printing.SyntaxHighlighting._ /** - * This class implements a Reporter that displays messages on a text - * console. + * This class implements a more Fancy version (with colors!) of the regular + * `ConsoleReporter` */ class FancyConsoleReporter( - 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 ConsoleReporter(reader, writer) { - /** maximal number of error messages to be printed */ - protected def ErrorLimit = 100 - - def sourceLine(pos: SourcePosition): (String, Int) = { + def sourceLine(pos: SourcePosition)(implicit ctx: Context): (String, Int) = { val lineNum = s"${pos.line}:" (lineNum + hl"${pos.lineContent.stripLineEnd}", lineNum.length) } @@ -74,11 +71,8 @@ class FancyConsoleReporter( } else "" - /** Prints the message. */ - 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 = { + override def printMessageAndPos(msg: String, pos: SourcePosition, kind: String = "")(implicit ctx: Context): Unit = { printMessage(posStr(pos, kind)) if (pos.exists) { val (src, offset) = sourceLine(pos) @@ -88,34 +82,4 @@ class FancyConsoleReporter( printMessage(List(src, marker, err).mkString("\n")) } else printMessage(msg) } - - override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { - case d: Error => - printMessageAndPos(d.message, d.pos, d.kind) - if (ctx.settings.prompt.value) displayPrompt() - case d: ConditionalWarning if !d.enablingOption.value => - case d: MigrationWarning => - printMessageAndPos(d.message, d.pos, d.kind) - case d: Warning => - printMessageAndPos(d.message, d.pos, d.kind) - case _ => - printMessageAndPos(d.message, d.pos) - } - - def displayPrompt(): Unit = { - writer.print("\na)bort, s)tack, r)esume: ") - writer.flush() - if (reader != null) { - val response = reader.read().asInstanceOf[Char].toLower - if (response == 'a' || response == 's') { - Thread.dumpStack() - if (response == 'a') - sys.exit(1) - } - writer.print("\n") - writer.flush() - } - } - - override def flush()(implicit ctx: Context): Unit = { writer.flush() } } From 18a69f7bd230bc06696e41be53a6735aa6e94ccc Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 16 Sep 2016 13:34:57 +0200 Subject: [PATCH 14/53] Rename Diagnostic to diagnostic.Message --- .../tools/dotc/printing/Formatting.scala | 5 ++- .../dotc/reporting/ConsoleReporter.scala | 19 ++++---- .../reporting/HideNonSensicalMessages.scala | 7 +-- src/dotty/tools/dotc/reporting/Reporter.scala | 43 ++++++++++++------- .../tools/dotc/reporting/StoreReporter.scala | 13 +++--- .../dotc/reporting/ThrowingReporter.scala | 7 +-- .../reporting/UniqueMessagePositions.scala | 13 +++--- .../{ => diagnostic}/Diagnostic.scala | 9 ++-- .../tools/dotc/typer/ErrorReporting.scala | 1 - 9 files changed, 67 insertions(+), 50 deletions(-) rename src/dotty/tools/dotc/reporting/{ => diagnostic}/Diagnostic.scala (87%) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index 8921c56a332a..f1bb57bd5279 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 +import reporting.diagnostic.Message object Formatting { @@ -75,7 +75,8 @@ object Formatting { case _ => true } val str = super.showArg(arg) - if (isSensical(arg)) str else Diagnostic.nonSensicalStartTag + str + Diagnostic.nonSensicalEndTag + if (isSensical(arg)) str + else Message.nonSensicalStartTag + str + Message.nonSensicalEndTag } } diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 1d9423b70f97..1ed889683e77 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -8,6 +8,7 @@ import core.Contexts._ import Reporter._ import java.io.{ BufferedReader, IOException, PrintWriter } import scala.reflect.internal.util._ +import diagnostic.Message /** * This class implements a Reporter that displays messages on a text @@ -40,17 +41,17 @@ class ConsoleReporter( } } - override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { - case d: Error => - printMessageAndPos(d.message, d.pos, d.kind) + override def doReport(m: Message)(implicit ctx: Context): Unit = m match { + case m: Error => + printMessageAndPos(m.message, m.pos, m.kind) if (ctx.settings.prompt.value) displayPrompt() - case d: ConditionalWarning if !d.enablingOption.value => - case d: MigrationWarning => - printMessageAndPos(d.message, d.pos, d.kind) - case d: Warning => - printMessageAndPos(d.message, d.pos, d.kind) + case m: ConditionalWarning if !m.enablingOption.value => + case m: MigrationWarning => + printMessageAndPos(m.message, m.pos, m.kind) + case m: Warning => + printMessageAndPos(m.message, m.pos, m.kind) case _ => - printMessageAndPos(d.message, d.pos, d.kind) + printMessageAndPos(m.message, m.pos, m.kind) } def displayPrompt(): Unit = { diff --git a/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala b/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala index a325fe9f707e..140777275f54 100644 --- a/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala +++ b/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala @@ -3,6 +3,7 @@ package dotc package reporting import core.Contexts.Context +import diagnostic.Message /** * This trait implements `isHidden` so that we avoid reporting non-sensical messages. @@ -11,9 +12,9 @@ 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(d: Diagnostic)(implicit ctx: Context): Boolean = - super.isHidden(d) || { - d.isNonSensical && + override def isHidden(m: Message)(implicit ctx: Context): Boolean = + super.isHidden(m) || { + m.isNonSensical && hasErrors && // if there are no errors yet, report even if diagnostic is non-sensical !ctx.settings.YshowSuppressedErrors.value } diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index db0922b2e51e..8c9308ba5612 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -13,36 +13,47 @@ import java.lang.System.currentTimeMillis import core.Mode import interfaces.Diagnostic.{ERROR, WARNING, INFO} import dotty.tools.dotc.core.Symbols.Symbol +import diagnostic.Message import ErrorMessages._ object Reporter { - class Error(msgFn: => String, pos: SourcePosition, kind: String = "Error") extends Diagnostic(msgFn, pos, ERROR, kind) - class Warning(msgFn: => String, pos: SourcePosition, kind: String = "Warning") extends Diagnostic(msgFn, pos, WARNING, kind) - class Info(msgFn: => String, pos: SourcePosition, kind: String = "Info") extends Diagnostic(msgFn, pos, INFO, kind) + class Error(msgFn: => String, pos: SourcePosition, kind: String = "Error") + extends Message(msgFn, pos, ERROR, kind) - abstract class ConditionalWarning(msgFn: => String, pos: SourcePosition, kind: String) extends Warning(msgFn, pos, kind) { + class Warning(msgFn: => String, pos: SourcePosition, kind: String = "Warning") + extends Message(msgFn, pos, WARNING, kind) + + class Info(msgFn: => String, pos: SourcePosition, kind: String = "Info") + extends Message(msgFn, pos, INFO, kind) + + abstract class ConditionalWarning(msgFn: => String, pos: SourcePosition, kind: String) + extends Warning(msgFn, pos, kind) { def enablingOption(implicit ctx: Context): Setting[Boolean] } - class FeatureWarning(msgFn: => String, pos: SourcePosition, kind: String = "Feature Warning") extends ConditionalWarning(msgFn, pos, kind) { + class FeatureWarning(msgFn: => String, pos: SourcePosition, kind: String = "Feature Warning") + extends ConditionalWarning(msgFn, pos, kind) { def enablingOption(implicit ctx: Context) = ctx.settings.feature } - class UncheckedWarning(msgFn: => String, pos: SourcePosition, kind: String = "Unchecked Warning") extends ConditionalWarning(msgFn, pos, kind) { + class UncheckedWarning(msgFn: => String, pos: SourcePosition, kind: String = "Unchecked Warning") + extends ConditionalWarning(msgFn, pos, kind) { def enablingOption(implicit ctx: Context) = ctx.settings.unchecked } - class DeprecationWarning(msgFn: => String, pos: SourcePosition, kind: String = "Deprecation Warning") extends ConditionalWarning(msgFn, pos, kind) { + class DeprecationWarning(msgFn: => String, pos: SourcePosition, kind: String = "Deprecation Warning") + extends ConditionalWarning(msgFn, pos, kind) { def enablingOption(implicit ctx: Context) = ctx.settings.deprecation } - class MigrationWarning(msgFn: => String, pos: SourcePosition, kind: String = "Migration Warning") extends ConditionalWarning(msgFn, pos, kind) { + class MigrationWarning(msgFn: => String, pos: SourcePosition, kind: String = "Migration Warning") extends + ConditionalWarning(msgFn, pos, kind) { def enablingOption(implicit ctx: Context) = ctx.settings.migration } /** Convert a SimpleReporter into a real Reporter */ def fromSimpleReporter(simple: interfaces.SimpleReporter): Reporter = new Reporter with UniqueMessagePositions with HideNonSensicalMessages { - override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { - case d: ConditionalWarning if !d.enablingOption.value => + override def doReport(m: Message)(implicit ctx: Context): Unit = m match { + case m: ConditionalWarning if !m.enablingOption.value => case _ => - simple.report(d) + simple.report(m) } } } @@ -211,7 +222,7 @@ trait Reporting { this: Context => abstract class Reporter extends interfaces.ReporterResult { /** Report a diagnostic */ - def doReport(d: Diagnostic)(implicit ctx: Context): Unit + def doReport(d: Message)(implicit ctx: Context): Unit /** Whether very long lines can be truncated. This exists so important * debugging information (like printing the classpath) is not rendered @@ -226,7 +237,7 @@ abstract class Reporter extends interfaces.ReporterResult { finally _truncationOK = saved } - type ErrorHandler = Diagnostic => Context => Unit + type ErrorHandler = Message => Context => Unit private var incompleteHandler: ErrorHandler = d => c => report(d)(c) def withIncompleteHandler[T](handler: ErrorHandler)(op: => T): T = { val saved = incompleteHandler @@ -255,7 +266,7 @@ abstract class Reporter extends interfaces.ReporterResult { override def default(key: String) = 0 } - def report(d: Diagnostic)(implicit ctx: Context): Unit = + def report(d: Message)(implicit ctx: Context): Unit = if (!isHidden(d)) { doReport(d)(ctx.addMode(Mode.Printing)) d match { @@ -269,7 +280,7 @@ abstract class Reporter extends interfaces.ReporterResult { } } - def incomplete(d: Diagnostic)(implicit ctx: Context): Unit = + def incomplete(d: Message)(implicit ctx: Context): Unit = incompleteHandler(d)(ctx) @@ -302,7 +313,7 @@ abstract class Reporter extends interfaces.ReporterResult { } /** Should this diagnostic not be reported at all? */ - def isHidden(d: Diagnostic)(implicit ctx: Context): Boolean = ctx.mode.is(Mode.Printing) + def isHidden(m: Message)(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 b7b7c1af0312..dd5935f4c5fe 100644 --- a/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -6,24 +6,25 @@ import core.Contexts.Context import collection.mutable import Reporter.{Error, Warning} import config.Printers.typr +import diagnostic.Message /** * This class implements a Reporter that stores all messages */ class StoreReporter(outer: Reporter) extends Reporter { - private var infos: mutable.ListBuffer[Diagnostic] = null + private var infos: mutable.ListBuffer[Message] = null - def doReport(d: Diagnostic)(implicit ctx: Context): Unit = { - typr.println(s">>>> StoredError: ${d.message}") // !!! DEBUG + def doReport(m: Message)(implicit ctx: Context): Unit = { + typr.println(s">>>> StoredError: ${m.message}") // !!! DEBUG if (infos == null) infos = new mutable.ListBuffer - infos += d + infos += m } override def hasPending: Boolean = infos != null && { infos exists { - case d: Error => true - case d: Warning => true + case _: Error => true + case _: Warning => true case _ => false } } diff --git a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala index 0264530369f5..25adc6a415fc 100644 --- a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala +++ b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala @@ -4,6 +4,7 @@ package reporting import core.Contexts.Context import collection.mutable +import diagnostic.Message import Reporter._ /** @@ -11,8 +12,8 @@ import Reporter._ * info to the underlying reporter. */ class ThrowingReporter(reportInfo: Reporter) extends Reporter { - def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { - case _: Error => throw d - case _ => reportInfo.doReport(d) + def doReport(m: Message)(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 32554e6b6b16..1ef8b34474a8 100644 --- a/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala +++ b/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala @@ -5,6 +5,7 @@ package reporting import scala.collection.mutable import util.{SourcePosition, SourceFile} import core.Contexts.Context +import diagnostic.Message /** * This trait implements `isHidden` so that multiple messages per position @@ -17,12 +18,12 @@ 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(d: Diagnostic)(implicit ctx: Context): Boolean = - super.isHidden(d) || { - d.pos.exists && { - positions get (ctx.source, d.pos.point) match { - case Some(level) if level >= d.level => true - case _ => positions((ctx.source, d.pos.point)) = d.level; false + override def isHidden(m: Message)(implicit ctx: Context): Boolean = + super.isHidden(m) || { + m.pos.exists && { + positions get (ctx.source, m.pos.point) match { + case Some(level) if level >= m.level => true + case _ => positions((ctx.source, m.pos.point)) = m.level; false } } } diff --git a/src/dotty/tools/dotc/reporting/Diagnostic.scala b/src/dotty/tools/dotc/reporting/diagnostic/Diagnostic.scala similarity index 87% rename from src/dotty/tools/dotc/reporting/Diagnostic.scala rename to src/dotty/tools/dotc/reporting/diagnostic/Diagnostic.scala index d08636802e53..230c1edc0d7f 100644 --- a/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/Diagnostic.scala @@ -1,19 +1,20 @@ package dotty.tools package dotc package reporting +package diagnostic import util.SourcePosition import java.util.Optional -object Diagnostic { +object Message { val nonSensicalStartTag = "" val nonSensicalEndTag = "" } -class Diagnostic(msgFn: => String, val pos: SourcePosition, val level: Int, val kind: String) - extends Exception with interfaces.Diagnostic { - import Diagnostic._ +class Message(msgFn: => String, val pos: SourcePosition, val level: Int, val kind: String) +extends Exception with interfaces.Diagnostic { + import Message._ private var myMsg: String = null private var myIsNonSensical: Boolean = false diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index ad84ff583dd2..c8e76aa97b58 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -8,7 +8,6 @@ import Trees._ import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._ import Applications._, Implicits._, Flags._ import util.Positions._ -import reporting.Diagnostic import printing.{Showable, RefinedPrinter} import scala.collection.mutable import java.util.regex.Matcher.quoteReplacement From 2b2cfe71aacb50e91d6956f0d4ee7d555537698a Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 16 Sep 2016 13:56:59 +0200 Subject: [PATCH 15/53] Refactor common error messages to `diagnostic.basic` --- .../tools/dotc/interfaces/Diagnostic.java | 3 + .../dotc/printing/SyntaxHighlighting.scala | 2 +- .../dotc/reporting/ConsoleReporter.scala | 1 + src/dotty/tools/dotc/reporting/Reporter.scala | 34 +------- .../tools/dotc/reporting/StoreReporter.scala | 1 + .../dotc/reporting/ThrowingReporter.scala | 1 + .../{Diagnostic.scala => Message.scala} | 35 +++++--- .../dotc/reporting/diagnostic/basic.scala | 80 +++++++++++++++++++ .../dotc/reporting/diagnostic/parser.scala | 8 ++ 9 files changed, 121 insertions(+), 44 deletions(-) rename src/dotty/tools/dotc/reporting/diagnostic/{Diagnostic.scala => Message.scala} (57%) create mode 100644 src/dotty/tools/dotc/reporting/diagnostic/basic.scala create mode 100644 src/dotty/tools/dotc/reporting/diagnostic/parser.scala diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java index ed1c37b1c2c2..e62c1193e921 100644 --- a/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java @@ -20,6 +20,9 @@ public interface Diagnostic { /** @return The message to report */ String message(); + /** @return The explanation behind the message */ + String explanation(); + /** @return Level of the diagnostic, can be either ERROR, WARNING or INFO */ int level(); diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 958e71086342..d94f6796d8d7 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -12,7 +12,7 @@ object SyntaxHighlighting { implicit class SyntaxFormatting(val sc: StringContext) extends AnyVal { def hl(args: Any*)(implicit ctx: Context): String = - if (ctx.settings.color.value == "never") sc.s(args) + if (ctx.settings.color.value == "never") sc.s(args: _*) else sc.s(args.map(x => new String(apply(x.toString).toArray)): _*) } diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 1ed889683e77..4d8897820d07 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.basic._ /** * This class implements a Reporter that displays messages on a text diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index 8c9308ba5612..ccbae94bfcb8 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -4,49 +4,17 @@ package reporting import core.Contexts._ import util.{SourcePosition, NoSourcePosition} -import util.{SourceFile, NoSource} import core.Decorators.PhaseListDecorator import collection.mutable -import config.Settings.Setting import config.Printers import java.lang.System.currentTimeMillis import core.Mode -import interfaces.Diagnostic.{ERROR, WARNING, INFO} import dotty.tools.dotc.core.Symbols.Symbol import diagnostic.Message import ErrorMessages._ +import diagnostic.basic._ object Reporter { - class Error(msgFn: => String, pos: SourcePosition, kind: String = "Error") - extends Message(msgFn, pos, ERROR, kind) - - class Warning(msgFn: => String, pos: SourcePosition, kind: String = "Warning") - extends Message(msgFn, pos, WARNING, kind) - - class Info(msgFn: => String, pos: SourcePosition, kind: String = "Info") - extends Message(msgFn, pos, INFO, kind) - - abstract class ConditionalWarning(msgFn: => String, pos: SourcePosition, kind: String) - extends Warning(msgFn, pos, kind) { - def enablingOption(implicit ctx: Context): Setting[Boolean] - } - class FeatureWarning(msgFn: => String, pos: SourcePosition, kind: String = "Feature Warning") - extends ConditionalWarning(msgFn, pos, kind) { - def enablingOption(implicit ctx: Context) = ctx.settings.feature - } - class UncheckedWarning(msgFn: => String, pos: SourcePosition, kind: String = "Unchecked Warning") - extends ConditionalWarning(msgFn, pos, kind) { - def enablingOption(implicit ctx: Context) = ctx.settings.unchecked - } - class DeprecationWarning(msgFn: => String, pos: SourcePosition, kind: String = "Deprecation Warning") - extends ConditionalWarning(msgFn, pos, kind) { - def enablingOption(implicit ctx: Context) = ctx.settings.deprecation - } - class MigrationWarning(msgFn: => String, pos: SourcePosition, kind: String = "Migration Warning") extends - ConditionalWarning(msgFn, pos, kind) { - def enablingOption(implicit ctx: Context) = ctx.settings.migration - } - /** Convert a SimpleReporter into a real Reporter */ def fromSimpleReporter(simple: interfaces.SimpleReporter): Reporter = new Reporter with UniqueMessagePositions with HideNonSensicalMessages { diff --git a/src/dotty/tools/dotc/reporting/StoreReporter.scala b/src/dotty/tools/dotc/reporting/StoreReporter.scala index dd5935f4c5fe..2aa2253a077b 100644 --- a/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -7,6 +7,7 @@ import collection.mutable import Reporter.{Error, Warning} import config.Printers.typr import diagnostic.Message +import diagnostic.basic._ /** * This class implements a Reporter that stores all messages diff --git a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala index 25adc6a415fc..b08145654312 100644 --- a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala +++ b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala @@ -5,6 +5,7 @@ package reporting import core.Contexts.Context import collection.mutable import diagnostic.Message +import diagnostic.basic.Error import Reporter._ /** diff --git a/src/dotty/tools/dotc/reporting/diagnostic/Diagnostic.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala similarity index 57% rename from src/dotty/tools/dotc/reporting/diagnostic/Diagnostic.scala rename to src/dotty/tools/dotc/reporting/diagnostic/Message.scala index 230c1edc0d7f..90ebf11a20f1 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/Diagnostic.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -12,8 +12,13 @@ object Message { val nonSensicalEndTag = "" } -class Message(msgFn: => String, val pos: SourcePosition, val level: Int, val kind: String) -extends Exception with interfaces.Diagnostic { +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 @@ -27,22 +32,32 @@ extends Exception with interfaces.Diagnostic { 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 might be composed of several d"..." invocations -> nested + // nonsensical tags possible + myMsg = + myMsg + .replaceAllLiterally(nonSensicalStartTag, "") + .replaceAllLiterally(nonSensicalEndTag, "") } } myMsg } - /** 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. + /** 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 } + +object NoExplanation { + def unapply(m: Message): Option[Message] = + if (m.explanation == "") Some(m) + else None +} diff --git a/src/dotty/tools/dotc/reporting/diagnostic/basic.scala b/src/dotty/tools/dotc/reporting/diagnostic/basic.scala new file mode 100644 index 000000000000..2da986d89c22 --- /dev/null +++ b/src/dotty/tools/dotc/reporting/diagnostic/basic.scala @@ -0,0 +1,80 @@ +package dotty.tools +package dotc +package reporting +package diagnostic + +import dotc.core.Contexts.Context +import util.{SourceFile, NoSource} +import util.{SourcePosition, NoSourcePosition} +import config.Settings.Setting +import interfaces.Diagnostic.{ERROR, WARNING, INFO} + +object basic { + + class Error( + msgFn: => String, + pos: SourcePosition, + kind: String = "Error", + explanation: String = "" + ) extends Message(msgFn, pos, ERROR, kind, explanation) + + class Warning( + msgFn: => String, + pos: SourcePosition, + kind: String = "Warning", + explanation: String = "" + ) extends Message(msgFn, pos, WARNING, kind, explanation) + + class Info( + msgFn: => String, + pos: SourcePosition, + kind: String = "Info", + explanation: String = "" + ) extends Message(msgFn, pos, INFO, kind, explanation) + + abstract class ConditionalWarning( + msgFn: => String, + pos: SourcePosition, + kind: String, + explanation: String = "" + ) extends Warning(msgFn, pos, kind, explanation) { + def enablingOption(implicit ctx: Context): Setting[Boolean] + } + + class FeatureWarning( + msgFn: => String, + pos: SourcePosition, + kind: String = "Feature Warning", + explanation: String = "" + ) extends ConditionalWarning(msgFn, pos, kind, explanation) { + def enablingOption(implicit ctx: Context) = ctx.settings.feature + } + + class UncheckedWarning( + msgFn: => String, + pos: SourcePosition, + kind: String = "Unchecked Warning", + explanation: String = "" + ) extends ConditionalWarning(msgFn, pos, kind, explanation) { + def enablingOption(implicit ctx: Context) = ctx.settings.unchecked + } + + class DeprecationWarning( + msgFn: => String, + pos: SourcePosition, + kind: String = "Deprecation Warning", + explanation: String = "" + ) extends ConditionalWarning(msgFn, pos, kind, explanation) { + def enablingOption(implicit ctx: Context) = ctx.settings.deprecation + } + + class MigrationWarning( + msgFn: => String, + pos: SourcePosition, + kind: String = "Migration Warning", + explanation: String = "" + ) extends ConditionalWarning(msgFn, pos, kind, explanation) { + def enablingOption(implicit ctx: Context) = ctx.settings.migration + } + +} diff --git a/src/dotty/tools/dotc/reporting/diagnostic/parser.scala b/src/dotty/tools/dotc/reporting/diagnostic/parser.scala new file mode 100644 index 000000000000..b73e353eba79 --- /dev/null +++ b/src/dotty/tools/dotc/reporting/diagnostic/parser.scala @@ -0,0 +1,8 @@ +package dotty.tools +package dotc +package reporting +package diagnostic + +object parser { + +} From 2764609bb17dfc8691d33fcc1c70a9891af59e70 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 16 Sep 2016 18:23:11 +0200 Subject: [PATCH 16/53] Complete better structure to diagnostic messages --- .../tools/dotc/config/ScalaSettings.scala | 2 +- .../tools/dotc/parsing/JavaParsers.scala | 1 - .../tools/dotc/parsing/MarkupParsers.scala | 1 - src/dotty/tools/dotc/parsing/Parsers.scala | 13 +- .../dotc/reporting/ConsoleReporter.scala | 2 +- .../tools/dotc/reporting/ErrorMessages.scala | 117 ------------------ src/dotty/tools/dotc/reporting/Reporter.scala | 22 ++-- .../tools/dotc/reporting/StoreReporter.scala | 2 +- .../dotc/reporting/ThrowingReporter.scala | 2 +- .../dotc/reporting/diagnostic/Message.scala | 7 +- .../reporting/diagnostic/MessageCreator.scala | 62 ++++++++++ .../{basic.scala => messages.scala} | 2 +- .../dotc/reporting/diagnostic/syntax.scala | 63 ++++++++++ .../tools/dotc/reporting/diagnostic/tpe.scala | 47 +++++++ src/dotty/tools/dotc/typer/Typer.scala | 5 +- 15 files changed, 198 insertions(+), 150 deletions(-) delete mode 100644 src/dotty/tools/dotc/reporting/ErrorMessages.scala create mode 100644 src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala rename src/dotty/tools/dotc/reporting/diagnostic/{basic.scala => messages.scala} (99%) create mode 100644 src/dotty/tools/dotc/reporting/diagnostic/syntax.scala create mode 100644 src/dotty/tools/dotc/reporting/diagnostic/tpe.scala diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index a4daefcba200..872cb0667af0 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -23,7 +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 explain = 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/JavaParsers.scala b/src/dotty/tools/dotc/parsing/JavaParsers.scala index 90eb908776e7..ed7cf9e3f9cd 100644 --- a/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -30,7 +30,6 @@ import scala.reflect.internal.util.Collections._ object JavaParsers { import ast.untpd._ - import reporting.ErrorMessages.Syntax._ class JavaParser(source: SourceFile)(implicit ctx: Context) extends ParserCommon(source) { diff --git a/src/dotty/tools/dotc/parsing/MarkupParsers.scala b/src/dotty/tools/dotc/parsing/MarkupParsers.scala index ea40eb568433..f648b9e2cdeb 100644 --- a/src/dotty/tools/dotc/parsing/MarkupParsers.scala +++ b/src/dotty/tools/dotc/parsing/MarkupParsers.scala @@ -32,7 +32,6 @@ import Utility._ object MarkupParsers { import ast.untpd._ - import reporting.ErrorMessages.Syntax._ case object MissingEndTagControl extends ControlThrowable { override def getMessage = "start tag was here: " diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 48422650c34c..1b451ced7b6f 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -17,7 +17,6 @@ import ast.Trees._ import Decorators._ import StdNames._ import util.Positions._ -import reporting.ErrorMessages._ import Constants._ import ScriptParsers._ import Comments._ @@ -28,7 +27,9 @@ import rewrite.Rewrites.patch object Parsers { import ast.untpd._ - import reporting.ErrorMessages.Syntax._ + import reporting.diagnostic.MessageCreator + import MessageCreator._ + import reporting.diagnostic.syntax._ case class OpInfo(operand: Tree, operator: Name, offset: Offset) @@ -99,7 +100,7 @@ object Parsers { /** Issue an error at given offset if beyond last error offset * and update lastErrorOffset. */ - def syntaxError(expl: ErrorMessage, offset: Int = in.offset): Unit = + def syntaxError(expl: MessageCreator, offset: Int = in.offset): Unit = if (offset > lastErrorOffset) { syntaxError(expl, Position(offset)) lastErrorOffset = in.offset @@ -108,7 +109,7 @@ object Parsers { /** Unconditionally issue an error at given position, without * updating lastErrorOffset. */ - def syntaxError(expl: ErrorMessage, pos: Position): Unit = + def syntaxError(expl: MessageCreator, pos: Position): Unit = ctx.explainError(expl, source atPos pos) } @@ -215,7 +216,7 @@ object Parsers { } } - def warning(msg: ErrorMessage, offset: Int = in.offset) = + def warning(msg: MessageCreator, offset: Int = in.offset) = ctx.explainWarning(msg, source atPos Position(offset)) def deprecationWarning(msg: String, offset: Int = in.offset) = @@ -1016,7 +1017,7 @@ object Parsers { handler match { case Block(Nil, EmptyTree) => - syntaxError(EmptyCatchBlock(body), handler.pos) + syntaxError(new EmptyCatchBlock(body), handler.pos) case _ => } diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 4d8897820d07..3a4f60aebbde 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -9,7 +9,7 @@ import Reporter._ import java.io.{ BufferedReader, IOException, PrintWriter } import scala.reflect.internal.util._ import diagnostic.Message -import diagnostic.basic._ +import diagnostic.messages._ /** * This class implements a Reporter that displays messages on a text diff --git a/src/dotty/tools/dotc/reporting/ErrorMessages.scala b/src/dotty/tools/dotc/reporting/ErrorMessages.scala deleted file mode 100644 index 2b4c0db7e9b3..000000000000 --- a/src/dotty/tools/dotc/reporting/ErrorMessages.scala +++ /dev/null @@ -1,117 +0,0 @@ -package dotty.tools -package dotc -package reporting - -import dotc.core._ -import Contexts.Context, Decorators._, Symbols._ -import dotc.printing.SyntaxHighlighting._ -import util.{SourcePosition, NoSourcePosition} - -object ErrorMessages { - import dotc.ast.Trees._ - import dotc.ast.untpd - - implicit class ShouldExplainCtx(val c: Context) extends AnyVal { - def shouldExplain(expl: ErrorMessage): Boolean = { - implicit val ctx = c - expl match { - case _: NoExplanation => false - case expl if ctx.settings.explainerrors.value => true - case _ => false - } - } - } - - trait ErrorMessage { - def kind: String - def msg: String - def explanation: String - } - - case class NoExplanation(msg: String)(implicit val kind: String) extends ErrorMessage { - val explanation = "" - } - - object Syntax { - implicit val kind: String = "Syntax" - implicit def stringToErrorMessage(s: String): ErrorMessage = NoExplanation(s) - - abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree)(implicit ctx: Context, val kind: String) extends ErrorMessage { - 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"""|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: - | - |$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 - } - } - - case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context, override val kind: String) - extends EmptyCatchOrFinallyBlock(tryBody) { - val msg = - hl"""The ${"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, override val kind: String) - extends EmptyCatchOrFinallyBlock(tryBody) { - val msg = - hl"""A ${"try"} without ${"catch"} or ${"finally"} is equivalent to putting its body in a block; no exceptions are handled.""" - } - } - - object Type { - implicit val kind: String = "Type" - implicit def stringToErrorMessage(s: String): ErrorMessage = NoExplanation(s) - - case class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context, val kind: String) extends ErrorMessage { - 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"""|Explanation - |=========== - |For each ${"case"} bound variable names have to be unique. In: - | - |$caseDef - | - |`${bind.name}` is not unique. Rename one of the bound variables!""".stripMargin - } - } - } -} diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index ccbae94bfcb8..5c3dcccb7bf1 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -10,9 +10,9 @@ import config.Printers import java.lang.System.currentTimeMillis import core.Mode import dotty.tools.dotc.core.Symbols.Symbol -import diagnostic.Message -import ErrorMessages._ -import diagnostic.basic._ +import diagnostic.messages._ +import diagnostic._ +import MessageCreator._ object Reporter { /** Convert a SimpleReporter into a real Reporter */ @@ -75,10 +75,10 @@ trait Reporting { this: Context => def warning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = reporter.report(new Warning(msg, pos)) - def explainWarning(err: => ErrorMessage, pos: SourcePosition = NoSourcePosition): Unit = { - reporter.report(new Warning(err.msg, pos, s"${err.kind} warning")) - if (this.shouldExplain(err)) - reporter.report(new Info(err.explanation, NoSourcePosition)) + def explainWarning(msg: => MessageCreator, pos: SourcePosition = NoSourcePosition): Unit = { + reporter.report(msg.warning(pos)) + if (this.shouldExplain(msg)) + reporter.report(new Info(msg.explanation, NoSourcePosition)) } def strictWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = @@ -90,10 +90,10 @@ trait Reporting { this: Context => reporter.report(new Error(msg, pos)) } - def explainError(err: => ErrorMessage, pos: SourcePosition = NoSourcePosition): Unit = { - reporter.report(new Error(err.msg, pos, s"${err.kind} error")) - if (this.shouldExplain(err)) - reporter.report(new Info(err.explanation, NoSourcePosition)) + def explainError(msg: => MessageCreator, pos: SourcePosition = NoSourcePosition): Unit = { + reporter.report(msg.error(pos)) + if (this.shouldExplain(msg)) + reporter.report(new Info(msg.explanation, NoSourcePosition)) } def errorOrMigrationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = diff --git a/src/dotty/tools/dotc/reporting/StoreReporter.scala b/src/dotty/tools/dotc/reporting/StoreReporter.scala index 2aa2253a077b..fa4fd991fdf9 100644 --- a/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -7,7 +7,7 @@ import collection.mutable import Reporter.{Error, Warning} import config.Printers.typr import diagnostic.Message -import diagnostic.basic._ +import diagnostic.messages._ /** * This class implements a Reporter that stores all messages diff --git a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala index b08145654312..f4e3b472d83b 100644 --- a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala +++ b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala @@ -5,7 +5,7 @@ package reporting import core.Contexts.Context import collection.mutable import diagnostic.Message -import diagnostic.basic.Error +import diagnostic.messages.Error import Reporter._ /** diff --git a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index 90ebf11a20f1..d7cfa2e2b3e4 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -4,6 +4,7 @@ package reporting package diagnostic import util.SourcePosition +import core.Contexts.Context import java.util.Optional @@ -55,9 +56,3 @@ class Message( 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/MessageCreator.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala new file mode 100644 index 000000000000..d74099f4f92e --- /dev/null +++ b/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala @@ -0,0 +1,62 @@ +package dotty.tools +package dotc +package reporting +package diagnostic + +import util.{SourcePosition, NoSourcePosition} +import core.Contexts.Context + +object MessageCreator { + implicit class DiagnosticContext(val c: Context) extends AnyVal { + def shouldExplain(msg: MessageCreator): Boolean = { + implicit val ctx: Context = c + msg match { + case NoExplanation(_) => false + case _ => ctx.settings.explain.value + } + } + } + + implicit def toNoExplanation(str: String) = + 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/basic.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala similarity index 99% rename from src/dotty/tools/dotc/reporting/diagnostic/basic.scala rename to src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 2da986d89c22..382e9a29502c 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/basic.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -9,7 +9,7 @@ import util.{SourcePosition, NoSourcePosition} import config.Settings.Setting import interfaces.Diagnostic.{ERROR, WARNING, INFO} -object basic { +object messages { class Error( msgFn: => String, diff --git a/src/dotty/tools/dotc/reporting/diagnostic/syntax.scala b/src/dotty/tools/dotc/reporting/diagnostic/syntax.scala new file mode 100644 index 000000000000..675cacbedaf4 --- /dev/null +++ b/src/dotty/tools/dotc/reporting/diagnostic/syntax.scala @@ -0,0 +1,63 @@ +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"""|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: + | + |$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 new file mode 100644 index 000000000000..4aa24c440949 --- /dev/null +++ b/src/dotty/tools/dotc/reporting/diagnostic/tpe.scala @@ -0,0 +1,47 @@ +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 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"""|Explanation + |=========== + |For each ${"case"} bound variable names have to be unique. In: + | + |$caseDef + | + |`${bind.name}` is not unique. Rename one of the bound variables!""".stripMargin + } + } +} diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 6fddd928bde9..32d5e0d96a4c 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -38,7 +38,6 @@ import NavigateAST._ import transform.SymUtils._ import language.implicitConversions import printing.SyntaxHighlighting._ -import reporting.ErrorMessages._ object Typer { @@ -66,7 +65,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit import tpd.{cpy => _, _} import untpd.cpy import Dynamic.isDynamicMethod - import reporting.ErrorMessages.Type._ + import reporting.diagnostic.tpe._ /** A temporary data item valid for a single typed ident: * The set of all root import symbols that have been @@ -848,7 +847,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(DuplicateBind(b, tree), b.pos) + else ctx.explainError(new DuplicateBind(b, tree), b.pos) b.symbol.info = elimWildcardSym(b.symbol.info) b case t => t From 15dfb56d63a893c5c1e34a8566004085a617c28b Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 16 Sep 2016 22:05:14 +0200 Subject: [PATCH 17/53] Add coloring util --- .../tools/dotc/printing/Highlighting.scala | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/dotty/tools/dotc/printing/Highlighting.scala diff --git a/src/dotty/tools/dotc/printing/Highlighting.scala b/src/dotty/tools/dotc/printing/Highlighting.scala new file mode 100644 index 000000000000..a496976a1e6a --- /dev/null +++ b/src/dotty/tools/dotc/printing/Highlighting.scala @@ -0,0 +1,60 @@ +package dotty.tools +package dotc +package printing + +import scala.collection.mutable + +object Highlighting { + + implicit def colorToString(c: Color): String = c.toString + implicit def cbufToString(cb: ColorBuffer): String = cb.toString + + abstract class Color(private val color: String) { + def text: String + + override def toString = color + text + Console.RESET + + def +(other: Color): ColorBuffer = + new ColorBuffer(this) + other + + def +(other: String): ColorBuffer = + new ColorBuffer(this) + other + } + + case class ColorBuffer(color: Color) { + val buffer = new mutable.ListBuffer[String] + + buffer += color.toString + + def +(color: Color): ColorBuffer = { + buffer += color.toString + this + } + + def +(str: String): ColorBuffer = { + buffer += str + this + } + + override def toString = + buffer.mkString + } + + case class Red(text: String) extends Color(Console.RED) + case class Blue(text: String) extends Color(Console.BLUE) + case class Cyan(text: String) extends Color(Console.CYAN) + case class Black(text: String) extends Color(Console.BLACK) + case class Green(text: String) extends Color(Console.GREEN) + case class White(text: String) extends Color(Console.WHITE) + case class Yellow(text: String) extends Color(Console.YELLOW) + case class Magenta(text: String) extends Color(Console.MAGENTA) + + case class RedB(text: String) extends Color(Console.RED_B) + case class BlueB(text: String) extends Color(Console.BLUE_B) + case class CyanB(text: String) extends Color(Console.CYAN_B) + case class BlackB(text: String) extends Color(Console.BLACK_B) + case class GreenB(text: String) extends Color(Console.GREEN_B) + case class WhiteB(text: String) extends Color(Console.WHITE_B) + case class YellowB(text: String) extends Color(Console.YELLOW_B) + case class MagentaB(text: String) extends Color(Console.MAGENTA_B) +} From 30b54109e3f535b417249057c2a548808082e06b Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 16 Sep 2016 22:07:36 +0200 Subject: [PATCH 18/53] Factor out explanation header to Reporter --- .../dotc/reporting/ConsoleReporter.scala | 36 +++++++++++++------ .../dotc/reporting/FancyConsoleReporter.scala | 31 +++++++++++++--- src/dotty/tools/dotc/reporting/Reporter.scala | 12 ++----- .../dotc/reporting/diagnostic/Message.scala | 10 ++++++ .../reporting/diagnostic/MessageCreator.scala | 10 ------ .../dotc/reporting/diagnostic/syntax.scala | 4 +-- .../tools/dotc/reporting/diagnostic/tpe.scala | 16 ++++++--- 7 files changed, 77 insertions(+), 42 deletions(-) diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 3a4f60aebbde..a0081332801a 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -20,6 +20,8 @@ class ConsoleReporter( writer: PrintWriter = new PrintWriter(Console.err, true)) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages { + import Message._ + /** maximal number of error messages to be printed */ protected def ErrorLimit = 100 @@ -42,17 +44,29 @@ class ConsoleReporter( } } - override def doReport(m: Message)(implicit ctx: Context): Unit = m match { - case m: Error => - printMessageAndPos(m.message, 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) - case m: Warning => - printMessageAndPos(m.message, m.pos, m.kind) - case _ => - printMessageAndPos(m.message, m.pos, m.kind) + def printExplanation(m: Message): Unit = + printMessage( + s"""| + |Explanation + |=========== + |${m.explanation}""".stripMargin + ) + + override def doReport(m: Message)(implicit ctx: Context): Unit = { + m match { + case m: Error => + printMessageAndPos(m.message, 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) + case m: Warning => + printMessageAndPos(m.message, m.pos, m.kind) + case _ => + printMessageAndPos(m.message, m.pos, m.kind) + } + + if (ctx.shouldExplain(m)) printExplanation(m) } def displayPrompt(): Unit = { diff --git a/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala index 80f15a4b0c27..3fc9ab4733b2 100644 --- a/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala @@ -9,6 +9,8 @@ import Reporter._ import java.io.{ BufferedReader, IOException, PrintWriter } import scala.reflect.internal.util._ import printing.SyntaxHighlighting._ +import printing.Highlighting._ +import diagnostic.Message /** * This class implements a more Fancy version (with colors!) of the regular @@ -56,7 +58,7 @@ class FancyConsoleReporter( } def posStr(pos: SourcePosition, kind: String)(implicit ctx: Context) = - if (pos.exists) { + if (pos.exists) Blue({ val file = pos.source.file.toString val outer = if (pos.outer.exists) { @@ -64,12 +66,11 @@ class FancyConsoleReporter( printStr(pos.outer) + "\n" + "-" * ctx.settings.pageWidth.value } else "" - val prefix = s"${Console.CYAN}-- $kind: $file " + val prefix = s"-- $kind: $file " prefix + ("-" * math.max(ctx.settings.pageWidth.value - prefix.replaceAll("\u001B\\[[;\\d]*m", "").length, 0)) + - "\n" + outer + NoColor - } - else "" + "\n" + outer + }).toString else "" /** Prints the message with the given position indication. */ override def printMessageAndPos(msg: String, pos: SourcePosition, kind: String = "")(implicit ctx: Context): Unit = { @@ -82,4 +83,24 @@ class FancyConsoleReporter( printMessage(List(src, marker, err).mkString("\n")) } else printMessage(msg) } + + override def printExplanation(m: Message): Unit = + printMessage( + s"""| + |${Blue("Explanation")} + |${Blue("===========")} + |${m.explanation}""".stripMargin + ) + + + override def summary: String = { + val b = new mutable.ListBuffer[String] + if (warningCount > 0) + b += countString(warningCount, Yellow("warning")) + " found" + if (errorCount > 0) + b += countString(errorCount, Red("error")) + " found" + for ((settingName, count) <- unreportedWarnings) + b += s"there were $count ${settingName.tail} ${Yellow("warning(s)")}; re-run with $settingName for details" + b.mkString("\n") + } } diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index 5c3dcccb7bf1..fe226b28494e 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -75,11 +75,8 @@ trait Reporting { this: Context => def warning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = reporter.report(new Warning(msg, pos)) - def explainWarning(msg: => MessageCreator, pos: SourcePosition = NoSourcePosition): Unit = { + def explainWarning(msg: => MessageCreator, pos: SourcePosition = NoSourcePosition): Unit = reporter.report(msg.warning(pos)) - if (this.shouldExplain(msg)) - reporter.report(new Info(msg.explanation, NoSourcePosition)) - } def strictWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = if (this.settings.strict.value) error(msg, pos) @@ -90,11 +87,8 @@ trait Reporting { this: Context => reporter.report(new Error(msg, pos)) } - def explainError(msg: => MessageCreator, pos: SourcePosition = NoSourcePosition): Unit = { + def explainError(msg: => MessageCreator, pos: SourcePosition = NoSourcePosition): Unit = reporter.report(msg.error(pos)) - if (this.shouldExplain(msg)) - reporter.report(new Info(msg.explanation, NoSourcePosition)) - } def errorOrMigrationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = if (ctx.scala2Mode) migrationWarning(msg, pos) else error(msg, pos) @@ -271,7 +265,7 @@ abstract class Reporter extends interfaces.ReporterResult { } /** Returns a string meaning "n elements". */ - private def countString(n: Int, elements: String): String = n match { + protected def countString(n: Int, elements: String): String = n match { case 0 => "no " + elements + "s" case 1 => "one " + elements case 2 => "two " + elements + "s" diff --git a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index d7cfa2e2b3e4..b3c19820fa08 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -11,6 +11,16 @@ 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 + } + } + } } class Message( diff --git a/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala index d74099f4f92e..4be325b6383f 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala @@ -7,16 +7,6 @@ import util.{SourcePosition, NoSourcePosition} import core.Contexts.Context object MessageCreator { - implicit class DiagnosticContext(val c: Context) extends AnyVal { - def shouldExplain(msg: MessageCreator): Boolean = { - implicit val ctx: Context = c - msg match { - case NoExplanation(_) => false - case _ => ctx.settings.explain.value - } - } - } - implicit def toNoExplanation(str: String) = new NoExplanation(str) } diff --git a/src/dotty/tools/dotc/reporting/diagnostic/syntax.scala b/src/dotty/tools/dotc/reporting/diagnostic/syntax.scala index 675cacbedaf4..b9a662c7ddf5 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/syntax.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/syntax.scala @@ -29,9 +29,7 @@ object syntax { | // perform your cleanup here! |}""".stripMargin - hl"""|Explanation: - |============ - |A ${"try"} expression should be followed by some mechanism to handle any exceptions + 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: | diff --git a/src/dotty/tools/dotc/reporting/diagnostic/tpe.scala b/src/dotty/tools/dotc/reporting/diagnostic/tpe.scala index 4aa24c440949..ee221f80d9c9 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/tpe.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/tpe.scala @@ -4,7 +4,7 @@ package reporting package diagnostic import dotc.core._ -import Contexts.Context, Decorators._, Symbols._ +import Contexts.Context, Decorators._, Symbols._, Names._ import dotc.printing.SyntaxHighlighting._ import util.{SourcePosition, NoSourcePosition} @@ -35,13 +35,21 @@ object tpe { val caseDef = s"case $pat$guard => $body" - hl"""|Explanation - |=========== - |For each ${"case"} bound variable names have to be unique. In: + 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 + } + } } From 5c243778279d8b258d7b1d34dd5146dff07eb437 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 16 Sep 2016 22:08:15 +0200 Subject: [PATCH 19/53] Add `MissingIdent` message to `Typer` --- src/dotty/tools/dotc/typer/Typer.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 32d5e0d96a4c..b29254b8924f 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -65,6 +65,7 @@ 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._ /** A temporary data item valid for a single typed ident: @@ -98,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: => String, pos: Position) = ctx.error(msg, pos) + def error(msg: => MessageCreator, pos: Position) = ctx.explainError(msg, pos) /** Is this import a root import that has been shadowed by an explicit * import in the same program? @@ -330,7 +331,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (rawType.exists) ensureAccessible(rawType, superAccess = false, tree.pos) else { - error(em"not found: $kind$name", tree.pos) + error(new MissingIdent(tree, kind, name), tree.pos) ErrorType } From 787a2ceec02fe07fbb9efee673d3abb7cac2969e Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 16 Sep 2016 22:28:27 +0200 Subject: [PATCH 20/53] Add modifiers to highlighting --- src/dotty/tools/dotc/Compiler.scala | 4 -- src/dotty/tools/dotc/Driver.scala | 6 +- .../tools/dotc/printing/Highlighting.scala | 70 +++++++++++-------- .../dotc/reporting/ConsoleReporter.scala | 2 +- .../reporting/diagnostic/MessageCreator.scala | 2 +- .../tools/dotc/typer/ErrorReporting.scala | 6 +- test/dotc/tests.scala | 3 +- test/test/CompilerTest.scala | 11 +-- test/test/OtherEntryPointsTest.scala | 3 +- tests/repl/errmsgs.check | 48 ++++++------- tests/repl/imports.check | 12 ++-- 11 files changed, 89 insertions(+), 78 deletions(-) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index d6e3cc89b0b5..ea6254f5b802 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -140,10 +140,6 @@ class Compiler { .setTyper(new Typer) .setMode(Mode.ImplicitsEnabled) .setTyperState(new MutableTyperState(ctx.typerState, ctx.typerState.reporter, isCommittable = true)) - .setReporter( - if (ctx.settings.color.value == "never") new ConsoleReporter() - else new FancyConsoleReporter() - ) ctx.initialize()(start) // re-initialize the base context with start def addImport(ctx: Context, refFn: () => TermRef) = ctx.fresh.setImportInfo(ImportInfo.rootImport(refFn)(ctx)) diff --git a/src/dotty/tools/dotc/Driver.scala b/src/dotty/tools/dotc/Driver.scala index f54a23ad2451..b2dc18d2a35e 100644 --- a/src/dotty/tools/dotc/Driver.scala +++ b/src/dotty/tools/dotc/Driver.scala @@ -22,7 +22,11 @@ abstract class Driver extends DotClass { protected def doCompile(compiler: Compiler, fileNames: List[String])(implicit ctx: Context): Reporter = if (fileNames.nonEmpty) try { - val run = compiler.newRun + val fresh = ctx.fresh.setReporter { + if (ctx.settings.color.value == "never") new ConsoleReporter() + else new FancyConsoleReporter() + } + val run = compiler.newRun(fresh) run.compile(fileNames) run.printSummary() } diff --git a/src/dotty/tools/dotc/printing/Highlighting.scala b/src/dotty/tools/dotc/printing/Highlighting.scala index a496976a1e6a..75f83bf29cd5 100644 --- a/src/dotty/tools/dotc/printing/Highlighting.scala +++ b/src/dotty/tools/dotc/printing/Highlighting.scala @@ -6,33 +6,38 @@ import scala.collection.mutable object Highlighting { - implicit def colorToString(c: Color): String = c.toString - implicit def cbufToString(cb: ColorBuffer): String = cb.toString + implicit def highlightToString(h: Highlight): String = h.toString + implicit def hbufToString(hb: HighlightBuffer): String = hb.toString - abstract class Color(private val color: String) { + abstract class Highlight(private val highlight: String) { def text: String - override def toString = color + text + Console.RESET + override def toString = highlight + text + Console.RESET - def +(other: Color): ColorBuffer = - new ColorBuffer(this) + other + def +(other: Highlight): HighlightBuffer = + new HighlightBuffer(this) + other - def +(other: String): ColorBuffer = - new ColorBuffer(this) + other + def +(other: String): HighlightBuffer = + new HighlightBuffer(this) + other } - case class ColorBuffer(color: Color) { + abstract class Modifier(private val mod: String, text: String) extends Highlight(Console.RESET) { + override def toString = + mod + super.toString + } + + case class HighlightBuffer(hl: Highlight) { val buffer = new mutable.ListBuffer[String] - buffer += color.toString + buffer += hl.toString - def +(color: Color): ColorBuffer = { - buffer += color.toString + def +(other: Highlight): HighlightBuffer = { + buffer += other.toString this } - def +(str: String): ColorBuffer = { - buffer += str + def +(other: String): HighlightBuffer = { + buffer += other this } @@ -40,21 +45,24 @@ object Highlighting { buffer.mkString } - case class Red(text: String) extends Color(Console.RED) - case class Blue(text: String) extends Color(Console.BLUE) - case class Cyan(text: String) extends Color(Console.CYAN) - case class Black(text: String) extends Color(Console.BLACK) - case class Green(text: String) extends Color(Console.GREEN) - case class White(text: String) extends Color(Console.WHITE) - case class Yellow(text: String) extends Color(Console.YELLOW) - case class Magenta(text: String) extends Color(Console.MAGENTA) - - case class RedB(text: String) extends Color(Console.RED_B) - case class BlueB(text: String) extends Color(Console.BLUE_B) - case class CyanB(text: String) extends Color(Console.CYAN_B) - case class BlackB(text: String) extends Color(Console.BLACK_B) - case class GreenB(text: String) extends Color(Console.GREEN_B) - case class WhiteB(text: String) extends Color(Console.WHITE_B) - case class YellowB(text: String) extends Color(Console.YELLOW_B) - case class MagentaB(text: String) extends Color(Console.MAGENTA_B) + case class Red(text: String) extends Highlight(Console.RED) + case class Blue(text: String) extends Highlight(Console.BLUE) + case class Cyan(text: String) extends Highlight(Console.CYAN) + case class Black(text: String) extends Highlight(Console.BLACK) + case class Green(text: String) extends Highlight(Console.GREEN) + case class White(text: String) extends Highlight(Console.WHITE) + case class Yellow(text: String) extends Highlight(Console.YELLOW) + case class Magenta(text: String) extends Highlight(Console.MAGENTA) + + case class RedB(text: String) extends Highlight(Console.RED_B) + case class BlueB(text: String) extends Highlight(Console.BLUE_B) + case class CyanB(text: String) extends Highlight(Console.CYAN_B) + case class BlackB(text: String) extends Highlight(Console.BLACK_B) + case class GreenB(text: String) extends Highlight(Console.GREEN_B) + case class WhiteB(text: String) extends Highlight(Console.WHITE_B) + case class YellowB(text: String) extends Highlight(Console.YELLOW_B) + case class MagentaB(text: String) extends Highlight(Console.MAGENTA_B) + + case class Bold(text: String) extends Modifier(Console.BOLD, text) + case class Underlined(text: String) extends Modifier(Console.UNDERLINED, text) } diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index a0081332801a..b532d05c22e7 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -37,7 +37,7 @@ class ConsoleReporter( /** Prints the message with the given position indication. */ def printMessageAndPos(msg: String, pos: SourcePosition, kind: String = "")(implicit ctx: Context): Unit = { val posStr = if (pos.exists) s"$pos: " else "" - printMessage(posStr + kind + msg) + printMessage(s"${posStr}$kind: $msg") if (pos.exists) { printSourceLine(pos) printColumnMarker(pos) diff --git a/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala index 4be325b6383f..99ccca4cc7b6 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/MessageCreator.scala @@ -7,7 +7,7 @@ import util.{SourcePosition, NoSourcePosition} import core.Contexts.Context object MessageCreator { - implicit def toNoExplanation(str: String) = + implicit def toNoExplanation(str: String): MessageCreator = new NoExplanation(str) } diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index c8e76aa97b58..43c09351050b 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -131,9 +131,9 @@ object ErrorReporting { val found1 = reported(found) reported.setVariance(-1) val expected1 = reported(expected) - ex"""type mismatch: - | found : $found1 - | required: $expected1""" + whyNoMatchStr(found, expected) + ex"""|type mismatch: + |found: $found1 + |required: $expected1""".stripMargin + whyNoMatchStr(found, expected) } /** Format `raw` implicitNotFound argument, replacing all diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index f161fefe3b4b..39e5f5ead4c4 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -23,7 +23,8 @@ class tests extends CompilerTest { val defaultOutputDir = "./out/" implicit val defaultOptions = noCheckOptions ++ List( - "-Yno-deep-subtypes", "-Yno-double-bindings", "-Yforce-sbt-phases", "-d", defaultOutputDir) ++ { + "-Yno-deep-subtypes", "-Yno-double-bindings", "-Yforce-sbt-phases", "-color:never", + "-d", defaultOutputDir) ++ { if (isRunByJenkins) List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") // should be Ycheck:all, but #725 else List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") } diff --git a/test/test/CompilerTest.scala b/test/test/CompilerTest.scala index dea6a30b1432..942948f085a1 100644 --- a/test/test/CompilerTest.scala +++ b/test/test/CompilerTest.scala @@ -5,6 +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 dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.config.CompilerCommand import dotty.tools.io.PlainFile @@ -237,13 +238,13 @@ abstract class CompilerTest { val storeReporter = new Reporter with UniqueMessagePositions with HideNonSensicalMessages { private val consoleReporter = new ConsoleReporter() private val innerStoreReporter = new StoreReporter(consoleReporter) - def doReport(d: Diagnostic)(implicit ctx: Context): Unit = { - if (d.level == ERROR) { + def doReport(m: Message)(implicit ctx: Context): Unit = { + if (m.level == ERROR) { innerStoreReporter.flush() - consoleReporter.doReport(d) + consoleReporter.doReport(m) } - else if (errorCount > 0) consoleReporter.doReport(d) - else innerStoreReporter.doReport(d) + else if (errorCount > 0) consoleReporter.doReport(m) + else innerStoreReporter.doReport(m) } } val reporter = processor.process(allArgs, storeReporter) diff --git a/test/test/OtherEntryPointsTest.scala b/test/test/OtherEntryPointsTest.scala index 5f8681d3864c..824034055632 100644 --- a/test/test/OtherEntryPointsTest.scala +++ b/test/test/OtherEntryPointsTest.scala @@ -5,6 +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.core.Contexts._ import java.io.File import scala.collection.mutable.ListBuffer @@ -50,7 +51,7 @@ class OtherEntryPointsTest { private class CustomReporter extends Reporter with UniqueMessagePositions with HideNonSensicalMessages { - def doReport(d: Diagnostic)(implicit ctx: Context): Unit = { + def doReport(m: Message)(implicit ctx: Context): Unit = { } } diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check index d8e863a28612..e32e72d1f7b0 100644 --- a/tests/repl/errmsgs.check +++ b/tests/repl/errmsgs.check @@ -1,41 +1,41 @@ scala> class Inv[T](x: T) defined class Inv scala> val x: List[String] = List(1) -:4: error: type mismatch: - found : Int(1) - required: String +:4: Error: type mismatch: +found: Int(1) +required: String val x: List[String] = List(1) ^ scala> val y: List[List[String]] = List(List(1)) -:4: error: type mismatch: - found : Int(1) - required: String +:4: Error: type mismatch: +found: Int(1) +required: String val y: List[List[String]] = List(List(1)) ^ scala> val z: (List[String], List[Int]) = (List(1), List("a")) -:4: error: type mismatch: - found : Int(1) - required: String +:4: Error: type mismatch: +found: Int(1) +required: String val z: (List[String], List[Int]) = (List(1), List("a")) ^ -:4: error: type mismatch: - found : String("a") - required: Int +:4: Error: type mismatch: +found: String("a") +required: Int val z: (List[String], List[Int]) = (List(1), List("a")) ^ scala> val a: Inv[String] = new Inv(new Inv(1)) -:5: error: type mismatch: - found : Inv[T] - required: String +:5: Error: type mismatch: +found: Inv[T] +required: String where T is a type variable with constraint >: Int(1) val a: Inv[String] = new Inv(new Inv(1)) ^ scala> val b: Inv[String] = new Inv(1) -:5: error: type mismatch: - found : Int(1) - required: String +:5: Error: type mismatch: +found: Int(1) +required: String val b: Inv[String] = new Inv(1) ^ scala> abstract class C { @@ -53,18 +53,18 @@ scala> abstract class C { } } } -:9: error: type mismatch: - found : C.this.T(C.this.x) - required: T' +:9: Error: type mismatch: +found: C.this.T(C.this.x) +required: T' where T is a type in class C T' is a type in the initalizer of value s which is an alias of String var y: T = x ^ -:13: error: type mismatch: - found : T(y) - required: T' +:13: Error: type mismatch: +found: T(y) +required: T' where T is a type in the initalizer of value s which is an alias of String T' is a type in method f which is an alias of Int diff --git a/tests/repl/imports.check b/tests/repl/imports.check index 3a7e9341efa2..50b7a93d7f03 100644 --- a/tests/repl/imports.check +++ b/tests/repl/imports.check @@ -7,14 +7,14 @@ defined module o scala> import o._ import o._ scala> buf += xs -:11: error: type mismatch: - found : scala.collection.immutable.List[Int](o.xs) - required: String +:11: Error: type mismatch: +found: scala.collection.immutable.List[Int](o.xs) +required: String buf += xs ^ -:11: error: type mismatch: - found : String - required: scala.collection.mutable.ListBuffer[Int] +:11: Error: type mismatch: +found: String +required: scala.collection.mutable.ListBuffer[Int] buf += xs ^ scala> buf ++= xs From 628b7f317756ce2c366359a7399b8dda9d0190b7 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Sun, 18 Sep 2016 08:45:29 +0200 Subject: [PATCH 21/53] Make `FancyConsoleReporter` and `Highlighting` obey color setting Fancy console reporter and the string interpolator for highlighting now obey the color setting - this means that the next step towards unifying the reporters is to make sure the tests work with `FancyConsoleReporter` under the `-color:never` flag. --- src/dotty/tools/dotc/Driver.scala | 6 +- .../tools/dotc/printing/Highlighting.scala | 33 +++++++---- .../dotc/printing/SyntaxHighlighting.scala | 13 ++++- .../dotc/reporting/ConsoleReporter.scala | 2 +- .../dotc/reporting/FancyConsoleReporter.scala | 55 +++++++++---------- src/dotty/tools/dotc/reporting/Reporter.scala | 2 +- 6 files changed, 64 insertions(+), 47 deletions(-) diff --git a/src/dotty/tools/dotc/Driver.scala b/src/dotty/tools/dotc/Driver.scala index b2dc18d2a35e..f54a23ad2451 100644 --- a/src/dotty/tools/dotc/Driver.scala +++ b/src/dotty/tools/dotc/Driver.scala @@ -22,11 +22,7 @@ abstract class Driver extends DotClass { protected def doCompile(compiler: Compiler, fileNames: List[String])(implicit ctx: Context): Reporter = if (fileNames.nonEmpty) try { - val fresh = ctx.fresh.setReporter { - if (ctx.settings.color.value == "never") new ConsoleReporter() - else new FancyConsoleReporter() - } - val run = compiler.newRun(fresh) + val run = compiler.newRun run.compile(fileNames) run.printSummary() } diff --git a/src/dotty/tools/dotc/printing/Highlighting.scala b/src/dotty/tools/dotc/printing/Highlighting.scala index 75f83bf29cd5..13e55722fa34 100644 --- a/src/dotty/tools/dotc/printing/Highlighting.scala +++ b/src/dotty/tools/dotc/printing/Highlighting.scala @@ -3,36 +3,47 @@ package dotc package printing import scala.collection.mutable +import core.Contexts.Context object Highlighting { - implicit def highlightToString(h: Highlight): String = h.toString - implicit def hbufToString(hb: HighlightBuffer): String = hb.toString + implicit def highlightShow(h: Highlight)(implicit ctx: Context): String = + h.show + implicit def highlightToString(h: Highlight): String = + h.toString + implicit def hbufToString(hb: HighlightBuffer): String = + hb.toString abstract class Highlight(private val highlight: String) { def text: String - override def toString = highlight + text + Console.RESET + def show(implicit ctx: Context) = + if (ctx.settings.color.value == "never") text + else highlight + text + Console.RESET - def +(other: Highlight): HighlightBuffer = + override def toString = + highlight + text + Console.RESET + + def +(other: Highlight)(implicit ctx: Context): HighlightBuffer = new HighlightBuffer(this) + other - def +(other: String): HighlightBuffer = + def +(other: String)(implicit ctx: Context): HighlightBuffer = new HighlightBuffer(this) + other } abstract class Modifier(private val mod: String, text: String) extends Highlight(Console.RESET) { - override def toString = - mod + super.toString + override def show(implicit ctx: Context) = + if (ctx.settings.color.value == "never") "" + else mod + super.show } - case class HighlightBuffer(hl: Highlight) { + case class HighlightBuffer(hl: Highlight)(implicit ctx: Context) { val buffer = new mutable.ListBuffer[String] - buffer += hl.toString + buffer += hl.show def +(other: Highlight): HighlightBuffer = { - buffer += other.toString + buffer += other.show this } @@ -45,6 +56,8 @@ object Highlighting { buffer.mkString } + case class NoColor(text: String) extends Highlight(Console.RESET) + case class Red(text: String) extends Highlight(Console.RED) case class Blue(text: String) extends Highlight(Console.BLUE) case class Cyan(text: String) extends Highlight(Console.CYAN) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index d94f6796d8d7..95e59ccf347e 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -6,14 +6,23 @@ import parsing.Tokens._ import scala.annotation.switch import scala.collection.mutable.StringBuilder import core.Contexts.Context +import Highlighting.{Highlight, HighlightBuffer} /** This object provides functions for syntax highlighting in the REPL */ object SyntaxHighlighting { implicit class SyntaxFormatting(val sc: StringContext) extends AnyVal { def hl(args: Any*)(implicit ctx: Context): String = - if (ctx.settings.color.value == "never") sc.s(args: _*) - else sc.s(args.map(x => new String(apply(x.toString).toArray)): _*) + sc.s(args.map ({ + case hl: Highlight => + hl.show + case hb: HighlightBuffer => + hb.toString + case x if ctx.settings.color.value != "never" => + new String(apply(x.toString).toArray) + case x => + x.toString + }): _*) } val NoColor = Console.RESET diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index b532d05c22e7..1ae6a1135035 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -44,7 +44,7 @@ class ConsoleReporter( } } - def printExplanation(m: Message): Unit = + def printExplanation(m: Message)(implicit ctx: Context): Unit = printMessage( s"""| |Explanation diff --git a/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala index 3fc9ab4733b2..d69396f5f942 100644 --- a/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala @@ -21,29 +21,29 @@ class FancyConsoleReporter( writer: PrintWriter = new PrintWriter(Console.err, true) ) extends ConsoleReporter(reader, writer) { + def stripColor(str: String): String = + str.replaceAll("\u001B\\[[;\\d]*m", "") + def sourceLine(pos: SourcePosition)(implicit ctx: Context): (String, Int) = { val lineNum = s"${pos.line}:" (lineNum + hl"${pos.lineContent.stripLineEnd}", lineNum.length) } - def columnMarker(pos: SourcePosition, offset: Int) = + def columnMarker(pos: SourcePosition, offset: Int)(implicit ctx: Context) = if (pos.startLine == pos.endLine) { val whitespace = " " * (pos.column + offset) val carets = - AnnotationColor + - ("^" * math.max(1, pos.endColumn - pos.startColumn)) + - NoColor + Red("^" * math.max(1, pos.endColumn - pos.startColumn)) - whitespace + carets + whitespace + carets.show } else { - " " * (pos.column + offset) + AnnotationColor + "^" + NoColor + Red(" " * (pos.column + offset) + "^").show } def errorMsg(pos: SourcePosition, msg: String, offset: Int)(implicit ctx: Context) = { var hasLongLines = false val leastWhitespace = msg.lines.foldLeft(Int.MaxValue) { (minPad, line) => - val lineLength = - line.replaceAll("\u001B\\[[;\\d]*m", "").length + val lineLength = stripColor(line).length val padding = math.min(math.max(0, ctx.settings.pageWidth.value - offset - lineLength), offset + pos.startColumn) @@ -68,9 +68,9 @@ class FancyConsoleReporter( val prefix = s"-- $kind: $file " prefix + - ("-" * math.max(ctx.settings.pageWidth.value - prefix.replaceAll("\u001B\\[[;\\d]*m", "").length, 0)) + + ("-" * math.max(ctx.settings.pageWidth.value - stripColor(prefix).length, 0)) + "\n" + outer - }).toString else "" + }).show else "" /** Prints the message with the given position indication. */ override def printMessageAndPos(msg: String, pos: SourcePosition, kind: String = "")(implicit ctx: Context): Unit = { @@ -84,23 +84,22 @@ class FancyConsoleReporter( } else printMessage(msg) } - override def printExplanation(m: Message): Unit = - printMessage( - s"""| - |${Blue("Explanation")} - |${Blue("===========")} - |${m.explanation}""".stripMargin - ) - - - override def summary: String = { - val b = new mutable.ListBuffer[String] - if (warningCount > 0) - b += countString(warningCount, Yellow("warning")) + " found" - if (errorCount > 0) - b += countString(errorCount, Red("error")) + " found" - for ((settingName, count) <- unreportedWarnings) - b += s"there were $count ${settingName.tail} ${Yellow("warning(s)")}; re-run with $settingName for details" - b.mkString("\n") + override def printExplanation(m: Message)(implicit ctx: Context): Unit = { + printMessage(hl"""| + |${Blue("Explanation")} + |${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/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index fe226b28494e..538464daa01b 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -247,7 +247,7 @@ abstract class Reporter extends interfaces.ReporterResult { /** Summary of warnings and errors */ - def summary: String = { + def summary/*(implicit ctx: Context)*/: String = { val b = new mutable.ListBuffer[String] if (warningCount > 0) b += countString(warningCount, "warning") + " found" From e24289ac19a21c6b3794d02e8fe42766224f173c Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 19 Sep 2016 19:26:16 +0200 Subject: [PATCH 22/53] Make relevant parts of compiler conform to new error handling --- src/dotty/tools/dotc/parsing/Parsers.scala | 23 ++- .../tools/dotc/printing/Formatting.scala | 5 +- .../dotc/reporting/ConsoleReporter.scala | 25 ++-- .../dotc/reporting/FancyConsoleReporter.scala | 18 +-- .../reporting/HideNonSensicalMessages.scala | 4 +- src/dotty/tools/dotc/reporting/Reporter.scala | 59 ++++---- .../tools/dotc/reporting/StoreReporter.scala | 6 +- .../dotc/reporting/ThrowingReporter.scala | 4 +- .../reporting/UniqueMessagePositions.scala | 4 +- .../dotc/reporting/diagnostic/Message.scala | 98 ++++++------- .../diagnostic/MessageContainer.scala | 76 ++++++++++ .../reporting/diagnostic/MessageCreator.scala | 52 ------- .../dotc/reporting/diagnostic/messages.scala | 134 ++++++++++++++++-- .../dotc/reporting/diagnostic/parser.scala | 8 -- .../dotc/reporting/diagnostic/syntax.scala | 61 -------- .../tools/dotc/reporting/diagnostic/tpe.scala | 55 ------- .../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 1b451ced7b6f..dcd59629832c 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 f1bb57bd5279..b39d5683e0c3 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 1ae6a1135035..92eea9fa9de3 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 d69396f5f942..725e69ff85d9 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 140777275f54..ba1ab9b33beb 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 538464daa01b..b969fa8787cd 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 fa4fd991fdf9..3744a35dd093 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 f4e3b472d83b..d8e03ab66ab8 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 1ef8b34474a8..c5ff8cb6b593 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 b3c19820fa08..443dc4de87af 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 000000000000..d15c1d2f1f8e --- /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 99ccca4cc7b6..000000000000 --- 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 382e9a29502c..4b7502287436 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 b73e353eba79..000000000000 --- 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 b9a662c7ddf5..000000000000 --- 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 ee221f80d9c9..000000000000 --- 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 43c09351050b..8b740c3dc656 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 b29254b8924f..0cc04613ad7a 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 942948f085a1..8cf6b2feb1d2 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 824034055632..abaa043c0111 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 = { } } From 1532c82788ab2366df04cac2e418d3db98390ef4 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 19 Sep 2016 20:00:44 +0200 Subject: [PATCH 23/53] Remove duplication of console reporters --- src/dotty/tools/dotc/Compiler.scala | 2 +- src/dotty/tools/dotc/core/Contexts.scala | 2 +- .../dotc/repl/CompilingInterpreter.scala | 40 +++----- .../dotc/reporting/ConsoleReporter.scala | 86 +++++++++++----- .../dotc/reporting/FancyConsoleReporter.scala | 93 ------------------ test/test/TestREPL.scala | 4 +- tests/repl/errmsgs.check | 97 ++++++++++--------- tests/repl/imports.check | 22 +++-- 8 files changed, 148 insertions(+), 198 deletions(-) delete mode 100644 src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index ea6254f5b802..178cba7c4080 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -8,7 +8,7 @@ import Symbols._ import Types._ import Scopes._ import typer.{FrontEnd, Typer, ImportInfo, RefChecks} -import reporting.{Reporter, ConsoleReporter, FancyConsoleReporter} +import reporting.{Reporter, ConsoleReporter} import Phases.Phase import transform._ import transform.TreeTransforms.{TreeTransform, TreeTransformer} diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index d1447a98f168..5c9fdaf886ee 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -501,7 +501,7 @@ object Contexts { outer = NoContext period = InitialPeriod mode = Mode.None - typerState = new TyperState(new FancyConsoleReporter()) + typerState = new TyperState(new ConsoleReporter()) printerFn = new RefinedPrinter(_) owner = NoSymbol sstate = settings.defaultState diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala index 70b32a16ae33..0964de303094 100644 --- a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala +++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -16,7 +16,7 @@ import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer} //import ast.parser.SyntaxAnalyzer import io.{PlainFile, VirtualDirectory} import scala.reflect.io.{PlainDirectory, Directory} -import reporting.{ConsoleReporter, FancyConsoleReporter, Reporter} +import reporting.{ConsoleReporter, Reporter} import core.Flags import util.{SourceFile, NameTransformer} import io.ClassPath @@ -117,30 +117,22 @@ class CompilingInterpreter( } } - private def customPrintMessage(msg: String) = { - if (!delayOutput) { - out.print(/*clean*/(msg) + "\n") - // Suppress clean for now for compiler messages - // Otherwise we will completely delete all references to - // line$object$ module classes. The previous interpreter did not - // have the project because the module class was written without the final `$' - // and therefore escaped the purge. We can turn this back on once - // we drop the final `$' from module classes. - out.flush() - } else { - previousOutput += (/*clean*/(msg) + "\n") + private def newReporter = + new ConsoleReporter(Console.in, out) { + override def printMessage(msg: String) = + if (!delayOutput) { + out.print(/*clean*/(msg) + "\n") + // Suppress clean for now for compiler messages + // Otherwise we will completely delete all references to + // line$object$ module classes. The previous interpreter did not + // have the project because the module class was written without the final `$' + // and therefore escaped the purge. We can turn this back on once + // we drop the final `$' from module classes. + out.flush() + } else { + previousOutput += (/*clean*/(msg) + "\n") + } } - } - - private def newReporter(implicit ctx: Context) = - if (ctx.settings.color.value == "never") - new ConsoleReporter(Console.in, out) { - override def printMessage(msg: String) = customPrintMessage(msg) - } - else - new FancyConsoleReporter(Console.in, out) { - override def printMessage(msg: String) = customPrintMessage(msg) - } /** the previous requests this interpreter has processed */ private val prevRequests = new ArrayBuffer[Request]() diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 92eea9fa9de3..a5979bd5bcc6 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -8,13 +8,14 @@ import core.Contexts._ import Reporter._ import java.io.{ BufferedReader, IOException, PrintWriter } import scala.reflect.internal.util._ -import diagnostic.Message -import diagnostic.MessageContainer +import printing.SyntaxHighlighting._ +import printing.Highlighting._ +import diagnostic.{ Message, MessageContainer } import diagnostic.messages._ /** - * This class implements a Reporter that displays messages on a text - * console. + * This class implements a more Fancy version (with colors!) of the regular + * `ConsoleReporter` */ class ConsoleReporter( reader: BufferedReader = Console.in, @@ -26,32 +27,72 @@ class ConsoleReporter( /** maximal number of error messages to be printed */ protected def ErrorLimit = 100 - def printSourceLine(pos: SourcePosition) = - printMessage(pos.lineContent.stripLineEnd) - - def printColumnMarker(pos: SourcePosition) = - if (pos.exists) { printMessage(" " * pos.column + "^") } - /** Prints the message. */ def printMessage(msg: String): Unit = { writer.print(msg + "\n"); writer.flush() } + def stripColor(str: String): String = + str.replaceAll("\u001B\\[[;\\d]*m", "") + + def sourceLine(pos: SourcePosition)(implicit ctx: Context): (String, Int) = { + val lineNum = s"${pos.line}:" + (lineNum + hl"${pos.lineContent.stripLineEnd}", lineNum.length) + } + + def columnMarker(pos: SourcePosition, offset: Int)(implicit ctx: Context) = + if (pos.startLine == pos.endLine) { + val whitespace = " " * (pos.column + offset) + val carets = + Red("^" * math.max(1, pos.endColumn - pos.startColumn)) + + whitespace + carets.show + } else { + Red(" " * (pos.column + offset) + "^").show + } + + def errorMsg(pos: SourcePosition, msg: String, offset: Int)(implicit ctx: Context) = { + var hasLongLines = false + val leastWhitespace = msg.lines.foldLeft(Int.MaxValue) { (minPad, line) => + val lineLength = stripColor(line).length + val padding = + math.min(math.max(0, ctx.settings.pageWidth.value - offset - lineLength), offset + pos.startColumn) + + if (padding < minPad) padding + else minPad + } + + msg + .lines + .map { line => " " * leastWhitespace + line } + .mkString(sys.props("line.separator")) + } + + def posStr(pos: SourcePosition, kind: String, errorId: String)(implicit ctx: Context) = + if (pos.exists) Blue({ + val file = pos.source.file.toString + val errId = if (errorId != "") s"[$errorId] " else "" + val prefix = s"-- ${errId}${kind}: $file " + prefix + + ("-" * math.max(ctx.settings.pageWidth.value - stripColor(prefix).length, 0)) + }).show else "" + /** Prints the message with the given position indication. */ 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.msg}") + printMessage(posStr(pos, kind, msg.errorId)) if (pos.exists) { - printSourceLine(pos) - printColumnMarker(pos) - } + val (src, offset) = sourceLine(pos) + val marker = columnMarker(pos, offset) + val err = errorMsg(pos, msg.msg, offset) + + printMessage(List(src, marker, err).mkString("\n")) + } else printMessage(msg.msg) } - def printExplanation(m: Message)(implicit ctx: Context): Unit = - printMessage( - s"""| - |Explanation - |=========== - |${m.explanation}""".stripMargin - ) + def printExplanation(m: Message)(implicit ctx: Context): Unit = { + printMessage(hl"""| + |${Blue("Explanation")} + |${Blue("===========")}""".stripMargin) + printMessage(m.explanation) + } override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { m match { @@ -87,3 +128,4 @@ class ConsoleReporter( override def flush()(implicit ctx: Context): Unit = { writer.flush() } } + diff --git a/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala b/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala deleted file mode 100644 index 725e69ff85d9..000000000000 --- a/src/dotty/tools/dotc/reporting/FancyConsoleReporter.scala +++ /dev/null @@ -1,93 +0,0 @@ -package dotty.tools -package dotc -package reporting - -import scala.collection.mutable -import util.SourcePosition -import core.Contexts._ -import Reporter._ -import java.io.{ BufferedReader, IOException, PrintWriter } -import scala.reflect.internal.util._ -import printing.SyntaxHighlighting._ -import printing.Highlighting._ -import diagnostic.Message - -/** - * This class implements a more Fancy version (with colors!) of the regular - * `ConsoleReporter` - */ -class FancyConsoleReporter( - reader: BufferedReader = Console.in, - writer: PrintWriter = new PrintWriter(Console.err, true) -) extends ConsoleReporter(reader, writer) { - - def stripColor(str: String): String = - str.replaceAll("\u001B\\[[;\\d]*m", "") - - def sourceLine(pos: SourcePosition)(implicit ctx: Context): (String, Int) = { - val lineNum = s"${pos.line}:" - (lineNum + hl"${pos.lineContent.stripLineEnd}", lineNum.length) - } - - def columnMarker(pos: SourcePosition, offset: Int)(implicit ctx: Context) = - if (pos.startLine == pos.endLine) { - val whitespace = " " * (pos.column + offset) - val carets = - Red("^" * math.max(1, pos.endColumn - pos.startColumn)) - - whitespace + carets.show - } else { - Red(" " * (pos.column + offset) + "^").show - } - - def errorMsg(pos: SourcePosition, msg: String, offset: Int)(implicit ctx: Context) = { - var hasLongLines = false - val leastWhitespace = msg.lines.foldLeft(Int.MaxValue) { (minPad, line) => - val lineLength = stripColor(line).length - val padding = - math.min(math.max(0, ctx.settings.pageWidth.value - offset - lineLength), offset + pos.startColumn) - - if (padding < minPad) padding - else minPad - } - - msg - .lines - .map { line => " " * leastWhitespace + line } - .mkString(sys.props("line.separator")) - } - - def posStr(pos: SourcePosition, kind: String)(implicit ctx: Context) = - if (pos.exists) Blue({ - val file = pos.source.file.toString - - val outer = if (pos.outer.exists) { - s"This location is in code that was inlined at ${pos.outer}:\n" + - printStr(pos.outer) + "\n" + "-" * ctx.settings.pageWidth.value - } else "" - - val prefix = s"-- $kind: $file " - prefix + - ("-" * math.max(ctx.settings.pageWidth.value - stripColor(prefix).length, 0)) + - "\n" + outer - }).show else "" - - /** Prints the message with the given position indication. */ - 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.msg, offset) - - printMessage(List(src, marker, err).mkString("\n")) - } else printMessage(msg.msg) - } - - override def printExplanation(m: Message)(implicit ctx: Context): Unit = { - printMessage(hl"""| - |${Blue("Explanation")} - |${Blue("===========")}""".stripMargin) - printMessage(m.explanation) - } -} diff --git a/test/test/TestREPL.scala b/test/test/TestREPL.scala index 9867cb4ecf20..090c774bb7d6 100644 --- a/test/test/TestREPL.scala +++ b/test/test/TestREPL.scala @@ -21,7 +21,9 @@ class TestREPL(script: String) extends REPL { override val output = new NewLinePrintWriter(out) override def context(ctx: Context) = - ctx.fresh.setSetting(ctx.settings.color, "never") + ctx.fresh + .setSetting(ctx.settings.color, "never") + .setSetting(ctx.settings.pageWidth, 80) override def input(in: Interpreter)(implicit ctx: Context) = new InteractiveReader { val lines = script.lines.buffered diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check index e32e72d1f7b0..d8c1b9215e74 100644 --- a/tests/repl/errmsgs.check +++ b/tests/repl/errmsgs.check @@ -1,43 +1,48 @@ scala> class Inv[T](x: T) defined class Inv scala> val x: List[String] = List(1) -:4: Error: type mismatch: -found: Int(1) -required: String -val x: List[String] = List(1) - ^ +-- Error: ------------------------------------------------------------ +3:val x: List[String] = List(1) + ^ + type mismatch: + found: Int(1) + required: String scala> val y: List[List[String]] = List(List(1)) -:4: Error: type mismatch: -found: Int(1) -required: String -val y: List[List[String]] = List(List(1)) - ^ +-- Error: ------------------------------------------------------------ +3:val y: List[List[String]] = List(List(1)) + ^ + type mismatch: + found: Int(1) + required: String scala> val z: (List[String], List[Int]) = (List(1), List("a")) -:4: Error: type mismatch: -found: Int(1) -required: String -val z: (List[String], List[Int]) = (List(1), List("a")) - ^ -:4: Error: type mismatch: -found: String("a") -required: Int -val z: (List[String], List[Int]) = (List(1), List("a")) - ^ +-- Error: ------------------------------------------------------------ +3:val z: (List[String], List[Int]) = (List(1), List("a")) + ^ + type mismatch: + found: Int(1) + required: String +-- Error: ------------------------------------------------------------ +3:val z: (List[String], List[Int]) = (List(1), List("a")) + ^ + type mismatch: + found: String("a") + required: Int scala> val a: Inv[String] = new Inv(new Inv(1)) -:5: Error: type mismatch: -found: Inv[T] -required: String - -where T is a type variable with constraint >: Int(1) - -val a: Inv[String] = new Inv(new Inv(1)) - ^ +-- Error: ------------------------------------------------------------ +4:val a: Inv[String] = new Inv(new Inv(1)) + ^^^^ + type mismatch: + found: Inv[T] + required: String + + where T is a type variable with constraint >: Int(1) scala> val b: Inv[String] = new Inv(1) -:5: Error: type mismatch: -found: Int(1) -required: String -val b: Inv[String] = new Inv(1) - ^ +-- Error: ------------------------------------------------------------ +4:val b: Inv[String] = new Inv(1) + ^ + type mismatch: + found: Int(1) + required: String scala> abstract class C { type T val x: T @@ -53,22 +58,22 @@ scala> abstract class C { } } } -:9: Error: type mismatch: -found: C.this.T(C.this.x) -required: T' - -where T is a type in class C - T' is a type in the initalizer of value s which is an alias of String - - var y: T = x - ^ -:13: Error: type mismatch: +-- Error: ------------------------------------------------------------ +8: var y: T = x + ^ + type mismatch: + found: C.this.T(C.this.x) + required: T' + + where T is a type in class C + T' is a type in the initalizer of value s which is an alias of String +-- Error: ------------------------------------------------------------ +12: val z: T = y + ^ +type mismatch: found: T(y) required: T' where T is a type in the initalizer of value s which is an alias of String T' is a type in method f which is an alias of Int - - val z: T = y - ^ scala> :quit diff --git a/tests/repl/imports.check b/tests/repl/imports.check index 50b7a93d7f03..f9e48c0119a1 100644 --- a/tests/repl/imports.check +++ b/tests/repl/imports.check @@ -7,16 +7,18 @@ defined module o scala> import o._ import o._ scala> buf += xs -:11: Error: type mismatch: -found: scala.collection.immutable.List[Int](o.xs) -required: String -buf += xs - ^ -:11: Error: type mismatch: -found: String -required: scala.collection.mutable.ListBuffer[Int] -buf += xs -^ +-- Error: ------------------------------------------------------------ +10:buf += xs + ^^ + type mismatch: + found: scala.collection.immutable.List[Int](o.xs) + required: String +-- Error: ------------------------------------------------------------ +10:buf += xs + ^^^^^^^^^ + type mismatch: + found: String + required: scala.collection.mutable.ListBuffer[Int] scala> buf ++= xs res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) scala> :quit From 146add1421f4a92396fc2461ff362fee4527d7f8 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 19 Sep 2016 23:03:03 +0200 Subject: [PATCH 24/53] Improve syntax highlighting for ValDefs --- .../dotc/printing/SyntaxHighlighting.scala | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 95e59ccf347e..863b089610cb 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -26,17 +26,20 @@ object SyntaxHighlighting { } val NoColor = Console.RESET - val CommentColor = Console.GREEN - val KeywordColor = Console.CYAN - val LiteralColor = Console.MAGENTA - val TypeColor = Console.GREEN - val AnnotationColor = Console.RED + val CommentColor = Console.BLUE + val KeywordColor = Console.YELLOW + val ValDefColor = Console.CYAN + val LiteralColor = Console.RED + val TypeColor = Console.MAGENTA + val AnnotationColor = Console.MAGENTA - private def none(str: String) = str - private def keyword(str: String) = KeywordColor + str + NoColor - private def typeDef(str: String) = TypeColor + str + NoColor - private def literal(str: String) = LiteralColor + str + NoColor - private def annotation(str: String) = AnnotationColor + str + NoColor + private def none(str: String) = str + private def keyword(str: String) = KeywordColor + str + NoColor + private def typeDef(str: String) = TypeColor + str + NoColor + private def literal(str: String) = LiteralColor + str + NoColor + private def valDef(str: String) = ValDefColor + str + NoColor + private def annotation(str: String) = + if (str.trim == "@") str else AnnotationColor + str + NoColor private val tripleQs = Console.RED_B + "???" + NoColor private val keywords: Seq[String] = for { @@ -57,6 +60,7 @@ object SyntaxHighlighting { var prev: Char = 0 var remaining = chars.toStream val newBuf = new StringBuilder + var lastToken = "" @inline def keywordStart = prev == 0 || prev == ' ' || prev == '{' || prev == '(' || prev == '\n' @@ -117,7 +121,7 @@ object SyntaxHighlighting { else newBuf += n prev = '#' case '@' => - appendWhile('@', _ != ' ', annotation) + appendWhile('@', !typeEnders.contains(_), annotation) case '\"' => appendLiteral('\"', multiline = remaining.take(2).mkString == "\"\"") case '\'' => @@ -191,7 +195,7 @@ object SyntaxHighlighting { prev = '$' } else if (next == '{') { var open = 1 // keep track of open blocks - newBuf append (KeywordColor + curr) + newBuf append (ValDefColor + curr) newBuf += next while (remaining.nonEmpty && open > 0) { var c = takeChar() @@ -201,7 +205,7 @@ object SyntaxHighlighting { } newBuf append LiteralColor } else { - newBuf append (KeywordColor + curr) + newBuf append (ValDefColor + curr) newBuf += next var c: Char = 'a' while (c.isLetterOrDigit && remaining.nonEmpty) { @@ -249,15 +253,31 @@ object SyntaxHighlighting { def append(c: Char, shouldHL: String => Boolean, highlight: String => String) = { var curr: Char = 0 val sb = new StringBuilder(s"$c") - while (remaining.nonEmpty && curr != ' ' && curr != '(' && curr != '\n') { + + def delim(c: Char) = (c: @switch) match { + case ' ' => true + case '\n' => true + case '(' => true + case ':' => true + case '@' => true + case _ => false + } + + while (remaining.nonEmpty && !delim(curr)) { curr = takeChar() - if (curr != ' ' && curr != '\n') sb += curr + if (!delim(curr)) sb += curr } val str = sb.toString - val toAdd = if (shouldHL(str)) highlight(str) else str - val suffix = if (curr == ' ' || curr == '\n') s"$curr" else "" + val toAdd = + if (shouldHL(str)) + highlight(str) + else if (lastToken == "val" || lastToken == "def" || lastToken == "case") + valDef(str) + else str + val suffix = if (delim(curr)) s"$curr" else "" newBuf append (toAdd + suffix) + lastToken = str prev = curr } From bf8803d6d5b49e20e043564129c2109892842fa7 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 19 Sep 2016 23:07:06 +0200 Subject: [PATCH 25/53] Add deprecation message on `with` type operator --- src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- .../dotc/reporting/diagnostic/messages.scala | 124 +++++++++++------- 2 files changed, 78 insertions(+), 48 deletions(-) diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index dcd59629832c..620cc1273272 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -734,7 +734,7 @@ object Parsers { def withTypeRest(t: Tree): Tree = if (in.token == WITH) { - deprecationWarning("`with' as a type operator has been deprecated; use `&' instead") + deprecationWarning(DeprecatedWithOperator()) in.nextToken() AndTypeTree(t, withType()) } diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 4b7502287436..cf67e8989549 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -13,7 +13,7 @@ import dotc.printing.SyntaxHighlighting._ object messages { - /** Concrete messages to be consumed by the reporter ---------------------- */ + /** Message container to be consumed by the reporter ---------------------- */ class Error( msgFn: => Message, pos: SourcePosition, @@ -98,54 +98,84 @@ object messages { /** 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 - } + 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 + } + + case class DeprecatedWithOperator()(implicit ctx: Context) + extends Message("E003") { + val kind = "Syntax" + val msg = + hl"""${"with"} as a type operator has been deprecated; use `&' instead""" + val explanation = { + val codeBlock1 = + """|trait A { + | type T = Int + |} + | + |trait B { + | type T = Double + |}""".stripMargin + + hl"""|Dotty introduces intersection types - `&' types. These replace the + |use of the ${"with"} keyword. There are a few differences in + |semantics between intersection types and using `${"with"}'. + | + |`${"A with B"}' is ordered, `${"A & B"}' is not. + | + |In: + | + |$codeBlock1 + | + |The type of `${"T"}' in `${"A with B"}' is ${"Int"} whereas in `${"A & B"}' + |the type of `${"T"}' is ${"Int & Double"}.""".stripMargin + } + } /** Type Errors ----------------------------------------------------------- */ class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context) - extends Message("E003") { + extends Message("E004") { val kind = "Naming" val msg = em"duplicate pattern variable: `${bind.name}`" @@ -172,7 +202,7 @@ object messages { } class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context) - extends Message("E004") { + extends Message("E005") { val kind = "Missing identifier" val msg = em"not found: $treeKind$name" From af25cb15ca67ac45a13c9b21519efab178409e7d Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 19 Sep 2016 23:13:51 +0200 Subject: [PATCH 26/53] Improve syntax highlighting on polymorphic defs --- src/dotty/tools/dotc/printing/SyntaxHighlighting.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 863b089610cb..cdb81d54041a 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -54,7 +54,8 @@ object SyntaxHighlighting { 'q' :: 'r' :: 's' :: 't' :: 'u' :: 'v' :: 'w' :: 'x' :: 'y' :: 'z' :: Nil private val typeEnders = - '{' :: '}' :: ')' :: '(' :: '=' :: ' ' :: ',' :: '.' :: '\n' :: Nil + '{' :: '}' :: ')' :: '(' :: '[' :: ']' :: '=' :: ' ' :: ',' :: '.' :: + '\n' :: Nil def apply(chars: Iterable[Char]): Iterable[Char] = { var prev: Char = 0 @@ -63,7 +64,8 @@ object SyntaxHighlighting { var lastToken = "" @inline def keywordStart = - prev == 0 || prev == ' ' || prev == '{' || prev == '(' || prev == '\n' + prev == 0 || prev == ' ' || prev == '{' || prev == '(' || + prev == '\n' || prev == '[' || prev == ',' @inline def numberStart(c: Char) = c.isDigit && (!prev.isLetter || prev == '.' || prev == ' ' || prev == '(' || prev == '\u0000') @@ -258,6 +260,7 @@ object SyntaxHighlighting { case ' ' => true case '\n' => true case '(' => true + case '[' => true case ':' => true case '@' => true case _ => false From 48000c6455694171b554fcb838daae60bc20d6eb Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 20 Sep 2016 09:15:38 +0200 Subject: [PATCH 27/53] Fix underline position --- src/dotty/tools/dotc/reporting/ConsoleReporter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index a5979bd5bcc6..321559e77297 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -40,7 +40,7 @@ class ConsoleReporter( def columnMarker(pos: SourcePosition, offset: Int)(implicit ctx: Context) = if (pos.startLine == pos.endLine) { - val whitespace = " " * (pos.column + offset) + val whitespace = " " * (pos.startColumn + offset) val carets = Red("^" * math.max(1, pos.endColumn - pos.startColumn)) From f1cc4f2a0c536989402c5ff667d3425e7a6e169d Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 20 Sep 2016 09:50:47 +0200 Subject: [PATCH 28/53] Better operator highlighting --- src/dotty/tools/dotc/printing/SyntaxHighlighting.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index cdb81d54041a..6ad29946a80e 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -38,6 +38,7 @@ object SyntaxHighlighting { private def typeDef(str: String) = TypeColor + str + NoColor private def literal(str: String) = LiteralColor + str + NoColor private def valDef(str: String) = ValDefColor + str + NoColor + private def operator(str: String) = TypeColor + str + NoColor private def annotation(str: String) = if (str.trim == "@") str else AnnotationColor + str + NoColor private val tripleQs = Console.RED_B + "???" + NoColor @@ -113,13 +114,13 @@ object SyntaxHighlighting { } } else newBuf += '/' case '=' => - append('=', _ == "=>", keyword) + append('=', _ == "=>", operator) case '<' => - append('<', { x => x == "<-" || x == "<:" || x == "<%" }, keyword) + append('<', { x => x == "<-" || x == "<:" || x == "<%" }, operator) case '>' => - append('>', { x => x == ">:" }, keyword) + append('>', { x => x == ">:" }, operator) case '#' => - if (prev != ' ' && prev != '.') newBuf append keyword("#") + if (prev != ' ' && prev != '.') newBuf append operator("#") else newBuf += n prev = '#' case '@' => From 68eae1a27da2ff6be1aa7747205d6e202d9ed781 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 20 Sep 2016 10:39:12 +0200 Subject: [PATCH 29/53] Indent 2 after newline in REPL --- src/dotty/tools/dotc/repl/AmmoniteReader.scala | 4 ++-- .../tools/dotc/repl/ammonite/filters/BasicFilters.scala | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/repl/AmmoniteReader.scala b/src/dotty/tools/dotc/repl/AmmoniteReader.scala index 435269081dde..f3b68e4b039d 100644 --- a/src/dotty/tools/dotc/repl/AmmoniteReader.scala +++ b/src/dotty/tools/dotc/repl/AmmoniteReader.scala @@ -28,8 +28,8 @@ class AmmoniteReader(val interpreter: Interpreter)(implicit ctx: Context) extend val selectionFilter = GUILikeFilters.SelectionFilter(indent = 2) val multilineFilter: Filter = Filter("multilineFilter") { case TermState(lb ~: rest, b, c, _) - if (lb == 10 || lb == 13) && incompleteInput(b.mkString) => - BasicFilters.injectNewLine(b, c, rest) + if (lb == 10 || lb == 13) && incompleteInput(b.mkString) => + BasicFilters.injectNewLine(b, c, rest, indent = 2) } def readLine(prompt: String): String = { diff --git a/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala b/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala index ebbcf214800b..faa97c348cdb 100644 --- a/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala +++ b/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala @@ -25,12 +25,11 @@ object BasicFilters { typingFilter ) - def injectNewLine(b: Vector[Char], c: Int, rest: LazyList[Int]) = { + def injectNewLine(b: Vector[Char], c: Int, rest: LazyList[Int], indent: Int = 0) = { val (first, last) = b.splitAt(c) - TermState(rest, (first :+ '\n') ++ last, c + 1) + TermState(rest, (first :+ '\n') ++ last ++ Vector.fill(indent)(' '), c + 1 + indent) } - def navFilter = Filter.merge( Case(Up)((b, c, m) => moveUp(b, c, m.width)), Case(Down)((b, c, m) => moveDown(b, c, m.width)), From 6b12f65244964ba740d1a2c2ef7a0b6e3c7b5eb0 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 20 Sep 2016 11:08:18 +0200 Subject: [PATCH 30/53] Fix #1525: start repl if first arg to dotr starts with hyphen --- bin/dotr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/dotr b/bin/dotr index b11712fe9642..8905afd11dd7 100755 --- a/bin/dotr +++ b/bin/dotr @@ -29,9 +29,13 @@ function runMain { fi } +first_arg=$1 + if [ -z "$1" ]; then echo "Starting dotty REPL..." eval "$DOTTY_ROOT/bin/dotc -repl" +elif [[ ${first_arg:0:1} == "-" ]]; then + eval "$DOTTY_ROOT/bin/dotc -repl $@" else runMain "$@" fi From 153c566d0d6ec8f2e55ab575e1e77f7881629cbc Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 20 Sep 2016 11:23:31 +0200 Subject: [PATCH 31/53] Fix multiple parsing errors on e.g. `try 1` The `CompilingInterpreter` will on a single compile run, make multiple parsings of the given line(s). This results in multiple warnings from the parser. As such, clear the warnings until the actual compile is performed. --- src/dotty/tools/dotc/repl/CompilingInterpreter.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala index 0964de303094..5b3669d5e472 100644 --- a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala +++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -212,8 +212,10 @@ class CompilingInterpreter( case None => Interpreter.Incomplete case Some(Nil) => Interpreter.Error // parse error or empty input case Some(tree :: Nil) if tree.isTerm && !tree.isInstanceOf[Assign] => + previousOutput.clear() // clear previous error reporting interpret(s"val $newVarName =\n$line") case Some(trees) => + previousOutput.clear() // clear previous error reporting val req = new Request(line, newLineName) if (!req.compile()) Interpreter.Error // an error happened during compilation, e.g. a type error @@ -314,9 +316,13 @@ class CompilingInterpreter( /** One line of code submitted by the user for interpretation */ private class Request(val line: String, val lineName: String)(implicit ctx: Context) { - private val trees = parse(line) match { - case Some(ts) => ts - case None => Nil + private val trees = { + val parsed = parse(line) + previousOutput.clear() // clear previous error reporting + parsed match { + case Some(ts) => ts + case None => Nil + } } /** name to use for the object that will compute "line" */ From 88e41465dd1dcecaa3cd8f0971e8d71e61d48490 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 20 Sep 2016 17:32:23 +0200 Subject: [PATCH 32/53] Add basic diffing for shown values --- src/dotty/tools/dotc/util/DiffUtil.scala | 28 +++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/util/DiffUtil.scala b/src/dotty/tools/dotc/util/DiffUtil.scala index b7c77ad62720..8bb39c88aa4c 100644 --- a/src/dotty/tools/dotc/util/DiffUtil.scala +++ b/src/dotty/tools/dotc/util/DiffUtil.scala @@ -12,9 +12,7 @@ object DiffUtil { private final val DELETION_COLOR = ANSI_RED private final val ADDITION_COLOR = ANSI_GREEN - def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = { - - @tailrec def splitTokens(str: String, acc: List[String] = Nil): List[String] = { + @tailrec private def splitTokens(str: String, acc: List[String] = Nil): List[String] = { if (str == "") { acc.reverse } else { @@ -33,6 +31,30 @@ object DiffUtil { } } + + /** @return a tuple of the (found, expected) diffs as strings */ + def mkColoredTypeDiff(found: String, expected: String): (String, String) = { + val foundTokens = splitTokens(found, Nil).toArray + val expectedTokens = splitTokens(expected, Nil).toArray + + val diffExp = hirschberg(foundTokens, expectedTokens) + val diffAct = hirschberg(expectedTokens, foundTokens) + + val exp = diffExp.collect { + case Unmodified(str) => str + case Inserted(str) => ADDITION_COLOR + str + ANSI_DEFAULT + }.mkString + + val fnd = diffAct.collect { + case Unmodified(str) => str + case Inserted(str) => DELETION_COLOR + str + ANSI_DEFAULT + }.mkString + + (fnd, exp) + } + + def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = { + val tokens = splitTokens(code, Nil).toArray val lastTokens = splitTokens(lastCode, Nil).toArray From 8743fa86e25133d0ddea3d85d7df0a5ceadef83a Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 22 Sep 2016 11:45:51 +0200 Subject: [PATCH 33/53] Unrainbow syntax highlighting --- src/dotty/tools/dotc/printing/SyntaxHighlighting.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 6ad29946a80e..80170dcad8b0 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -92,7 +92,9 @@ object SyntaxHighlighting { if (n.isUpper && keywordStart) { appendWhile(n, !typeEnders.contains(_), typeDef) } else if (keywordStart) { - append(n, keywords.contains(_), keyword) + append(n, keywords.contains(_), { kw => + if (kw == "new") typeDef(kw) else keyword(kw) + }) } else { newBuf += n prev = n From aa559359bb55729913d34588462542f10c42e147 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 21 Sep 2016 10:09:13 +0200 Subject: [PATCH 34/53] Refactor explanation interpolator --- .../tools/dotc/printing/Formatting.scala | 131 +++++++++++------- .../dotc/printing/SyntaxHighlighting.scala | 2 +- .../tools/dotc/reporting/StoreReporter.scala | 1 - .../dotc/reporting/diagnostic/messages.scala | 27 +++- src/dotty/tools/dotc/typer/Applications.scala | 15 +- .../tools/dotc/typer/ErrorReporting.scala | 11 +- src/dotty/tools/dotc/typer/RefChecks.scala | 2 +- test/test/TestREPL.scala | 4 +- tests/repl/errmsgs.check | 61 ++++---- tests/repl/imports.check | 8 +- 10 files changed, 154 insertions(+), 108 deletions(-) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index b39d5683e0c3..89f9eb814714 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -9,6 +9,9 @@ import Decorators._ import scala.annotation.switch import scala.util.control.NonFatal import reporting.diagnostic.MessageContainer +import util.DiffUtil +import Highlighting.{ highlightToString => _, _ } +import SyntaxHighlighting._ object Formatting { @@ -113,65 +116,99 @@ object Formatting { seen.record(super.polyParamNameString(param), param) } - def explained2(op: Context => String)(implicit ctx: Context): String = { - val seen = new Seen - val explainCtx = ctx.printer match { - case dp: ExplainingPrinter => ctx // re-use outer printer and defer explanation to it - case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx)) + def explanation(entry: Recorded)(implicit ctx: Context): String = { + def boundStr(bound: Type, default: ClassSymbol, cmp: String) = + if (bound.isRef(default)) "" else i"$cmp $bound" + + def boundsStr(bounds: TypeBounds): String = { + val lo = boundStr(bounds.lo, defn.NothingClass, ">:") + val hi = boundStr(bounds.hi, defn.AnyClass, "<:") + if (lo.isEmpty) hi + else if (hi.isEmpty) lo + else s"$lo and $hi" } - def explanation(entry: Recorded): String = { - def boundStr(bound: Type, default: ClassSymbol, cmp: String) = - if (bound.isRef(default)) "" else i"$cmp $bound" + def addendum(cat: String, info: Type): String = info match { + case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty => + if (lo eq hi) i" which is an alias of $lo" + else i" with $cat ${boundsStr(bounds)}" + case _ => + "" + } - def boundsStr(bounds: TypeBounds): String = { - val lo = boundStr(bounds.lo, defn.NothingClass, ">:") - val hi = boundStr(bounds.hi, defn.AnyClass, "<:") - if (lo.isEmpty) hi - else if (hi.isEmpty) lo - else s"$lo and $hi" - } + entry match { + case param: PolyParam => + s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}" + case sym: Symbol => + s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}" + } + } - def addendum(cat: String, info: Type)(implicit ctx: Context): String = info match { - case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty => - if (lo eq hi) i" which is an alias of $lo" - else i" with $cat ${boundsStr(bounds)}" - case _ => - "" - } + private def explanations(seen: Seen)(implicit ctx: Context): String = { + def needsExplanation(entry: Recorded) = entry match { + case param: PolyParam => ctx.typerState.constraint.contains(param) + case _ => false + } - entry match { - case param: PolyParam => - s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}" - case sym: Symbol => - s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}" + val toExplain: List[(String, Recorded)] = seen.toList.flatMap { + case (str, entry :: Nil) => + if (needsExplanation(entry)) (str, entry) :: Nil else Nil + case (str, entries) => + entries.map(alt => (seen.record(str, alt), alt)) + }.sortBy(_._1) + + def columnar(parts: List[(String, String)]): List[String] = { + lazy val maxLen = parts.map(_._1.length).max + parts.map { + case (leader, trailer) => + val variable = hl"$leader" + s"""$variable${" " * (maxLen - leader.length)} $trailer""" } } - def explanations(seen: Seen)(implicit ctx: Context): String = { - def needsExplanation(entry: Recorded) = entry match { - case param: PolyParam => ctx.typerState.constraint.contains(param) - case _ => false + val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) } + val explainLines = columnar(explainParts) + if (explainLines.isEmpty) "" else i"$explainLines%\n%\n" + } + + private def explainCtx(seen: Seen)(implicit ctx: Context): Context = ctx.printer match { + case dp: ExplainingPrinter => + ctx // re-use outer printer and defer explanation to it + case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx)) + } + + def explained2(op: Context => String)(implicit ctx: Context): String = { + val seen = new Seen + op(explainCtx(seen)) ++ explanations(seen) + } + + def disambiguateTypes(args: Type*)(implicit ctx: Context): String = { + val seen = new Seen + object polyparams extends TypeTraverser { + def traverse(tp: Type): Unit = tp match { + case tp: TypeRef => + seen.record(tp.show(explainCtx(seen)), tp.symbol) + traverseChildren(tp) + case tp: TermRef => + seen.record(tp.show(explainCtx(seen)), tp.symbol) + traverseChildren(tp) + case tp: PolyParam => + seen.record(tp.show(explainCtx(seen)), tp) + traverseChildren(tp) + case _ => + traverseChildren(tp) } - val toExplain: List[(String, Recorded)] = seen.toList.flatMap { - case (str, entry :: Nil) => - if (needsExplanation(entry)) (str, entry) :: Nil else Nil - case (str, entries) => - entries.map(alt => (seen.record(str, alt), alt)) - }.sortBy(_._1) - val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) } - val explainLines = columnar(explainParts, " ") - if (explainLines.isEmpty) "" else i"\n\nwhere $explainLines%\n %\n" } - - op(explainCtx) ++ explanations(seen) + args.foreach(polyparams.traverse) + explanations(seen) } - def columnar(parts: List[(String, String)], sep: String): List[String] = { - lazy val maxLen = parts.map(_._1.length).max - parts.map { - case (leader, trailer) => - s"$leader${" " * (maxLen - leader.length)}$sep$trailer" + def typeDiff(found: Type, expected: Type)(implicit ctx: Context): (String, String) = { + (found, expected) match { + case (rf1: RefinedType, rf2: RefinedType) => + DiffUtil.mkColoredTypeDiff(rf1.show, rf2.show) + case _ => + (hl"${found.show}", hl"${expected.show}") } } } diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 80170dcad8b0..7d4d12394929 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -278,7 +278,7 @@ object SyntaxHighlighting { val toAdd = if (shouldHL(str)) highlight(str) - else if (lastToken == "val" || lastToken == "def" || lastToken == "case") + else if (("var" :: "val" :: "def" :: "case" :: Nil).contains(lastToken)) valDef(str) else str val suffix = if (delim(curr)) s"$curr" else "" diff --git a/src/dotty/tools/dotc/reporting/StoreReporter.scala b/src/dotty/tools/dotc/reporting/StoreReporter.scala index 3744a35dd093..e85017ed2bb5 100644 --- a/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -4,7 +4,6 @@ package reporting import core.Contexts.Context import collection.mutable -import Reporter.{Error, Warning} import config.Printers.typr import diagnostic.MessageContainer import diagnostic.messages._ diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index cf67e8989549..00963659377a 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -4,12 +4,13 @@ package reporting package diagnostic import dotc.core._ -import Contexts.Context, Decorators._, Symbols._, Names._ +import Contexts.Context, Decorators._, Symbols._, Names._, Types._ import util.{SourceFile, NoSource} import util.{SourcePosition, NoSourcePosition} import config.Settings.Setting import interfaces.Diagnostic.{ERROR, WARNING, INFO} -import dotc.printing.SyntaxHighlighting._ +import printing.SyntaxHighlighting._ +import printing.Formatting object messages { @@ -127,7 +128,7 @@ object messages { } } - class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context) + case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context) extends EmptyCatchOrFinallyBlock(tryBody, "E001") { val kind = "Syntax" val msg = @@ -174,7 +175,7 @@ object messages { } /** Type Errors ----------------------------------------------------------- */ - class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context) + case class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context) extends Message("E004") { val kind = "Naming" val msg = em"duplicate pattern variable: `${bind.name}`" @@ -201,9 +202,9 @@ object messages { } } - class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context) + case class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context) extends Message("E005") { - val kind = "Missing identifier" + val kind = "Missing Identifier" val msg = em"not found: $treeKind$name" val explanation = { @@ -211,4 +212,18 @@ object messages { |has either been misspelt or you're forgetting an import""".stripMargin } } + + case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "")(implicit ctx: Context) + extends Message("E006") { + val kind = "Type Mismatch" + private val where = Formatting.disambiguateTypes(found, expected) + private val (fnd, exp) = Formatting.typeDiff(found, expected) + val msg = + s"""|found: $fnd + |required: $exp + | + |$where""".stripMargin + whyNoMatch + + val explanation = "" + } } diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 2c9039db1fc4..56595a637e75 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -27,6 +27,7 @@ import collection.mutable import config.Printers.{typr, unapp, overload} import TypeApplications._ import language.implicitConversions +import reporting.diagnostic.Message object Applications { import tpd._ @@ -132,10 +133,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic => protected def harmonizeArgs(args: List[TypedArg]): List[TypedArg] /** Signal failure with given message at position of given argument */ - protected def fail(msg: => String, arg: Arg): Unit + protected def fail(msg: => Message, arg: Arg): Unit /** Signal failure with given message at position of the application itself */ - protected def fail(msg: => String): Unit + protected def fail(msg: => Message): Unit protected def appPos: Position @@ -186,7 +187,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => // it might be healed by an implicit conversion assert(ctx.typerState.constraint eq savedConstraint) else - fail(err.typeMismatchStr(methType.resultType, resultType)) + fail(err.typeMismatchMsg(methType.resultType, resultType)) } // match all arguments with corresponding formal parameters matchArgs(orderedArgs, methType.paramTypes, 0) @@ -388,9 +389,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def addArg(arg: TypedArg, formal: Type) = ok = ok & isCompatible(argType(arg, formal), formal) def makeVarArg(n: Int, elemFormal: Type) = {} - def fail(msg: => String, arg: Arg) = + def fail(msg: => Message, arg: Arg) = ok = false - def fail(msg: => String) = + def fail(msg: => Message) = ok = false def appPos = NoPosition lazy val normalizedFun = ref(methRef) @@ -455,12 +456,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => override def appPos = app.pos - def fail(msg: => String, arg: Trees.Tree[T]) = { + def fail(msg: => Message, arg: Trees.Tree[T]) = { ctx.error(msg, arg.pos) ok = false } - def fail(msg: => String) = { + def fail(msg: => Message) = { ctx.error(msg, app.pos) ok = false } diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index 8b740c3dc656..1fd4fc96e1a7 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -12,12 +12,13 @@ import printing.{Showable, RefinedPrinter} import scala.collection.mutable import java.util.regex.Matcher.quoteReplacement import reporting.diagnostic.Message +import reporting.diagnostic.messages._ object ErrorReporting { import tpd._ - def errorTree(tree: untpd.Tree, msg: => String)(implicit ctx: Context): tpd.Tree = + def errorTree(tree: untpd.Tree, msg: => Message)(implicit ctx: Context): tpd.Tree = tree withType errorType(msg, tree.pos) def errorType(msg: => Message, pos: Position)(implicit ctx: Context): ErrorType = { @@ -101,7 +102,7 @@ object ErrorReporting { def patternConstrStr(tree: Tree): String = ??? def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = - errorTree(tree, typeMismatchStr(normalize(tree.tpe, pt), pt) + implicitFailure.postscript) + errorTree(tree, typeMismatchMsg(normalize(tree.tpe, pt), pt) /*+ implicitFailure.postscript*/) /** A subtype log explaining why `found` does not conform to `expected` */ def whyNoMatchStr(found: Type, expected: Type) = @@ -110,7 +111,7 @@ object ErrorReporting { else "" - def typeMismatchStr(found: Type, expected: Type) = { + def typeMismatchMsg(found: Type, expected: Type) = { // replace constrained polyparams and their typevars by their bounds where possible object reported extends TypeMap { def setVariance(v: Int) = variance = v @@ -132,9 +133,7 @@ object ErrorReporting { val found1 = reported(found) reported.setVariance(-1) val expected1 = reported(expected) - ex"""|type mismatch: - |found: $found1 - |required: $expected1""".stripMargin + whyNoMatchStr(found, expected) + TypeMismatch(found1, expected1, whyNoMatchStr(found, expected)) } /** Format `raw` implicitNotFound argument, replacing all diff --git a/src/dotty/tools/dotc/typer/RefChecks.scala b/src/dotty/tools/dotc/typer/RefChecks.scala index 1f150c5192c7..4d82a2d12ce0 100644 --- a/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/src/dotty/tools/dotc/typer/RefChecks.scala @@ -200,7 +200,7 @@ object RefChecks { infoStringWithLocation(other), infoStringWithLocation(member)) else if (ctx.settings.debug.value) - err.typeMismatchStr(memberTp, otherTp) + err.typeMismatchMsg(memberTp, otherTp) else "" "overriding %s;\n %s %s%s".format( diff --git a/test/test/TestREPL.scala b/test/test/TestREPL.scala index 090c774bb7d6..9867cb4ecf20 100644 --- a/test/test/TestREPL.scala +++ b/test/test/TestREPL.scala @@ -21,9 +21,7 @@ class TestREPL(script: String) extends REPL { override val output = new NewLinePrintWriter(out) override def context(ctx: Context) = - ctx.fresh - .setSetting(ctx.settings.color, "never") - .setSetting(ctx.settings.pageWidth, 80) + ctx.fresh.setSetting(ctx.settings.color, "never") override def input(in: Interpreter)(implicit ctx: Context) = new InteractiveReader { val lines = script.lines.buffered diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check index d8c1b9215e74..8f86aac083c7 100644 --- a/tests/repl/errmsgs.check +++ b/tests/repl/errmsgs.check @@ -1,48 +1,47 @@ scala> class Inv[T](x: T) defined class Inv scala> val x: List[String] = List(1) --- Error: ------------------------------------------------------------ +-- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 3:val x: List[String] = List(1) ^ - type mismatch: found: Int(1) required: String + scala> val y: List[List[String]] = List(List(1)) --- Error: ------------------------------------------------------------ +-- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 3:val y: List[List[String]] = List(List(1)) ^ - type mismatch: found: Int(1) required: String + scala> val z: (List[String], List[Int]) = (List(1), List("a")) --- Error: ------------------------------------------------------------ +-- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 3:val z: (List[String], List[Int]) = (List(1), List("a")) ^ - type mismatch: found: Int(1) required: String --- Error: ------------------------------------------------------------ + +-- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 3:val z: (List[String], List[Int]) = (List(1), List("a")) - ^ - type mismatch: + ^^^ found: String("a") required: Int + scala> val a: Inv[String] = new Inv(new Inv(1)) --- Error: ------------------------------------------------------------ +-- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 4:val a: Inv[String] = new Inv(new Inv(1)) - ^^^^ - type mismatch: - found: Inv[T] - required: String - - where T is a type variable with constraint >: Int(1) + ^^^^^ + found: Inv[T] + required: String + + T is a type variable with constraint >: Int(1) scala> val b: Inv[String] = new Inv(1) --- Error: ------------------------------------------------------------ +-- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 4:val b: Inv[String] = new Inv(1) ^ - type mismatch: found: Int(1) required: String + scala> abstract class C { type T val x: T @@ -58,22 +57,20 @@ scala> abstract class C { } } } --- Error: ------------------------------------------------------------ +-- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 8: var y: T = x ^ - type mismatch: - found: C.this.T(C.this.x) - required: T' - - where T is a type in class C - T' is a type in the initalizer of value s which is an alias of String --- Error: ------------------------------------------------------------ + found: C.this.T(C.this.x) + required: T + + T is a type in class C + T' is a type in the initalizer of value s which is an alias of String +-- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 12: val z: T = y ^ -type mismatch: -found: T(y) -required: T' - -where T is a type in the initalizer of value s which is an alias of String - T' is a type in method f which is an alias of Int + found: T(y) + required: T + + T is a type in the initalizer of value s which is an alias of String + T' is a type in method f which is an alias of Int scala> :quit diff --git a/tests/repl/imports.check b/tests/repl/imports.check index f9e48c0119a1..2e8d5dcf967f 100644 --- a/tests/repl/imports.check +++ b/tests/repl/imports.check @@ -7,18 +7,18 @@ defined module o scala> import o._ import o._ scala> buf += xs --- Error: ------------------------------------------------------------ +-- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 10:buf += xs ^^ - type mismatch: found: scala.collection.immutable.List[Int](o.xs) required: String --- Error: ------------------------------------------------------------ + +-- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 10:buf += xs ^^^^^^^^^ - type mismatch: found: String required: scala.collection.mutable.ListBuffer[Int] + scala> buf ++= xs res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) scala> :quit From 33d44903ec95cff9b79523683175733fd49ba140 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 26 Sep 2016 13:40:59 +0200 Subject: [PATCH 35/53] Make `typeDiff` aware of placeholder types --- .../tools/dotc/printing/Formatting.scala | 55 ++++++++++++------- .../dotc/reporting/diagnostic/messages.scala | 4 +- tests/repl/errmsgs.check | 14 ++--- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index 89f9eb814714..dd391ea80cf6 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -116,6 +116,7 @@ object Formatting { seen.record(super.polyParamNameString(param), param) } + /** Create explanation for single `Recorded` type or symbol */ def explanation(entry: Recorded)(implicit ctx: Context): String = { def boundStr(bound: Type, default: ClassSymbol, cmp: String) = if (bound.isRef(default)) "" else i"$cmp $bound" @@ -144,6 +145,7 @@ object Formatting { } } + /** Turns a `Seen => String` to produce a `where: T is...` clause */ private def explanations(seen: Seen)(implicit ctx: Context): String = { def needsExplanation(entry: Recorded) = entry match { case param: PolyParam => ctx.typerState.constraint.contains(param) @@ -168,41 +170,56 @@ object Formatting { val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) } val explainLines = columnar(explainParts) - if (explainLines.isEmpty) "" else i"$explainLines%\n%\n" + if (explainLines.isEmpty) "" else i"where: $explainLines%\n %\n" } + /** Context with correct printer set for explanations */ private def explainCtx(seen: Seen)(implicit ctx: Context): Context = ctx.printer match { case dp: ExplainingPrinter => ctx // re-use outer printer and defer explanation to it case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx)) } + /** Entrypoint for explanation string interpolator: + * + * ``` + * ex"disambiguate $tpe1 and $tpe2" + * ``` + */ def explained2(op: Context => String)(implicit ctx: Context): String = { val seen = new Seen op(explainCtx(seen)) ++ explanations(seen) } - def disambiguateTypes(args: Type*)(implicit ctx: Context): String = { + /** When getting a type mismatch it is useful to disambiguate placeholders like: + * + * ``` + * found: List[Int] + * required: List[T] + * where: T is a type in the initalizer of value s which is an alias of + * String + * ``` + * + * @return the `where` section as well as the printing context for the + * placeholders - `("T is a...", printCtx)` + */ + def disambiguateTypes(args: Type*)(implicit ctx: Context): (String, Context) = { val seen = new Seen - object polyparams extends TypeTraverser { - def traverse(tp: Type): Unit = tp match { - case tp: TypeRef => - seen.record(tp.show(explainCtx(seen)), tp.symbol) - traverseChildren(tp) - case tp: TermRef => - seen.record(tp.show(explainCtx(seen)), tp.symbol) - traverseChildren(tp) - case tp: PolyParam => - seen.record(tp.show(explainCtx(seen)), tp) - traverseChildren(tp) - case _ => - traverseChildren(tp) - } - } - args.foreach(polyparams.traverse) - explanations(seen) + val printCtx = explainCtx(seen) + args.foreach(_.show(printCtx)) // showing each member will put it into `seen` + (explanations(seen), printCtx) } + /** This method will produce a colored type diff from the given arguments. + * The idea is to do this for known cases that are useful and then fall back + * on regular syntax highlighting for the cases which are unhandled. + * + * Please not that if used in combination with `disambiguateTypes` the + * correct `Context` for printing should also be passed when calling the + * method. + * + * @return the (found, expected) with coloring to highlight the difference + */ def typeDiff(found: Type, expected: Type)(implicit ctx: Context): (String, String) = { (found, expected) match { case (rf1: RefinedType, rf2: RefinedType) => diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 00963659377a..01cf00cf74a4 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -216,8 +216,8 @@ object messages { case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "")(implicit ctx: Context) extends Message("E006") { val kind = "Type Mismatch" - private val where = Formatting.disambiguateTypes(found, expected) - private val (fnd, exp) = Formatting.typeDiff(found, expected) + private val (where, printCtx) = Formatting.disambiguateTypes(found, expected) + private val (fnd, exp) = Formatting.typeDiff(found, expected)(printCtx) val msg = s"""|found: $fnd |required: $exp diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check index 8f86aac083c7..b8cff5ba2df6 100644 --- a/tests/repl/errmsgs.check +++ b/tests/repl/errmsgs.check @@ -34,7 +34,7 @@ scala> val a: Inv[String] = new Inv(new Inv(1)) found: Inv[T] required: String - T is a type variable with constraint >: Int(1) + where: T is a type variable with constraint >: Int(1) scala> val b: Inv[String] = new Inv(1) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 4:val b: Inv[String] = new Inv(1) @@ -61,16 +61,16 @@ scala> abstract class C { 8: var y: T = x ^ found: C.this.T(C.this.x) - required: T + required: T' - T is a type in class C - T' is a type in the initalizer of value s which is an alias of String + where: T is a type in class C + T' is a type in the initalizer of value s which is an alias of String -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 12: val z: T = y ^ found: T(y) - required: T + required: T' - T is a type in the initalizer of value s which is an alias of String - T' is a type in method f which is an alias of Int + where: T is a type in the initalizer of value s which is an alias of String + T' is a type in method f which is an alias of Int scala> :quit From 24bcdfdcdaa04e3cf2aa3c7289415f8a98d3d376 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 27 Sep 2016 12:59:08 +0200 Subject: [PATCH 36/53] Add `dotty.jar` to gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 17eba04681ba..416a12effd24 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ *.log *.swp *~ -*.swp # sbt specific dist/* @@ -29,6 +28,7 @@ classes/ /.worksheet/ # Partest +dotty.jar tests/partest-generated/ tests/locks/ /test-classes/ From f5ad8492d076051c8805370c05610d8294ccb241 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 27 Sep 2016 16:07:56 +0200 Subject: [PATCH 37/53] Fix reporting of ErrorTypes in highlighted segments --- src/dotty/tools/dotc/core/Decorators.scala | 4 ++ .../tools/dotc/printing/Formatting.scala | 50 +++++++++++++------ .../dotc/printing/SyntaxHighlighting.scala | 14 ------ .../dotc/reporting/ConsoleReporter.scala | 2 +- .../diagnostic/MessageContainer.scala | 2 +- .../dotc/reporting/diagnostic/messages.scala | 4 +- src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/repl/errmsgs.check | 16 +++--- tests/repl/imports.check | 4 +- 9 files changed, 55 insertions(+), 43 deletions(-) diff --git a/src/dotty/tools/dotc/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala index 3bf17730a9f9..b0f1f0c98c5d 100644 --- a/src/dotty/tools/dotc/core/Decorators.scala +++ b/src/dotty/tools/dotc/core/Decorators.scala @@ -176,6 +176,10 @@ object Decorators { */ def ex(args: Any*)(implicit ctx: Context): String = explained2(implicit ctx => em(args: _*)) + + /** Formatter that adds syntax highlighting to all interpolated values */ + def hl(args: Any*)(implicit ctx: Context): String = + new SyntaxFormatter(sc).assemble(args) } } diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index dd391ea80cf6..41bd91607ea7 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -69,21 +69,40 @@ object Formatting { * message composition methods, this is crucial. */ class ErrorMessageFormatter(sc: StringContext) extends StringFormatter(sc) { + override protected def showArg(arg: Any)(implicit ctx: Context): String = + wrapNonSensical(arg, super.showArg(arg)) + } + + class SyntaxFormatter(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 - case sym: Symbol if sym.isCompleted => - sym.info != ErrorType && sym.info != TypeAlias(ErrorType) && sym.info.exists - case _ => true + arg match { + case arg: Showable => + val highlighted = + SyntaxHighlighting(wrapNonSensical(arg, super.showArg(arg))) + new String(highlighted.toArray) + case hl: Highlight => + hl.show + case hb: HighlightBuffer => + hb.toString + case _ => super.showArg(arg) } - val str = super.showArg(arg) - if (isSensical(arg)) str - else nonSensicalStartTag + str + nonSensicalEndTag } } + private def wrapNonSensical(arg: Any, str: String)(implicit ctx: Context): String = { + import MessageContainer._ + def isSensical(arg: Any): Boolean = arg match { + case tpe: Type => + tpe.exists && !tpe.isErroneous + case sym: Symbol if sym.isCompleted => + sym.info != ErrorType && sym.info != TypeAlias(ErrorType) && sym.info.exists + case _ => true + } + + if (isSensical(arg)) str + else nonSensicalStartTag + str + nonSensicalEndTag + } + private type Recorded = AnyRef /*Symbol | PolyParam*/ private class Seen extends mutable.HashMap[String, List[Recorded]] { @@ -117,7 +136,7 @@ object Formatting { } /** Create explanation for single `Recorded` type or symbol */ - def explanation(entry: Recorded)(implicit ctx: Context): String = { + def explanation(entry: AnyRef)(implicit ctx: Context): String = { def boundStr(bound: Type, default: ClassSymbol, cmp: String) = if (bound.isRef(default)) "" else i"$cmp $bound" @@ -221,11 +240,14 @@ object Formatting { * @return the (found, expected) with coloring to highlight the difference */ def typeDiff(found: Type, expected: Type)(implicit ctx: Context): (String, String) = { + val fnd = wrapNonSensical(found, found.show) + val exp = wrapNonSensical(expected, found.show) + (found, expected) match { - case (rf1: RefinedType, rf2: RefinedType) => - DiffUtil.mkColoredTypeDiff(rf1.show, rf2.show) + case (_: RefinedType, _: RefinedType) => + DiffUtil.mkColoredTypeDiff(fnd, exp) case _ => - (hl"${found.show}", hl"${expected.show}") + (hl"$fnd", hl"$exp") } } } diff --git a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 7d4d12394929..86f34e64d451 100644 --- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -11,20 +11,6 @@ import Highlighting.{Highlight, HighlightBuffer} /** This object provides functions for syntax highlighting in the REPL */ object SyntaxHighlighting { - implicit class SyntaxFormatting(val sc: StringContext) extends AnyVal { - def hl(args: Any*)(implicit ctx: Context): String = - sc.s(args.map ({ - case hl: Highlight => - hl.show - case hb: HighlightBuffer => - hb.toString - case x if ctx.settings.color.value != "never" => - new String(apply(x.toString).toArray) - case x => - x.toString - }): _*) - } - val NoColor = Console.RESET val CommentColor = Console.BLUE val KeywordColor = Console.YELLOW diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 321559e77297..261357924af5 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -4,7 +4,7 @@ package reporting import scala.collection.mutable import util.SourcePosition -import core.Contexts._ +import core.Contexts._, core.Decorators._ import Reporter._ import java.io.{ BufferedReader, IOException, PrintWriter } import scala.reflect.internal.util._ diff --git a/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala index d15c1d2f1f8e..7bb092da222d 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala @@ -41,7 +41,7 @@ class MessageContainer( /** The message to report */ def message: String = { if (myMsg == null) { - myMsg = msgFn.msg + myMsg = msgFn.msg.replaceAll("\u001B\\[[;\\d]*m", "") if (myMsg.contains(nonSensicalStartTag)) { myIsNonSensical = true // myMsg might be composed of several d"..." invocations -> nested diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 01cf00cf74a4..76b2fd3e9521 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -202,13 +202,13 @@ object messages { } } - case class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context) + case class MissingIdent(tree: untpd.Ident, treeKind: String, name: String)(implicit ctx: Context) extends Message("E005") { 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 + hl"""|An identifier for `$treeKind$name` 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/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 0cc04613ad7a..a4dc2f87138d 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -331,7 +331,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (rawType.exists) ensureAccessible(rawType, superAccess = false, tree.pos) else { - error(new MissingIdent(tree, kind, name), tree.pos) + error(new MissingIdent(tree, kind, name.show), tree.pos) ErrorType } diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check index b8cff5ba2df6..b791123f4366 100644 --- a/tests/repl/errmsgs.check +++ b/tests/repl/errmsgs.check @@ -5,34 +5,34 @@ scala> val x: List[String] = List(1) 3:val x: List[String] = List(1) ^ found: Int(1) - required: String + required: Int(1) scala> val y: List[List[String]] = List(List(1)) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 3:val y: List[List[String]] = List(List(1)) ^ found: Int(1) - required: String + required: Int(1) scala> val z: (List[String], List[Int]) = (List(1), List("a")) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 3:val z: (List[String], List[Int]) = (List(1), List("a")) ^ found: Int(1) - required: String + required: Int(1) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 3:val z: (List[String], List[Int]) = (List(1), List("a")) ^^^ found: String("a") - required: Int + required: String("a") scala> val a: Inv[String] = new Inv(new Inv(1)) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 4:val a: Inv[String] = new Inv(new Inv(1)) ^^^^^ found: Inv[T] - required: String + required: Inv[T] where: T is a type variable with constraint >: Int(1) scala> val b: Inv[String] = new Inv(1) @@ -40,7 +40,7 @@ scala> val b: Inv[String] = new Inv(1) 4:val b: Inv[String] = new Inv(1) ^ found: Int(1) - required: String + required: Int(1) scala> abstract class C { type T @@ -61,7 +61,7 @@ scala> abstract class C { 8: var y: T = x ^ found: C.this.T(C.this.x) - required: T' + required: C.this.T(C.this.x) where: T is a type in class C T' is a type in the initalizer of value s which is an alias of String @@ -69,7 +69,7 @@ scala> abstract class C { 12: val z: T = y ^ found: T(y) - required: T' + required: T(y) where: T is a type in the initalizer of value s which is an alias of String T' is a type in method f which is an alias of Int diff --git a/tests/repl/imports.check b/tests/repl/imports.check index 2e8d5dcf967f..817adae87bf9 100644 --- a/tests/repl/imports.check +++ b/tests/repl/imports.check @@ -11,13 +11,13 @@ scala> buf += xs 10:buf += xs ^^ found: scala.collection.immutable.List[Int](o.xs) - required: String + required: scala.collection.immutable.List[Int](o.xs) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 10:buf += xs ^^^^^^^^^ found: String - required: scala.collection.mutable.ListBuffer[Int] + required: String scala> buf ++= xs res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) From a7d3f6e1b85f772bb343c5d006fd1e4edc844a93 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 27 Sep 2016 16:52:30 +0200 Subject: [PATCH 38/53] Don't force Message twice in MessageContainer --- src/dotty/tools/dotc/printing/Formatting.scala | 2 +- .../tools/dotc/reporting/diagnostic/MessageContainer.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index 41bd91607ea7..ae3819513577 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -241,7 +241,7 @@ object Formatting { */ def typeDiff(found: Type, expected: Type)(implicit ctx: Context): (String, String) = { val fnd = wrapNonSensical(found, found.show) - val exp = wrapNonSensical(expected, found.show) + val exp = wrapNonSensical(expected, expected.show) (found, expected) match { case (_: RefinedType, _: RefinedType) => diff --git a/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala index 7bb092da222d..a1a99f17d3a6 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala @@ -41,7 +41,7 @@ class MessageContainer( /** The message to report */ def message: String = { if (myMsg == null) { - myMsg = msgFn.msg.replaceAll("\u001B\\[[;\\d]*m", "") + myMsg = contained.msg.replaceAll("\u001B\\[[;\\d]*m", "") if (myMsg.contains(nonSensicalStartTag)) { myIsNonSensical = true // myMsg might be composed of several d"..." invocations -> nested From 7561db09c19bff7871cfd96c327f6f7882480ebd Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 27 Sep 2016 17:27:59 +0200 Subject: [PATCH 39/53] Fix TypeMismatch not getting nonsensical tags in some cases Thanks @smarter! --- .../dotc/reporting/diagnostic/messages.scala | 4 ++-- src/dotty/tools/dotc/typer/ErrorReporting.scala | 6 +++--- tests/repl/errmsgs.check | 16 ++++++++-------- tests/repl/imports.check | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 76b2fd3e9521..034e14267635 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -213,7 +213,7 @@ object messages { } } - case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "")(implicit ctx: Context) + case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "", implicitFailure: String = "")(implicit ctx: Context) extends Message("E006") { val kind = "Type Mismatch" private val (where, printCtx) = Formatting.disambiguateTypes(found, expected) @@ -222,7 +222,7 @@ object messages { s"""|found: $fnd |required: $exp | - |$where""".stripMargin + whyNoMatch + |$where""".stripMargin + whyNoMatch + implicitFailure val explanation = "" } diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index 1fd4fc96e1a7..1d22dc6465ed 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -102,7 +102,7 @@ object ErrorReporting { def patternConstrStr(tree: Tree): String = ??? def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = - errorTree(tree, typeMismatchMsg(normalize(tree.tpe, pt), pt) /*+ implicitFailure.postscript*/) + errorTree(tree, typeMismatchMsg(normalize(tree.tpe, pt), pt, implicitFailure.postscript)) /** A subtype log explaining why `found` does not conform to `expected` */ def whyNoMatchStr(found: Type, expected: Type) = @@ -111,7 +111,7 @@ object ErrorReporting { else "" - def typeMismatchMsg(found: Type, expected: Type) = { + def typeMismatchMsg(found: Type, expected: Type, postScript: String = "") = { // replace constrained polyparams and their typevars by their bounds where possible object reported extends TypeMap { def setVariance(v: Int) = variance = v @@ -133,7 +133,7 @@ object ErrorReporting { val found1 = reported(found) reported.setVariance(-1) val expected1 = reported(expected) - TypeMismatch(found1, expected1, whyNoMatchStr(found, expected)) + TypeMismatch(found1, expected1, whyNoMatchStr(found, expected), postScript) } /** Format `raw` implicitNotFound argument, replacing all diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check index b791123f4366..b8cff5ba2df6 100644 --- a/tests/repl/errmsgs.check +++ b/tests/repl/errmsgs.check @@ -5,34 +5,34 @@ scala> val x: List[String] = List(1) 3:val x: List[String] = List(1) ^ found: Int(1) - required: Int(1) + required: String scala> val y: List[List[String]] = List(List(1)) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 3:val y: List[List[String]] = List(List(1)) ^ found: Int(1) - required: Int(1) + required: String scala> val z: (List[String], List[Int]) = (List(1), List("a")) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 3:val z: (List[String], List[Int]) = (List(1), List("a")) ^ found: Int(1) - required: Int(1) + required: String -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 3:val z: (List[String], List[Int]) = (List(1), List("a")) ^^^ found: String("a") - required: String("a") + required: Int scala> val a: Inv[String] = new Inv(new Inv(1)) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 4:val a: Inv[String] = new Inv(new Inv(1)) ^^^^^ found: Inv[T] - required: Inv[T] + required: String where: T is a type variable with constraint >: Int(1) scala> val b: Inv[String] = new Inv(1) @@ -40,7 +40,7 @@ scala> val b: Inv[String] = new Inv(1) 4:val b: Inv[String] = new Inv(1) ^ found: Int(1) - required: Int(1) + required: String scala> abstract class C { type T @@ -61,7 +61,7 @@ scala> abstract class C { 8: var y: T = x ^ found: C.this.T(C.this.x) - required: C.this.T(C.this.x) + required: T' where: T is a type in class C T' is a type in the initalizer of value s which is an alias of String @@ -69,7 +69,7 @@ scala> abstract class C { 12: val z: T = y ^ found: T(y) - required: T(y) + required: T' where: T is a type in the initalizer of value s which is an alias of String T' is a type in method f which is an alias of Int diff --git a/tests/repl/imports.check b/tests/repl/imports.check index 817adae87bf9..2e8d5dcf967f 100644 --- a/tests/repl/imports.check +++ b/tests/repl/imports.check @@ -11,13 +11,13 @@ scala> buf += xs 10:buf += xs ^^ found: scala.collection.immutable.List[Int](o.xs) - required: scala.collection.immutable.List[Int](o.xs) + required: String -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- 10:buf += xs ^^^^^^^^^ found: String - required: String + required: scala.collection.mutable.ListBuffer[Int] scala> buf ++= xs res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) From 18d63fe71ebc80bc8e111dbb20b6b0ea1f3700af Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 28 Sep 2016 10:39:12 +0200 Subject: [PATCH 40/53] Get rid of `kind` in `MessageContainer` --- .../tools/dotc/interfaces/Diagnostic.java | 3 -- .../tools/dotc/printing/Formatting.scala | 5 ++- .../dotc/reporting/ConsoleReporter.scala | 33 ++++++++++++------- .../dotc/reporting/diagnostic/Message.scala | 18 ++++------ .../diagnostic/MessageContainer.scala | 1 - .../dotc/reporting/diagnostic/messages.scala | 24 +++++--------- 6 files changed, 40 insertions(+), 44 deletions(-) diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java index e62c1193e921..f94b08e9bf37 100644 --- a/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java @@ -14,9 +14,6 @@ public interface Diagnostic { public static final int WARNING = 1; public static final int INFO = 0; - /** @return The kind of message being reported */ - String kind(); - /** @return The message to report */ String message(); diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index ae3819513577..95ac036470f1 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -75,7 +75,7 @@ object Formatting { class SyntaxFormatter(sc: StringContext) extends StringFormatter(sc) { override protected def showArg(arg: Any)(implicit ctx: Context): String = { - arg match { + if (ctx.settings.color.value != "never") arg match { case arg: Showable => val highlighted = SyntaxHighlighting(wrapNonSensical(arg, super.showArg(arg))) @@ -84,8 +84,11 @@ object Formatting { hl.show case hb: HighlightBuffer => hb.toString + case str: String => + new String(SyntaxHighlighting(str).toArray) case _ => super.showArg(arg) } + else super.showArg(arg) } } diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 261357924af5..e4c24643b9cf 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -14,9 +14,8 @@ import diagnostic.{ Message, MessageContainer } import diagnostic.messages._ /** - * This class implements a more Fancy version (with colors!) of the regular - * `ConsoleReporter` - */ + * This class implements a Reporter that displays messages on a text console + */ class ConsoleReporter( reader: BufferedReader = Console.in, writer: PrintWriter = new PrintWriter(Console.err, true) @@ -66,18 +65,22 @@ class ConsoleReporter( .mkString(sys.props("line.separator")) } - def posStr(pos: SourcePosition, kind: String, errorId: String)(implicit ctx: Context) = + def posStr(pos: SourcePosition, diagnosticLevel: String, message: Message)(implicit ctx: Context) = if (pos.exists) Blue({ val file = pos.source.file.toString - val errId = if (errorId != "") s"[$errorId] " else "" + val errId = if (message.errorId != "") s"[${message.errorId}] " else "" + val kind = + if (message.kind == "") diagnosticLevel + else s"${message.kind} $diagnosticLevel" val prefix = s"-- ${errId}${kind}: $file " + prefix + ("-" * math.max(ctx.settings.pageWidth.value - stripColor(prefix).length, 0)) }).show else "" /** Prints the message with the given position indication. */ - def printMessageAndPos(msg: Message, pos: SourcePosition, kind: String)(implicit ctx: Context): Unit = { - printMessage(posStr(pos, kind, msg.errorId)) + def printMessageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(implicit ctx: Context): Unit = { + printMessage(posStr(pos, diagnosticLevel, msg)) if (pos.exists) { val (src, offset) = sourceLine(pos) val marker = columnMarker(pos, offset) @@ -97,15 +100,21 @@ class ConsoleReporter( override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { m match { case m: Error => - printMessageAndPos(m.contained, m.pos, m.kind) + printMessageAndPos(m.contained, m.pos, "Error") if (ctx.settings.prompt.value) displayPrompt() case m: ConditionalWarning if !m.enablingOption.value => + case m: FeatureWarning => + printMessageAndPos(m.contained, m.pos, "Feature Warning") + case m: DeprecationWarning => + printMessageAndPos(m.contained, m.pos, "Deprecation Warning") + case m: UncheckedWarning => + printMessageAndPos(m.contained, m.pos, "Unchecked Warning") case m: MigrationWarning => - printMessageAndPos(m.contained, m.pos, m.kind) + printMessageAndPos(m.contained, m.pos, "Migration Warning") case m: Warning => - printMessageAndPos(m.contained, m.pos, m.kind) - case _ => - printMessageAndPos(m.contained, m.pos, m.kind) + printMessageAndPos(m.contained, m.pos, "Warning") + case m: Info => + printMessageAndPos(m.contained, m.pos, "Info") } if (ctx.shouldExplain(m)) printExplanation(m.contained) diff --git a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index 443dc4de87af..125ac2e1c67f 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -18,10 +18,6 @@ abstract class Message(val errorId: String) { self => def kind: String def explanation: String - def container(c: String) = - if (kind == "") c - else s"$kind $c" - def mapMsg(f: String => String) = new Message(errorId) { val msg = f(self.msg) val kind = self.kind @@ -29,25 +25,25 @@ abstract class Message(val errorId: String) { self => } def error(pos: SourcePosition) = - new Error(self, pos, container("Error"), explanation) + new Error(self, pos, explanation) def warning(pos: SourcePosition) = - new Warning(self, pos, container("Warning"), explanation) + new Warning(self, pos, explanation) def info(pos: SourcePosition) = - new Info(self, pos, container("Info"), explanation) + new Info(self, pos, explanation) def featureWarning(pos: SourcePosition) = - new FeatureWarning(self, pos, container("Feature Warning"), explanation) + new FeatureWarning(self, pos, explanation) def uncheckedWarning(pos: SourcePosition) = - new UncheckedWarning(self, pos, container("Unchecked Warning"), explanation) + new UncheckedWarning(self, pos, explanation) def deprecationWarning(pos: SourcePosition) = - new DeprecationWarning(self, pos, container("Deprecation Warning"), explanation) + new DeprecationWarning(self, pos, explanation) def migrationWarning(pos: SourcePosition) = - new MigrationWarning(self, pos, container("Migration Warning"), explanation) + new MigrationWarning(self, pos, explanation) } class NoExplanation(val msg: String) extends Message("") { diff --git a/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala index a1a99f17d3a6..f49bebd94f07 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala @@ -27,7 +27,6 @@ class MessageContainer( msgFn: => Message, val pos: SourcePosition, val level: Int, - val kind: String, val explanation: String ) extends Exception with interfaces.Diagnostic { import MessageContainer._ diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 034e14267635..26c5622facb8 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -18,66 +18,58 @@ object messages { class Error( msgFn: => Message, pos: SourcePosition, - kind: String, explanation: String = "" - ) extends MessageContainer(msgFn, pos, ERROR, kind, explanation) + ) extends MessageContainer(msgFn, pos, ERROR, explanation) class Warning( msgFn: => Message, pos: SourcePosition, - kind: String, explanation: String = "" - ) extends MessageContainer(msgFn, pos, WARNING, kind, explanation) + ) extends MessageContainer(msgFn, pos, WARNING, explanation) class Info( msgFn: => Message, pos: SourcePosition, - kind: String, explanation: String = "" - ) extends MessageContainer(msgFn, pos, INFO, kind, explanation) + ) extends MessageContainer(msgFn, pos, INFO, explanation) abstract class ConditionalWarning( msgFn: => Message, pos: SourcePosition, - kind: String, explanation: String = "" - ) extends Warning(msgFn, pos, kind, explanation) { + ) extends Warning(msgFn, pos, explanation) { def enablingOption(implicit ctx: Context): Setting[Boolean] } class FeatureWarning( msgFn: => Message, pos: SourcePosition, - kind: String = "Feature Warning", explanation: String = "" - ) extends ConditionalWarning(msgFn, pos, kind, explanation) { + ) extends ConditionalWarning(msgFn, pos, explanation) { def enablingOption(implicit ctx: Context) = ctx.settings.feature } class UncheckedWarning( msgFn: => Message, pos: SourcePosition, - kind: String = "Unchecked Warning", explanation: String = "" - ) extends ConditionalWarning(msgFn, pos, kind, explanation) { + ) extends ConditionalWarning(msgFn, pos, explanation) { def enablingOption(implicit ctx: Context) = ctx.settings.unchecked } class DeprecationWarning( msgFn: => Message, pos: SourcePosition, - kind: String = "Deprecation Warning", explanation: String = "" - ) extends ConditionalWarning(msgFn, pos, kind, explanation) { + ) extends ConditionalWarning(msgFn, pos, explanation) { def enablingOption(implicit ctx: Context) = ctx.settings.deprecation } class MigrationWarning( msgFn: => Message, pos: SourcePosition, - kind: String = "Migration Warning", explanation: String = "" - ) extends ConditionalWarning(msgFn, pos, kind, explanation) { + ) extends ConditionalWarning(msgFn, pos, explanation) { def enablingOption(implicit ctx: Context) = ctx.settings.migration } From f7b8980fad5adb20e9a420a16f5b3416927dccbc Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 28 Sep 2016 11:41:36 +0200 Subject: [PATCH 41/53] Improve documentation for message framework --- src/dotty/tools/dotc/ast/Trees.scala | 4 +- .../dotc/reporting/diagnostic/Message.scala | 40 ++++++++++++++++++- .../dotc/reporting/diagnostic/messages.scala | 14 +++---- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 70701ecd773f..0f9198b795dd 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -3,8 +3,8 @@ package dotc package ast import core._ -import Types._, Names._, Flags._, util.Positions._, Contexts._, Constants._, SymDenotations._, Symbols._ -import Denotations._, StdNames._, Comments._ +import Types._, Names._, Flags._, util.Positions._, Contexts._, Constants._ +import SymDenotations._, Symbols._, Denotations._, StdNames._, Comments._ import annotation.tailrec import language.higherKinds import collection.IndexedSeqOptimized diff --git a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index 125ac2e1c67f..e365851bee38 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -3,10 +3,14 @@ package dotc package reporting package diagnostic -import util.{SourcePosition, NoSourcePosition} +import util.SourcePosition import core.Contexts.Context object Message { + /** This implicit conversion provides a fallback for error messages that have + * not yet been ported to the new scheme. Comment out this `implicit def` to + * see where old errors still exist + */ implicit def toNoExplanation(str: String): Message = new NoExplanation(str) } @@ -14,43 +18,77 @@ object Message { abstract class Message(val errorId: String) { self => import messages._ + /** The `msg` contains the diagnostic message e.g: + * + * > expected: String + * > found: Int + * + * This message wil be placed underneath the position given by the enclosing + * `MessageContainer` + */ def msg: String + + /** The kind of the error message is something like "Syntax" or "Type + * Mismatch" + */ def kind: String + + /** The explanation should provide a detailed description of why the error + * occurred and use examples from the user's own code to illustrate how to + * avoid these errors. + */ def explanation: String + /** It is possible to map `msg` to add details, this is at the loss of + * precision since the type of the resulting `Message` won't be original + * extending class + * + * @return a `Message` with the mapped message + */ def mapMsg(f: String => String) = new Message(errorId) { val msg = f(self.msg) val kind = self.kind val explanation = self.explanation } + /** Enclose this message in an `Error` container */ def error(pos: SourcePosition) = new Error(self, pos, explanation) + /** Enclose this message in an `Warning` container */ def warning(pos: SourcePosition) = new Warning(self, pos, explanation) + /** Enclose this message in an `Info` container */ def info(pos: SourcePosition) = new Info(self, pos, explanation) + /** Enclose this message in an `FeatureWarning` container */ def featureWarning(pos: SourcePosition) = new FeatureWarning(self, pos, explanation) + /** Enclose this message in an `UncheckedWarning` container */ def uncheckedWarning(pos: SourcePosition) = new UncheckedWarning(self, pos, explanation) + /** Enclose this message in an `DeprecationWarning` container */ def deprecationWarning(pos: SourcePosition) = new DeprecationWarning(self, pos, explanation) + /** Enclose this message in an `MigrationWarning` container */ def migrationWarning(pos: SourcePosition) = new MigrationWarning(self, pos, explanation) } +/** The fallback `Message` containing no explanation and having no `kind` */ class NoExplanation(val msg: String) extends Message("") { val explanation = "" val kind = "" } +/** The extractor for `NoExplanation` can be used to check whether any error + * lacks an explanation + */ object NoExplanation { def unapply(m: Message): Option[Message] = if (m.explanation == "") Some(m) diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 26c5622facb8..11c6b43d92b3 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -14,7 +14,7 @@ import printing.Formatting object messages { - /** Message container to be consumed by the reporter ---------------------- */ + // `MessageContainer`s to be consumed by `Reporter` ---------------------- // class Error( msgFn: => Message, pos: SourcePosition, @@ -73,22 +73,22 @@ object messages { def enablingOption(implicit ctx: Context) = ctx.settings.migration } - /** Messages ---------------------------------------------------------------- - * + /** 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 + * EmptyCatchBlock(tree).error(pos) // res: Error + * EmptyCatchBlock(tree).warning(pos) // res: Warning * ``` */ import dotc.ast.Trees._ import dotc.ast.untpd - /** Syntax Errors --------------------------------------------------------- */ + // Syntax Errors ---------------------------------------------------------- // abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree, errNo: String)(implicit ctx: Context) extends Message(errNo) { val explanation = { @@ -166,7 +166,7 @@ object messages { } } - /** Type Errors ----------------------------------------------------------- */ + // Type Errors ------------------------------------------------------------ // case class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context) extends Message("E004") { val kind = "Naming" From e42bb303231b9c91f679c410d51455ba91df024d Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 28 Sep 2016 13:03:04 +0200 Subject: [PATCH 42/53] Change Message#errorId to type Int --- .../tools/dotc/reporting/ConsoleReporter.scala | 7 +++++-- .../tools/dotc/reporting/diagnostic/Message.scala | 6 ++++-- .../tools/dotc/reporting/diagnostic/messages.scala | 14 +++++++------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index e4c24643b9cf..8ded4035ce66 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -10,7 +10,7 @@ import java.io.{ BufferedReader, IOException, PrintWriter } import scala.reflect.internal.util._ import printing.SyntaxHighlighting._ import printing.Highlighting._ -import diagnostic.{ Message, MessageContainer } +import diagnostic.{ Message, MessageContainer, NoExplanation } import diagnostic.messages._ /** @@ -68,7 +68,10 @@ class ConsoleReporter( def posStr(pos: SourcePosition, diagnosticLevel: String, message: Message)(implicit ctx: Context) = if (pos.exists) Blue({ val file = pos.source.file.toString - val errId = if (message.errorId != "") s"[${message.errorId}] " else "" + val errId = + if (message.errorId != NoExplanation.ID) + s"[E${"0" * (3 - message.errorId.toString.length) + message.errorId}] " + else "" val kind = if (message.kind == "") diagnosticLevel else s"${message.kind} $diagnosticLevel" diff --git a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index e365851bee38..f19191f4fcad 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -15,7 +15,7 @@ object Message { new NoExplanation(str) } -abstract class Message(val errorId: String) { self => +abstract class Message(val errorId: Int) { self => import messages._ /** The `msg` contains the diagnostic message e.g: @@ -81,7 +81,7 @@ abstract class Message(val errorId: String) { self => } /** The fallback `Message` containing no explanation and having no `kind` */ -class NoExplanation(val msg: String) extends Message("") { +class NoExplanation(val msg: String) extends Message(NoExplanation.ID) { val explanation = "" val kind = "" } @@ -90,6 +90,8 @@ class NoExplanation(val msg: String) extends Message("") { * lacks an explanation */ object NoExplanation { + final val ID = -1 + def unapply(m: Message): Option[Message] = 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 11c6b43d92b3..4553a2d221cc 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -89,7 +89,7 @@ object messages { import dotc.ast.untpd // Syntax Errors ---------------------------------------------------------- // - abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree, errNo: String)(implicit ctx: Context) + abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree, errNo: Int)(implicit ctx: Context) extends Message(errNo) { val explanation = { val tryString = tryBody match { @@ -121,7 +121,7 @@ object messages { } case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context) - extends EmptyCatchOrFinallyBlock(tryBody, "E001") { + extends EmptyCatchOrFinallyBlock(tryBody, 1) { val kind = "Syntax" val msg = hl"""|The ${"catch"} block does not contain a valid expression, try @@ -129,7 +129,7 @@ object messages { } case class EmptyCatchAndFinallyBlock(tryBody: untpd.Tree)(implicit ctx: Context) - extends EmptyCatchOrFinallyBlock(tryBody, "E002") { + extends EmptyCatchOrFinallyBlock(tryBody, 2) { val kind = "Syntax" val msg = hl"""|A ${"try"} without ${"catch"} or ${"finally"} is equivalent to putting @@ -137,7 +137,7 @@ object messages { } case class DeprecatedWithOperator()(implicit ctx: Context) - extends Message("E003") { + extends Message(3) { val kind = "Syntax" val msg = hl"""${"with"} as a type operator has been deprecated; use `&' instead""" @@ -168,7 +168,7 @@ object messages { // Type Errors ------------------------------------------------------------ // case class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context) - extends Message("E004") { + extends Message(4) { val kind = "Naming" val msg = em"duplicate pattern variable: `${bind.name}`" @@ -195,7 +195,7 @@ object messages { } case class MissingIdent(tree: untpd.Ident, treeKind: String, name: String)(implicit ctx: Context) - extends Message("E005") { + extends Message(5) { val kind = "Missing Identifier" val msg = em"not found: $treeKind$name" @@ -206,7 +206,7 @@ object messages { } case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "", implicitFailure: String = "")(implicit ctx: Context) - extends Message("E006") { + extends Message(6) { val kind = "Type Mismatch" private val (where, printCtx) = Formatting.disambiguateTypes(found, expected) private val (fnd, exp) = Formatting.typeDiff(found, expected)(printCtx) From a0026a09d76c09266c368c7a07a0cc2c5994367e Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 29 Sep 2016 15:27:18 +0200 Subject: [PATCH 43/53] Make reporter hint about existing explanations --- src/dotty/tools/dotc/reporting/ConsoleReporter.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 8ded4035ce66..45933d9548a3 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -120,7 +120,10 @@ class ConsoleReporter( printMessageAndPos(m.contained, m.pos, "Info") } - if (ctx.shouldExplain(m)) printExplanation(m.contained) + if (ctx.shouldExplain(m)) + printExplanation(m.contained) + else if (m.contained.explanation.nonEmpty) + printMessage("\nlonger explanation available when compiling with `-explain`") } def displayPrompt(): Unit = { From 0781b31fa4e3d22cb6a51882b8d632ea9a16ed6f Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 29 Sep 2016 15:45:46 +0200 Subject: [PATCH 44/53] Handle multiline messages in ConsoleReporter --- .../dotc/reporting/ConsoleReporter.scala | 46 ++++++---- src/dotty/tools/dotc/util/SourceFile.scala | 2 +- .../tools/dotc/util/SourcePosition.scala | 11 +++ tests/repl/errmsgs.check | 90 +++++++++---------- tests/repl/imports.check | 16 ++-- 5 files changed, 90 insertions(+), 75 deletions(-) diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 45933d9548a3..d96ff48a4b56 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -32,24 +32,35 @@ class ConsoleReporter( def stripColor(str: String): String = str.replaceAll("\u001B\\[[;\\d]*m", "") - def sourceLine(pos: SourcePosition)(implicit ctx: Context): (String, Int) = { - val lineNum = s"${pos.line}:" - (lineNum + hl"${pos.lineContent.stripLineEnd}", lineNum.length) - } + def sourceLines(pos: SourcePosition)(implicit ctx: Context): (List[String], Int) = { + var maxLen = Int.MinValue + val lines = pos.lines + .map { lineNbr => + val prefix = s"${lineNbr + 1} |" + maxLen = math.max(maxLen, prefix.length) + (prefix, pos.lineContent(lineNbr).stripLineEnd) + } + .map { case (prefix, line) => + val lnum = Red(" " * math.max(0, maxLen - prefix.length) + prefix) + hl"$lnum$line" + } - def columnMarker(pos: SourcePosition, offset: Int)(implicit ctx: Context) = - if (pos.startLine == pos.endLine) { - val whitespace = " " * (pos.startColumn + offset) - val carets = - Red("^" * math.max(1, pos.endColumn - pos.startColumn)) + (lines, maxLen) + } - whitespace + carets.show - } else { - Red(" " * (pos.column + offset) + "^").show + def columnMarker(pos: SourcePosition, offset: Int)(implicit ctx: Context) = { + val prefix = " " * (offset - 1) + val whitespace = " " * pos.startColumn + val carets = Red { + if (pos.startLine == pos.endLine) + "^" * math.max(1, pos.endColumn - pos.startColumn) + else "^" } + s"$prefix|$whitespace${carets.show}" + } + def errorMsg(pos: SourcePosition, msg: String, offset: Int)(implicit ctx: Context) = { - var hasLongLines = false val leastWhitespace = msg.lines.foldLeft(Int.MaxValue) { (minPad, line) => val lineLength = stripColor(line).length val padding = @@ -59,9 +70,8 @@ class ConsoleReporter( else minPad } - msg - .lines - .map { line => " " * leastWhitespace + line } + msg.lines + .map { line => " " * (offset - 1) + "|" + (" " * (leastWhitespace - offset)) + line } .mkString(sys.props("line.separator")) } @@ -85,11 +95,11 @@ class ConsoleReporter( def printMessageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(implicit ctx: Context): Unit = { printMessage(posStr(pos, diagnosticLevel, msg)) if (pos.exists) { - val (src, offset) = sourceLine(pos) + val (srcLines, offset) = sourceLines(pos) val marker = columnMarker(pos, offset) val err = errorMsg(pos, msg.msg, offset) - printMessage(List(src, marker, err).mkString("\n")) + printMessage((srcLines ::: marker :: err :: Nil).mkString("\n")) } else printMessage(msg.msg) } diff --git a/src/dotty/tools/dotc/util/SourceFile.scala b/src/dotty/tools/dotc/util/SourceFile.scala index 8bd0ecfd6706..1d4c9c2ab6f2 100644 --- a/src/dotty/tools/dotc/util/SourceFile.scala +++ b/src/dotty/tools/dotc/util/SourceFile.scala @@ -97,7 +97,7 @@ case class SourceFile(file: AbstractFile, content: Array[Char]) extends interfac private lazy val lineIndices: Array[Int] = calculateLineIndices(content) /** Map line to offset of first character in line */ - def lineToOffset(index : Int): Int = lineIndices(index) + def lineToOffset(index: Int): Int = lineIndices(index) /** A cache to speed up offsetToLine searches to similar lines */ private var lastLine = 0 diff --git a/src/dotty/tools/dotc/util/SourcePosition.scala b/src/dotty/tools/dotc/util/SourcePosition.scala index 68a9b64036d8..d0f9cb887e6d 100644 --- a/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/src/dotty/tools/dotc/util/SourcePosition.scala @@ -14,6 +14,17 @@ extends interfaces.SourcePosition { def point: Int = pos.point /** The line of the position, starting at 0 */ def line: Int = source.offsetToLine(point) + + /** The lines of the position */ + def lines: List[Int] = + List.range(source.offsetToLine(start), source.offsetToLine(end)) match { + case Nil => line :: Nil + case xs => xs + } + + def lineContent(lineNumber: Int): String = + source.lineContent(source.lineToOffset(lineNumber)) + /** The column of the position, starting at 0 */ def column: Int = source.column(point) diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check index b8cff5ba2df6..2a65fd9491d8 100644 --- a/tests/repl/errmsgs.check +++ b/tests/repl/errmsgs.check @@ -2,46 +2,46 @@ scala> class Inv[T](x: T) defined class Inv scala> val x: List[String] = List(1) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- -3:val x: List[String] = List(1) - ^ - found: Int(1) - required: String - +4 |val x: List[String] = List(1) + | ^ + | found: Int(1) + | required: String + | scala> val y: List[List[String]] = List(List(1)) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- -3:val y: List[List[String]] = List(List(1)) - ^ - found: Int(1) - required: String - +4 |val y: List[List[String]] = List(List(1)) + | ^ + | found: Int(1) + | required: String + | scala> val z: (List[String], List[Int]) = (List(1), List("a")) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- -3:val z: (List[String], List[Int]) = (List(1), List("a")) - ^ - found: Int(1) - required: String - +4 |val z: (List[String], List[Int]) = (List(1), List("a")) + | ^ + | found: Int(1) + | required: String + | -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- -3:val z: (List[String], List[Int]) = (List(1), List("a")) - ^^^ - found: String("a") - required: Int - +4 |val z: (List[String], List[Int]) = (List(1), List("a")) + | ^^^ + | found: String("a") + | required: Int + | scala> val a: Inv[String] = new Inv(new Inv(1)) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- -4:val a: Inv[String] = new Inv(new Inv(1)) - ^^^^^ - found: Inv[T] - required: String - - where: T is a type variable with constraint >: Int(1) +5 |val a: Inv[String] = new Inv(new Inv(1)) + | ^^^^^ + | found: Inv[T] + | required: String + | + | where: T is a type variable with constraint >: Int(1) scala> val b: Inv[String] = new Inv(1) -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- -4:val b: Inv[String] = new Inv(1) - ^ - found: Int(1) - required: String - +5 |val b: Inv[String] = new Inv(1) + | ^ + | found: Int(1) + | required: String + | scala> abstract class C { type T val x: T @@ -58,19 +58,19 @@ scala> abstract class C { } } -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- -8: var y: T = x - ^ - found: C.this.T(C.this.x) - required: T' - - where: T is a type in class C - T' is a type in the initalizer of value s which is an alias of String +9 | var y: T = x + | ^ + | found: C.this.T(C.this.x) + | required: T' + | + | where: T is a type in class C + | T' is a type in the initalizer of value s which is an alias of String -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- -12: val z: T = y - ^ - found: T(y) - required: T' - - where: T is a type in the initalizer of value s which is an alias of String - T' is a type in method f which is an alias of Int +13 | val z: T = y + | ^ + | found: T(y) + | required: T' + | + | where: T is a type in the initalizer of value s which is an alias of String + | T' is a type in method f which is an alias of Int scala> :quit diff --git a/tests/repl/imports.check b/tests/repl/imports.check index 2e8d5dcf967f..9743d2b9430e 100644 --- a/tests/repl/imports.check +++ b/tests/repl/imports.check @@ -8,17 +8,11 @@ scala> import o._ import o._ scala> buf += xs -- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- -10:buf += xs - ^^ - found: scala.collection.immutable.List[Int](o.xs) - required: String - --- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- -10:buf += xs - ^^^^^^^^^ - found: String - required: scala.collection.mutable.ListBuffer[Int] - +11 |buf += xs + | ^^ + | found: scala.collection.immutable.List[Int](o.xs) + | required: String + | scala> buf ++= xs res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) scala> :quit From f23ff3abba8663a0e7f64f79b556efd36cc86a83 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 29 Sep 2016 17:10:45 +0200 Subject: [PATCH 45/53] Improve positions for MemberDefs using `namePos` --- src/dotty/tools/dotc/ast/Desugar.scala | 7 +++++-- src/dotty/tools/dotc/parsing/Parsers.scala | 20 ++++++++++++++++--- .../tools/dotc/printing/Formatting.scala | 10 ++++------ .../dotc/reporting/ConsoleReporter.scala | 1 + .../reporting/UniqueMessagePositions.scala | 16 ++++++++------- .../dotc/reporting/diagnostic/messages.scala | 19 +++++++++++++++--- src/dotty/tools/dotc/transform/TailRec.scala | 13 ++++++++---- src/dotty/tools/dotc/typer/Checking.scala | 8 ++++---- src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg/applydynamic_sip.scala | 6 +++--- tests/neg/assignments.scala | 2 +- tests/neg/dynamicApplyDynamicTest3.scala | 2 +- tests/neg/i1424.scala | 2 +- tests/neg/tailcall/t6574.scala | 2 +- tests/repl/errmsgs.check | 16 +++++++-------- tests/repl/imports.check | 2 +- 16 files changed, 82 insertions(+), 46 deletions(-) diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index ecb6a3212f70..af34164dce9d 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -9,6 +9,7 @@ import Decorators._ import language.higherKinds import collection.mutable.ListBuffer import util.Property +import reporting.diagnostic.messages._ object desugar { import untpd._ @@ -71,7 +72,9 @@ object desugar { val defctx = ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next var local = defctx.denotNamed(tp.name).suchThat(_ is ParamOrAccessor).symbol if (local.exists) (defctx.owner.thisType select local).dealias - else throw new Error(s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}") + else throw new java.lang.Error( + s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}" + ) case _ => mapOver(tp) } @@ -281,7 +284,7 @@ object desugar { val constrVparamss = if (constr1.vparamss.isEmpty) { // ensure parameter list is non-empty if (isCaseClass) - ctx.error("case class needs to have at least one parameter list", cdef.pos) + ctx.error(CaseClassMissingParamList(cdef), cdef.namePos) ListOfNil } else constr1.vparamss.nestedMap(toDefParam) diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 620cc1273272..6d40107a8cc0 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -215,6 +215,9 @@ object Parsers { } } + def warning(msg: Message, sourcePos: SourcePosition) = + ctx.warning(msg, sourcePos) + def warning(msg: Message, offset: Int = in.offset) = ctx.warning(msg, source atPos Position(offset)) @@ -1006,6 +1009,7 @@ object Parsers { DoWhile(body, cond) } case TRY => + val tryOffset = in.offset atPos(in.skipToken()) { val body = expr() val handler = @@ -1014,16 +1018,26 @@ object Parsers { expr() } else EmptyTree + // A block ends before RBRACE, if next token is RBRACE, simply add 1 + def realEnd(pos: Position) = + if (in.token == RBRACE) pos.end + 1 + else pos.end + handler match { - case Block(Nil, EmptyTree) => - syntaxError(new EmptyCatchBlock(body), handler.pos) + case Block(Nil, EmptyTree) => syntaxError( + new EmptyCatchBlock(body), + Position(tryOffset, realEnd(handler.pos)) + ) case _ => } val finalizer = if (in.token == FINALLY) { accept(FINALLY); expr() } else { - if (handler.isEmpty) warning(EmptyCatchAndFinallyBlock(body)) + if (handler.isEmpty) warning( + EmptyCatchAndFinallyBlock(body), + source atPos Position(tryOffset, realEnd(body.pos)) + ) EmptyTree } ParsedTry(body, handler, finalizer) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index 95ac036470f1..9cbf079142a1 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -74,9 +74,9 @@ object Formatting { } class SyntaxFormatter(sc: StringContext) extends StringFormatter(sc) { - override protected def showArg(arg: Any)(implicit ctx: Context): String = { - if (ctx.settings.color.value != "never") arg match { - case arg: Showable => + override protected def showArg(arg: Any)(implicit ctx: Context): String = + arg match { + case arg: Showable if ctx.settings.color.value != "never" => val highlighted = SyntaxHighlighting(wrapNonSensical(arg, super.showArg(arg))) new String(highlighted.toArray) @@ -84,12 +84,10 @@ object Formatting { hl.show case hb: HighlightBuffer => hb.toString - case str: String => + case str: String if ctx.settings.color.value != "never" => new String(SyntaxHighlighting(str).toArray) case _ => super.showArg(arg) } - else super.showArg(arg) - } } private def wrapNonSensical(arg: Any, str: String)(implicit ctx: Context): String = { diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index d96ff48a4b56..6ebd53beaec3 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -108,6 +108,7 @@ class ConsoleReporter( |${Blue("Explanation")} |${Blue("===========")}""".stripMargin) printMessage(m.explanation) + if (m.explanation.lastOption != Some('\n')) printMessage("") } override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { diff --git a/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala b/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala index c5ff8cb6b593..6fd971c2a4c0 100644 --- a/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala +++ b/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala @@ -7,10 +7,8 @@ import util.{SourcePosition, SourceFile} import core.Contexts.Context import diagnostic.MessageContainer -/** - * This trait implements `isHidden` so that multiple messages per position - * are suppressed, unless they are of increasing severity. - */ +/** This trait implements `isHidden` so that multiple messages per position + * are suppressed, unless they are of increasing severity. */ trait UniqueMessagePositions extends Reporter { private val positions = new mutable.HashMap[(SourceFile, Int), Int] @@ -21,10 +19,14 @@ trait UniqueMessagePositions extends Reporter { override def isHidden(m: MessageContainer)(implicit ctx: Context): Boolean = super.isHidden(m) || { m.pos.exists && { - positions get (ctx.source, m.pos.point) match { - case Some(level) if level >= m.level => true - case _ => positions((ctx.source, m.pos.point)) = m.level; false + var shouldHide = false + for (pos <- m.pos.start to m.pos.end) { + positions get (ctx.source, pos) match { + case Some(level) if level >= m.level => shouldHide = true + case _ => positions((ctx.source, pos)) = m.level + } } + shouldHide } } } diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 4553a2d221cc..14978449a92c 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -166,9 +166,22 @@ object messages { } } + case class CaseClassMissingParamList(cdef: untpd.TypeDef)(implicit ctx: Context) + extends Message(4) { + val kind = "Syntax" + val msg = + hl"""|A ${"case class"} must have at least one parameter list""" + + val explanation = + hl"""|${cdef.name} must have at least one parameter list, if you would rather + |have a singleton representation of ${cdef.name}, use a "${"case object"}". + |Or, add an explicit `()' as a parameter list to ${cdef.name}.""".stripMargin + } + + // Type Errors ------------------------------------------------------------ // case class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context) - extends Message(4) { + extends Message(5) { val kind = "Naming" val msg = em"duplicate pattern variable: `${bind.name}`" @@ -195,7 +208,7 @@ object messages { } case class MissingIdent(tree: untpd.Ident, treeKind: String, name: String)(implicit ctx: Context) - extends Message(5) { + extends Message(6) { val kind = "Missing Identifier" val msg = em"not found: $treeKind$name" @@ -206,7 +219,7 @@ object messages { } case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "", implicitFailure: String = "")(implicit ctx: Context) - extends Message(6) { + extends Message(7) { val kind = "Type Mismatch" private val (where, printCtx) = Formatting.disambiguateTypes(found, expected) private val (fnd, exp) = Formatting.typeDiff(found, expected)(printCtx) diff --git a/src/dotty/tools/dotc/transform/TailRec.scala b/src/dotty/tools/dotc/transform/TailRec.scala index b345dda61848..065bcb397690 100644 --- a/src/dotty/tools/dotc/transform/TailRec.scala +++ b/src/dotty/tools/dotc/transform/TailRec.scala @@ -145,17 +145,22 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete }) Block(List(labelDef), ref(label).appliedToArgss(vparamss0.map(_.map(x=> ref(x.symbol))))) }} else { - if (mandatory) - ctx.error("TailRec optimisation not applicable, method not tail recursive", dd.pos) + if (mandatory) ctx.error( + "TailRec optimisation not applicable, method not tail recursive", + // FIXME: want to report this error on `dd.namePos`, but + // because of extension method getting a weird pos, it is + // better to report on symbol so there's no overlap + sym.pos + ) dd.rhs } }) } case d: DefDef if d.symbol.hasAnnotation(defn.TailrecAnnot) || methodsWithInnerAnnots.contains(d.symbol) => - ctx.error("TailRec optimisation not applicable, method is neither private nor final so can be overridden", d.pos) + ctx.error("TailRec optimisation not applicable, method is neither private nor final so can be overridden", sym.pos) d case d if d.symbol.hasAnnotation(defn.TailrecAnnot) || methodsWithInnerAnnots.contains(d.symbol) => - ctx.error("TailRec optimisation not applicable, not a method", d.pos) + ctx.error("TailRec optimisation not applicable, not a method", sym.pos) d case _ => tree } diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index b02b0ad21fc8..7ba66e3d8a61 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -45,7 +45,7 @@ object Checking { for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate)) ctx.error( ex"Type argument ${arg.tpe} does not conform to $which bound $bound ${err.whyNoMatchStr(arg.tpe, bound)}", - arg.pos) + arg.pos.focus) } /** Check that type arguments `args` conform to corresponding bounds in `poly` @@ -98,9 +98,9 @@ object Checking { checkWildcardHKApply(tycon.tpe.appliedTo(args.map(_.tpe)), tree.pos) checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply)) case Select(qual, name) if name.isTypeName => - checkRealizable(qual.tpe, qual.pos) + checkRealizable(qual.tpe, qual.pos.focus) case SingletonTypeTree(ref) => - checkRealizable(ref.tpe, ref.pos) + checkRealizable(ref.tpe, ref.pos.focus) case _ => } traverseChildren(tree) @@ -378,7 +378,7 @@ object Checking { if (tp.symbol.is(Private) && !accessBoundary(sym).isContainedIn(tp.symbol.owner)) { errors = (em"non-private $sym refers to private ${tp.symbol}\n in its type signature ${sym.info}", - pos) :: errors + sym.pos) :: errors tp } else mapOver(tp) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index a4dc2f87138d..bbb20bcf5d5d 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1259,7 +1259,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) .withType(dummy.nonMemberTermRef) checkVariance(impl1) - if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.pos) + if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.namePos) val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls) if (ctx.phase.isTyper && cdef1.tpe.derivesFrom(defn.DynamicClass) && !ctx.dynamicsEnabled) { val isRequired = parents1.exists(_.tpe.isRef(defn.DynamicClass)) diff --git a/tests/neg/applydynamic_sip.scala b/tests/neg/applydynamic_sip.scala index 7b131e7ff908..86cff5fc4d57 100644 --- a/tests/neg/applydynamic_sip.scala +++ b/tests/neg/applydynamic_sip.scala @@ -18,9 +18,9 @@ object Test extends App { } val bad1 = new Bad1 bad1.sel // error - bad1.sel(1) // error // error - bad1.sel(a = 1) // error // error - bad1.sel = 1 // error // error + bad1.sel(1) // error + bad1.sel(a = 1) // error + bad1.sel = 1 // error class Bad2 extends Dynamic { def selectDynamic = 1 diff --git a/tests/neg/assignments.scala b/tests/neg/assignments.scala index 5be107717510..273419cb50ba 100644 --- a/tests/neg/assignments.scala +++ b/tests/neg/assignments.scala @@ -13,7 +13,7 @@ object assignments { x = x + 1 x *= 2 - x_= = 2 // error should give missing arguments + // error reassignment to val + x_= = 2 // error should give missing arguments } var c = new C diff --git a/tests/neg/dynamicApplyDynamicTest3.scala b/tests/neg/dynamicApplyDynamicTest3.scala index 61d3c96773b1..d68132b02229 100644 --- a/tests/neg/dynamicApplyDynamicTest3.scala +++ b/tests/neg/dynamicApplyDynamicTest3.scala @@ -3,5 +3,5 @@ import scala.language.dynamics class Foo extends scala.Dynamic object DynamicTest { - new Foo().bazApply _ // error // error + new Foo().bazApply _ // error } diff --git a/tests/neg/i1424.scala b/tests/neg/i1424.scala index 3586260c19a6..8eba3284211b 100644 --- a/tests/neg/i1424.scala +++ b/tests/neg/i1424.scala @@ -1,3 +1,3 @@ class Test { - (x: Int) => x // error: not a legal self type clause // error: package x is not a value // error: package x is not a value + (x: Int) => x // error: not a legal self type clause // error: not found x } diff --git a/tests/neg/tailcall/t6574.scala b/tests/neg/tailcall/t6574.scala index d9ba2882ddab..462ef800f435 100644 --- a/tests/neg/tailcall/t6574.scala +++ b/tests/neg/tailcall/t6574.scala @@ -4,7 +4,7 @@ class Bad[X, Y](val v: Int) extends AnyVal { println("tail") } - @annotation.tailrec final def differentTypeArgs : Unit = { // error + @annotation.tailrec final def differentTypeArgs: Unit = { // error {(); new Bad[String, Unit](0)}.differentTypeArgs // error } } diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check index 2a65fd9491d8..066d98d0f91d 100644 --- a/tests/repl/errmsgs.check +++ b/tests/repl/errmsgs.check @@ -1,34 +1,34 @@ scala> class Inv[T](x: T) defined class Inv scala> val x: List[String] = List(1) --- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- +-- [E007] Type Mismatch Error: ------------------------------------------------------------------------------- 4 |val x: List[String] = List(1) | ^ | found: Int(1) | required: String | scala> val y: List[List[String]] = List(List(1)) --- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- +-- [E007] Type Mismatch Error: ------------------------------------------------------------------------------- 4 |val y: List[List[String]] = List(List(1)) | ^ | found: Int(1) | required: String | scala> val z: (List[String], List[Int]) = (List(1), List("a")) --- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- +-- [E007] Type Mismatch Error: ------------------------------------------------------------------------------- 4 |val z: (List[String], List[Int]) = (List(1), List("a")) | ^ | found: Int(1) | required: String | --- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- +-- [E007] Type Mismatch Error: ------------------------------------------------------------------------------- 4 |val z: (List[String], List[Int]) = (List(1), List("a")) | ^^^ | found: String("a") | required: Int | scala> val a: Inv[String] = new Inv(new Inv(1)) --- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- +-- [E007] Type Mismatch Error: ------------------------------------------------------------------------------- 5 |val a: Inv[String] = new Inv(new Inv(1)) | ^^^^^ | found: Inv[T] @@ -36,7 +36,7 @@ scala> val a: Inv[String] = new Inv(new Inv(1)) | | where: T is a type variable with constraint >: Int(1) scala> val b: Inv[String] = new Inv(1) --- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- +-- [E007] Type Mismatch Error: ------------------------------------------------------------------------------- 5 |val b: Inv[String] = new Inv(1) | ^ | found: Int(1) @@ -57,7 +57,7 @@ scala> abstract class C { } } } --- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- +-- [E007] Type Mismatch Error: ------------------------------------------------------------------------------- 9 | var y: T = x | ^ | found: C.this.T(C.this.x) @@ -65,7 +65,7 @@ scala> abstract class C { | | where: T is a type in class C | T' is a type in the initalizer of value s which is an alias of String --- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- +-- [E007] Type Mismatch Error: ------------------------------------------------------------------------------- 13 | val z: T = y | ^ | found: T(y) diff --git a/tests/repl/imports.check b/tests/repl/imports.check index 9743d2b9430e..26b725637481 100644 --- a/tests/repl/imports.check +++ b/tests/repl/imports.check @@ -7,7 +7,7 @@ defined module o scala> import o._ import o._ scala> buf += xs --- [E006] Type Mismatch Error: ------------------------------------------------------------------------------- +-- [E007] Type Mismatch Error: ------------------------------------------------------------------------------- 11 |buf += xs | ^^ | found: scala.collection.immutable.List[Int](o.xs) From 41d642947c678efdd392bd01c632ecd53fd26b48 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 3 Oct 2016 10:02:00 +0200 Subject: [PATCH 46/53] Get rid of unnecessary fields in `MessageContainer` --- .../tools/dotc/interfaces/Diagnostic.java | 3 -- src/dotty/tools/dotc/ast/Trees.scala | 2 - src/dotty/tools/dotc/reporting/Reporter.scala | 2 +- .../dotc/reporting/diagnostic/Message.scala | 14 +++---- .../diagnostic/MessageContainer.scala | 5 +-- .../dotc/reporting/diagnostic/messages.scala | 40 ++++++++----------- 6 files changed, 26 insertions(+), 40 deletions(-) diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java index f94b08e9bf37..c46360afaa3d 100644 --- a/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java @@ -17,9 +17,6 @@ public interface Diagnostic { /** @return The message to report */ String message(); - /** @return The explanation behind the message */ - String explanation(); - /** @return Level of the diagnostic, can be either ERROR, WARNING or INFO */ int level(); diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 0f9198b795dd..ed3690795763 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -308,8 +308,6 @@ object Trees { if (rawMods.is(Synthetic)) Position(pos.point, pos.point) else Position(pos.point, pos.point + name.length, pos.point) else pos - - } /** A ValDef or DefDef tree */ diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index b969fa8787cd..b38334412cc7 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -35,7 +35,7 @@ trait Reporting { this: Context => if (this.settings.verbose.value) this.echo(msg, pos) def echo(msg: => String, pos: SourcePosition = NoSourcePosition): Unit = - reporter.report(new Info(msg, pos, "Info")) + reporter.report(new Info(msg, pos)) def deprecationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = reporter.report(msg.deprecationWarning(pos)) diff --git a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index f19191f4fcad..bdc899ea8b42 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -53,31 +53,31 @@ abstract class Message(val errorId: Int) { self => /** Enclose this message in an `Error` container */ def error(pos: SourcePosition) = - new Error(self, pos, explanation) + new Error(self, pos) /** Enclose this message in an `Warning` container */ def warning(pos: SourcePosition) = - new Warning(self, pos, explanation) + new Warning(self, pos) /** Enclose this message in an `Info` container */ def info(pos: SourcePosition) = - new Info(self, pos, explanation) + new Info(self, pos) /** Enclose this message in an `FeatureWarning` container */ def featureWarning(pos: SourcePosition) = - new FeatureWarning(self, pos, explanation) + new FeatureWarning(self, pos) /** Enclose this message in an `UncheckedWarning` container */ def uncheckedWarning(pos: SourcePosition) = - new UncheckedWarning(self, pos, explanation) + new UncheckedWarning(self, pos) /** Enclose this message in an `DeprecationWarning` container */ def deprecationWarning(pos: SourcePosition) = - new DeprecationWarning(self, pos, explanation) + new DeprecationWarning(self, pos) /** Enclose this message in an `MigrationWarning` container */ def migrationWarning(pos: SourcePosition) = - new MigrationWarning(self, pos, explanation) + new MigrationWarning(self, pos) } /** The fallback `Message` containing no explanation and having no `kind` */ diff --git a/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala index f49bebd94f07..7fd50bfdcdb5 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala @@ -15,7 +15,7 @@ object MessageContainer { implicit class MessageContext(val c: Context) extends AnyVal { def shouldExplain(cont: MessageContainer): Boolean = { implicit val ctx: Context = c - cont.explanation match { + cont.contained.explanation match { case "" => false case _ => ctx.settings.explain.value } @@ -26,8 +26,7 @@ object MessageContainer { class MessageContainer( msgFn: => Message, val pos: SourcePosition, - val level: Int, - val explanation: String + val level: Int ) extends Exception with interfaces.Diagnostic { import MessageContainer._ private var myMsg: String = null diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 14978449a92c..e2b99af4185d 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -17,59 +17,51 @@ object messages { // `MessageContainer`s to be consumed by `Reporter` ---------------------- // class Error( msgFn: => Message, - pos: SourcePosition, - explanation: String = "" - ) extends MessageContainer(msgFn, pos, ERROR, explanation) + pos: SourcePosition + ) extends MessageContainer(msgFn, pos, ERROR) class Warning( msgFn: => Message, - pos: SourcePosition, - explanation: String = "" - ) extends MessageContainer(msgFn, pos, WARNING, explanation) + pos: SourcePosition + ) extends MessageContainer(msgFn, pos, WARNING) class Info( msgFn: => Message, - pos: SourcePosition, - explanation: String = "" - ) extends MessageContainer(msgFn, pos, INFO, explanation) + pos: SourcePosition + ) extends MessageContainer(msgFn, pos, INFO) abstract class ConditionalWarning( msgFn: => Message, - pos: SourcePosition, - explanation: String = "" - ) extends Warning(msgFn, pos, explanation) { + pos: SourcePosition + ) extends Warning(msgFn, pos) { def enablingOption(implicit ctx: Context): Setting[Boolean] } class FeatureWarning( msgFn: => Message, - pos: SourcePosition, - explanation: String = "" - ) extends ConditionalWarning(msgFn, pos, explanation) { + pos: SourcePosition + ) extends ConditionalWarning(msgFn, pos) { def enablingOption(implicit ctx: Context) = ctx.settings.feature } class UncheckedWarning( msgFn: => Message, - pos: SourcePosition, - explanation: String = "" - ) extends ConditionalWarning(msgFn, pos, explanation) { + pos: SourcePosition + ) extends ConditionalWarning(msgFn, pos) { def enablingOption(implicit ctx: Context) = ctx.settings.unchecked } class DeprecationWarning( msgFn: => Message, - pos: SourcePosition, - explanation: String = "" - ) extends ConditionalWarning(msgFn, pos, explanation) { + pos: SourcePosition + ) extends ConditionalWarning(msgFn, pos) { def enablingOption(implicit ctx: Context) = ctx.settings.deprecation } class MigrationWarning( msgFn: => Message, - pos: SourcePosition, - explanation: String = "" - ) extends ConditionalWarning(msgFn, pos, explanation) { + pos: SourcePosition + ) extends ConditionalWarning(msgFn, pos) { def enablingOption(implicit ctx: Context) = ctx.settings.migration } From 29d19ba41622b1a904d4960869866c0967db6c37 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 3 Oct 2016 14:18:46 +0200 Subject: [PATCH 47/53] Correct line extraction from SourcePosition --- src/dotty/tools/dotc/parsing/Parsers.scala | 24 +++++++++---------- .../tools/dotc/util/SourcePosition.scala | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 6d40107a8cc0..f22556f27d00 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1012,22 +1012,20 @@ object Parsers { val tryOffset = in.offset atPos(in.skipToken()) { val body = expr() - val handler = + val (handler, handlerStart) = if (in.token == CATCH) { + val pos = in.offset in.nextToken() - expr() - } else EmptyTree - - // A block ends before RBRACE, if next token is RBRACE, simply add 1 - def realEnd(pos: Position) = - if (in.token == RBRACE) pos.end + 1 - else pos.end + (expr(), pos) + } else (EmptyTree, -1) handler match { - case Block(Nil, EmptyTree) => syntaxError( - new EmptyCatchBlock(body), - Position(tryOffset, realEnd(handler.pos)) - ) + case Block(Nil, EmptyTree) => + assert(handlerStart != -1) + syntaxError( + new EmptyCatchBlock(body), + Position(handlerStart, handler.pos.end) + ) case _ => } @@ -1036,7 +1034,7 @@ object Parsers { else { if (handler.isEmpty) warning( EmptyCatchAndFinallyBlock(body), - source atPos Position(tryOffset, realEnd(body.pos)) + source atPos Position(tryOffset, body.pos.end) ) EmptyTree } diff --git a/src/dotty/tools/dotc/util/SourcePosition.scala b/src/dotty/tools/dotc/util/SourcePosition.scala index d0f9cb887e6d..a64f44417124 100644 --- a/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/src/dotty/tools/dotc/util/SourcePosition.scala @@ -17,7 +17,7 @@ extends interfaces.SourcePosition { /** The lines of the position */ def lines: List[Int] = - List.range(source.offsetToLine(start), source.offsetToLine(end)) match { + List.range(source.offsetToLine(start), source.offsetToLine(end + 1)) match { case Nil => line :: Nil case xs => xs } From d2b620541b18bb50d2a2b89194e1778c64bba567 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 3 Oct 2016 18:21:28 +0200 Subject: [PATCH 48/53] Insert message "inline" into multiline code at point --- .../tools/dotc/printing/Formatting.scala | 10 ++++-- .../tools/dotc/printing/Highlighting.scala | 4 --- .../dotc/reporting/ConsoleReporter.scala | 12 ++++--- .../dotc/reporting/diagnostic/Message.scala | 8 +++++ .../dotc/reporting/diagnostic/messages.scala | 34 ++++++------------- .../tools/dotc/util/SourcePosition.scala | 6 ++++ 6 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index 9cbf079142a1..76d2bdc18440 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -10,7 +10,7 @@ import scala.annotation.switch import scala.util.control.NonFatal import reporting.diagnostic.MessageContainer import util.DiffUtil -import Highlighting.{ highlightToString => _, _ } +import Highlighting._ import SyntaxHighlighting._ object Formatting { @@ -165,7 +165,11 @@ object Formatting { } } - /** Turns a `Seen => String` to produce a `where: T is...` clause */ + /** Turns a `Seen` into a `String` to produce an explanation for types on the + * form `where: T is...` + * + * @return string disambiguating types + */ private def explanations(seen: Seen)(implicit ctx: Context): String = { def needsExplanation(entry: Recorded) = entry match { case param: PolyParam => ctx.typerState.constraint.contains(param) @@ -245,7 +249,7 @@ object Formatting { val exp = wrapNonSensical(expected, expected.show) (found, expected) match { - case (_: RefinedType, _: RefinedType) => + case (_: RefinedType, _: RefinedType) if ctx.settings.color.value != "never" => DiffUtil.mkColoredTypeDiff(fnd, exp) case _ => (hl"$fnd", hl"$exp") diff --git a/src/dotty/tools/dotc/printing/Highlighting.scala b/src/dotty/tools/dotc/printing/Highlighting.scala index 13e55722fa34..3bda7fb7ae64 100644 --- a/src/dotty/tools/dotc/printing/Highlighting.scala +++ b/src/dotty/tools/dotc/printing/Highlighting.scala @@ -9,10 +9,6 @@ object Highlighting { implicit def highlightShow(h: Highlight)(implicit ctx: Context): String = h.show - implicit def highlightToString(h: Highlight): String = - h.toString - implicit def hbufToString(hb: HighlightBuffer): String = - hb.toString abstract class Highlight(private val highlight: String) { def text: String diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 6ebd53beaec3..82edd6a83a95 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -32,9 +32,10 @@ class ConsoleReporter( def stripColor(str: String): String = str.replaceAll("\u001B\\[[;\\d]*m", "") - def sourceLines(pos: SourcePosition)(implicit ctx: Context): (List[String], Int) = { + def sourceLines(pos: SourcePosition)(implicit ctx: Context): (List[String], List[String], Int) = { var maxLen = Int.MinValue - val lines = pos.lines + def render(xs: List[Int]) = + xs.map(pos.source.offsetToLine(_)) .map { lineNbr => val prefix = s"${lineNbr + 1} |" maxLen = math.max(maxLen, prefix.length) @@ -45,7 +46,8 @@ class ConsoleReporter( hl"$lnum$line" } - (lines, maxLen) + val (before, after) = pos.beforeAndAfterPoint + (render(before), render(after), maxLen) } def columnMarker(pos: SourcePosition, offset: Int)(implicit ctx: Context) = { @@ -95,11 +97,11 @@ class ConsoleReporter( def printMessageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(implicit ctx: Context): Unit = { printMessage(posStr(pos, diagnosticLevel, msg)) if (pos.exists) { - val (srcLines, offset) = sourceLines(pos) + val (srcBefore, srcAfter, offset) = sourceLines(pos) val marker = columnMarker(pos, offset) val err = errorMsg(pos, msg.msg, offset) - printMessage((srcLines ::: marker :: err :: Nil).mkString("\n")) + printMessage((srcBefore ::: marker :: err :: srcAfter).mkString("\n")) } else printMessage(msg.msg) } diff --git a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index bdc899ea8b42..8b1f6567309f 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -15,6 +15,14 @@ object Message { new NoExplanation(str) } +/** A `Message` contains all semantic information necessary to easily + * comprehend what caused the message to be logged. Each message can be turned + * into a `MessageContainer` which contains the log level and can later be + * consumed by a subclass of `Reporter`. + * + * @param errorId a unique number identifying the message, this will later be + * used to reference documentation online + */ abstract class Message(val errorId: Int) { self => import messages._ diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index e2b99af4185d..cc062ff92bde 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -90,8 +90,10 @@ object messages { } val code1 = - s"""|try $tryString catch { - | case t: Throwable => ??? + s"""|import scala.util.control.NonFatal + | + |try $tryString catch { + | case NonFatal(e) => ??? |}""".stripMargin val code2 = @@ -108,7 +110,10 @@ object messages { |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 + |$code2 + | + |It is recommended to use the ${"NonFatal"} extractor to catch all exceptions as it + |correctly handles transfer functions like ${"return"}.""".stripMargin } } @@ -133,29 +138,10 @@ object messages { val kind = "Syntax" val msg = hl"""${"with"} as a type operator has been deprecated; use `&' instead""" - val explanation = { - val codeBlock1 = - """|trait A { - | type T = Int - |} - | - |trait B { - | type T = Double - |}""".stripMargin - + val explanation = hl"""|Dotty introduces intersection types - `&' types. These replace the |use of the ${"with"} keyword. There are a few differences in - |semantics between intersection types and using `${"with"}'. - | - |`${"A with B"}' is ordered, `${"A & B"}' is not. - | - |In: - | - |$codeBlock1 - | - |The type of `${"T"}' in `${"A with B"}' is ${"Int"} whereas in `${"A & B"}' - |the type of `${"T"}' is ${"Int & Double"}.""".stripMargin - } + |semantics between intersection types and using `${"with"}'.""".stripMargin } case class CaseClassMissingParamList(cdef: untpd.TypeDef)(implicit ctx: Context) diff --git a/src/dotty/tools/dotc/util/SourcePosition.scala b/src/dotty/tools/dotc/util/SourcePosition.scala index a64f44417124..595ea34ca168 100644 --- a/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/src/dotty/tools/dotc/util/SourcePosition.scala @@ -22,9 +22,15 @@ extends interfaces.SourcePosition { case xs => xs } + def lineOffsets: List[Int] = + lines.map(source.lineToOffset(_)) + def lineContent(lineNumber: Int): String = source.lineContent(source.lineToOffset(lineNumber)) + def beforeAndAfterPoint: (List[Int], List[Int]) = + lineOffsets.partition(_ < point) + /** The column of the position, starting at 0 */ def column: Int = source.column(point) From e754a2d790e0cf5ab2c1480166a42cbb908add0f Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 4 Oct 2016 15:51:15 +0200 Subject: [PATCH 49/53] Change `typeDiff` to highlight changes less than 50% --- src/dotty/tools/dotc/printing/Formatting.scala | 12 ++++++------ src/dotty/tools/dotc/util/DiffUtil.scala | 15 ++++++++++----- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index 76d2bdc18440..719c23e847e5 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -242,17 +242,17 @@ object Formatting { * correct `Context` for printing should also be passed when calling the * method. * - * @return the (found, expected) with coloring to highlight the difference + * @return the (found, expected, changePercentage) with coloring to + * highlight the difference */ def typeDiff(found: Type, expected: Type)(implicit ctx: Context): (String, String) = { val fnd = wrapNonSensical(found, found.show) val exp = wrapNonSensical(expected, expected.show) - (found, expected) match { - case (_: RefinedType, _: RefinedType) if ctx.settings.color.value != "never" => - DiffUtil.mkColoredTypeDiff(fnd, exp) - case _ => - (hl"$fnd", hl"$exp") + DiffUtil.mkColoredTypeDiff(fnd, exp) match { + case _ if ctx.settings.color.value == "never" => (fnd, exp) + case (fnd, exp, change) if change < 0.5 => (fnd, exp) + case _ => (fnd, exp) } } } diff --git a/src/dotty/tools/dotc/util/DiffUtil.scala b/src/dotty/tools/dotc/util/DiffUtil.scala index 8bb39c88aa4c..b55aee719045 100644 --- a/src/dotty/tools/dotc/util/DiffUtil.scala +++ b/src/dotty/tools/dotc/util/DiffUtil.scala @@ -32,8 +32,9 @@ object DiffUtil { } - /** @return a tuple of the (found, expected) diffs as strings */ - def mkColoredTypeDiff(found: String, expected: String): (String, String) = { + /** @return a tuple of the (found, expected, changedPercentage) diffs as strings */ + def mkColoredTypeDiff(found: String, expected: String): (String, String, Double) = { + var totalChange = 0 val foundTokens = splitTokens(found, Nil).toArray val expectedTokens = splitTokens(expected, Nil).toArray @@ -42,15 +43,19 @@ object DiffUtil { val exp = diffExp.collect { case Unmodified(str) => str - case Inserted(str) => ADDITION_COLOR + str + ANSI_DEFAULT + case Inserted(str) => + totalChange += str.length + ADDITION_COLOR + str + ANSI_DEFAULT }.mkString val fnd = diffAct.collect { case Unmodified(str) => str - case Inserted(str) => DELETION_COLOR + str + ANSI_DEFAULT + case Inserted(str) => + totalChange += str.length + DELETION_COLOR + str + ANSI_DEFAULT }.mkString - (fnd, exp) + (fnd, exp, totalChange.toDouble / (expected.length + found.length)) } def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = { From 45a2df1033c8cb13a3d80f429e31872e3d4174e1 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 7 Oct 2016 13:47:15 +0200 Subject: [PATCH 50/53] Fix TreeChecker mismatch string --- src/dotty/tools/dotc/transform/TreeChecker.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 4b3927ccfcf9..fd8e41dc3fa9 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -425,10 +425,11 @@ class TreeChecker extends Phase with SymTransformer { !tree.isEmpty && !isPrimaryConstructorReturn && !pt.isInstanceOf[FunProto]) - assert(tree.tpe <:< pt, - i"""error at ${sourcePos(tree.pos)} - |${err.typeMismatchStr(tree.tpe, pt)} - |tree = $tree""") + assert(tree.tpe <:< pt, { + val mismatch = err.typeMismatchMsg(tree.tpe, pt) + i"""|${mismatch.msg} + |tree = $tree""".stripMargin + }) tree } } From b9e03b8d9246134afeacc925651139c079275479 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 7 Oct 2016 13:47:27 +0200 Subject: [PATCH 51/53] Remove unnecessary printing of hints for `-explain` --- src/dotty/tools/dotc/printing/Formatting.scala | 2 +- .../tools/dotc/reporting/ConsoleReporter.scala | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala index 719c23e847e5..e7968b14a949 100644 --- a/src/dotty/tools/dotc/printing/Formatting.scala +++ b/src/dotty/tools/dotc/printing/Formatting.scala @@ -90,7 +90,7 @@ object Formatting { } } - private def wrapNonSensical(arg: Any, str: String)(implicit ctx: Context): String = { + private def wrapNonSensical(arg: Any /* Type | Symbol */, str: String)(implicit ctx: Context): String = { import MessageContainer._ def isSensical(arg: Any): Boolean = arg match { case tpe: Type => diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 82edd6a83a95..da3df6984954 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -94,7 +94,7 @@ class ConsoleReporter( }).show else "" /** Prints the message with the given position indication. */ - def printMessageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(implicit ctx: Context): Unit = { + def printMessageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(implicit ctx: Context): Boolean = { printMessage(posStr(pos, diagnosticLevel, msg)) if (pos.exists) { val (srcBefore, srcAfter, offset) = sourceLines(pos) @@ -103,6 +103,7 @@ class ConsoleReporter( printMessage((srcBefore ::: marker :: err :: srcAfter).mkString("\n")) } else printMessage(msg.msg) + true } def printExplanation(m: Message)(implicit ctx: Context): Unit = { @@ -114,11 +115,13 @@ class ConsoleReporter( } override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { - m match { + val didPrint = m match { case m: Error => - printMessageAndPos(m.contained, m.pos, "Error") + val didPrint = printMessageAndPos(m.contained, m.pos, "Error") if (ctx.settings.prompt.value) displayPrompt() + didPrint case m: ConditionalWarning if !m.enablingOption.value => + false case m: FeatureWarning => printMessageAndPos(m.contained, m.pos, "Feature Warning") case m: DeprecationWarning => @@ -133,9 +136,9 @@ class ConsoleReporter( printMessageAndPos(m.contained, m.pos, "Info") } - if (ctx.shouldExplain(m)) + if (didPrint && ctx.shouldExplain(m)) printExplanation(m.contained) - else if (m.contained.explanation.nonEmpty) + else if (didPrint && m.contained.explanation.nonEmpty) printMessage("\nlonger explanation available when compiling with `-explain`") } From d490f7d9ebe072b5ade305265100c7bc433885a9 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Sat, 8 Oct 2016 00:22:08 +0200 Subject: [PATCH 52/53] Add Levenshtein distance for member values and types --- .../dotc/reporting/diagnostic/messages.scala | 71 ++++++++++++++++++- src/dotty/tools/dotc/typer/TypeAssigner.scala | 4 +- tests/repl/errmsgs.check | 5 ++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index cc062ff92bde..9cfac4801853 100644 --- a/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -199,13 +199,78 @@ object messages { case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "", implicitFailure: String = "")(implicit ctx: Context) extends Message(7) { val kind = "Type Mismatch" - private val (where, printCtx) = Formatting.disambiguateTypes(found, expected) - private val (fnd, exp) = Formatting.typeDiff(found, expected)(printCtx) - val msg = + val msg = { + val (where, printCtx) = Formatting.disambiguateTypes(found, expected) + val (fnd, exp) = Formatting.typeDiff(found, expected)(printCtx) s"""|found: $fnd |required: $exp | |$where""".stripMargin + whyNoMatch + implicitFailure + } + + val explanation = "" + } + + case class NotAMember(site: Type, name: Name, selected: String)(implicit ctx: Context) + extends Message(8) { + val kind = "Member Not Found" + + val msg = { + import core.Flags._ + val maxDist = 3 + val decls = site.decls.flatMap { sym => + if (sym.is(Synthetic | PrivateOrLocal) || sym.isConstructor) Nil + else List((sym.name.show, sym)) + } + + // Calculate Levenshtein distance + def distance(n1: Iterable[_], n2: Iterable[_]) = + n1.foldLeft(List.range(0, n2.size)) { (prev, x) => + (prev zip prev.tail zip n2).scanLeft(prev.head + 1) { + case (h, ((d, v), y)) => math.min( + math.min(h + 1, v + 1), + if (x == y) d else d + 1 + ) + } + }.last + + // Count number of wrong characters + def incorrectChars(x: (String, Int, Symbol)): (String, Symbol, Int) = { + val (currName, _, sym) = x + val matching = name.show.zip(currName).foldLeft(0) { + case (acc, (x,y)) => if (x != y) acc + 1 else acc + } + (currName, sym, matching) + } + + // Get closest match in `site` + val closest = + decls + .map { case (n, sym) => (n, distance(n, name.show), sym) } + .collect { case (n, dist, sym) if dist <= maxDist => (n, dist, sym) } + .groupBy(_._2).toList + .sortBy(_._1) + .headOption.map(_._2).getOrElse(Nil) + .map(incorrectChars).toList + .sortBy(_._3) + .take(1).map { case (n, sym, _) => (n, sym) } + + val siteName = site match { + case site: NamedType => site.name.show + case site => i"$site" + } + + val closeMember = closest match { + case (n, sym) :: Nil => hl""" - did you mean `${s"$siteName.$n"}`?""" + case Nil => "" + case _ => assert( + false, + "Could not single out one distinct member to match on input with" + ) + } + + ex"$selected `$name` is not a member of $site$closeMember" + } val explanation = "" } diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 0c55d977e67a..262d3f7312ef 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -12,6 +12,8 @@ import config.Printers.typr import ast.Trees._ import NameOps._ import collection.mutable +import reporting.diagnostic.Message +import reporting.diagnostic.messages._ trait TypeAssigner { import tpd._ @@ -220,7 +222,7 @@ trait TypeAssigner { else "" ctx.error( if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" - else ex"$kind $name is not a member of $site$addendum", + else NotAMember(site, name, kind), pos) } ErrorType diff --git a/tests/repl/errmsgs.check b/tests/repl/errmsgs.check index 066d98d0f91d..2bcb40eb0c64 100644 --- a/tests/repl/errmsgs.check +++ b/tests/repl/errmsgs.check @@ -73,4 +73,9 @@ scala> abstract class C { | | where: T is a type in the initalizer of value s which is an alias of String | T' is a type in method f which is an alias of Int +scala> class Foo() { def bar: Int = 1 }; val foo = new Foo(); foo.barr +-- [E008] Member Not Found Error: ---------------------------------------------------------------------------- +4 |class Foo() { def bar: Int = 1 }; val foo = new Foo(); foo.barr + | ^^^^^^^^ + | value `barr` is not a member of Foo(foo) - did you mean `foo.bar`? scala> :quit From 550c643a2a9ad785318e9a727ddc82e3e2f244aa Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 10 Oct 2016 21:31:53 +0200 Subject: [PATCH 53/53] Adopt delegating reporter to new scheme --- .../main/scala/xsbt/DelegatingReporter.scala | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/bridge/src/main/scala/xsbt/DelegatingReporter.scala b/bridge/src/main/scala/xsbt/DelegatingReporter.scala index 726570d71d91..446ef287ec8c 100644 --- a/bridge/src/main/scala/xsbt/DelegatingReporter.scala +++ b/bridge/src/main/scala/xsbt/DelegatingReporter.scala @@ -6,6 +6,8 @@ package xsbt import dotty.tools._ import dotc._ import reporting._ +import reporting.diagnostic.MessageContainer +import reporting.diagnostic.messages import core.Contexts._ import xsbti.{Maybe, Position} @@ -16,19 +18,19 @@ final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter override def printSummary(implicit ctx: Context): Unit = delegate.printSummary() - def doReport(d: Diagnostic)(implicit ctx: Context): Unit = { - val severity = - d match { - case _: Reporter.Error => xsbti.Severity.Error - case _: Reporter.Warning => xsbti.Severity.Warn + def doReport(cont: MessageContainer)(implicit ctx: Context): Unit = { + val severity = + cont match { + case _: messages.Error => xsbti.Severity.Error + case _: messages.Warning => xsbti.Severity.Warn case _ => xsbti.Severity.Info } - val pos = - if (d.pos.exists) Some(d.pos) + val pos = + if (cont.pos.exists) Some(cont.pos) else None - val file = - if (d.pos.source.file.exists) Option(d.pos.source.file.file) + val file = + if (cont.pos.source.file.exists) Option(cont.pos.source.file.file) else None val offset0 = pos.map(_.point) @@ -43,16 +45,16 @@ final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter def sourcePath: Maybe[String] = maybe(file.map(_.getPath)) } - delegate.log(position, d.message, severity) + delegate.log(position, cont.message, severity) } - private[this] def maybe[T](opt: Option[T]): Maybe[T] = opt match { + private[this] def maybe[T](opt: Option[T]): Maybe[T] = opt match { case None => Maybe.nothing[T] case Some(s) => Maybe.just[T](s) } import java.lang.{ Integer => I } - private[this] def maybeInt(opt: Option[Int]): Maybe[I] = opt match { + private[this] def maybeInt(opt: Option[Int]): Maybe[I] = opt match { case None => Maybe.nothing[I] case Some(s) => Maybe.just[I](s) } -} \ No newline at end of file +}