diff options
46 files changed, 1186 insertions, 419 deletions
diff --git a/.gitignore b/.gitignore index 17eba0468..416a12eff 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/ @@ -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 diff --git a/bridge/src/main/scala/xsbt/DelegatingReporter.scala b/bridge/src/main/scala/xsbt/DelegatingReporter.scala index 726570d71..446ef287e 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 +} 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) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index f161fefe3..39e5f5ead 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 dea6a30b1..8cf6b2feb 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.MessageContainer 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: MessageContainer)(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 5f8681d38..abaa043c0 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.MessageContainer 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: MessageContainer)(implicit ctx: Context): Unit = { } } diff --git a/tests/neg/applydynamic_sip.scala b/tests/neg/applydynamic_sip.scala index 7b131e7ff..86cff5fc4 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 5be107717..273419cb5 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 61d3c9677..d68132b02 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 3586260c1..8eba32842 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 d9ba2882d..462ef800f 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 d8e863a28..2bcb40eb0 100644 --- a/tests/repl/errmsgs.check +++ b/tests/repl/errmsgs.check @@ -1,43 +1,47 @@ scala> class Inv[T](x: T) defined class Inv scala> val x: List[String] = List(1) -<console>:4: error: type mismatch: - found : Int(1) - required: String -val x: List[String] = List(1) - ^ +-- [E007] Type Mismatch Error: <console> ------------------------------------------------------------------------------- +4 |val x: List[String] = List(1) + | ^ + | found: Int(1) + | required: String + | scala> val y: List[List[String]] = List(List(1)) -<console>:4: error: type mismatch: - found : Int(1) - required: String -val y: List[List[String]] = List(List(1)) - ^ +-- [E007] Type Mismatch Error: <console> ------------------------------------------------------------------------------- +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")) -<console>:4: error: type mismatch: - found : Int(1) - required: String -val z: (List[String], List[Int]) = (List(1), List("a")) - ^ -<console>:4: error: type mismatch: - found : String("a") - required: Int -val z: (List[String], List[Int]) = (List(1), List("a")) - ^ +-- [E007] Type Mismatch Error: <console> ------------------------------------------------------------------------------- +4 |val z: (List[String], List[Int]) = (List(1), List("a")) + | ^ + | found: Int(1) + | required: String + | +-- [E007] Type Mismatch Error: <console> ------------------------------------------------------------------------------- +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)) -<console>: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)) - ^ +-- [E007] Type Mismatch Error: <console> ------------------------------------------------------------------------------- +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) -<console>:5: error: type mismatch: - found : Int(1) - required: String -val b: Inv[String] = new Inv(1) - ^ +-- [E007] Type Mismatch Error: <console> ------------------------------------------------------------------------------- +5 |val b: Inv[String] = new Inv(1) + | ^ + | found: Int(1) + | required: String + | scala> abstract class C { type T val x: T @@ -53,22 +57,25 @@ scala> abstract class C { } } } -<console>: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 - ^ -<console>: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 - - val z: T = y - ^ +-- [E007] Type Mismatch Error: <console> ------------------------------------------------------------------------------- +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 +-- [E007] Type Mismatch Error: <console> ------------------------------------------------------------------------------- +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> class Foo() { def bar: Int = 1 }; val foo = new Foo(); foo.barr +-- [E008] Member Not Found Error: <console> ---------------------------------------------------------------------------- +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 diff --git a/tests/repl/imports.check b/tests/repl/imports.check index 3a7e9341e..26b725637 100644 --- a/tests/repl/imports.check +++ b/tests/repl/imports.check @@ -7,16 +7,12 @@ defined module o scala> import o._ import o._ scala> buf += xs -<console>:11: error: type mismatch: - found : scala.collection.immutable.List[Int](o.xs) - required: String -buf += xs - ^ -<console>:11: error: type mismatch: - found : String - required: scala.collection.mutable.ListBuffer[Int] -buf += xs -^ +-- [E007] Type Mismatch Error: <console> ------------------------------------------------------------------------------- +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 |