aboutsummaryrefslogtreecommitdiff
path: root/src/dotty/tools/dotc/printing
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2016-08-07 17:29:24 +0200
committerMartin Odersky <odersky@gmail.com>2016-08-16 17:34:42 +0200
commit8d4d9a363d90cc24bd79b18ea2ef7cba6a746bef (patch)
tree89305ec17cdc4d421047e0ec4a742a79b91a286f /src/dotty/tools/dotc/printing
parentd5f42680803e40f9b3698404848450d088fca07a (diff)
downloaddotty-8d4d9a363d90cc24bd79b18ea2ef7cba6a746bef.tar.gz
dotty-8d4d9a363d90cc24bd79b18ea2ef7cba6a746bef.tar.bz2
dotty-8d4d9a363d90cc24bd79b18ea2ef7cba6a746bef.zip
New string infterpolators
Roll `sm` and `i` into one interpolator (also called `i`) Evolve `d` to `em` interpolator (for error messages) New interpolator `ex` with more explanations, replaces disambiguation.
Diffstat (limited to 'src/dotty/tools/dotc/printing')
-rw-r--r--src/dotty/tools/dotc/printing/Disambiguation.scala86
-rw-r--r--src/dotty/tools/dotc/printing/Formatting.scala175
-rw-r--r--src/dotty/tools/dotc/printing/PlainPrinter.scala40
-rw-r--r--src/dotty/tools/dotc/printing/Printer.scala3
-rw-r--r--src/dotty/tools/dotc/printing/RefinedPrinter.scala4
5 files changed, 211 insertions, 97 deletions
diff --git a/src/dotty/tools/dotc/printing/Disambiguation.scala b/src/dotty/tools/dotc/printing/Disambiguation.scala
deleted file mode 100644
index aa3fae2de..000000000
--- a/src/dotty/tools/dotc/printing/Disambiguation.scala
+++ /dev/null
@@ -1,86 +0,0 @@
-package dotty.tools.dotc
-package printing
-
-import core._
-import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Contexts._
-import collection.mutable
-import scala.annotation.switch
-
-object Disambiguation {
-
- private class State {
- var hasConflicts = false
- val symString = new mutable.HashMap[Symbol, String]
- val variants = new mutable.HashMap[String, mutable.ListBuffer[Symbol]]
- }
-
- def newPrinter: Context => RefinedPrinter = {
- val state = new State
- new Printer(state)(_)
- }
-
- private class Printer(state: State)(_ctx: Context) extends RefinedPrinter(_ctx) {
- import state._
-
- override def simpleNameString(sym: Symbol): String = {
- if ((sym is ModuleClass) && sym.sourceModule.exists) simpleNameString(sym.sourceModule)
- else symString.getOrElse(sym, recordedNameString(sym))
- }
-
- private def rawNameString(sym: Symbol) = super.simpleNameString(sym)
-
- private def recordedNameString(sym: Symbol): String = {
- val str = rawNameString(sym)
- val existing = variants.getOrElse(str, new mutable.ListBuffer[Symbol])
- // Dotty deviation: without a type parameter on ListBuffer, inference
- // will compute ListBuffer[Symbol] | ListBuffer[Nothing] as the type of "existing"
- // and then the assignment to variants below will fail.
- // We need to find a way to avoid such useless inferred types.
- if (!(existing contains sym)) {
- hasConflicts |= existing.nonEmpty
- variants(str) = (existing += sym)
- }
- str
- }
-
- def disambiguated(): Boolean = {
- val res = hasConflicts
- while (hasConflicts) disambiguate()
- res
- }
-
- private def qualifiers: Stream[String] =
- Stream("", "(some other)", "(some 3rd)") ++ (Stream.from(4) map (n => s"(some ${n}th)"))
-
- private def disambiguate(): Unit = {
- def update(sym: Symbol, str: String) = if (!(symString contains sym)) symString(sym) = str
- def disambiguated(sym: Symbol, owner: Symbol) = s"${rawNameString(sym)}(in ${simpleNameString(owner)})"
- hasConflicts = false
- for ((name, vs) <- variants.toList)
- if (vs.tail.nonEmpty) {
- for ((owner, syms) <- vs.groupBy(_.effectiveOwner)) {
- if (syms.tail.isEmpty) update(syms.head, disambiguated(syms.head, owner))
- else
- for {
- (kind, syms1) <- syms.groupBy(kindString)
- (sym, qual) <- syms1 zip qualifiers
- } {
- update(sym, s"$qual$kind ${disambiguated(sym, owner)}")
- }
- }
- }
- }
- }
-
- def disambiguated(op: Context => String)(implicit ctx: Context): String = {
- val dctx = ctx.printer match {
- case dp: Printer => ctx
- case _ => ctx.fresh.setPrinterFn(newPrinter)
- }
- val res = op(dctx)
- dctx.printer match {
- case dp: Printer if dp.disambiguated() => op(dctx)
- case _ => res
- }
- }
-}
diff --git a/src/dotty/tools/dotc/printing/Formatting.scala b/src/dotty/tools/dotc/printing/Formatting.scala
new file mode 100644
index 000000000..174d801d1
--- /dev/null
+++ b/src/dotty/tools/dotc/printing/Formatting.scala
@@ -0,0 +1,175 @@
+package dotty.tools.dotc
+package printing
+
+import core._
+import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Contexts._
+import collection.mutable
+import collection.Map
+import Decorators._
+import scala.annotation.switch
+import scala.util.control.NonFatal
+import reporting.Diagnostic
+
+object Formatting {
+
+ /** General purpose string formatter, with the following features:
+ *
+ * 1) On all Showables, `show` is called instead of `toString`
+ * 2) Exceptions raised by a `show` are handled by falling back to `toString`.
+ * 3) Sequences can be formatted using the desired separator between two `%` signs,
+ * eg `i"myList = (${myList}%, %)"`
+ * 4) Safe handling of multi-line margins. Left margins are skipped om the parts
+ * of the string context *before* inserting the arguments. That way, we guard
+ * against accidentally treating an interpolated value as a margin.
+ */
+ class StringFormatter(protected val sc: StringContext) {
+
+ protected def showArg(arg: Any)(implicit ctx: Context): String = arg match {
+ case arg: Showable =>
+ try arg.show(ctx.addMode(Mode.FutureDefsOK))
+ catch {
+ case NonFatal(ex) => s"(missing due to $ex)"
+ }
+ case _ => arg.toString
+ }
+
+ private def treatArg(arg: Any, suffix: String)(implicit ctx: Context): (Any, String) = arg match {
+ case arg: Seq[_] if suffix.nonEmpty && suffix.head == '%' =>
+ val (rawsep, rest) = suffix.tail.span(_ != '%')
+ val sep = StringContext.treatEscapes(rawsep)
+ if (rest.nonEmpty) (arg.map(showArg).mkString(sep), rest.tail)
+ else (arg, suffix)
+ case _ =>
+ (showArg(arg), suffix)
+ }
+
+ def assemble(args: Seq[Any])(implicit ctx: Context): String = {
+ def isLineBreak(c: Char) = c == '\n' || c == '\f' // compatible with StringLike#isLineBreak
+ def stripTrailingPart(s: String) = {
+ val (pre, post) = s.span(c => !isLineBreak(c))
+ pre ++ post.stripMargin
+ }
+ val (prefix, suffixes) = sc.parts.toList match {
+ case head :: tail => (head.stripMargin, tail map stripTrailingPart)
+ case Nil => ("", Nil)
+ }
+ val (args1, suffixes1) = (args, suffixes).zipped.map(treatArg(_, _)).unzip
+ new StringContext(prefix :: suffixes1.toList: _*).s(args1: _*)
+ }
+ }
+
+ /** The d string interpolator works like the i string interpolator, but marks nonsensical errors
+ * using `<nonsensical>...</nonsensical>` tags.
+ * Note: Instead of these tags, it would be nicer to return a data structure containing the message string
+ * and a boolean indicating whether the message is sensical, but then we cannot use string operations
+ * like concatenation, stripMargin etc on the values returned by d"...", and in the current error
+ * 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
+ }
+ val str = super.showArg(arg)
+ if (isSensical(arg)) str else Diagnostic.nonSensicalStartTag + str + Diagnostic.nonSensicalEndTag
+ }
+ }
+
+ private type Recorded = AnyRef /*Symbol | PolyParam*/
+
+ private class Seen extends mutable.HashMap[String, List[Recorded]] {
+
+ override def default(key: String) = Nil
+
+ def record(str: String, entry: Recorded): String = {
+ var alts = apply(str).dropWhile(entry ne _)
+ if (alts.isEmpty) {
+ alts = entry :: apply(str)
+ update(str, alts)
+ }
+ str + "'" * (alts.length - 1)
+ }
+ }
+
+ private class ExplainingPrinter(seen: Seen)(_ctx: Context) extends RefinedPrinter(_ctx) {
+ override def simpleNameString(sym: Symbol): String =
+ if ((sym is ModuleClass) && sym.sourceModule.exists) simpleNameString(sym.sourceModule)
+ else seen.record(super.simpleNameString(sym), sym)
+
+ override def polyParamNameString(param: PolyParam): String =
+ 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): 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 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 =>
+ val ownerStr =
+ if (!sym.exists) ""
+ else {
+ var owner = sym.effectiveOwner
+ if (owner.isLocalDummy) i" locally defined in ${owner.owner}"
+ else i" in $owner"
+ }
+ s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}"
+ }
+ }
+
+ 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)
+ 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)
+ }
+
+ 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"
+ }
+ }
+}
diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala
index acf4514ea..a92095d9b 100644
--- a/src/dotty/tools/dotc/printing/PlainPrinter.scala
+++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala
@@ -188,22 +188,22 @@ class PlainPrinter(_ctx: Context) extends Printer {
case tp: TypeLambda =>
typeLambdaText(tp.paramNames.map(_.toString), tp.variances, tp.paramBounds, tp.resultType)
case tp: PolyType =>
- def paramText(name: TypeName, bounds: TypeBounds) =
- toText(polyParamName(name)) ~ polyHash(tp) ~ toText(bounds)
+ def paramText(name: TypeName, bounds: TypeBounds): Text =
+ polyParamNameString(name) ~ polyHash(tp) ~ toText(bounds)
changePrec(GlobalPrec) {
"[" ~
Text((tp.paramNames, tp.paramBounds).zipped map paramText, ", ") ~
"]" ~ toText(tp.resultType)
}
- case PolyParam(pt, n) =>
- toText(polyParamName(pt.paramNames(n))) ~ polyHash(pt)
+ case tp: PolyParam =>
+ polyParamNameString(tp) ~ polyHash(tp.binder)
case AnnotatedType(tpe, annot) =>
toTextLocal(tpe) ~ " " ~ toText(annot)
case HKApply(tycon, args) =>
toTextLocal(tycon) ~ "[" ~ Text(args.map(argText), ", ") ~ "]"
case tp: TypeVar =>
if (tp.isInstantiated)
- toTextLocal(tp.instanceOpt) ~ "'" // debug for now, so that we can see where the TypeVars are.
+ toTextLocal(tp.instanceOpt) ~ "^" // debug for now, so that we can see where the TypeVars are.
else {
val constr = ctx.typerState.constraint
val bounds =
@@ -219,7 +219,9 @@ class PlainPrinter(_ctx: Context) extends Printer {
}
}.close
- protected def polyParamName(name: TypeName): TypeName = name
+ protected def polyParamNameString(name: TypeName): String = name.toString
+
+ protected def polyParamNameString(param: PolyParam): String = polyParamNameString(param.binder.paramNames(param.paramNum))
/** The name of the symbol without a unique id. Under refined printing,
* the decoded original name.
@@ -416,13 +418,33 @@ class PlainPrinter(_ctx: Context) extends Printer {
def locationText(sym: Symbol): Text =
if (!sym.exists) ""
else {
- val owns = sym.effectiveOwner
- if (owns.isClass && !isEmptyPrefix(owns)) " in " ~ toText(owns) else Text()
- }
+ val ownr = sym.effectiveOwner
+ if (ownr.isClass && !isEmptyPrefix(ownr)) " in " ~ toText(ownr) else Text()
+ }
def locatedText(sym: Symbol): Text =
(toText(sym) ~ locationText(sym)).close
+ def extendedLocationText(sym: Symbol): Text =
+ if (!sym.exists) ""
+ else {
+ def recur(ownr: Symbol, innerLocation: String): Text = {
+ def nextOuter(innerKind: String): Text =
+ recur(ownr.effectiveOwner,
+ if (!innerLocation.isEmpty) innerLocation
+ else s" in an anonymous $innerKind")
+ def showLocation(ownr: Symbol, where: String): Text =
+ innerLocation ~ " " ~ where ~ " " ~ toText(ownr)
+ if (ownr.isAnonymousClass) nextOuter("class")
+ else if (ownr.isAnonymousFunction) nextOuter("function")
+ else if (isEmptyPrefix(ownr)) ""
+ else if (ownr.isLocalDummy) showLocation(ownr.owner, "locally defined in")
+ else if (ownr.isTerm && !ownr.is(Module | Method)) showLocation(ownr, "in the initalizer of")
+ else showLocation(ownr, "in")
+ }
+ recur(sym.owner, "")
+ }
+
def toText(denot: Denotation): Text = toText(denot.symbol) ~ "/D"
@switch private def escapedChar(ch: Char): String = ch match {
diff --git a/src/dotty/tools/dotc/printing/Printer.scala b/src/dotty/tools/dotc/printing/Printer.scala
index 360874522..14b63012e 100644
--- a/src/dotty/tools/dotc/printing/Printer.scala
+++ b/src/dotty/tools/dotc/printing/Printer.scala
@@ -68,6 +68,9 @@ abstract class Printer {
/** Textual representation of symbol and its location */
def locatedText(sym: Symbol): Text
+ /** A description of sym's location */
+ def extendedLocationText(sym: Symbol): Text
+
/** Textual representation of denotation */
def toText(denot: Denotation): Text
diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala
index ce063f06a..83f61c976 100644
--- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala
+++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala
@@ -532,8 +532,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
def optText[T >: Untyped](tree: List[Tree[T]])(encl: Text => Text): Text =
if (tree.exists(!_.isEmpty)) encl(blockText(tree)) else ""
- override protected def polyParamName(name: TypeName): TypeName =
- name.unexpandedName
+ override protected def polyParamNameString(name: TypeName): String =
+ name.unexpandedName.toString
override protected def treatAsTypeParam(sym: Symbol): Boolean = sym is TypeParam