aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFelix Mulder <felix.mulder@gmail.com>2016-10-10 22:38:00 +0200
committerGitHub <noreply@github.com>2016-10-10 22:38:00 +0200
commit618cebbec6fd5727b51974fa372ae984c03f9f99 (patch)
tree969d3dc103a6ad06f0ad263a692ee726afd85b46 /src
parent9ef03414b78622ce181d300c1bf7f607fe4ddea8 (diff)
parent550c643a2a9ad785318e9a727ddc82e3e2f244aa (diff)
downloaddotty-618cebbec6fd5727b51974fa372ae984c03f9f99.tar.gz
dotty-618cebbec6fd5727b51974fa372ae984c03f9f99.tar.bz2
dotty-618cebbec6fd5727b51974fa372ae984c03f9f99.zip
Merge pull request #1521 from felixmulder/topic/better-errormessages
Operation beautify error messages
Diffstat (limited to 'src')
-rw-r--r--src/dotty/tools/dotc/ast/Desugar.scala7
-rw-r--r--src/dotty/tools/dotc/ast/Trees.scala6
-rw-r--r--src/dotty/tools/dotc/config/ScalaSettings.scala1
-rw-r--r--src/dotty/tools/dotc/core/Decorators.scala4
-rw-r--r--src/dotty/tools/dotc/core/TypeOps.scala2
-rw-r--r--src/dotty/tools/dotc/parsing/Parsers.scala44
-rw-r--r--src/dotty/tools/dotc/printing/Formatting.scala199
-rw-r--r--src/dotty/tools/dotc/printing/Highlighting.scala77
-rw-r--r--src/dotty/tools/dotc/printing/SyntaxHighlighting.scala88
-rw-r--r--src/dotty/tools/dotc/repl/AmmoniteReader.scala6
-rw-r--r--src/dotty/tools/dotc/repl/CompilingInterpreter.scala30
-rw-r--r--src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala5
-rw-r--r--src/dotty/tools/dotc/reporting/ConsoleReporter.scala151
-rw-r--r--src/dotty/tools/dotc/reporting/Diagnostic.scala47
-rw-r--r--src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala7
-rw-r--r--src/dotty/tools/dotc/reporting/Reporter.scala85
-rw-r--r--src/dotty/tools/dotc/reporting/StoreReporter.scala15
-rw-r--r--src/dotty/tools/dotc/reporting/ThrowingReporter.scala8
-rw-r--r--src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala23
-rw-r--r--src/dotty/tools/dotc/reporting/diagnostic/Message.scala106
-rw-r--r--src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala74
-rw-r--r--src/dotty/tools/dotc/reporting/diagnostic/messages.scala277
-rw-r--r--src/dotty/tools/dotc/transform/TailRec.scala13
-rw-r--r--src/dotty/tools/dotc/transform/TreeChecker.scala9
-rw-r--r--src/dotty/tools/dotc/typer/Applications.scala15
-rw-r--r--src/dotty/tools/dotc/typer/Checking.scala8
-rw-r--r--src/dotty/tools/dotc/typer/ErrorReporting.scala15
-rw-r--r--src/dotty/tools/dotc/typer/RefChecks.scala2
-rw-r--r--src/dotty/tools/dotc/typer/TypeAssigner.scala4
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala35
-rw-r--r--src/dotty/tools/dotc/util/DiffUtil.scala33
-rw-r--r--src/dotty/tools/dotc/util/SourceFile.scala2
-rw-r--r--src/dotty/tools/dotc/util/SourcePosition.scala17
33 files changed, 1085 insertions, 330 deletions
diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala
index ecb6a3212..af34164dc 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/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala
index 70701ecd7..ed3690795 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
@@ -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/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala
index ff17a9939..872cb0667 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 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/core/Decorators.scala b/src/dotty/tools/dotc/core/Decorators.scala
index 3bf17730a..b0f1f0c98 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/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala
index 5ba9a3351..bee69ae69 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
diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala
index 9aadf0c61..f22556f27 100644
--- a/src/dotty/tools/dotc/parsing/Parsers.scala
+++ b/src/dotty/tools/dotc/parsing/Parsers.scala
@@ -27,6 +27,8 @@ import rewrite.Rewrites.patch
object Parsers {
import ast.untpd._
+ import reporting.diagnostic.Message
+ import reporting.diagnostic.messages._
case class OpInfo(operand: Tree, operator: Name, offset: Offset)
@@ -97,7 +99,7 @@ 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(msg: Message, offset: Int = in.offset): Unit =
if (offset > lastErrorOffset) {
syntaxError(msg, Position(offset))
lastErrorOffset = in.offset
@@ -106,7 +108,7 @@ object Parsers {
/** Unconditionally issue an error at given position, without
* updating lastErrorOffset.
*/
- def syntaxError(msg: String, pos: Position): Unit =
+ def syntaxError(msg: Message, pos: Position): Unit =
ctx.error(msg, source atPos pos)
}
@@ -213,20 +215,23 @@ object Parsers {
}
}
- def warning(msg: String, offset: Int = in.offset) =
+ 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))
- 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)
@@ -732,7 +737,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())
}
@@ -1004,28 +1009,33 @@ object Parsers {
DoWhile(body, cond)
}
case TRY =>
+ 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
+ (expr(), pos)
+ } else (EmptyTree, -1)
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) =>
+ assert(handlerStart != -1)
+ syntaxError(
+ new EmptyCatchBlock(body),
+ Position(handlerStart, handler.pos.end)
+ )
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),
+ source atPos Position(tryOffset, body.pos.end)
+ )
EmptyTree
}
ParsedTry(body, handler, finalizer)
diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala
index 8921c56a3..e7968b14a 100644
--- a/src/dotty/tools/dotc/printing/Formatting.scala
+++ b/src/dotty/tools/dotc/printing/Formatting.scala
@@ -8,7 +8,10 @@ import collection.Map
import Decorators._
import scala.annotation.switch
import scala.util.control.NonFatal
-import reporting.Diagnostic
+import reporting.diagnostic.MessageContainer
+import util.DiffUtil
+import Highlighting._
+import SyntaxHighlighting._
object Formatting {
@@ -66,17 +69,39 @@ 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 = {
- 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
+ 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 =
+ arg match {
+ case arg: Showable if ctx.settings.color.value != "never" =>
+ val highlighted =
+ SyntaxHighlighting(wrapNonSensical(arg, super.showArg(arg)))
+ new String(highlighted.toArray)
+ case hl: Highlight =>
+ hl.show
+ case hb: HighlightBuffer =>
+ hb.toString
+ case str: String if ctx.settings.color.value != "never" =>
+ new String(SyntaxHighlighting(str).toArray)
+ case _ => super.showArg(arg)
}
- val str = super.showArg(arg)
- if (isSensical(arg)) str else Diagnostic.nonSensicalStartTag + str + Diagnostic.nonSensicalEndTag
+ }
+
+ 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 =>
+ 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*/
@@ -111,65 +136,123 @@ 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))
- }
+ /** Create explanation for single `Recorded` type or symbol */
+ def explanation(entry: AnyRef)(implicit ctx: Context): String = {
+ def boundStr(bound: Type, default: ClassSymbol, cmp: String) =
+ if (bound.isRef(default)) "" else i"$cmp $bound"
- def explanation(entry: Recorded): 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 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 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 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 _ =>
- ""
- }
+ 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)}"
+ }
+ }
- 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)}"
- }
+ /** 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)
+ case _ => false
}
- 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 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"""
}
- 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)
+ val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) }
+ val explainLines = columnar(explainParts)
+ if (explainLines.isEmpty) "" else i"where: $explainLines%\n %\n"
}
- 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"
+ /** 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)
+ }
+
+ /** 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
+ 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, 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)
+
+ 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/printing/Highlighting.scala b/src/dotty/tools/dotc/printing/Highlighting.scala
new file mode 100644
index 000000000..3bda7fb7a
--- /dev/null
+++ b/src/dotty/tools/dotc/printing/Highlighting.scala
@@ -0,0 +1,77 @@
+package dotty.tools
+package dotc
+package printing
+
+import scala.collection.mutable
+import core.Contexts.Context
+
+object Highlighting {
+
+ implicit def highlightShow(h: Highlight)(implicit ctx: Context): String =
+ h.show
+
+ abstract class Highlight(private val highlight: String) {
+ def text: String
+
+ def show(implicit ctx: Context) =
+ if (ctx.settings.color.value == "never") text
+ else highlight + text + Console.RESET
+
+ override def toString =
+ highlight + text + Console.RESET
+
+ def +(other: Highlight)(implicit ctx: Context): HighlightBuffer =
+ new HighlightBuffer(this) + other
+
+ 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 show(implicit ctx: Context) =
+ if (ctx.settings.color.value == "never") ""
+ else mod + super.show
+ }
+
+ case class HighlightBuffer(hl: Highlight)(implicit ctx: Context) {
+ val buffer = new mutable.ListBuffer[String]
+
+ buffer += hl.show
+
+ def +(other: Highlight): HighlightBuffer = {
+ buffer += other.show
+ this
+ }
+
+ def +(other: String): HighlightBuffer = {
+ buffer += other
+ this
+ }
+
+ override def toString =
+ 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)
+ 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/printing/SyntaxHighlighting.scala b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala
index 83c428976..86f34e64d 100644
--- a/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala
+++ b/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala
@@ -5,21 +5,29 @@ package printing
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 {
+
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 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
private val keywords: Seq[String] = for {
index <- IF to INLINE // All alpha keywords
@@ -33,15 +41,18 @@ 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]): Vector[Char] = {
+ def apply(chars: Iterable[Char]): Iterable[Char] = {
var prev: Char = 0
var remaining = chars.toStream
val newBuf = new StringBuilder
+ 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')
@@ -67,7 +78,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
@@ -89,17 +102,17 @@ 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 '@' =>
- appendWhile('@', _ != ' ', annotation)
+ appendWhile('@', !typeEnders.contains(_), annotation)
case '\"' =>
appendLiteral('\"', multiline = remaining.take(2).mkString == "\"\"")
case '\'' =>
@@ -107,7 +120,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)
@@ -169,7 +186,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()
@@ -179,7 +196,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) {
@@ -227,15 +244,32 @@ 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 '@' => 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 (("var" :: "val" :: "def" :: "case" :: Nil).contains(lastToken))
+ valDef(str)
+ else str
+ val suffix = if (delim(curr)) s"$curr" else ""
newBuf append (toAdd + suffix)
+ lastToken = str
prev = curr
}
@@ -265,6 +299,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 614654a28..f3b68e4b0 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 = {
@@ -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
)
diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
index 163bbea16..5b3669d5e 100644
--- a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
+++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
@@ -117,22 +117,22 @@ class CompilingInterpreter(
}
}
- private def newReporter = new ConsoleReporter(Console.in, out) {
- override def printMessage(msg: String) = {
- if (!delayOutput) {
- out.print(/*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")
- }
+ out.flush()
+ } else {
+ previousOutput += (/*clean*/(msg) + "\n")
+ }
}
- }
/** the previous requests this interpreter has processed */
private val prevRequests = new ArrayBuffer[Request]()
@@ -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" */
diff --git a/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala b/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala
index ebbcf2148..faa97c348 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)),
diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala
index deb772db5..da3df6984 100644
--- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala
+++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala
@@ -4,54 +4,142 @@ 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._
+import printing.SyntaxHighlighting._
+import printing.Highlighting._
+import diagnostic.{ Message, MessageContainer, NoExplanation }
+import diagnostic.messages._
/**
- * This class implements a Reporter that displays messages on a text
- * console.
- */
+ * 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 {
+ reader: BufferedReader = Console.in,
+ writer: PrintWriter = new PrintWriter(Console.err, true)
+) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages {
+
+ import MessageContainer._
/** maximal number of error messages to be printed */
protected def ErrorLimit = 100
- 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)
+ /** 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 sourceLines(pos: SourcePosition)(implicit ctx: Context): (List[String], List[String], Int) = {
+ var maxLen = Int.MinValue
+ def render(xs: List[Int]) =
+ xs.map(pos.source.offsetToLine(_))
+ .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"
}
+
+ val (before, after) = pos.beforeAndAfterPoint
+ (render(before), render(after), maxLen)
+ }
+
+ 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 "^"
}
- /** Prints the message. */
- def printMessage(msg: String): Unit = { writer.print(msg + "\n"); writer.flush() }
+ s"$prefix|$whitespace${carets.show}"
+ }
+
+ def errorMsg(pos: SourcePosition, msg: String, offset: Int)(implicit ctx: Context) = {
+ 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 => " " * (offset - 1) + "|" + (" " * (leastWhitespace - offset)) + line }
+ .mkString(sys.props("line.separator"))
+ }
+
+ 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 != NoExplanation.ID)
+ s"[E${"0" * (3 - message.errorId.toString.length) + 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: String, pos: SourcePosition)(implicit ctx: Context): Unit = {
- val posStr = if (pos.exists) s"$pos: " else ""
- printMessage(posStr + msg)
- printPos(pos)
+ 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)
+ val marker = columnMarker(pos, offset)
+ val err = errorMsg(pos, msg.msg, offset)
+
+ printMessage((srcBefore ::: marker :: err :: srcAfter).mkString("\n"))
+ } else printMessage(msg.msg)
+ true
+ }
+
+ def printExplanation(m: Message)(implicit ctx: Context): Unit = {
+ printMessage(hl"""|
+ |${Blue("Explanation")}
+ |${Blue("===========")}""".stripMargin)
+ printMessage(m.explanation)
+ if (m.explanation.lastOption != Some('\n')) printMessage("")
}
- override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match {
- case d: Error =>
- printMessageAndPos(s"error: ${d.message}", d.pos)
- if (ctx.settings.prompt.value) displayPrompt()
- case d: ConditionalWarning if !d.enablingOption.value =>
- case d: MigrationWarning =>
- printMessageAndPos(s"migration warning: ${d.message}", d.pos)
- case d: Warning =>
- printMessageAndPos(s"warning: ${d.message}", d.pos)
- case _ =>
- printMessageAndPos(d.message, d.pos)
+ override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = {
+ val didPrint = m match {
+ case m: 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 =>
+ 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, "Migration Warning")
+ case m: Warning =>
+ printMessageAndPos(m.contained, m.pos, "Warning")
+ case m: Info =>
+ printMessageAndPos(m.contained, m.pos, "Info")
+ }
+
+ if (didPrint && ctx.shouldExplain(m))
+ printExplanation(m.contained)
+ else if (didPrint && m.contained.explanation.nonEmpty)
+ printMessage("\nlonger explanation available when compiling with `-explain`")
}
def displayPrompt(): Unit = {
@@ -71,3 +159,4 @@ class ConsoleReporter(
override def flush()(implicit ctx: Context): Unit = { writer.flush() }
}
+
diff --git a/src/dotty/tools/dotc/reporting/Diagnostic.scala b/src/dotty/tools/dotc/reporting/Diagnostic.scala
deleted file mode 100644
index bcf55e993..000000000
--- a/src/dotty/tools/dotc/reporting/Diagnostic.scala
+++ /dev/null
@@ -1,47 +0,0 @@
-package dotty.tools
-package dotc
-package reporting
-
-import util.SourcePosition
-
-import java.util.Optional
-
-object Diagnostic {
- val nonSensicalStartTag = "<nonsensical>"
- val nonSensicalEndTag = "</nonsensical>"
-}
-
-class Diagnostic(msgFn: => String, val pos: SourcePosition, val level: Int)
- extends Exception with interfaces.Diagnostic {
- import Diagnostic._
- private var myMsg: String = null
- private var myIsNonSensical: Boolean = false
-
- 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
- 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
- }
-
- /** A message is non-sensical if it contains references to <nonsensical> 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/HideNonSensicalMessages.scala b/src/dotty/tools/dotc/reporting/HideNonSensicalMessages.scala
index a325fe9f7..ba1ab9b33 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.MessageContainer
/**
* 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: MessageContainer)(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 75113d823..b38334412 100644
--- a/src/dotty/tools/dotc/reporting/Reporter.scala
+++ b/src/dotty/tools/dotc/reporting/Reporter.scala
@@ -4,44 +4,24 @@ 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.messages._
+import diagnostic._
+import Message._
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)
-
- abstract class ConditionalWarning(msgFn: => String, pos: SourcePosition) extends Warning(msgFn, pos) {
- def enablingOption(implicit ctx: Context): Setting[Boolean]
- }
- class FeatureWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) {
- def enablingOption(implicit ctx: Context) = ctx.settings.feature
- }
- class UncheckedWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) {
- def enablingOption(implicit ctx: Context) = ctx.settings.unchecked
- }
- class DeprecationWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) {
- def enablingOption(implicit ctx: Context) = ctx.settings.deprecation
- }
- class MigrationWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) {
- 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: MessageContainer)(implicit ctx: Context): Unit = m match {
+ case m: ConditionalWarning if !m.enablingOption.value =>
case _ =>
- simple.report(d)
+ simple.report(m)
}
}
}
@@ -57,17 +37,17 @@ trait Reporting { this: Context =>
def echo(msg: => String, pos: SourcePosition = NoSourcePosition): Unit =
reporter.report(new Info(msg, pos))
- def deprecationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit =
- reporter.report(new DeprecationWarning(msg, pos))
+ 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))
+ 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))
+ 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))
+ 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 = {
@@ -92,26 +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 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)
+ else warning(msg.mapMsg(_ + "\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))
- }
+ 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
@@ -198,7 +176,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: 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
@@ -213,7 +191,7 @@ abstract class Reporter extends interfaces.ReporterResult {
finally _truncationOK = saved
}
- type ErrorHandler = Diagnostic => 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
@@ -242,7 +220,7 @@ abstract class Reporter extends interfaces.ReporterResult {
override def default(key: String) = 0
}
- def report(d: Diagnostic)(implicit ctx: Context): Unit =
+ def report(d: MessageContainer)(implicit ctx: Context): Unit =
if (!isHidden(d)) {
doReport(d)(ctx.addMode(Mode.Printing))
d match {
@@ -256,10 +234,9 @@ abstract class Reporter extends interfaces.ReporterResult {
}
}
- def incomplete(d: Diagnostic)(implicit ctx: Context): Unit =
+ def incomplete(d: MessageContainer)(implicit ctx: Context): Unit =
incompleteHandler(d)(ctx)
-
/** Summary of warnings and errors */
def summary: String = {
val b = new mutable.ListBuffer[String]
@@ -279,7 +256,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"
@@ -289,7 +266,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: 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 b7b7c1af0..e85017ed2 100644
--- a/src/dotty/tools/dotc/reporting/StoreReporter.scala
+++ b/src/dotty/tools/dotc/reporting/StoreReporter.scala
@@ -4,26 +4,27 @@ package reporting
import core.Contexts.Context
import collection.mutable
-import Reporter.{Error, Warning}
import config.Printers.typr
+import diagnostic.MessageContainer
+import diagnostic.messages._
/**
* 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[MessageContainer] = null
- def doReport(d: Diagnostic)(implicit ctx: Context): Unit = {
- typr.println(s">>>> StoredError: ${d.message}") // !!! DEBUG
+ def doReport(m: MessageContainer)(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 026453036..d8e03ab66 100644
--- a/src/dotty/tools/dotc/reporting/ThrowingReporter.scala
+++ b/src/dotty/tools/dotc/reporting/ThrowingReporter.scala
@@ -4,6 +4,8 @@ package reporting
import core.Contexts.Context
import collection.mutable
+import diagnostic.MessageContainer
+import diagnostic.messages.Error
import Reporter._
/**
@@ -11,8 +13,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: 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 32554e6b6..6fd971c2a 100644
--- a/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala
+++ b/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala
@@ -5,11 +5,10 @@ package reporting
import scala.collection.mutable
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]
@@ -17,13 +16,17 @@ 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: MessageContainer)(implicit ctx: Context): Boolean =
+ super.isHidden(m) || {
+ m.pos.exists && {
+ 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/Message.scala b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala
new file mode 100644
index 000000000..8b1f65673
--- /dev/null
+++ b/src/dotty/tools/dotc/reporting/diagnostic/Message.scala
@@ -0,0 +1,106 @@
+package dotty.tools
+package dotc
+package reporting
+package diagnostic
+
+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)
+}
+
+/** 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._
+
+ /** 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)
+
+ /** Enclose this message in an `Warning` container */
+ def warning(pos: SourcePosition) =
+ new Warning(self, pos)
+
+ /** Enclose this message in an `Info` container */
+ def info(pos: SourcePosition) =
+ new Info(self, pos)
+
+ /** Enclose this message in an `FeatureWarning` container */
+ def featureWarning(pos: SourcePosition) =
+ new FeatureWarning(self, pos)
+
+ /** Enclose this message in an `UncheckedWarning` container */
+ def uncheckedWarning(pos: SourcePosition) =
+ new UncheckedWarning(self, pos)
+
+ /** Enclose this message in an `DeprecationWarning` container */
+ def deprecationWarning(pos: SourcePosition) =
+ new DeprecationWarning(self, pos)
+
+ /** Enclose this message in an `MigrationWarning` container */
+ def migrationWarning(pos: SourcePosition) =
+ new MigrationWarning(self, pos)
+}
+
+/** The fallback `Message` containing no explanation and having no `kind` */
+class NoExplanation(val msg: String) extends Message(NoExplanation.ID) {
+ val explanation = ""
+ val kind = ""
+}
+
+/** The extractor for `NoExplanation` can be used to check whether any error
+ * 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/MessageContainer.scala b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala
new file mode 100644
index 000000000..7fd50bfdc
--- /dev/null
+++ b/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala
@@ -0,0 +1,74 @@
+package dotty.tools
+package dotc
+package reporting
+package diagnostic
+
+import util.SourcePosition
+import core.Contexts.Context
+
+import java.util.Optional
+
+object MessageContainer {
+ val nonSensicalStartTag = "<nonsensical>"
+ val nonSensicalEndTag = "</nonsensical>"
+
+ implicit class MessageContext(val c: Context) extends AnyVal {
+ def shouldExplain(cont: MessageContainer): Boolean = {
+ implicit val ctx: Context = c
+ cont.contained.explanation match {
+ case "" => false
+ case _ => ctx.settings.explain.value
+ }
+ }
+ }
+}
+
+class MessageContainer(
+ msgFn: => Message,
+ val pos: SourcePosition,
+ val level: Int
+) 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 = contained.msg.replaceAll("\u001B\\[[;\\d]*m", "")
+ 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 <nonsensical>
+ * 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/messages.scala b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
new file mode 100644
index 000000000..9cfac4801
--- /dev/null
+++ b/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
@@ -0,0 +1,277 @@
+package dotty.tools
+package dotc
+package reporting
+package diagnostic
+
+import dotc.core._
+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 printing.SyntaxHighlighting._
+import printing.Formatting
+
+object messages {
+
+ // `MessageContainer`s to be consumed by `Reporter` ---------------------- //
+ class Error(
+ msgFn: => Message,
+ pos: SourcePosition
+ ) extends MessageContainer(msgFn, pos, ERROR)
+
+ class Warning(
+ msgFn: => Message,
+ pos: SourcePosition
+ ) extends MessageContainer(msgFn, pos, WARNING)
+
+ class Info(
+ msgFn: => Message,
+ pos: SourcePosition
+ ) extends MessageContainer(msgFn, pos, INFO)
+
+ abstract class ConditionalWarning(
+ msgFn: => Message,
+ pos: SourcePosition
+ ) extends Warning(msgFn, pos) {
+ def enablingOption(implicit ctx: Context): Setting[Boolean]
+ }
+
+ class FeatureWarning(
+ msgFn: => Message,
+ pos: SourcePosition
+ ) extends ConditionalWarning(msgFn, pos) {
+ def enablingOption(implicit ctx: Context) = ctx.settings.feature
+ }
+
+ class UncheckedWarning(
+ msgFn: => Message,
+ pos: SourcePosition
+ ) extends ConditionalWarning(msgFn, pos) {
+ def enablingOption(implicit ctx: Context) = ctx.settings.unchecked
+ }
+
+ class DeprecationWarning(
+ msgFn: => Message,
+ pos: SourcePosition
+ ) extends ConditionalWarning(msgFn, pos) {
+ def enablingOption(implicit ctx: Context) = ctx.settings.deprecation
+ }
+
+ class MigrationWarning(
+ msgFn: => Message,
+ pos: SourcePosition
+ ) extends ConditionalWarning(msgFn, pos) {
+ 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(pos) // res: Error
+ * EmptyCatchBlock(tree).warning(pos) // res: Warning
+ * ```
+ */
+ import dotc.ast.Trees._
+ import dotc.ast.untpd
+
+ // Syntax Errors ---------------------------------------------------------- //
+ abstract class EmptyCatchOrFinallyBlock(tryBody: untpd.Tree, errNo: Int)(implicit ctx: Context)
+ extends Message(errNo) {
+ val explanation = {
+ val tryString = tryBody match {
+ case Block(Nil, untpd.EmptyTree) => "{}"
+ case _ => tryBody.show
+ }
+
+ val code1 =
+ s"""|import scala.util.control.NonFatal
+ |
+ |try $tryString catch {
+ | case NonFatal(e) => ???
+ |}""".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
+ |
+ |It is recommended to use the ${"NonFatal"} extractor to catch all exceptions as it
+ |correctly handles transfer functions like ${"return"}.""".stripMargin
+ }
+ }
+
+ case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context)
+ extends EmptyCatchOrFinallyBlock(tryBody, 1) {
+ 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, 2) {
+ 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(3) {
+ val kind = "Syntax"
+ val msg =
+ hl"""${"with"} as a type operator has been deprecated; use `&' instead"""
+ 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"}'.""".stripMargin
+ }
+
+ 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(5) {
+ 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
+ }
+ }
+
+ case class MissingIdent(tree: untpd.Ident, treeKind: String, name: String)(implicit ctx: Context)
+ extends Message(6) {
+ val kind = "Missing Identifier"
+ val msg = em"not found: $treeKind$name"
+
+ val explanation = {
+ hl"""|An identifier for `$treeKind$name` is missing. This means that something
+ |has either been misspelt or you're forgetting an import""".stripMargin
+ }
+ }
+
+ case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "", implicitFailure: String = "")(implicit ctx: Context)
+ extends Message(7) {
+ val kind = "Type Mismatch"
+ 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/transform/TailRec.scala b/src/dotty/tools/dotc/transform/TailRec.scala
index b345dda61..065bcb397 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/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala
index 4b3927ccf..fd8e41dc3 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
}
}
diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala
index 2c9039db1..56595a637 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/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala
index b02b0ad21..7ba66e3d8 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/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala
index ad84ff583..1d22dc646 100644
--- a/src/dotty/tools/dotc/typer/ErrorReporting.scala
+++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala
@@ -8,19 +8,20 @@ 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
+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: => String, pos: Position)(implicit ctx: Context): ErrorType = {
+ def errorType(msg: => Message, pos: Position)(implicit ctx: Context): ErrorType = {
ctx.error(msg, pos)
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, postScript: String = "") = {
// 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""" + whyNoMatchStr(found, expected)
+ TypeMismatch(found1, expected1, whyNoMatchStr(found, expected), postScript)
}
/** 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 1f150c519..4d82a2d12 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/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala
index 0c55d977e..262d3f731 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/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala
index 3e3bb32f5..bbb20bcf5 100644
--- a/src/dotty/tools/dotc/typer/Typer.scala
+++ b/src/dotty/tools/dotc/typer/Typer.scala
@@ -37,6 +37,7 @@ import rewrite.Rewrites.patch
import NavigateAST._
import transform.SymUtils._
import language.implicitConversions
+import printing.SyntaxHighlighting._
object Typer {
@@ -64,6 +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.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
@@ -96,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: => 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?
@@ -141,9 +144,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
* imported by <tree>
* or defined in <symbol>
*/
- 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 +171,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 +186,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
}
@@ -326,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.show), tree.pos)
ErrorType
}
@@ -767,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}")
@@ -839,11 +844,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.error(new DuplicateBind(b, tree), b.pos)
b.symbol.info = elimWildcardSym(b.symbol.info)
b
case t => t
@@ -1254,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/src/dotty/tools/dotc/util/DiffUtil.scala b/src/dotty/tools/dotc/util/DiffUtil.scala
index b7c77ad62..b55aee719 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,35 @@ object DiffUtil {
}
}
+
+ /** @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
+
+ val diffExp = hirschberg(foundTokens, expectedTokens)
+ val diffAct = hirschberg(expectedTokens, foundTokens)
+
+ val exp = diffExp.collect {
+ case Unmodified(str) => str
+ case Inserted(str) =>
+ totalChange += str.length
+ ADDITION_COLOR + str + ANSI_DEFAULT
+ }.mkString
+
+ val fnd = diffAct.collect {
+ case Unmodified(str) => str
+ case Inserted(str) =>
+ totalChange += str.length
+ DELETION_COLOR + str + ANSI_DEFAULT
+ }.mkString
+
+ (fnd, exp, totalChange.toDouble / (expected.length + found.length))
+ }
+
+ def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = {
+
val tokens = splitTokens(code, Nil).toArray
val lastTokens = splitTokens(lastCode, Nil).toArray
diff --git a/src/dotty/tools/dotc/util/SourceFile.scala b/src/dotty/tools/dotc/util/SourceFile.scala
index 8bd0ecfd6..1d4c9c2ab 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 68a9b6403..595ea34ca 100644
--- a/src/dotty/tools/dotc/util/SourcePosition.scala
+++ b/src/dotty/tools/dotc/util/SourcePosition.scala
@@ -14,6 +14,23 @@ 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 + 1)) match {
+ case Nil => line :: Nil
+ 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)