Skip to content

Commit 75f2e3e

Browse files
committed
Refactor explanation interpolator
1 parent 7ccad6a commit 75f2e3e

File tree

6 files changed

+124
-68
lines changed

6 files changed

+124
-68
lines changed

src/dotty/tools/dotc/printing/Formatting.scala

Lines changed: 88 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import Decorators._
99
import scala.annotation.switch
1010
import scala.util.control.NonFatal
1111
import reporting.diagnostic.MessageContainer
12+
import util.DiffUtil
13+
import Highlighting.{ highlightToString => _, _ }
14+
import SyntaxHighlighting._
1215

1316
object Formatting {
1417

@@ -113,65 +116,103 @@ object Formatting {
113116
seen.record(super.polyParamNameString(param), param)
114117
}
115118

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

123-
def explanation(entry: Recorded): String = {
124-
def boundStr(bound: Type, default: ClassSymbol, cmp: String) =
125-
if (bound.isRef(default)) "" else i"$cmp $bound"
123+
def boundsStr(bounds: TypeBounds): String = {
124+
val lo = boundStr(bounds.lo, defn.NothingClass, ">:")
125+
val hi = boundStr(bounds.hi, defn.AnyClass, "<:")
126+
if (lo.isEmpty) hi
127+
else if (hi.isEmpty) lo
128+
else s"$lo and $hi"
129+
}
126130

127-
def boundsStr(bounds: TypeBounds): String = {
128-
val lo = boundStr(bounds.lo, defn.NothingClass, ">:")
129-
val hi = boundStr(bounds.hi, defn.AnyClass, "<:")
130-
if (lo.isEmpty) hi
131-
else if (hi.isEmpty) lo
132-
else s"$lo and $hi"
133-
}
131+
def addendum(cat: String, info: Type): String = info match {
132+
case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty =>
133+
if (lo eq hi) i" which is an alias of $lo"
134+
else i" with $cat ${boundsStr(bounds)}"
135+
case _ =>
136+
""
137+
}
134138

135-
def addendum(cat: String, info: Type)(implicit ctx: Context): String = info match {
136-
case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty =>
137-
if (lo eq hi) i" which is an alias of $lo"
138-
else i" with $cat ${boundsStr(bounds)}"
139-
case _ =>
140-
""
141-
}
139+
entry match {
140+
case param: PolyParam =>
141+
s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}"
142+
case sym: Symbol =>
143+
s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}"
144+
}
145+
}
142146

143-
entry match {
144-
case param: PolyParam =>
145-
s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}"
146-
case sym: Symbol =>
147-
s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}"
148-
}
147+
private def explanations(seen: Seen)(implicit ctx: Context): String = {
148+
def needsExplanation(entry: Recorded) = entry match {
149+
case param: PolyParam => ctx.typerState.constraint.contains(param)
150+
case _ => false
149151
}
150152

151-
def explanations(seen: Seen)(implicit ctx: Context): String = {
152-
def needsExplanation(entry: Recorded) = entry match {
153-
case param: PolyParam => ctx.typerState.constraint.contains(param)
154-
case _ => false
153+
val toExplain: List[(String, Recorded)] = seen.toList.flatMap {
154+
case (str, entry :: Nil) =>
155+
if (needsExplanation(entry)) (str, entry) :: Nil else Nil
156+
case (str, entries) =>
157+
entries.map(alt => (seen.record(str, alt), alt))
158+
}.sortBy(_._1)
159+
160+
def columnar(parts: List[(String, String)], sep: String): List[String] = {
161+
lazy val maxLen = parts.map(_._1.length).max
162+
parts.map {
163+
case (leader, trailer) =>
164+
s"`$leader`${" " * (maxLen - leader.length)}$sep$trailer"
155165
}
156-
val toExplain: List[(String, Recorded)] = seen.toList.flatMap {
157-
case (str, entry :: Nil) =>
158-
if (needsExplanation(entry)) (str, entry) :: Nil else Nil
159-
case (str, entries) =>
160-
entries.map(alt => (seen.record(str, alt), alt))
161-
}.sortBy(_._1)
162-
val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) }
163-
val explainLines = columnar(explainParts, " ")
164-
if (explainLines.isEmpty) "" else i"\n\nwhere $explainLines%\n %\n"
165166
}
166167

168+
val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) }
169+
val explainLines = columnar(explainParts, " ")
170+
if (explainLines.isEmpty) "" else i"where $explainLines%\n %\n"
171+
}
172+
173+
def explained2(op: Context => String)(implicit ctx: Context): String = {
174+
val seen = new Seen
175+
val explainCtx = ctx.printer match {
176+
case dp: ExplainingPrinter =>
177+
ctx // re-use outer printer and defer explanation to it
178+
case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx))
179+
}
167180
op(explainCtx) ++ explanations(seen)
168181
}
169182

170-
def columnar(parts: List[(String, String)], sep: String): List[String] = {
171-
lazy val maxLen = parts.map(_._1.length).max
172-
parts.map {
173-
case (leader, trailer) =>
174-
s"$leader${" " * (maxLen - leader.length)}$sep$trailer"
183+
def disambiguateTypes(args: Type*)(implicit ctx: Context): String = {
184+
val seen = new Seen
185+
object polyparams extends TypeTraverser {
186+
def traverse(tp: Type): Unit = tp match {
187+
case tp: NamedType =>
188+
val sym = tp.symbol
189+
if (!sym.is(Package)) {
190+
if (!sym.isClass) traverse(tp.info)
191+
traverse(tp.prefix)
192+
}
193+
case tp: ThisType =>
194+
traverse(tp.underlying)
195+
case tp: ConstantType =>
196+
traverse(tp.underlying)
197+
case tp: MethodParam =>
198+
traverse(tp.underlying)
199+
case tp: PolyParam =>
200+
seen.record(tp.show, tp)
201+
traverse(tp.underlying)
202+
case _ =>
203+
traverseChildren(tp)
204+
}
205+
}
206+
args.foreach(polyparams.traverse)
207+
explanations(seen)
208+
}
209+
210+
def typeDiff(found: Type, expected: Type)(implicit ctx: Context): (String, String) = {
211+
(found, expected) match {
212+
case (rf1: RefinedType, rf2: RefinedType) =>
213+
DiffUtil.mkColoredTypeDiff(rf1.show, rf2.show)
214+
case _ =>
215+
(hl"${found.show}", hl"${expected.show}")
175216
}
176217
}
177218
}

src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ package reporting
44
package diagnostic
55

66
import dotc.core._
7-
import Contexts.Context, Decorators._, Symbols._, Names._
7+
import Contexts.Context, Decorators._, Symbols._, Names._, Types._
88
import util.{SourceFile, NoSource}
99
import util.{SourcePosition, NoSourcePosition}
1010
import config.Settings.Setting
1111
import interfaces.Diagnostic.{ERROR, WARNING, INFO}
12-
import dotc.printing.SyntaxHighlighting._
12+
import printing.SyntaxHighlighting._
13+
import printing.Formatting
1314

1415
object messages {
1516

@@ -127,7 +128,7 @@ object messages {
127128
}
128129
}
129130

130-
class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context)
131+
case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context)
131132
extends EmptyCatchOrFinallyBlock(tryBody, "E001") {
132133
val kind = "Syntax"
133134
val msg =
@@ -174,7 +175,7 @@ object messages {
174175
}
175176

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

204-
class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context)
205+
case class MissingIdent(tree: untpd.Ident, treeKind: String, name: Name)(implicit ctx: Context)
205206
extends Message("E005") {
206-
val kind = "Missing identifier"
207+
val kind = "Missing Identifier"
207208
val msg = em"not found: $treeKind$name"
208209

209210
val explanation = {
210211
hl"""|An identifier for `${name.show}` is missing. This means that something
211212
|has either been misspelt or you're forgetting an import""".stripMargin
212213
}
213214
}
215+
216+
case class TypeMismatch(found: Type, expected: Type, whyNoMatch: String = "")(implicit ctx: Context)
217+
extends Message("E006") {
218+
val kind = "Type Mismatch"
219+
private val where = Formatting.disambiguateTypes(found, expected)
220+
private val (fnd, exp) = Formatting.typeDiff(found, expected)
221+
val msg =
222+
s"""|found: $fnd
223+
|required: $exp
224+
|
225+
|$where""".stripMargin + whyNoMatch
226+
227+
val explanation = ""
228+
}
214229
}

src/dotty/tools/dotc/transform/TreeChecker.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ class TreeChecker extends Phase with SymTransformer {
429429
!pt.isInstanceOf[FunProto])
430430
assert(tree.tpe <:< pt,
431431
s"error at ${sourcePos(tree.pos)}\n" +
432-
err.typeMismatchStr(tree.tpe, pt) + "\ntree = " + tree)
432+
err.typeMismatchMsg(tree.tpe, pt) + "\ntree = " + tree)
433433
tree
434434
}
435435
}

src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import collection.mutable
2727
import config.Printers._
2828
import TypeApplications._
2929
import language.implicitConversions
30+
import reporting.diagnostic.Message
3031

3132
object Applications {
3233
import tpd._
@@ -132,10 +133,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
132133
protected def harmonizeArgs(args: List[TypedArg]): List[TypedArg]
133134

134135
/** Signal failure with given message at position of given argument */
135-
protected def fail(msg: => String, arg: Arg): Unit
136+
protected def fail(msg: => Message, arg: Arg): Unit
136137

137138
/** Signal failure with given message at position of the application itself */
138-
protected def fail(msg: => String): Unit
139+
protected def fail(msg: => Message): Unit
139140

140141
protected def appPos: Position
141142

@@ -186,7 +187,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
186187
// it might be healed by an implicit conversion
187188
assert(ctx.typerState.constraint eq savedConstraint)
188189
else
189-
fail(err.typeMismatchStr(methType.resultType, resultType))
190+
fail(err.typeMismatchMsg(methType.resultType, resultType))
190191
}
191192
// match all arguments with corresponding formal parameters
192193
matchArgs(orderedArgs, methType.paramTypes, 0)
@@ -388,9 +389,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
388389
def addArg(arg: TypedArg, formal: Type) =
389390
ok = ok & isCompatible(argType(arg, formal), formal)
390391
def makeVarArg(n: Int, elemFormal: Type) = {}
391-
def fail(msg: => String, arg: Arg) =
392+
def fail(msg: => Message, arg: Arg) =
392393
ok = false
393-
def fail(msg: => String) =
394+
def fail(msg: => Message) =
394395
ok = false
395396
def appPos = NoPosition
396397
lazy val normalizedFun = ref(methRef)
@@ -455,12 +456,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
455456

456457
override def appPos = app.pos
457458

458-
def fail(msg: => String, arg: Trees.Tree[T]) = {
459+
def fail(msg: => Message, arg: Trees.Tree[T]) = {
459460
ctx.error(msg, arg.pos)
460461
ok = false
461462
}
462463

463-
def fail(msg: => String) = {
464+
def fail(msg: => Message) = {
464465
ctx.error(msg, app.pos)
465466
ok = false
466467
}

src/dotty/tools/dotc/typer/ErrorReporting.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import printing.{Showable, RefinedPrinter}
1212
import scala.collection.mutable
1313
import java.util.regex.Matcher.quoteReplacement
1414
import reporting.diagnostic.Message
15+
import reporting.diagnostic.messages._
1516

1617
object ErrorReporting {
1718

1819
import tpd._
1920

20-
def errorTree(tree: untpd.Tree, msg: => String)(implicit ctx: Context): tpd.Tree =
21+
def errorTree(tree: untpd.Tree, msg: => Message)(implicit ctx: Context): tpd.Tree =
2122
tree withType errorType(msg, tree.pos)
2223

2324
def errorType(msg: => Message, pos: Position)(implicit ctx: Context): ErrorType = {
@@ -101,7 +102,7 @@ object ErrorReporting {
101102
def patternConstrStr(tree: Tree): String = ???
102103

103104
def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree =
104-
errorTree(tree, typeMismatchStr(normalize(tree.tpe, pt), pt) + implicitFailure.postscript)
105+
errorTree(tree, typeMismatchMsg(normalize(tree.tpe, pt), pt) /*+ implicitFailure.postscript*/)
105106

106107
/** A subtype log explaining why `found` does not conform to `expected` */
107108
def whyNoMatchStr(found: Type, expected: Type) =
@@ -110,7 +111,7 @@ object ErrorReporting {
110111
else
111112
""
112113

113-
def typeMismatchStr(found: Type, expected: Type) = {
114+
def typeMismatchMsg(found: Type, expected: Type) = {
114115
// replace constrained polyparams and their typevars by their bounds where possible
115116
object reported extends TypeMap {
116117
def setVariance(v: Int) = variance = v
@@ -132,9 +133,7 @@ object ErrorReporting {
132133
val found1 = reported(found)
133134
reported.setVariance(-1)
134135
val expected1 = reported(expected)
135-
ex"""|type mismatch:
136-
|found: $found1
137-
|required: $expected1""".stripMargin + whyNoMatchStr(found, expected)
136+
TypeMismatch(found1, expected1, whyNoMatchStr(found, expected))
138137
}
139138

140139
/** Format `raw` implicitNotFound argument, replacing all

src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ object RefChecks {
200200
infoStringWithLocation(other),
201201
infoStringWithLocation(member))
202202
else if (ctx.settings.debug.value)
203-
err.typeMismatchStr(memberTp, otherTp)
203+
err.typeMismatchMsg(memberTp, otherTp)
204204
else ""
205205

206206
"overriding %s;\n %s %s%s".format(

0 commit comments

Comments
 (0)