Skip to content

Commit

Permalink
Refactor explanation interpolator
Browse files Browse the repository at this point in the history
  • Loading branch information
felixmulder committed Sep 22, 2016
1 parent 7ccad6a commit 949c974
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 69 deletions.
121 changes: 74 additions & 47 deletions src/dotty/tools/dotc/printing/Formatting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import Decorators._
import scala.annotation.switch
import scala.util.control.NonFatal
import reporting.diagnostic.MessageContainer
import util.DiffUtil
import Highlighting.{ highlightToString => _, _ }
import SyntaxHighlighting._

object Formatting {

Expand Down Expand Up @@ -113,65 +116,89 @@ object Formatting {
seen.record(super.polyParamNameString(param), param)
}

def explained2(op: Context => String)(implicit ctx: Context): String = {
val seen = new Seen
val explainCtx = ctx.printer match {
case dp: ExplainingPrinter => ctx // re-use outer printer and defer explanation to it
case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx))
def explanation(entry: Recorded)(implicit ctx: Context): String = {
def boundStr(bound: Type, default: ClassSymbol, cmp: String) =
if (bound.isRef(default)) "" else i"$cmp $bound"

def boundsStr(bounds: TypeBounds): String = {
val lo = boundStr(bounds.lo, defn.NothingClass, ">:")
val hi = boundStr(bounds.hi, defn.AnyClass, "<:")
if (lo.isEmpty) hi
else if (hi.isEmpty) lo
else s"$lo and $hi"
}

def explanation(entry: Recorded): String = {
def boundStr(bound: Type, default: ClassSymbol, cmp: String) =
if (bound.isRef(default)) "" else i"$cmp $bound"
def addendum(cat: String, info: Type): String = info match {
case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty =>
if (lo eq hi) i" which is an alias of $lo"
else i" with $cat ${boundsStr(bounds)}"
case _ =>
""
}

def boundsStr(bounds: TypeBounds): String = {
val lo = boundStr(bounds.lo, defn.NothingClass, ">:")
val hi = boundStr(bounds.hi, defn.AnyClass, "<:")
if (lo.isEmpty) hi
else if (hi.isEmpty) lo
else s"$lo and $hi"
}
entry match {
case param: PolyParam =>
s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}"
case sym: Symbol =>
s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}"
}
}

def addendum(cat: String, info: Type)(implicit ctx: Context): String = info match {
case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty =>
if (lo eq hi) i" which is an alias of $lo"
else i" with $cat ${boundsStr(bounds)}"
case _ =>
""
}
private def explanations(seen: Seen)(implicit ctx: Context): String = {
def needsExplanation(entry: Recorded) = entry match {
case param: PolyParam => ctx.typerState.constraint.contains(param)
case _ => false
}

entry match {
case param: PolyParam =>
s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}"
case sym: Symbol =>
s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}"
val toExplain: List[(String, Recorded)] = seen.toList.flatMap {
case (str, entry :: Nil) =>
if (needsExplanation(entry)) (str, entry) :: Nil else Nil
case (str, entries) =>
entries.map(alt => (seen.record(str, alt), alt))
}.sortBy(_._1)

def columnar(parts: List[(String, String)], sep: String): List[String] = {
lazy val maxLen = parts.map(_._1.length).max
parts.map {
case (leader, trailer) =>
s"`$leader`${" " * (maxLen - leader.length)}$sep$trailer"
}
}

def explanations(seen: Seen)(implicit ctx: Context): String = {
def needsExplanation(entry: Recorded) = entry match {
case param: PolyParam => ctx.typerState.constraint.contains(param)
case _ => false
val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) }
val explainLines = columnar(explainParts, " ")
if (explainLines.isEmpty) "" else i"where $explainLines%\n %\n"
}

private def explainCtx(seen: Seen)(implicit ctx: Context): Context = ctx.printer match {
case dp: ExplainingPrinter =>
ctx // re-use outer printer and defer explanation to it
case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx))
}

def explained2(op: Context => String)(implicit ctx: Context): String = {
val seen = new Seen
op(explainCtx(seen)) ++ explanations(seen)
}

def disambiguateTypes(args: Type*)(implicit ctx: Context): String = {
val seen = new Seen
object polyparams extends TypeTraverser {
def traverse(tp: Type): Unit = {
seen.record(tp.show(explainCtx(seen)), tp)
traverseChildren(tp)
}
val toExplain: List[(String, Recorded)] = seen.toList.flatMap {
case (str, entry :: Nil) =>
if (needsExplanation(entry)) (str, entry) :: Nil else Nil
case (str, entries) =>
entries.map(alt => (seen.record(str, alt), alt))
}.sortBy(_._1)
val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) }
val explainLines = columnar(explainParts, " ")
if (explainLines.isEmpty) "" else i"\n\nwhere $explainLines%\n %\n"
}

op(explainCtx) ++ explanations(seen)
args.foreach(polyparams.traverse)
explanations(seen)
}

def columnar(parts: List[(String, String)], sep: String): List[String] = {
lazy val maxLen = parts.map(_._1.length).max
parts.map {
case (leader, trailer) =>
s"$leader${" " * (maxLen - leader.length)}$sep$trailer"
def typeDiff(found: Type, expected: Type)(implicit ctx: Context): (String, String) = {
(found, expected) match {
case (rf1: RefinedType, rf2: RefinedType) =>
DiffUtil.mkColoredTypeDiff(rf1.show, rf2.show)
case _ =>
(hl"${found.show}", hl"${expected.show}")
}
}
}
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/printing/SyntaxHighlighting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ object SyntaxHighlighting {
val toAdd =
if (shouldHL(str))
highlight(str)
else if (lastToken == "val" || lastToken == "def" || lastToken == "case")
else if (("var" :: "val" :: "def" :: "case" :: Nil).contains(lastToken))
valDef(str)
else str
val suffix = if (delim(curr)) s"$curr" else ""
Expand Down
27 changes: 21 additions & 6 deletions src/dotty/tools/dotc/reporting/diagnostic/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ package reporting
package diagnostic

import dotc.core._
import Contexts.Context, Decorators._, Symbols._, Names._
import Contexts.Context, Decorators._, Symbols._, Names._, Types._
import util.{SourceFile, NoSource}
import util.{SourcePosition, NoSourcePosition}
import config.Settings.Setting
import interfaces.Diagnostic.{ERROR, WARNING, INFO}
import dotc.printing.SyntaxHighlighting._
import printing.SyntaxHighlighting._
import printing.Formatting

object messages {

Expand Down Expand Up @@ -127,7 +128,7 @@ object messages {
}
}

class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context)
case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context)
extends EmptyCatchOrFinallyBlock(tryBody, "E001") {
val kind = "Syntax"
val msg =
Expand Down Expand Up @@ -174,7 +175,7 @@ object messages {
}

/** Type Errors ----------------------------------------------------------- */
class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context)
case class DuplicateBind(bind: untpd.Bind, tree: untpd.CaseDef)(implicit ctx: Context)
extends Message("E004") {
val kind = "Naming"
val msg = em"duplicate pattern variable: `${bind.name}`"
Expand All @@ -201,14 +202,28 @@ object messages {
}
}

class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context)
case class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context)
extends Message("E005") {
val kind = "Missing identifier"
val kind = "Missing Identifier"
val msg = em"not found: $treeKind$name"

val explanation = {
hl"""|An identifier for `${name.show}` 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 = "")(implicit ctx: Context)
extends Message("E006") {
val kind = "Type Mismatch"
private val where = Formatting.disambiguateTypes(found, expected)
private val (fnd, exp) = Formatting.typeDiff(found, expected)
val msg =
s"""|found: $fnd
|required: $exp
|
|$where""".stripMargin + whyNoMatch

val explanation = ""
}
}
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ class TreeChecker extends Phase with SymTransformer {
!pt.isInstanceOf[FunProto])
assert(tree.tpe <:< pt,
s"error at ${sourcePos(tree.pos)}\n" +
err.typeMismatchStr(tree.tpe, pt) + "\ntree = " + tree)
err.typeMismatchMsg(tree.tpe, pt) + "\ntree = " + tree)
tree
}
}
Expand Down
15 changes: 8 additions & 7 deletions src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import collection.mutable
import config.Printers._
import TypeApplications._
import language.implicitConversions
import reporting.diagnostic.Message

object Applications {
import tpd._
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
11 changes: 5 additions & 6 deletions src/dotty/tools/dotc/typer/ErrorReporting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import printing.{Showable, RefinedPrinter}
import scala.collection.mutable
import java.util.regex.Matcher.quoteReplacement
import reporting.diagnostic.Message
import reporting.diagnostic.messages._

object ErrorReporting {

import tpd._

def errorTree(tree: untpd.Tree, msg: => String)(implicit ctx: Context): tpd.Tree =
def errorTree(tree: untpd.Tree, msg: => Message)(implicit ctx: Context): tpd.Tree =
tree withType errorType(msg, tree.pos)

def errorType(msg: => Message, pos: Position)(implicit ctx: Context): ErrorType = {
Expand Down Expand Up @@ -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) =
Expand All @@ -110,7 +111,7 @@ object ErrorReporting {
else
""

def typeMismatchStr(found: Type, expected: Type) = {
def typeMismatchMsg(found: Type, expected: Type) = {
// replace constrained polyparams and their typevars by their bounds where possible
object reported extends TypeMap {
def setVariance(v: Int) = variance = v
Expand All @@ -132,9 +133,7 @@ object ErrorReporting {
val found1 = reported(found)
reported.setVariance(-1)
val expected1 = reported(expected)
ex"""|type mismatch:
|found: $found1
|required: $expected1""".stripMargin + whyNoMatchStr(found, expected)
TypeMismatch(found1, expected1, whyNoMatchStr(found, expected))
}

/** Format `raw` implicitNotFound argument, replacing all
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 949c974

Please sign in to comment.