aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/printing
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/src/dotty/tools/dotc/printing')
-rw-r--r--compiler/src/dotty/tools/dotc/printing/Formatting.scala258
-rw-r--r--compiler/src/dotty/tools/dotc/printing/Highlighting.scala77
-rw-r--r--compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala500
-rw-r--r--compiler/src/dotty/tools/dotc/printing/Printer.scala105
-rw-r--r--compiler/src/dotty/tools/dotc/printing/Printers.scala14
-rw-r--r--compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala652
-rw-r--r--compiler/src/dotty/tools/dotc/printing/Showable.scala34
-rw-r--r--compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala304
-rw-r--r--compiler/src/dotty/tools/dotc/printing/Texts.scala168
-rw-r--r--compiler/src/dotty/tools/dotc/printing/package.scala17
10 files changed, 2129 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala
new file mode 100644
index 000000000..b321d3736
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala
@@ -0,0 +1,258 @@
+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.MessageContainer
+import util.DiffUtil
+import Highlighting._
+import SyntaxHighlighting._
+
+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
+ catch {
+ case NonFatal(ex) => s"[cannot display due to $ex, raw string = $toString]"
+ }
+ 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 `em` 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 em"...", 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 =
+ 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)
+ }
+ }
+
+ 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*/
+
+ private class Seen extends mutable.HashMap[String, List[Recorded]] {
+
+ override def default(key: String) = Nil
+
+ def record(str: String, entry: Recorded)(implicit ctx: Context): String = {
+ def followAlias(e1: Recorded): Recorded = e1 match {
+ case e1: Symbol if e1.isAliasType =>
+ val underlying = e1.typeRef.underlyingClassRef(refinementOK = false).typeSymbol
+ if (underlying.name == e1.name) underlying else e1
+ case _ => e1
+ }
+ lazy val dealiased = followAlias(entry)
+ var alts = apply(str).dropWhile(alt => dealiased ne followAlias(alt))
+ 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)
+ }
+
+ /** 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 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 _ =>
+ ""
+ }
+
+ 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
+ }
+
+ 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 explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) }
+ val explainLines = columnar(explainParts)
+ if (explainLines.isEmpty) "" else i"where: $explainLines%\n %\n"
+ }
+
+ /** 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/compiler/src/dotty/tools/dotc/printing/Highlighting.scala b/compiler/src/dotty/tools/dotc/printing/Highlighting.scala
new file mode 100644
index 000000000..3bda7fb7a
--- /dev/null
+++ b/compiler/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/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
new file mode 100644
index 000000000..15c382bb0
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
@@ -0,0 +1,500 @@
+package dotty.tools.dotc
+package printing
+
+import core._
+import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._, Denotations._
+import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation
+import StdNames.{nme, tpnme}
+import ast.Trees._, ast._
+import config.Config
+import java.lang.Integer.toOctalString
+import config.Config.summarizeDepth
+import scala.annotation.switch
+
+class PlainPrinter(_ctx: Context) extends Printer {
+ protected[this] implicit def ctx: Context = _ctx.addMode(Mode.Printing)
+
+ private var openRecs: List[RecType] = Nil
+
+ protected def maxToTextRecursions = 100
+
+ protected final def controlled(op: => Text): Text =
+ if (ctx.toTextRecursions < maxToTextRecursions && ctx.toTextRecursions < maxSummarized)
+ try {
+ ctx.toTextRecursions += 1
+ op
+ } finally {
+ ctx.toTextRecursions -= 1
+ }
+ else {
+ if (ctx.toTextRecursions >= maxToTextRecursions)
+ recursionLimitExceeded()
+ "..."
+ }
+
+ protected def recursionLimitExceeded() = {
+ ctx.warning("Exceeded recursion depth attempting to print.")
+ if (ctx.debug) Thread.dumpStack()
+ }
+
+ /** If true, tweak output so it is the same before and after pickling */
+ protected def homogenizedView: Boolean = ctx.settings.YtestPickler.value
+
+ def homogenize(tp: Type): Type =
+ if (homogenizedView)
+ tp match {
+ case tp: ThisType if tp.cls.is(Package) && !tp.cls.isEffectiveRoot =>
+ ctx.requiredPackage(tp.cls.fullName).termRef
+ case tp: TypeVar if tp.isInstantiated =>
+ homogenize(tp.instanceOpt)
+ case AndType(tp1, tp2) =>
+ homogenize(tp1) & homogenize(tp2)
+ case OrType(tp1, tp2) =>
+ homogenize(tp1) | homogenize(tp2)
+ case tp: SkolemType =>
+ homogenize(tp.info)
+ case tp: LazyRef =>
+ homogenize(tp.ref)
+ case HKApply(tycon, args) =>
+ tycon.dealias.appliedTo(args)
+ case _ =>
+ tp
+ }
+ else tp
+
+ private def selfRecName(n: Int) = s"z$n"
+
+ /** Render elements alternating with `sep` string */
+ protected def toText(elems: Traversable[Showable], sep: String) =
+ Text(elems map (_ toText this), sep)
+
+ /** Render element within highest precedence */
+ protected def toTextLocal(elem: Showable): Text =
+ atPrec(DotPrec) { elem.toText(this) }
+
+ /** Render element within lowest precedence */
+ protected def toTextGlobal(elem: Showable): Text =
+ atPrec(GlobalPrec) { elem.toText(this) }
+
+ protected def toTextLocal(elems: Traversable[Showable], sep: String) =
+ atPrec(DotPrec) { toText(elems, sep) }
+
+ protected def toTextGlobal(elems: Traversable[Showable], sep: String) =
+ atPrec(GlobalPrec) { toText(elems, sep) }
+
+ /** If the name of the symbol's owner should be used when you care about
+ * seeing an interesting name: in such cases this symbol is e.g. a method
+ * parameter with a synthetic name, a constructor named "this", an object
+ * "package", etc. The kind string, if non-empty, will be phrased relative
+ * to the name of the owner.
+ */
+ protected def hasMeaninglessName(sym: Symbol) = (
+ (sym is Param) && sym.owner.isSetter // x$1
+ || sym.isClassConstructor // this
+ || (sym.name == nme.PACKAGE) // package
+ )
+
+ def nameString(name: Name): String = name.toString + {
+ if (ctx.settings.debugNames.value)
+ if (name.isTypeName) "/T" else "/V"
+ else ""
+ }
+
+ def toText(name: Name): Text = Str(nameString(name))
+
+ /** String representation of a name used in a refinement
+ * In refined printing this undoes type parameter expansion
+ */
+ protected def refinementNameString(tp: RefinedType) = nameString(tp.refinedName)
+
+ /** String representation of a refinement */
+ protected def toTextRefinement(rt: RefinedType) =
+ (refinementNameString(rt) ~ toTextRHS(rt.refinedInfo)).close
+
+ protected def argText(arg: Type): Text = arg match {
+ case arg: TypeBounds => "_" ~ toTextGlobal(arg)
+ case _ => toTextGlobal(arg)
+ }
+
+ /** The longest sequence of refinement types, starting at given type
+ * and following parents.
+ */
+ private def refinementChain(tp: Type): List[Type] =
+ tp :: (tp match {
+ case tp: RefinedType => refinementChain(tp.parent.stripTypeVar)
+ case _ => Nil
+ })
+
+ def toText(tp: Type): Text = controlled {
+ homogenize(tp) match {
+ case tp: TypeType =>
+ toTextRHS(tp)
+ case tp: TermRef
+ if !tp.denotationIsCurrent && !homogenizedView || // always print underyling when testing picklers
+ tp.symbol.is(Module) ||
+ tp.symbol.name.isImportName =>
+ toTextRef(tp) ~ ".type"
+ case tp: TermRef if tp.denot.isOverloaded =>
+ "<overloaded " ~ toTextRef(tp) ~ ">"
+ case tp: SingletonType =>
+ toTextLocal(tp.underlying) ~ "(" ~ toTextRef(tp) ~ ")"
+ case tp: TypeRef =>
+ toTextPrefix(tp.prefix) ~ selectionString(tp)
+ case tp: RefinedType =>
+ val parent :: (refined: List[RefinedType @unchecked]) =
+ refinementChain(tp).reverse
+ toTextLocal(parent) ~ "{" ~ Text(refined map toTextRefinement, "; ").close ~ "}"
+ case tp: RecType =>
+ try {
+ openRecs = tp :: openRecs
+ "{" ~ selfRecName(openRecs.length) ~ " => " ~ toTextGlobal(tp.parent) ~ "}"
+ }
+ finally openRecs = openRecs.tail
+ case AndType(tp1, tp2) =>
+ changePrec(AndPrec) { toText(tp1) ~ " & " ~ toText(tp2) }
+ case OrType(tp1, tp2) =>
+ changePrec(OrPrec) { toText(tp1) ~ " | " ~ toText(tp2) }
+ case ErrorType =>
+ "<error>"
+ case tp: WildcardType =>
+ if (tp.optBounds.exists) "(?" ~ toTextRHS(tp.bounds) ~ ")" else "?"
+ case NoType =>
+ "<notype>"
+ case NoPrefix =>
+ "<noprefix>"
+ case tp: MethodType =>
+ def paramText(name: TermName, tp: Type) = toText(name) ~ ": " ~ toText(tp)
+ changePrec(GlobalPrec) {
+ (if (tp.isImplicit) "(implicit " else "(") ~
+ Text((tp.paramNames, tp.paramTypes).zipped map paramText, ", ") ~
+ ")" ~ toText(tp.resultType)
+ }
+ case tp: ExprType =>
+ changePrec(GlobalPrec) { "=> " ~ toText(tp.resultType) }
+ case tp: PolyType =>
+ def paramText(variance: Int, name: Name, bounds: TypeBounds): Text =
+ varianceString(variance) ~ name.toString ~ toText(bounds)
+ changePrec(GlobalPrec) {
+ "[" ~ Text((tp.variances, tp.paramNames, tp.paramBounds).zipped.map(paramText), ", ") ~
+ "] => " ~ toTextGlobal(tp.resultType)
+ }
+ 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.
+ else {
+ val constr = ctx.typerState.constraint
+ val bounds =
+ if (constr.contains(tp)) constr.fullBounds(tp.origin)(ctx.addMode(Mode.Printing))
+ else TypeBounds.empty
+ if (ctx.settings.YshowVarBounds.value) "(" ~ toText(tp.origin) ~ "?" ~ toText(bounds) ~ ")"
+ else toText(tp.origin)
+ }
+ case tp: LazyRef =>
+ "LazyRef(" ~ toTextGlobal(tp.ref) ~ ")" // TODO: only print this during debug mode?
+ case _ =>
+ tp.fallbackToText(this)
+ }
+ }.close
+
+ 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.
+ */
+ protected def simpleNameString(sym: Symbol): String = nameString(sym.name)
+
+ /** If -uniqid is set, the hashcode of the polytype, after a # */
+ protected def polyHash(pt: PolyType): Text =
+ if (ctx.settings.uniqid.value) "#" + pt.hashCode else ""
+
+ /** If -uniqid is set, the unique id of symbol, after a # */
+ protected def idString(sym: Symbol): String =
+ if (ctx.settings.uniqid.value) "#" + sym.id else ""
+
+ def nameString(sym: Symbol): String =
+ simpleNameString(sym) + idString(sym) // + "<" + (if (sym.exists) sym.owner else "") + ">"
+
+ def fullNameString(sym: Symbol): String =
+ if (sym.isRoot || sym == NoSymbol || sym.owner.isEffectiveRoot)
+ nameString(sym)
+ else
+ fullNameString(fullNameOwner(sym)) + "." + nameString(sym)
+
+ protected def fullNameOwner(sym: Symbol): Symbol = sym.effectiveOwner.enclosingClass
+
+ protected def objectPrefix = "object "
+ protected def packagePrefix = "package "
+
+ protected def trimPrefix(text: Text) =
+ text.stripPrefix(objectPrefix).stripPrefix(packagePrefix)
+
+ protected def selectionString(tp: NamedType) = {
+ val sym = if (homogenizedView) tp.symbol else tp.currentSymbol
+ if (sym.exists) nameString(sym) else nameString(tp.name)
+ }
+
+ /** The string representation of this type used as a prefix */
+ protected def toTextRef(tp: SingletonType): Text = controlled {
+ tp match {
+ case tp: TermRef =>
+ toTextPrefix(tp.prefix) ~ selectionString(tp)
+ case tp: ThisType =>
+ nameString(tp.cls) + ".this"
+ case SuperType(thistpe: SingletonType, _) =>
+ toTextRef(thistpe).map(_.replaceAll("""\bthis$""", "super"))
+ case SuperType(thistpe, _) =>
+ "Super(" ~ toTextGlobal(thistpe) ~ ")"
+ case tp @ ConstantType(value) =>
+ toText(value)
+ case MethodParam(mt, idx) =>
+ nameString(mt.paramNames(idx))
+ case tp: RecThis =>
+ val idx = openRecs.reverse.indexOf(tp.binder)
+ if (idx >= 0) selfRecName(idx + 1)
+ else "{...}.this" // TODO move underlying type to an addendum, e.g. ... z3 ... where z3: ...
+ case tp: SkolemType =>
+ if (homogenizedView) toText(tp.info) else tp.repr
+ }
+ }
+
+ /** The string representation of this type used as a prefix */
+ protected def toTextPrefix(tp: Type): Text = controlled {
+ homogenize(tp) match {
+ case NoPrefix => ""
+ case tp: SingletonType => toTextRef(tp) ~ "."
+ case tp => trimPrefix(toTextLocal(tp)) ~ "#"
+ }
+ }
+
+ protected def isOmittablePrefix(sym: Symbol): Boolean =
+ defn.UnqualifiedOwnerTypes.exists(_.symbol == sym) || isEmptyPrefix(sym)
+
+ protected def isEmptyPrefix(sym: Symbol): Boolean =
+ sym.isEffectiveRoot || sym.isAnonymousClass || sym.name.isReplWrapperName
+
+ /** String representation of a definition's type following its name,
+ * if symbol is completed, "?" otherwise.
+ */
+ protected def toTextRHS(optType: Option[Type]): Text = optType match {
+ case Some(tp) => toTextRHS(tp)
+ case None => "?"
+ }
+
+ /** String representation of a definition's type following its name */
+ protected def toTextRHS(tp: Type): Text = controlled {
+ homogenize(tp) match {
+ case tp @ TypeBounds(lo, hi) =>
+ if (lo eq hi) {
+ val eql =
+ if (tp.variance == 1) " =+ "
+ else if (tp.variance == -1) " =- "
+ else " = "
+ eql ~ toText(lo)
+ }
+ else
+ (if (lo isRef defn.NothingClass) Text() else " >: " ~ toText(lo)) ~
+ (if (hi isRef defn.AnyClass) Text() else " <: " ~ toText(hi))
+ case tp @ ClassInfo(pre, cls, cparents, decls, selfInfo) =>
+ val preText = toTextLocal(pre)
+ val (tparams, otherDecls) = decls.toList partition treatAsTypeParam
+ val tparamsText =
+ if (tparams.isEmpty) Text() else ("[" ~ dclsText(tparams) ~ "]").close
+ val selfText: Text = selfInfo match {
+ case NoType => Text()
+ case sym: Symbol if !sym.isCompleted => "this: ? =>"
+ case _ => "this: " ~ atPrec(InfixPrec) { toText(tp.selfType) } ~ " =>"
+ }
+ val trueDecls = otherDecls.filterNot(treatAsTypeArg)
+ val declsText =
+ if (trueDecls.isEmpty || !ctx.settings.debug.value) Text()
+ else dclsText(trueDecls)
+ tparamsText ~ " extends " ~ toTextParents(tp.parents) ~ "{" ~ selfText ~ declsText ~
+ "} at " ~ preText
+ case tp =>
+ ": " ~ toTextGlobal(tp)
+ }
+ }
+
+ protected def toTextParents(parents: List[Type]): Text = Text(parents.map(toTextLocal), " with ")
+
+ protected def treatAsTypeParam(sym: Symbol): Boolean = false
+ protected def treatAsTypeArg(sym: Symbol): Boolean = false
+
+ /** String representation of symbol's kind. */
+ def kindString(sym: Symbol): String = {
+ val flags = sym.flagsUNSAFE
+ if (flags is PackageClass) "package class"
+ else if (flags is PackageVal) "package"
+ else if (sym.isPackageObject)
+ if (sym.isClass) "package object class"
+ else "package object"
+ else if (sym.isAnonymousClass) "anonymous class"
+ else if (flags is ModuleClass) "module class"
+ else if (flags is ModuleVal) "module"
+ else if (flags is ImplClass) "implementation class"
+ else if (flags is Trait) "trait"
+ else if (sym.isClass) "class"
+ else if (sym.isType) "type"
+ else if (sym.isGetter) "getter"
+ else if (sym.isSetter) "setter"
+ else if (flags is Lazy) "lazy value"
+ else if (flags is Mutable) "variable"
+ else if (sym.isClassConstructor && sym.isPrimaryConstructor) "primary constructor"
+ else if (sym.isClassConstructor) "constructor"
+ else if (sym.is(Method)) "method"
+ else if (sym.isTerm) "value"
+ else ""
+ }
+
+ /** String representation of symbol's definition key word */
+ protected def keyString(sym: Symbol): String = {
+ val flags = sym.flagsUNSAFE
+ if (flags is JavaTrait) "interface"
+ else if ((flags is Trait) && !(flags is ImplClass)) "trait"
+ else if (sym.isClass) "class"
+ else if (sym.isType) "type"
+ else if (flags is Mutable) "var"
+ else if (flags is Package) "package"
+ else if (flags is Module) "object"
+ else if (sym is Method) "def"
+ else if (sym.isTerm && (!(flags is Param))) "val"
+ else ""
+ }
+
+ /** String representation of symbol's flags */
+ protected def toTextFlags(sym: Symbol): Text =
+ Text(sym.flagsUNSAFE.flagStrings map stringToText, " ")
+
+ /** String representation of symbol's variance or "" if not applicable */
+ protected def varianceString(sym: Symbol): String = varianceString(sym.variance)
+
+ protected def varianceString(v: Int): String = v match {
+ case -1 => "-"
+ case 1 => "+"
+ case _ => ""
+ }
+
+ def annotsText(sym: Symbol): Text = Text(sym.annotations.map(toText))
+
+ def dclText(sym: Symbol): Text = dclTextWithInfo(sym, sym.unforcedInfo)
+
+ def dclText(d: SingleDenotation): Text = dclTextWithInfo(d.symbol, Some(d.info))
+
+ private def dclTextWithInfo(sym: Symbol, info: Option[Type]): Text =
+ (toTextFlags(sym) ~~ keyString(sym) ~~
+ (varianceString(sym) ~ nameString(sym)) ~ toTextRHS(info)).close
+
+ def toText(sym: Symbol): Text =
+ (kindString(sym) ~~ {
+ if (sym.isAnonymousClass) toText(sym.info.parents, " with ") ~ "{...}"
+ else if (hasMeaninglessName(sym)) simpleNameString(sym.owner) + idString(sym)
+ else nameString(sym)
+ }).close
+
+ def locationText(sym: Symbol): Text =
+ if (!sym.exists) ""
+ else {
+ 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 {
+ case '\b' => "\\b"
+ case '\t' => "\\t"
+ case '\n' => "\\n"
+ case '\f' => "\\f"
+ case '\r' => "\\r"
+ case '"' => "\\\""
+ case '\'' => "\\\'"
+ case '\\' => "\\\\"
+ case _ => if (ch.isControl) "\\0" + toOctalString(ch) else String.valueOf(ch)
+ }
+
+ def toText(const: Constant): Text = const.tag match {
+ case StringTag => "\"" + escapedString(const.value.toString) + "\""
+ case ClazzTag => "classOf[" ~ toText(const.typeValue.classSymbol) ~ "]"
+ case CharTag => s"'${escapedChar(const.charValue)}'"
+ case LongTag => const.longValue.toString + "L"
+ case EnumTag => const.symbolValue.name.toString
+ case _ => String.valueOf(const.value)
+ }
+
+ def toText(annot: Annotation): Text = s"@${annot.symbol.name}" // for now
+
+ protected def escapedString(str: String): String = str flatMap escapedChar
+
+ def dclsText(syms: List[Symbol], sep: String): Text = Text(syms map dclText, sep)
+
+ def toText(sc: Scope): Text =
+ ("Scope{" ~ dclsText(sc.toList) ~ "}").close
+
+ def toText[T >: Untyped](tree: Tree[T]): Text = {
+ tree match {
+ case node: Positioned =>
+ def toTextElem(elem: Any): Text = elem match {
+ case elem: Showable => elem.toText(this)
+ case elem: List[_] => "List(" ~ Text(elem map toTextElem, ",") ~ ")"
+ case elem => elem.toString
+ }
+ val nodeName = node.productPrefix
+ val elems =
+ Text(node.productIterator.map(toTextElem).toList, ", ")
+ val tpSuffix =
+ if (ctx.settings.printtypes.value && tree.hasType)
+ " | " ~ toText(tree.typeOpt)
+ else
+ Text()
+
+ nodeName ~ "(" ~ elems ~ tpSuffix ~ ")" ~ node.pos.toString
+ case _ =>
+ tree.fallbackToText(this)
+ }
+ }.close // todo: override in refined printer
+
+ private var maxSummarized = Int.MaxValue
+
+ def summarized[T](depth: Int)(op: => T): T = {
+ val saved = maxSummarized
+ maxSummarized = ctx.toTextRecursions + depth
+ try op
+ finally maxSummarized = depth
+ }
+
+ def summarized[T](op: => T): T = summarized(summarizeDepth)(op)
+
+ def plain = this
+}
+
diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala
new file mode 100644
index 000000000..14b63012e
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala
@@ -0,0 +1,105 @@
+package dotty.tools.dotc
+package printing
+
+import core._
+import Texts._, ast.Trees._
+import Types.Type, Symbols.Symbol, Contexts.Context, Scopes.Scope, Constants.Constant,
+ Names.Name, Denotations._, Annotations.Annotation
+
+/** The base class of all printers
+ */
+abstract class Printer {
+
+ private[this] var prec: Precedence = GlobalPrec
+
+ /** The current precedence level */
+ def currentPrecedence = prec
+
+ /** Generate text using `op`, assuming a given precedence level `prec`. */
+ def atPrec(prec: Precedence)(op: => Text): Text = {
+ val outerPrec = this.prec
+ this.prec = prec
+ try op
+ finally this.prec = outerPrec
+ }
+
+ /** Generate text using `op`, assuming a given precedence level `prec`.
+ * If new level `prec` is lower than previous level, put text in parentheses.
+ */
+ def changePrec(prec: Precedence)(op: => Text): Text =
+ if (prec < this.prec) atPrec(prec) ("(" ~ op ~ ")") else atPrec(prec)(op)
+
+ /** The name, possibley with with namespace suffix if debugNames is set:
+ * /L for local names, /V for other term names, /T for type names
+ */
+ def nameString(name: Name): String
+
+ /** The name of the given symbol.
+ * If !settings.debug, the original name where
+ * expansions of operators are translated back to operator symbol.
+ * E.g. $eq => =.
+ * If settings.uniqid, adds id.
+ */
+ def nameString(sym: Symbol): String
+
+ /** The fully qualified name of the symbol */
+ def fullNameString(sym: Symbol): String
+
+ /** The kind of the symbol */
+ def kindString(sym: Symbol): String
+
+ /** The name as a text */
+ def toText(name: Name): Text
+
+ /** Textual representation, including symbol's kind e.g., "class Foo", "method Bar".
+ * If hasMeaninglessName is true, uses the owner's name to disambiguate identity.
+ */
+ def toText(sym: Symbol): Text
+
+ /** Textual representation of symbol's declaration */
+ def dclText(sym: Symbol): Text
+
+ /** Textual representation of single denotation's declaration */
+ def dclText(sd: SingleDenotation): Text
+
+ /** If symbol's owner is a printable class C, the text "in C", otherwise "" */
+ def locationText(sym: Symbol): Text
+
+ /** 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
+
+ /** Textual representation of constant */
+ def toText(const: Constant): Text
+
+ /** Textual representation of annotation */
+ def toText(annot: Annotation): Text
+
+ /** Textual representation of type */
+ def toText(tp: Type): Text
+
+ /** Textual representation of all symbols in given list,
+ * using `dclText` for displaying each.
+ */
+ def dclsText(syms: List[Symbol], sep: String = "\n"): Text
+
+ /** Textual representation of all definitions in a scope using `dclText` for each */
+ def toText(sc: Scope): Text
+
+ /** Textual representation of tree */
+ def toText[T >: Untyped](tree: Tree[T]): Text
+
+ /** Perform string or text-producing operation `op` so that only a
+ * summarized text with given recursion depth is shown
+ */
+ def summarized[T](depth: Int)(op: => T): T
+
+ /** A plain printer without any embellishments */
+ def plain: Printer
+}
+
diff --git a/compiler/src/dotty/tools/dotc/printing/Printers.scala b/compiler/src/dotty/tools/dotc/printing/Printers.scala
new file mode 100644
index 000000000..36043a4ff
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/printing/Printers.scala
@@ -0,0 +1,14 @@
+package dotty.tools.dotc
+package printing
+
+import core.Contexts.Context
+
+trait Printers { this: Context =>
+
+ /** A function creating a printer */
+ def printer = {
+ val pr = printerFn(this)
+ if (this.settings.YplainPrinter.value) pr.plain else pr
+ }
+}
+
diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
new file mode 100644
index 000000000..29e1d4869
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
@@ -0,0 +1,652 @@
+package dotty.tools.dotc
+package printing
+
+import core._
+import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._
+import TypeErasure.ErasedValueType
+import Contexts.Context, Scopes.Scope, Denotations._, SymDenotations._, Annotations.Annotation
+import StdNames.{nme, tpnme}
+import ast.{Trees, untpd, tpd}
+import typer.{Namer, Inliner}
+import typer.ProtoTypes.{SelectionProto, ViewProto, FunProto, IgnoredProto, dummyTreeOfType}
+import Trees._
+import TypeApplications._
+import Decorators._
+import config.Config
+import scala.annotation.switch
+import language.implicitConversions
+
+class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
+
+ /** A stack of enclosing DefDef, TypeDef, or ClassDef, or ModuleDefs nodes */
+ private var enclosingDef: untpd.Tree = untpd.EmptyTree
+ private var myCtx: Context = _ctx
+ private var printPos = ctx.settings.Yprintpos.value
+ override protected[this] implicit def ctx: Context = myCtx
+
+ def withEnclosingDef(enclDef: Tree[_ >: Untyped])(op: => Text): Text = {
+ val savedCtx = myCtx
+ if (enclDef.hasType && enclDef.symbol.exists)
+ myCtx = ctx.withOwner(enclDef.symbol)
+ val savedDef = enclosingDef
+ enclosingDef = enclDef
+ try op finally {
+ myCtx = savedCtx
+ enclosingDef = savedDef
+ }
+ }
+
+ def inPattern(op: => Text): Text = {
+ val savedCtx = myCtx
+ myCtx = ctx.addMode(Mode.Pattern)
+ try op finally myCtx = savedCtx
+ }
+
+ def withoutPos(op: => Text): Text = {
+ val savedPrintPos = printPos
+ printPos = false
+ try op finally printPos = savedPrintPos
+ }
+
+ private def enclDefIsClass = enclosingDef match {
+ case owner: TypeDef[_] => owner.isClassDef
+ case owner: untpd.ModuleDef => true
+ case _ => false
+ }
+
+ override protected def recursionLimitExceeded() = {}
+
+ protected val PrintableFlags = (SourceModifierFlags | Label | Module | Local).toCommonFlags
+
+ override def nameString(name: Name): String = name.decode.toString
+
+ override protected def simpleNameString(sym: Symbol): String = {
+ val name = sym.originalName
+ nameString(if (sym is ExpandedTypeParam) name.asTypeName.unexpandedName else name)
+ }
+
+ override def fullNameString(sym: Symbol): String =
+ if (isEmptyPrefix(sym.maybeOwner)) nameString(sym)
+ else super.fullNameString(sym)
+
+ override protected def fullNameOwner(sym: Symbol) = {
+ val owner = super.fullNameOwner(sym)
+ if (owner is ModuleClass) owner.sourceModule else owner
+ }
+
+ override def toTextRef(tp: SingletonType): Text = controlled {
+ tp match {
+ case tp: ThisType =>
+ if (tp.cls.isAnonymousClass) return "this"
+ if (tp.cls is ModuleClass) return fullNameString(tp.cls.sourceModule)
+ case _ =>
+ }
+ super.toTextRef(tp)
+ }
+
+ override def toTextPrefix(tp: Type): Text = controlled {
+ def isOmittable(sym: Symbol) =
+ if (ctx.settings.verbose.value) false
+ else if (homogenizedView) isEmptyPrefix(sym) // drop <root> and anonymous classes, but not scala, Predef.
+ else isOmittablePrefix(sym)
+ tp match {
+ case tp: ThisType =>
+ if (isOmittable(tp.cls)) return ""
+ case tp @ TermRef(pre, _) =>
+ val sym = tp.symbol
+ if (sym.isPackageObject) return toTextPrefix(pre)
+ if (isOmittable(sym)) return ""
+ case _ =>
+ }
+ super.toTextPrefix(tp)
+ }
+
+ override protected def refinementNameString(tp: RefinedType): String =
+ if (tp.parent.isInstanceOf[WildcardType] || tp.refinedName == nme.WILDCARD)
+ super.refinementNameString(tp)
+ else {
+ val tsym = tp.parent.member(tp.refinedName).symbol
+ if (!tsym.exists) super.refinementNameString(tp)
+ else simpleNameString(tsym)
+ }
+
+ override def toText(tp: Type): Text = controlled {
+ def toTextTuple(args: List[Type]): Text =
+ "(" ~ Text(args.map(argText), ", ") ~ ")"
+ def toTextFunction(args: List[Type]): Text =
+ changePrec(GlobalPrec) {
+ val argStr: Text =
+ if (args.length == 2 && !defn.isTupleType(args.head))
+ atPrec(InfixPrec) { argText(args.head) }
+ else
+ toTextTuple(args.init)
+ argStr ~ " => " ~ argText(args.last)
+ }
+ homogenize(tp) match {
+ case AppliedType(tycon, args) =>
+ val cls = tycon.typeSymbol
+ if (tycon.isRepeatedParam) return toTextLocal(args.head) ~ "*"
+ if (defn.isFunctionClass(cls)) return toTextFunction(args)
+ if (defn.isTupleClass(cls)) return toTextTuple(args)
+ return (toTextLocal(tycon) ~ "[" ~ Text(args map argText, ", ") ~ "]").close
+ case tp: TypeRef =>
+ val hideType = tp.symbol is AliasPreferred
+ if (hideType && !ctx.phase.erasedTypes && !tp.symbol.isCompleting) {
+ tp.info match {
+ case TypeAlias(alias) => return toText(alias)
+ case _ => if (tp.prefix.isInstanceOf[ThisType]) return nameString(tp.symbol)
+ }
+ }
+ else if (tp.symbol.isAnonymousClass && !ctx.settings.uniqid.value)
+ return toText(tp.info)
+ case ExprType(result) =>
+ return "=> " ~ toText(result)
+ case ErasedValueType(tycon, underlying) =>
+ return "ErasedValueType(" ~ toText(tycon) ~ ", " ~ toText(underlying) ~ ")"
+ case tp: ClassInfo =>
+ return toTextParents(tp.parentsWithArgs) ~ "{...}"
+ case JavaArrayType(elemtp) =>
+ return toText(elemtp) ~ "[]"
+ case tp: AnnotatedType if homogenizedView =>
+ // Positions of annotations in types are not serialized
+ // (they don't need to because we keep the original type tree with
+ // the original annotation anyway. Therefore, there will always be
+ // one version of the annotation tree that has the correct positions).
+ withoutPos(super.toText(tp))
+ case tp: SelectionProto =>
+ return "?{ " ~ toText(tp.name) ~ (" " provided !tp.name.decode.last.isLetterOrDigit) ~
+ ": " ~ toText(tp.memberProto) ~ " }"
+ case tp: ViewProto =>
+ return toText(tp.argType) ~ " ?=>? " ~ toText(tp.resultType)
+ case tp @ FunProto(args, resultType, _) =>
+ val argsText = args match {
+ case dummyTreeOfType(tp) :: Nil if !(tp isRef defn.NullClass) => "null: " ~ toText(tp)
+ case _ => toTextGlobal(args, ", ")
+ }
+ return "FunProto(" ~ argsText ~ "):" ~ toText(resultType)
+ case tp: IgnoredProto =>
+ return "?"
+ case _ =>
+ }
+ super.toText(tp)
+ }
+
+ def blockText[T >: Untyped](trees: List[Tree[T]]): Text =
+ ("{" ~ toText(trees, "\n") ~ "}").close
+
+ override def toText[T >: Untyped](tree: Tree[T]): Text = controlled {
+
+ import untpd.{modsDeco => _, _}
+
+ /** Print modifiers from symbols if tree has type, overriding the untpd behavior. */
+ implicit def modsDeco(mdef: untpd.MemberDef)(implicit ctx: Context): untpd.ModsDecorator =
+ new untpd.ModsDecorator {
+ def mods = if (mdef.hasType) Modifiers(mdef.symbol) else mdef.rawMods
+ }
+
+ def Modifiers(sym: Symbol)(implicit ctx: Context): Modifiers = untpd.Modifiers(
+ sym.flags & (if (sym.isType) ModifierFlags | VarianceFlags else ModifierFlags),
+ if (sym.privateWithin.exists) sym.privateWithin.asType.name else tpnme.EMPTY,
+ sym.annotations map (_.tree))
+
+ def isLocalThis(tree: Tree) = tree.typeOpt match {
+ case tp: ThisType => tp.cls == ctx.owner.enclosingClass
+ case _ => false
+ }
+
+ def optDotPrefix(tree: This) = optText(tree.qual)(_ ~ ".") provided !isLocalThis(tree)
+
+ def optAscription(tpt: untpd.Tree) = optText(tpt)(": " ~ _)
+ // Dotty deviation: called with an untpd.Tree, so cannot be a untpd.Tree[T] (seems to be a Scala2 problem to allow this)
+ // More deviations marked below as // DD
+
+ def tparamsText[T >: Untyped](params: List[Tree]): Text =
+ "[" ~ toText(params, ", ") ~ "]" provided params.nonEmpty
+
+ def addVparamssText(txt: Text, vparamss: List[List[ValDef]]): Text =
+ (txt /: vparamss)((txt, vparams) => txt ~ "(" ~ toText(vparams, ", ") ~ ")")
+
+ def caseBlockText(tree: Tree): Text = tree match {
+ case Block(stats, expr) => toText(stats :+ expr, "\n")
+ case expr => toText(expr)
+ }
+
+ def enumText(tree: untpd.Tree) = tree match { // DD
+ case _: untpd.GenFrom | _: untpd.GenAlias => toText(tree)
+ case _ => "if " ~ toText(tree)
+ }
+
+ def forText(enums: List[untpd.Tree], expr: untpd.Tree, sep: String): Text = // DD
+ changePrec(GlobalPrec) { "for " ~ Text(enums map enumText, "; ") ~ sep ~ toText(expr) }
+
+ def cxBoundToText(bound: untpd.Tree): Text = bound match { // DD
+ case AppliedTypeTree(tpt, _) => " : " ~ toText(tpt)
+ case untpd.Function(_, tpt) => " <% " ~ toText(tpt)
+ }
+
+ def constrText(tree: untpd.Tree): Text = toTextLocal(tree).stripPrefix("new ") // DD
+
+ def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD
+
+ def useSymbol =
+ tree.hasType && tree.symbol.exists && ctx.settings.YprintSyms.value
+
+ def modText(mods: untpd.Modifiers, kw: String): Text = { // DD
+ val suppressKw = if (enclDefIsClass) mods is ParamAndLocal else mods is Param
+ var flagMask =
+ if (ctx.settings.debugFlags.value) AnyFlags
+ else if (suppressKw) PrintableFlags &~ Private
+ else PrintableFlags
+ if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes
+ val flagsText = (mods.flags & flagMask).toString
+ Text(mods.annotations.map(annotText), " ") ~~ flagsText ~~ (kw provided !suppressKw)
+ }
+
+ def varianceText(mods: untpd.Modifiers) =
+ if (mods is Covariant) "+"
+ else if (mods is Contravariant) "-"
+ else ""
+
+ def argText(arg: Tree): Text = arg match {
+ case arg: TypeBoundsTree => "_" ~ toTextGlobal(arg)
+ case arg: TypeTree =>
+ arg.typeOpt match {
+ case tp: TypeBounds => "_" ~ toTextGlobal(arg)
+ case _ => toTextGlobal(arg)
+ }
+ case _ => toTextGlobal(arg)
+ }
+
+ def dclTextOr(treeText: => Text) =
+ if (useSymbol)
+ annotsText(tree.symbol) ~~ dclText(tree.symbol) ~
+ ( " <in " ~ toText(tree.symbol.owner) ~ ">" provided ctx.settings.debugOwners.value)
+ else treeText
+
+ def idText(tree: untpd.Tree): Text = {
+ if (ctx.settings.uniqid.value && tree.hasType && tree.symbol.exists) s"#${tree.symbol.id}" else ""
+ }
+
+ def nameIdText(tree: untpd.NameTree): Text =
+ if (tree.hasType && tree.symbol.exists) nameString(tree.symbol)
+ else toText(tree.name) ~ idText(tree)
+
+ def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = {
+ val Template(constr @ DefDef(_, tparams, vparamss, _, _), parents, self, _) = impl
+ val tparamsTxt = withEnclosingDef(constr) { tparamsText(tparams) }
+ val primaryConstrs = if (constr.rhs.isEmpty) Nil else constr :: Nil
+ val prefix: Text =
+ if (vparamss.isEmpty || primaryConstrs.nonEmpty) tparamsTxt
+ else {
+ var modsText = modText(constr.mods, "")
+ if (!modsText.isEmpty) modsText = " " ~ modsText
+ if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this"
+ withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) }
+ }
+ val parentsText = Text(parents map constrText, " with ")
+ val selfText = {
+ val selfName = if (self.name == nme.WILDCARD) "this" else self.name.toString
+ (selfName ~ optText(self.tpt)(": " ~ _) ~ " =>").close
+ } provided !self.isEmpty
+ val bodyText = "{" ~~ selfText ~~ toTextGlobal(primaryConstrs ::: impl.body, "\n") ~ "}"
+ prefix ~ (" extends" provided !ofNew) ~~ parentsText ~~ bodyText
+ }
+
+ def toTextPackageId(pid: Tree): Text =
+ if (homogenizedView && pid.hasType) toTextLocal(pid.tpe)
+ else toTextLocal(pid)
+
+ def toTextCore(tree: Tree): Text = tree match {
+ case id: Trees.BackquotedIdent[_] if !homogenizedView =>
+ "`" ~ toText(id.name) ~ "`"
+ case Ident(name) =>
+ tree.typeOpt match {
+ case tp: NamedType if name != nme.WILDCARD =>
+ val pre = if (tp.symbol is JavaStatic) tp.prefix.widen else tp.prefix
+ toTextPrefix(pre) ~ selectionString(tp)
+ case _ =>
+ toText(name)
+ }
+ case tree @ Select(qual, name) =>
+ if (qual.isType) toTextLocal(qual) ~ "#" ~ toText(name)
+ else toTextLocal(qual) ~ ("." ~ nameIdText(tree) provided name != nme.CONSTRUCTOR)
+ case tree: This =>
+ optDotPrefix(tree) ~ "this" ~ idText(tree)
+ case Super(qual: This, mix) =>
+ optDotPrefix(qual) ~ "super" ~ optText(mix)("[" ~ _ ~ "]")
+ case Apply(fun, args) =>
+ if (fun.hasType && fun.symbol == defn.throwMethod)
+ changePrec (GlobalPrec) {
+ "throw " ~ toText(args.head)
+ }
+ else
+ toTextLocal(fun) ~ "(" ~ toTextGlobal(args, ", ") ~ ")"
+ case TypeApply(fun, args) =>
+ toTextLocal(fun) ~ "[" ~ toTextGlobal(args, ", ") ~ "]"
+ case Literal(c) =>
+ tree.typeOpt match {
+ case ConstantType(tc) => toText(tc)
+ case _ => toText(c)
+ }
+ case New(tpt) =>
+ "new " ~ {
+ tpt match {
+ case tpt: Template => toTextTemplate(tpt, ofNew = true)
+ case _ =>
+ if (tpt.hasType)
+ toTextLocal(tpt.typeOpt.underlyingClassRef(refinementOK = false))
+ else
+ toTextLocal(tpt)
+ }
+ }
+ case Typed(expr, tpt) =>
+ changePrec(InfixPrec) { toText(expr) ~ ": " ~ toText(tpt) }
+ case NamedArg(name, arg) =>
+ toText(name) ~ " = " ~ toText(arg)
+ case Assign(lhs, rhs) =>
+ changePrec(GlobalPrec) { toTextLocal(lhs) ~ " = " ~ toText(rhs) }
+ case Block(stats, expr) =>
+ blockText(stats :+ expr)
+ case If(cond, thenp, elsep) =>
+ changePrec(GlobalPrec) {
+ "if " ~ toText(cond) ~ (" then" provided !cond.isInstanceOf[Parens]) ~~ toText(thenp) ~ optText(elsep)(" else " ~ _)
+ }
+ case Closure(env, ref, target) =>
+ "closure(" ~ (toTextGlobal(env, ", ") ~ " | " provided env.nonEmpty) ~
+ toTextGlobal(ref) ~ (":" ~ toText(target) provided !target.isEmpty) ~ ")"
+ case Match(sel, cases) =>
+ if (sel.isEmpty) blockText(cases)
+ else changePrec(GlobalPrec) { toText(sel) ~ " match " ~ blockText(cases) }
+ case CaseDef(pat, guard, body) =>
+ "case " ~ inPattern(toText(pat)) ~ optText(guard)(" if " ~ _) ~ " => " ~ caseBlockText(body)
+ case Return(expr, from) =>
+ changePrec(GlobalPrec) { "return" ~ optText(expr)(" " ~ _) }
+ case Try(expr, cases, finalizer) =>
+ changePrec(GlobalPrec) {
+ "try " ~ toText(expr) ~ optText(cases)(" catch " ~ _) ~ optText(finalizer)(" finally " ~ _)
+ }
+ case Throw(expr) =>
+ changePrec(GlobalPrec) {
+ "throw " ~ toText(expr)
+ }
+ case SeqLiteral(elems, elemtpt) =>
+ "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]"
+ case tree @ Inlined(call, bindings, body) =>
+ (("/* inlined from " ~ toText(call) ~ "*/ ") provided !homogenizedView) ~
+ blockText(bindings :+ body)
+ case tpt: untpd.DerivedTypeTree =>
+ "<derived typetree watching " ~ summarized(toText(tpt.watched)) ~ ">"
+ case TypeTree() =>
+ toText(tree.typeOpt)
+ case SingletonTypeTree(ref) =>
+ toTextLocal(ref) ~ ".type"
+ case AndTypeTree(l, r) =>
+ changePrec(AndPrec) { toText(l) ~ " & " ~ toText(r) }
+ case OrTypeTree(l, r) =>
+ changePrec(OrPrec) { toText(l) ~ " | " ~ toText(r) }
+ case RefinedTypeTree(tpt, refines) =>
+ toTextLocal(tpt) ~ " " ~ blockText(refines)
+ case AppliedTypeTree(tpt, args) =>
+ toTextLocal(tpt) ~ "[" ~ Text(args map argText, ", ") ~ "]"
+ case PolyTypeTree(tparams, body) =>
+ changePrec(GlobalPrec) {
+ tparamsText(tparams) ~ " -> " ~ toText(body)
+ }
+ case ByNameTypeTree(tpt) =>
+ "=> " ~ toTextLocal(tpt)
+ case TypeBoundsTree(lo, hi) =>
+ optText(lo)(" >: " ~ _) ~ optText(hi)(" <: " ~ _)
+ case Bind(name, body) =>
+ changePrec(InfixPrec) { toText(name) ~ " @ " ~ toText(body) }
+ case Alternative(trees) =>
+ changePrec(OrPrec) { toText(trees, " | ") }
+ case UnApply(fun, implicits, patterns) =>
+ val extractor = fun match {
+ case Select(extractor, nme.unapply) => extractor
+ case _ => fun
+ }
+ toTextLocal(extractor) ~
+ "(" ~ toTextGlobal(patterns, ", ") ~ ")" ~
+ ("(" ~ toTextGlobal(implicits, ", ") ~ ")" provided implicits.nonEmpty)
+ case tree @ ValDef(name, tpt, _) =>
+ dclTextOr {
+ modText(tree.mods, if (tree.mods is Mutable) "var" else "val") ~~
+ nameIdText(tree) ~ optAscription(tpt) ~
+ withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) }
+ }
+ case tree @ DefDef(name, tparams, vparamss, tpt, _) =>
+ dclTextOr {
+ val prefix = modText(tree.mods, "def") ~~ nameIdText(tree)
+ withEnclosingDef(tree) {
+ addVparamssText(prefix ~ tparamsText(tparams), vparamss) ~ optAscription(tpt) ~
+ optText(tree.rhs)(" = " ~ _)
+ }
+ }
+ case tree @ TypeDef(name, rhs) =>
+ def typeDefText(tparamsText: => Text, rhsText: => Text) =
+ dclTextOr {
+ modText(tree.mods, "type") ~~ (varianceText(tree.mods) ~ nameIdText(tree)) ~
+ withEnclosingDef(tree) {
+ if (tree.hasType) toText(tree.symbol.info) // TODO: always print RHS, once we pickle/unpickle type trees
+ else tparamsText ~ rhsText
+ }
+ }
+ def recur(rhs: Tree, tparamsTxt: => Text): Text = rhs match {
+ case impl: Template =>
+ modText(tree.mods, if ((tree).mods is Trait) "trait" else "class") ~~
+ nameIdText(tree) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~
+ (if (tree.hasType && ctx.settings.verbose.value) i"[decls = ${tree.symbol.info.decls}]" else "")
+ case rhs: TypeBoundsTree =>
+ typeDefText(tparamsTxt, toText(rhs))
+ case PolyTypeTree(tparams, body) =>
+ recur(body, tparamsText(tparams))
+ case rhs =>
+ typeDefText(tparamsTxt, optText(rhs)(" = " ~ _))
+ }
+ recur(rhs, "")
+ case Import(expr, selectors) =>
+ def selectorText(sel: Tree): Text = sel match {
+ case Thicket(l :: r :: Nil) => toTextGlobal(l) ~ " => " ~ toTextGlobal(r)
+ case _ => toTextGlobal(sel)
+ }
+ val selectorsText: Text = selectors match {
+ case id :: Nil => toText(id)
+ case _ => "{" ~ Text(selectors map selectorText, ", ") ~ "}"
+ }
+ "import " ~ toTextLocal(expr) ~ "." ~ selectorsText
+ case PackageDef(pid, stats) =>
+ val statsText = stats match {
+ case (pdef: PackageDef) :: Nil => toText(pdef)
+ case _ => toTextGlobal(stats, "\n")
+ }
+ val bodyText =
+ if (currentPrecedence == TopLevelPrec) "\n" ~ statsText else " {" ~ statsText ~ "}"
+ "package " ~ toTextPackageId(pid) ~ bodyText
+ case tree: Template =>
+ toTextTemplate(tree)
+ case Annotated(arg, annot) =>
+ toTextLocal(arg) ~~ annotText(annot)
+ case EmptyTree =>
+ "<empty>"
+ case TypedSplice(t) =>
+ toText(t)
+ case tree @ ModuleDef(name, impl) =>
+ withEnclosingDef(tree) {
+ modText(tree.mods, "object") ~~ nameIdText(tree) ~ toTextTemplate(impl)
+ }
+ case SymbolLit(str) =>
+ "'" + str
+ case InterpolatedString(id, segments) =>
+ def strText(str: Literal) = Str(escapedString(str.const.stringValue))
+ def segmentText(segment: Tree) = segment match {
+ case Thicket(List(str: Literal, expr)) => strText(str) ~ "{" ~ toTextGlobal(expr) ~ "}"
+ case str: Literal => strText(str)
+ }
+ toText(id) ~ "\"" ~ Text(segments map segmentText, "") ~ "\""
+ case Function(args, body) =>
+ var implicitSeen: Boolean = false
+ def argToText(arg: Tree) = arg match {
+ case arg @ ValDef(name, tpt, _) =>
+ val implicitText =
+ if ((arg.mods is Implicit) && !implicitSeen) { implicitSeen = true; "implicit " }
+ else ""
+ implicitText ~ toText(name) ~ optAscription(tpt)
+ case _ =>
+ toText(arg)
+ }
+ val argsText = args match {
+ case (arg @ ValDef(_, tpt, _)) :: Nil if tpt.isEmpty => argToText(arg)
+ case _ => "(" ~ Text(args map argToText, ", ") ~ ")"
+ }
+ changePrec(GlobalPrec) {
+ argsText ~ " => " ~ toText(body)
+ }
+ case InfixOp(l, op, r) =>
+ val opPrec = parsing.precedence(op)
+ changePrec(opPrec) { toText(l) ~ " " ~ toText(op) ~ " " ~ toText(r) }
+ case PostfixOp(l, op) =>
+ changePrec(InfixPrec) { toText(l) ~ " " ~ toText(op) }
+ case PrefixOp(op, r) =>
+ changePrec(DotPrec) { toText(op) ~ " " ~ toText(r) }
+ case Parens(t) =>
+ "(" ~ toTextGlobal(t) ~ ")"
+ case Tuple(ts) =>
+ "(" ~ toTextGlobal(ts, ", ") ~ ")"
+ case WhileDo(cond, body) =>
+ changePrec(GlobalPrec) { "while " ~ toText(cond) ~ " do " ~ toText(body) }
+ case DoWhile(cond, body) =>
+ changePrec(GlobalPrec) { "do " ~ toText(body) ~ " while " ~ toText(cond) }
+ case ForYield(enums, expr) =>
+ forText(enums, expr, " yield ")
+ case ForDo(enums, expr) =>
+ forText(enums, expr, " do ")
+ case GenFrom(pat, expr) =>
+ toText(pat) ~ " <- " ~ toText(expr)
+ case GenAlias(pat, expr) =>
+ toText(pat) ~ " = " ~ toText(expr)
+ case ContextBounds(bounds, cxBounds) =>
+ (toText(bounds) /: cxBounds) {(t, cxb) =>
+ t ~ cxBoundToText(cxb)
+ }
+ case PatDef(mods, pats, tpt, rhs) =>
+ modText(mods, "val") ~~ toText(pats, ", ") ~ optAscription(tpt) ~
+ optText(rhs)(" = " ~ _)
+ case ParsedTry(expr, handler, finalizer) =>
+ changePrec(GlobalPrec) {
+ "try " ~ toText(expr) ~ " catch {" ~ toText(handler) ~ "}" ~ optText(finalizer)(" finally " ~ _)
+ }
+ case Thicket(trees) =>
+ "Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
+ case _ =>
+ tree.fallbackToText(this)
+ }
+
+ var txt = toTextCore(tree)
+
+ def suppressTypes =
+ tree.isType || tree.isDef || // don't print types of types or defs
+ homogenizedView && ctx.mode.is(Mode.Pattern)
+ // When comparing pickled info, disregard types of patterns.
+ // The reason is that GADT matching can rewrite types of pattern trees
+ // without changing the trees themselves. (see Typer.typedCase.indexPatterns.transform).
+ // But then pickling and unpickling the original trees will yield trees
+ // with the original types before they are rewritten, which causes a discrepancy.
+
+ def suppressPositions = tree match {
+ case _: WithoutTypeOrPos[_] | _: TypeTree => true // TypeTrees never have an interesting position
+ case _ => false
+ }
+
+ if (ctx.settings.printtypes.value && tree.hasType) {
+ // add type to term nodes; replace type nodes with their types unless -Yprintpos is also set.
+ def tp = tree.typeOpt match {
+ case tp: TermRef if tree.isInstanceOf[RefTree] && !tp.denot.isOverloaded => tp.underlying
+ case tp => tp
+ }
+ if (!suppressTypes)
+ txt = ("<" ~ txt ~ ":" ~ toText(tp) ~ ">").close
+ else if (tree.isType && !homogenizedView)
+ txt = toText(tp)
+ }
+ if (printPos && !suppressPositions) {
+ // add positions
+ val pos =
+ if (homogenizedView && !tree.isInstanceOf[MemberDef]) tree.pos.toSynthetic
+ else tree.pos
+ val clsStr = ""//if (tree.isType) tree.getClass.toString else ""
+ txt = (txt ~ "@" ~ pos.toString ~ clsStr).close
+ }
+ tree match {
+ case Block(_, _) | Template(_, _, _, _) => txt
+ case _ => txt.close
+ }
+ }
+
+ def optText(name: Name)(encl: Text => Text): Text =
+ if (name.isEmpty) "" else encl(toText(name))
+
+ def optText[T >: Untyped](tree: Tree[T])(encl: Text => Text): Text =
+ if (tree.isEmpty) "" else encl(toText(tree))
+
+ def optText[T >: Untyped](tree: List[Tree[T]])(encl: Text => Text): Text =
+ if (tree.exists(!_.isEmpty)) encl(blockText(tree)) else ""
+
+ override protected def polyParamNameString(name: TypeName): String =
+ name.unexpandedName.toString
+
+ override protected def treatAsTypeParam(sym: Symbol): Boolean = sym is TypeParam
+
+ override protected def treatAsTypeArg(sym: Symbol) =
+ sym.isType && (sym is ProtectedLocal) &&
+ (sym.allOverriddenSymbols exists (_ is TypeParam))
+
+ override def toText(sym: Symbol): Text = {
+ if (sym.isImport) {
+ def importString(tree: untpd.Tree) = s"import ${tree.show}"
+ sym.infoOrCompleter match {
+ case info: Namer#Completer => return importString(info.original)
+ case info: ImportType => return importString(info.expr)
+ case _ =>
+ }
+ }
+ if (sym.is(ModuleClass))
+ kindString(sym) ~~ (nameString(sym.name.stripModuleClassSuffix) + idString(sym))
+ else
+ super.toText(sym)
+ }
+
+ override def kindString(sym: Symbol) = {
+ val flags = sym.flagsUNSAFE
+ if (flags is Package) "package"
+ else if (sym.isPackageObject) "package object"
+ else if (flags is Module) "object"
+ else if (flags is ImplClass) "class"
+ else if (sym.isClassConstructor) "constructor"
+ else super.kindString(sym)
+ }
+
+ override protected def keyString(sym: Symbol): String = {
+ val flags = sym.flagsUNSAFE
+ if (sym.isType && sym.owner.isTerm) ""
+ else super.keyString(sym)
+ }
+
+ override def toTextFlags(sym: Symbol) =
+ if (ctx.settings.debugFlags.value)
+ super.toTextFlags(sym)
+ else {
+ var flags = sym.flagsUNSAFE
+ if (flags is TypeParam) flags = flags &~ Protected
+ Text((flags & PrintableFlags).flagStrings map stringToText, " ")
+ }
+
+ override def toText(denot: Denotation): Text = denot match {
+ case denot: MultiDenotation => Text(denot.alternatives.map(dclText), " <and> ")
+ case NoDenotation => "NoDenotation"
+ case _ =>
+ if (denot.symbol.exists) toText(denot.symbol)
+ else "some " ~ toText(denot.info)
+ }
+
+ override def plain = new PlainPrinter(_ctx)
+}
diff --git a/compiler/src/dotty/tools/dotc/printing/Showable.scala b/compiler/src/dotty/tools/dotc/printing/Showable.scala
new file mode 100644
index 000000000..efddb26f7
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/printing/Showable.scala
@@ -0,0 +1,34 @@
+package dotty.tools.dotc
+package printing
+
+import core._
+
+import Contexts._, Texts._, Decorators._
+import config.Config.summarizeDepth
+import scala.util.control.NonFatal
+
+trait Showable extends Any {
+
+ /** The text representation of this showable element.
+ * This normally dispatches to a pattern matching
+ * method in Printers.
+ */
+ def toText(printer: Printer): Text
+
+ /** A fallback text representation, if the pattern matching
+ * in Printers does not have a case for this showable element
+ */
+ def fallbackToText(printer: Printer): Text = toString
+
+ /** The string representation of this showable element. */
+ def show(implicit ctx: Context): String = toText(ctx.printer).show
+
+ /** The summarized string representation of this showable element.
+ * Recursion depth is limited to some smallish value. Default is
+ * Config.summarizeDepth.
+ */
+ def showSummary(depth: Int)(implicit ctx: Context): String =
+ ctx.printer.summarized(depth)(show)
+
+ def showSummary(implicit ctx: Context): String = showSummary(summarizeDepth)
+}
diff --git a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala
new file mode 100644
index 000000000..86f34e64d
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala
@@ -0,0 +1,304 @@
+package dotty.tools
+package dotc
+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.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 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
+ } yield tokenString(index)
+
+ private val interpolationPrefixes =
+ 'A' :: 'B' :: 'C' :: 'D' :: 'E' :: 'F' :: 'G' :: 'H' :: 'I' :: 'J' :: 'K' ::
+ 'L' :: 'M' :: 'N' :: 'O' :: 'P' :: 'Q' :: 'R' :: 'S' :: 'T' :: 'U' :: 'V' ::
+ 'W' :: 'X' :: 'Y' :: 'Z' :: '$' :: '_' :: 'a' :: 'b' :: 'c' :: 'd' :: 'e' ::
+ 'f' :: 'g' :: 'h' :: 'i' :: 'j' :: 'k' :: 'l' :: 'm' :: 'n' :: 'o' :: 'p' ::
+ 'q' :: 'r' :: 's' :: 't' :: 'u' :: 'v' :: 'w' :: 'x' :: 'y' :: 'z' :: Nil
+
+ private val typeEnders =
+ '{' :: '}' :: ')' :: '(' :: '[' :: ']' :: '=' :: ' ' :: ',' :: '.' ::
+ '\n' :: Nil
+
+ 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 == '[' || prev == ','
+
+ @inline def numberStart(c: Char) =
+ c.isDigit && (!prev.isLetter || prev == '.' || prev == ' ' || prev == '(' || prev == '\u0000')
+
+ def takeChar(): Char = takeChars(1).head
+ def takeChars(x: Int): Seq[Char] = {
+ val taken = remaining.take(x)
+ remaining = remaining.drop(x)
+ taken
+ }
+
+ while (remaining.nonEmpty) {
+ val n = takeChar()
+ if (interpolationPrefixes.contains(n)) {
+ // Interpolation prefixes are a superset of the keyword start chars
+ val next = remaining.take(3).mkString
+ if (next.startsWith("\"")) {
+ newBuf += n
+ prev = n
+ if (remaining.nonEmpty) takeChar() // drop 1 for appendLiteral
+ appendLiteral('"', next == "\"\"\"")
+ } else {
+ if (n.isUpper && keywordStart) {
+ appendWhile(n, !typeEnders.contains(_), typeDef)
+ } else if (keywordStart) {
+ append(n, keywords.contains(_), { kw =>
+ if (kw == "new") typeDef(kw) else keyword(kw)
+ })
+ } else {
+ newBuf += n
+ prev = n
+ }
+ }
+ } else {
+ (n: @switch) match {
+ case '/' =>
+ if (remaining.nonEmpty) {
+ remaining.head match {
+ case '/' =>
+ takeChar()
+ eolComment()
+ case '*' =>
+ takeChar()
+ blockComment()
+ case x =>
+ newBuf += '/'
+ }
+ } else newBuf += '/'
+ case '=' =>
+ append('=', _ == "=>", operator)
+ case '<' =>
+ append('<', { x => x == "<-" || x == "<:" || x == "<%" }, operator)
+ case '>' =>
+ append('>', { x => x == ">:" }, operator)
+ case '#' =>
+ if (prev != ' ' && prev != '.') newBuf append operator("#")
+ else newBuf += n
+ prev = '#'
+ case '@' =>
+ appendWhile('@', !typeEnders.contains(_), annotation)
+ case '\"' =>
+ appendLiteral('\"', multiline = remaining.take(2).mkString == "\"\"")
+ case '\'' =>
+ appendLiteral('\'')
+ case '`' =>
+ appendTo('`', _ == '`', none)
+ case _ => {
+ 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)
+ else
+ newBuf += n; prev = n
+ }
+ }
+ }
+ }
+
+ def eolComment() = {
+ newBuf append (CommentColor + "//")
+ var curr = '/'
+ while (curr != '\n' && remaining.nonEmpty) {
+ curr = takeChar()
+ newBuf += curr
+ }
+ prev = curr
+ newBuf append NoColor
+ }
+
+ def blockComment() = {
+ newBuf append (CommentColor + "/*")
+ var curr = '*'
+ var open = 1
+ while (open > 0 && remaining.nonEmpty) {
+ curr = takeChar()
+ newBuf += curr
+
+ if (curr == '*' && remaining.nonEmpty) {
+ curr = takeChar()
+ newBuf += curr
+ if (curr == '/') open -= 1
+ } else if (curr == '/' && remaining.nonEmpty) {
+ curr = takeChar()
+ newBuf += curr
+ if (curr == '*') open += 1
+ }
+ }
+ prev = curr
+ newBuf append NoColor
+ }
+
+ def appendLiteral(delim: Char, multiline: Boolean = false) = {
+ var curr: Char = 0
+ var continue = true
+ var closing = 0
+ val inInterpolation = interpolationPrefixes.contains(prev)
+ newBuf append (LiteralColor + delim)
+
+ def shouldInterpolate =
+ inInterpolation && curr == '$' && prev != '$' && remaining.nonEmpty
+
+ def interpolate() = {
+ val next = takeChar()
+ if (next == '$') {
+ newBuf += curr
+ newBuf += next
+ prev = '$'
+ } else if (next == '{') {
+ var open = 1 // keep track of open blocks
+ newBuf append (ValDefColor + curr)
+ newBuf += next
+ while (remaining.nonEmpty && open > 0) {
+ var c = takeChar()
+ newBuf += c
+ if (c == '}') open -= 1
+ else if (c == '{') open += 1
+ }
+ newBuf append LiteralColor
+ } else {
+ newBuf append (ValDefColor + curr)
+ newBuf += next
+ var c: Char = 'a'
+ while (c.isLetterOrDigit && remaining.nonEmpty) {
+ c = takeChar()
+ if (c != '"') newBuf += c
+ }
+ newBuf append LiteralColor
+ if (c == '"') {
+ newBuf += c
+ continue = false
+ }
+ }
+ closing = 0
+ }
+
+ while (continue && remaining.nonEmpty) {
+ curr = takeChar()
+ if (curr == '\\' && remaining.nonEmpty) {
+ val next = takeChar()
+ newBuf append (KeywordColor + curr)
+ if (next == 'u') {
+ val code = "u" + takeChars(4).mkString
+ newBuf append code
+ } else newBuf += next
+ newBuf append LiteralColor
+ closing = 0
+ } else if (shouldInterpolate) {
+ interpolate()
+ } else if (curr == delim && multiline) {
+ closing += 1
+ if (closing == 3) continue = false
+ newBuf += curr
+ } else if (curr == delim) {
+ continue = false
+ newBuf += curr
+ } else {
+ newBuf += curr
+ closing = 0
+ }
+ }
+ newBuf append NoColor
+ prev = curr
+ }
+
+ def append(c: Char, shouldHL: String => Boolean, highlight: String => String) = {
+ var curr: Char = 0
+ val sb = new StringBuilder(s"$c")
+
+ 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 (!delim(curr)) sb += curr
+ }
+
+ val str = sb.toString
+ 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
+ }
+
+ def appendWhile(c: Char, pred: Char => Boolean, highlight: String => String) = {
+ var curr: Char = 0
+ val sb = new StringBuilder(s"$c")
+ while (remaining.nonEmpty && pred(curr)) {
+ curr = takeChar()
+ if (pred(curr)) sb += curr
+ }
+
+ val str = sb.toString
+ val suffix = if (!pred(curr)) s"$curr" else ""
+ newBuf append (highlight(str) + suffix)
+ prev = curr
+ }
+
+ def appendTo(c: Char, pred: Char => Boolean, highlight: String => String) = {
+ var curr: Char = 0
+ val sb = new StringBuilder(s"$c")
+ while (remaining.nonEmpty && !pred(curr)) {
+ curr = takeChar()
+ sb += curr
+ }
+
+ newBuf append highlight(sb.toString)
+ prev = curr
+ }
+
+ newBuf.toIterable
+ }
+}
diff --git a/compiler/src/dotty/tools/dotc/printing/Texts.scala b/compiler/src/dotty/tools/dotc/printing/Texts.scala
new file mode 100644
index 000000000..db81cab7a
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/printing/Texts.scala
@@ -0,0 +1,168 @@
+package dotty.tools.dotc
+package printing
+import core.Contexts.Context
+import language.implicitConversions
+
+object Texts {
+
+ abstract class Text {
+
+ protected def indentMargin = 2
+
+ def relems: List[Text]
+
+ def isEmpty: Boolean = this match {
+ case Str(s) => s.isEmpty
+ case Fluid(relems) => relems forall (_.isEmpty)
+ case Vertical(relems) => relems.isEmpty
+ }
+
+ def isVertical = isInstanceOf[Vertical]
+ def isClosed = isVertical || isInstanceOf[Closed]
+ def isFluid = isInstanceOf[Fluid]
+ def isSplittable = isFluid && !isClosed
+
+ def close = new Closed(relems)
+
+ def remaining(width: Int): Int = this match {
+ case Str(s) =>
+ width - s.length
+ case Fluid(Nil) =>
+ width
+ case Fluid(last :: prevs) =>
+ val r = last remaining width
+ if (r < 0) r else Fluid(prevs) remaining r
+ case Vertical(_) =>
+ -1
+ }
+
+ def lastLine: String = this match {
+ case Str(s) => s
+ case _ => relems.head.lastLine
+ }
+
+ def appendToLastLine(that: Text): Text = that match {
+ case Str(s2) =>
+ this match {
+ case Str(s1) => Str(s1 + s2)
+ case Fluid(Str(s1) :: prev) => Fluid(Str(s1 + s2) :: prev)
+ case Fluid(relems) => Fluid(that :: relems)
+ }
+ case Fluid(relems) =>
+ (this /: relems.reverse)(_ appendToLastLine _)
+ }
+
+ private def appendIndented(that: Text)(width: Int): Text =
+ Vertical(that.layout(width - indentMargin).indented :: this.relems)
+
+ private def append(width: Int)(that: Text): Text = {
+ if (this.isEmpty) that.layout(width)
+ else if (that.isEmpty) this
+ else if (that.isVertical) appendIndented(that)(width)
+ else if (this.isVertical) Fluid(that.layout(width) :: this.relems)
+ else if (that.remaining(width - lastLine.length) >= 0) appendToLastLine(that)
+ else if (that.isSplittable) (this /: that.relems.reverse)(_.append(width)(_))
+ else appendIndented(that)(width)
+ }
+
+ def layout(width: Int): Text = this match {
+ case Str(_) =>
+ this
+ case Fluid(relems) =>
+ ((Str(""): Text) /: relems.reverse)(_.append(width)(_))
+ case Vertical(relems) =>
+ Vertical(relems map (_ layout width))
+ }
+
+ def map(f: String => String): Text = this match {
+ case Str(s) => Str(f(s))
+ case Fluid(relems) => Fluid(relems map (_ map f))
+ case Vertical(relems) => Vertical(relems map (_ map f))
+ }
+
+ def stripPrefix(pre: String): Text = this match {
+ case Str(s) =>
+ if (s.startsWith(pre)) s drop pre.length else s
+ case Fluid(relems) =>
+ val elems = relems.reverse
+ val head = elems.head.stripPrefix(pre)
+ if (head eq elems.head) this else Fluid((head :: elems.tail).reverse)
+ case Vertical(relems) =>
+ val elems = relems.reverse
+ val head = elems.head.stripPrefix(pre)
+ if (head eq elems.head) this else Vertical((head :: elems.tail).reverse)
+ }
+
+ private def indented: Text = this match {
+ case Str(s) => Str((" " * indentMargin) + s)
+ case Fluid(relems) => Fluid(relems map (_.indented))
+ case Vertical(relems) => Vertical(relems map (_.indented))
+ }
+
+ def print(sb: StringBuilder): Unit = this match {
+ case Str(s) =>
+ sb.append(s)
+ case _ =>
+ var follow = false
+ for (elem <- relems.reverse) {
+ if (follow) sb.append("\n")
+ elem.print(sb)
+ follow = true
+ }
+ }
+
+ def mkString(width: Int): String = {
+ val sb = new StringBuilder
+ layout(width).print(sb)
+ sb.toString
+ }
+
+ def ~ (that: Text) =
+ if (this.isEmpty) that
+ else if (that.isEmpty) this
+ else Fluid(that :: this :: Nil)
+
+ def ~~ (that: Text) =
+ if (this.isEmpty) that
+ else if (that.isEmpty) this
+ else Fluid(that :: Str(" ") :: this :: Nil)
+
+ def over (that: Text) =
+ if (this.isVertical) Vertical(that :: this.relems)
+ else Vertical(that :: this :: Nil)
+
+ def provided(pred: Boolean) = if (pred) this else Str("")
+ }
+
+ object Text {
+
+ /** The empty text */
+ def apply(): Text = Str("")
+
+ /** A concatenation of elements in `xs` and interspersed with
+ * separator strings `sep`.
+ */
+ def apply(xs: Traversable[Text], sep: String = " "): Text = {
+ if (sep == "\n") lines(xs)
+ else {
+ val ys = xs filterNot (_.isEmpty)
+ if (ys.isEmpty) Str("")
+ else ys reduce (_ ~ sep ~ _)
+ }
+ }
+
+ /** The given texts `xs`, each on a separate line */
+ def lines(xs: Traversable[Text]) = Vertical(xs.toList.reverse)
+ }
+
+ case class Str(s: String) extends Text {
+ override def relems: List[Text] = List(this)
+ }
+
+ case class Vertical(relems: List[Text]) extends Text
+ case class Fluid(relems: List[Text]) extends Text
+
+ class Closed(relems: List[Text]) extends Fluid(relems)
+
+ implicit def stringToText(s: String): Text = Str(s)
+}
diff --git a/compiler/src/dotty/tools/dotc/printing/package.scala b/compiler/src/dotty/tools/dotc/printing/package.scala
new file mode 100644
index 000000000..814eb2ad0
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/printing/package.scala
@@ -0,0 +1,17 @@
+package dotty.tools.dotc
+
+import core.StdNames.nme
+import parsing.{precedence, minPrec, maxPrec, minInfixPrec}
+
+package object printing {
+
+ type Precedence = Int
+
+ val DotPrec = parsing.maxPrec
+ val AndPrec = parsing.precedence(nme.raw.AMP)
+ val OrPrec = parsing.precedence(nme.raw.BAR)
+ val InfixPrec = parsing.minInfixPrec
+ val GlobalPrec = parsing.minPrec
+ val TopLevelPrec = parsing.minPrec - 1
+
+}