diff options
author | Martin Odersky <odersky@gmail.com> | 2016-08-07 17:29:24 +0200 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2016-08-16 17:34:42 +0200 |
commit | 8d4d9a363d90cc24bd79b18ea2ef7cba6a746bef (patch) | |
tree | 89305ec17cdc4d421047e0ec4a742a79b91a286f /src/dotty/tools/dotc/printing | |
parent | d5f42680803e40f9b3698404848450d088fca07a (diff) | |
download | dotty-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.scala | 86 | ||||
-rw-r--r-- | src/dotty/tools/dotc/printing/Formatting.scala | 175 | ||||
-rw-r--r-- | src/dotty/tools/dotc/printing/PlainPrinter.scala | 40 | ||||
-rw-r--r-- | src/dotty/tools/dotc/printing/Printer.scala | 3 | ||||
-rw-r--r-- | src/dotty/tools/dotc/printing/RefinedPrinter.scala | 4 |
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 |