From 7fead8de53cc0e1679fb649f3f1d89dee1754185 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Mon, 6 Dec 2021 17:01:41 +0800 Subject: [PATCH] Overhaul and simplify TPrint implementation (#72) Fixes https://github.com/com-lihaoyi/Ammonite/issues/221 Fixes https://github.com/com-lihaoyi/Ammonite/issues/629 Fixes https://github.com/com-lihaoyi/Ammonite/issues/670 Fixes https://github.com/com-lihaoyi/PPrint/pull/45 Fixes https://github.com/com-lihaoyi/PPrint/issues/44 The old TPrint implementation did a clever thing where it allowed a user to over-ride the TPrinting of a given type by providing an appropriate implicit. While that worked in most cases, it was fiendishly complex, and the intricate nesting of implicit resolution and macro resolution ended up providing and endless source of hard to resolve bugs. This new implementation is much simpler and less flexible: we simply walk the type data structure in the macro, and spit out a colored `fansi.Str` with the type names hard-coded to `fansi.Green`. The only runtime support necessary is in the `def recolor` function, which parses the incoming `fansi.Str` and replaces the hardcoded `fansi.Green` colors with whatever is specified by the implicit `TPrintColors`. As implicits cannot be used to override tprinting anymore, we now have hardcoded support for tprinting functions and tuples. While the old macro generated a complex tree of Scala function calls that is evaluated to generate the output `fansi.Str` at runtime, the new macro simply spits out a single `fansi.Str` that is serialized into a `java.lang.String` and deserialized back into a `fansi.Str` for usage at runtime. We propagate a `WrapType` enumeration up the recursion, to help the callers decide if they need to wrap things in parens or not. This gives up a bit of flexibility, but AFAIK nobody was really using that flexibility anyway. In exchange, we fix a whole bunch of long-standing bugs, and have a drastically simpler implementation. The fixed bugs are covered by regression unit tests added to `TPrintTests.scala`. All existing tests also pass, so hopefully that'll catch any potential regressions. There's probably more bugs where we're not properly setting or handling the `WrapType`, but exhaustively testing/surfacing/fixing all of those is beyond the scope of this PR. For now, I just kept the current set of tests passing. Managed to get the Scala3 side working. I didn't realize how half-baked the Scala3 implementation of TPrint is; so much of the Scala2 functionality just isn't implemented and doesn't work. Nevertheless, fixing that is beyond the scope of this PR. I just kept it green with the existing set of green tests passing (except for the custom tprinter test, which is no longer applicable) Review by @lolgab. --- build.sc | 35 +-- pprint/src-2/TPrintImpl.scala | 219 ++++++++++-------- pprint/src-3/TPrintImpl.scala | 125 +++++----- pprint/src/pprint/TPrint.scala | 28 ++- pprint/src/pprint/package.scala | 2 +- .../test/src-2/test/pprint/TPrintTests.scala | 82 ++++--- .../src-3/test/src/pprint/TPrintTests.scala | 173 +++++++------- 7 files changed, 337 insertions(+), 327 deletions(-) diff --git a/build.sc b/build.sc index d974012..709b030 100644 --- a/build.sc +++ b/build.sc @@ -7,7 +7,7 @@ import com.github.lolgab.mill.mima._ val dottyVersions = sys.props.get("dottyVersion").toList -val scalaVersions = "2.12.13" :: "2.13.4" :: "2.11.12" :: "3.0.0" :: dottyVersions +val scalaVersions = "2.12.13" :: "2.13.4" :: "2.11.12" :: "3.0.2" :: dottyVersions val scala2Versions = scalaVersions.filter(_.startsWith("2.")) val scalaJSVersions = for { @@ -64,39 +64,6 @@ trait PPrintMainModule extends CrossScalaModule { ) ) ) - def generatedSources = T{ - val dir = T.ctx().dest - val file = dir/"pprint"/"TPrintGen.scala" - - val typeGen = for(i <- 2 to 22) yield { - val ts = (1 to i).map("T" + _).mkString(", ") - val tsBounded = (1 to i).map("T" + _ + ": Type").mkString(", ") - val tsGet = (1 to i).map("get[T" + _ + "](cfg)").mkString(" + \", \" + ") - s""" - implicit def F${i}TPrint[$tsBounded, R: Type]: Type[($ts) => R] = make[($ts) => R](cfg => - "(" + $tsGet + ") => " + get[R](cfg) - ) - implicit def T${i}TPrint[$tsBounded]: Type[($ts)] = make[($ts)](cfg => - "(" + $tsGet + ")" - ) - """ - } - val output = s""" - package pprint - trait TPrintGen[Type[_], Cfg]{ - def make[T](f: Cfg => String): Type[T] - def get[T: Type](cfg: Cfg): String - implicit def F0TPrint[R: Type]: Type[() => R] = make[() => R](cfg => "() => " + get[R](cfg)) - implicit def F1TPrint[T1: Type, R: Type]: Type[T1 => R] = { - make[T1 => R](cfg => get[T1](cfg) + " => " + get[R](cfg)) - } - ${typeGen.mkString("\n")} - } - """.stripMargin - os.write(file, output, createFolders = true) - Seq(PathRef(file)) - } - } diff --git a/pprint/src-2/TPrintImpl.scala b/pprint/src-2/TPrintImpl.scala index c64b2fe..020bdfb 100644 --- a/pprint/src-2/TPrintImpl.scala +++ b/pprint/src-2/TPrintImpl.scala @@ -7,27 +7,45 @@ trait TPrintLowPri{ implicit def default[T]: TPrint[T] = macro TPrintLowPri.typePrintImpl[T] } object TPrintLowPri{ + sealed trait WrapType + object WrapType{ + case object NoWrap extends WrapType + case object Infix extends WrapType + case object Tuple extends WrapType + } def typePrintImpl[T: c.WeakTypeTag](c: Context): c.Expr[TPrint[T]] = { // Used to provide "empty string" values in quasiquotes + import c.universe._ - val s = "" val tpe = weakTypeOf[T] + val rendered = typePrintImplRec(c)(tpe, rightMost = true).render + val res = c.Expr[TPrint[T]]( + q"_root_.pprint.TPrint.recolor(_root_.fansi.Str($rendered))" + ) + res + } + + val functionTypes = Range.inclusive(0, 22).map(i => s"scala.Function$i").toSet + val tupleTypes = Range.inclusive(0, 22).map(i => s"scala.Tuple$i").toSet + + def typePrintImplRec[T](c: Context)(tpe: c.Type, rightMost: Boolean): fansi.Str = { + typePrintImplRec0(c)(tpe, rightMost)._1 + } + def typePrintImplRec0[T](c: Context)(tpe: c.Type, rightMost: Boolean): (fansi.Str, WrapType) = { + import c.universe._ def printSymString(s: Symbol) = if (s.name.decodedName.toString.startsWith("_$")) "_" else s.name.decodedName.toString.stripSuffix(".type") - def literalColor(s: Tree) = { - q"$cfgSym.typeColor($s).render" - } - def printSym(s: Symbol): Tree = { - literalColor(q"${printSymString(s)}") - } + def literalColor(s: fansi.Str): fansi.Str = fansi.Color.Green(s) + def printSym(s: Symbol): fansi.Str = literalColor(printSymString(s)) - def printSymFull(s: Symbol): Tree = { - if (lookup(s)) printSym(s) - else q"""${printSymFull(s.owner)} + "." + ${printSym(s)}""" + def printSymFull(s: Symbol): fansi.Str = { + if (lookup(s)) printSym(s) + else printSymFull(s.owner) ++ "." ++ printSym(s) } + /** * Looks up a symbol in the enclosing scope and returns * whether it exists in scope by the same name @@ -55,64 +73,43 @@ object TPrintLowPri{ )} } - def prefixFor(pre: Type, sym: Symbol): Tree = { + def prefixFor(pre: Type, sym: Symbol): fansi.Str = { // Depending on what the prefix is, you may use `#`, `.` // or even need to wrap the prefix in parentheses val sep = pre match{ - case x if x.toString.endsWith(".type") => q""" ${rec0(pre)} + "." """ - case x: TypeRef => q""" ${literalColor(implicitRec(pre))} + "#" """ - case x: SingleType => q""" ${literalColor(rec0(pre))} + "." """ - case x: ThisType => q""" ${literalColor(rec0(pre))} + "." """ - case x => q""" "(" + ${implicitRec(pre)} + ")#" """ + case x if x.toString.endsWith(".type") => typePrintImplRec(c)(pre, false) ++ "." + case x: TypeRef => literalColor(typePrintImplRec(c)(pre, true)) ++ "#" + case x: SingleType => literalColor(typePrintImplRec(c)(pre, false)) ++ "." + case x: ThisType => literalColor(typePrintImplRec(c)(pre, false)) ++ "." + case x => fansi.Str("(") ++ typePrintImplRec(c)(pre, true) ++ ")#" } - val prefix = if (!lookup(sym)) sep else q"$s" - q"$prefix + ${printSym(sym)}" + val prefix = if (!lookup(sym)) sep else fansi.Str("") + prefix ++ printSym(sym) } - def printArgSyms(args: List[Symbol]): Tree = { - def added = args.map{x => - val TypeBounds(lo, hi) = x.info - q""" ${printSym(x)} + ${printBounds(lo, hi)}""" - }.reduceLeft[Tree]((l, r) => q"""$l + ", " + $r""") - if (args == Nil) q"$s" else q""" "[" + $added + "]" """ - } - def printArgs(args: List[Type]): Tree = { - def added = args.map(implicitRec(_)) - .reduceLeft[Tree]((l, r) => q"""$l + ", " + $r""") + def printArgSyms(args: List[Symbol]): fansi.Str = { + def added = args + .map{x => + val TypeBounds(lo, hi) = x.info + printSym(x) ++ printBounds(lo, hi) + } + .reduceLeft[fansi.Str]((l, r) => l ++ ", " ++ r) - if (args == Nil) q"$s" else q""" "[" + $added + "]" """ + if (args == Nil) fansi.Str("") else fansi.Str("[") ++ added ++ "]" } + def printArgs(args: List[Type]): fansi.Str = { + def added = args.map(typePrintImplRec(c)(_, true)) + .reduceLeft[fansi.Str]((l, r) => l ++ ", " ++ r) - - def implicitRec(tpe: Type) = { - val byName = (tpe: Type) match{ - case t: TypeRef if t.toString.startsWith("=> ") => Some(t.args(0)) - case _ => None - } - - try { - // Make sure the type isn't higher-kinded or some other weird - // thing, and actually can fit inside the square brackets - - byName match{ - case Some(t) => - c.typecheck(q"null.asInstanceOf[$tpe]") - q""" "=> " + _root_.pprint.TPrint.implicitly[$t].render($cfgSym) """ - case _ => - c.typecheck(q"null.asInstanceOf[$tpe]") - q""" _root_.pprint.TPrint.implicitly[$tpe].render($cfgSym) """ - } - - }catch{case e: TypecheckException => - rec0(tpe) - } + if (args == Nil) fansi.Str("") else fansi.Str("[") ++ added ++ "]" } + def printBounds(lo: Type, hi: Type) = { - val loTree = if (lo =:= typeOf[Nothing]) q"$s" else q""" " >: " + ${implicitRec(lo)} """ - val hiTree = if (hi =:= typeOf[Any]) q"$s" else q""" " <: " + ${implicitRec(hi)} """ - q"$loTree + $hiTree" + val loTree = if (lo =:= typeOf[Nothing]) fansi.Str("") else fansi.Str(" >: ") ++ typePrintImplRec(c)(lo, true) + val hiTree = if (hi =:= typeOf[Any]) fansi.Str("") else fansi.Str(" <: ") ++ typePrintImplRec(c)(hi, true) + loTree ++ hiTree } def showRefinement(quantified: List[Symbol]) = { @@ -122,10 +119,10 @@ object TPrintLowPri{ case PolyType(typeParams, resultType) => val paramTree = printArgSyms(t.asInstanceOf[TypeSymbol].typeParams) val resultBounds = - if (resultType =:= typeOf[Any]) q"$s" - else q""" " <: " + ${implicitRec(resultType)} """ + if (resultType =:= typeOf[Any]) fansi.Str("") + else fansi.Str(" <: ") ++ typePrintImplRec(c)(resultType, true) - Some(q""" $paramTree + $resultBounds""") + Some(paramTree ++ resultBounds) case TypeBounds(lo, hi) if t.toString.contains("$") && lo =:= typeOf[Nothing] && hi =:= typeOf[Any] => None @@ -141,67 +138,91 @@ object TPrintLowPri{ defs ) - q""" - "val " + ${literalColor(q"${t.name.toString.stripSuffix(".type")}")} + - ": " + ${implicitRec(filtered)} - """ + fansi.Str("val ") ++ literalColor(t.name.toString.stripSuffix(".type")) ++ + ": " ++ typePrintImplRec(c)(filtered, true) }else { - q""" "type " + ${printSym(t)} + $suffix """ + fansi.Str("type ") ++ printSym(t) ++ suffix } } if (stmts.length == 0) None - else Some(stmts.reduceLeft((l, r) => q""" $l + "; " + $r """)) + else Some(stmts.reduceLeft((l, r) => l + "; " + r)) } - /** - * Decide how to pretty-print, based on the type. - * - * This is recursive, but we only rarely use direct recursion: more - * often, we'll use `implicitRec`, which goes through the normal - * implicit search channel and can thus - */ - def rec0(tpe: Type, end: Boolean = false): Tree = tpe match { + + tpe match { case TypeBounds(lo, hi) => val res = printBounds(lo, hi) - q""" "_" + $res """ + (fansi.Str("_") ++ res, WrapType.NoWrap) case ThisType(sym) => - q"${printSymFull(sym)} + ${if(sym.isPackage || sym.isModuleClass) "" else ".this.type"}" + (printSymFull(sym) + (if(sym.isPackage || sym.isModuleClass) "" else ".this.type"), WrapType.NoWrap) - case SingleType(NoPrefix, sym) => q"${printSym(sym)} + ${if (end) ".type" else ""}" - case SingleType(pre, sym) => q"${prefixFor(pre, sym)} + ${if (end) ".type" else ""}" + case SingleType(NoPrefix, sym) => (printSym(sym) ++ (if (rightMost) ".type" else ""), WrapType.NoWrap) + case SingleType(pre, sym) => (prefixFor(pre, sym) ++ (if (rightMost) ".type" else ""), WrapType.NoWrap) // Special-case operator two-parameter types as infix case TypeRef(pre, sym, List(left, right)) if lookup(sym) && sym.name.encodedName.toString != sym.name.decodedName.toString => - q"""${implicitRec(left)} + " " + ${printSym(sym)} + " " +${implicitRec(right)}""" + ( + typePrintImplRec(c)(left, true) ++ " " ++ printSym(sym) ++ " " ++ typePrintImplRec(c)(right, true), + WrapType.Infix + ) - case TypeRef(NoPrefix, sym, args) => q"${printSym(sym)} + ${printArgs(args)}" - case TypeRef(pre, sym, args) => q"${prefixFor(pre, sym)} + ${printArgs(args)}" - case et @ ExistentialType(quantified, underlying) => - showRefinement(quantified) match{ - case None => implicitRec(underlying) - case Some(block) => q"""${implicitRec(underlying)} + " forSome { " + $block + " }" """ + case TypeRef(pre, sym, args) if functionTypes.contains(sym.fullName) => + args match{ + case Seq(r) => (fansi.Str("() => ") ++ typePrintImplRec(c)(r, true), WrapType.Infix) + + case many => + val (left, leftWrap) = typePrintImplRec0(c)(many.head, true) + + if (many.size == 2 && leftWrap == WrapType.NoWrap){ + (left ++ " => " ++ typePrintImplRec(c)(many(1), true), WrapType.Infix) + }else ( + fansi.Str("(") ++ + fansi.Str.join( + (left +: many.init.tail.map(typePrintImplRec(c)(_, true))) + .flatMap(Seq(_, fansi.Str(", "))).init:_* + ) ++ + ") => " ++ typePrintImplRec(c)(many.last, true), + WrapType.Infix + ) } + case TypeRef(pre, sym, args) if tupleTypes.contains(sym.fullName) => + ( + fansi.Str("(") ++ + fansi.Str.join(args.map(typePrintImplRec(c)(_, true)).flatMap(Seq(_, fansi.Str(", "))).init:_*) ++ + ")", + WrapType.Tuple + ) + + case TypeRef(NoPrefix, sym, args) => (printSym(sym) ++ printArgs(args), WrapType.NoWrap) + case TypeRef(pre, sym, args) => + if (sym.fullName == "scala.") (fansi.Str("=> ") ++ typePrintImplRec(c)(args(0), true), WrapType.Infix) + else (prefixFor(pre, sym) ++ printArgs(args), WrapType.NoWrap) + case et @ ExistentialType(quantified, underlying) => + ( + showRefinement(quantified) match{ + case None => typePrintImplRec(c)(underlying, true) + case Some(block) => typePrintImplRec(c)(underlying, true) ++ " forSome { " ++ block ++ " }" + }, + WrapType.NoWrap + ) case AnnotatedType(annots, tp) => - val mapped = annots.map(x => q""" " @" + ${implicitRec(x.tpe)}""") - .reduceLeft((x, y) => q"$x + $y") - q"${implicitRec(tp)} + $mapped" + val mapped = annots.map(x => " @" + typePrintImplRec(c)(x.tpe, true)) + .reduceLeft((x, y) => x + y) + + ( + typePrintImplRec(c)(tp, true) + mapped, + WrapType.NoWrap + ) case RefinedType(parents, defs) => val pre = - if (parents.forall(_ =:= typeOf[AnyRef])) q""" "" """ - else parents.map(implicitRec(_)).reduceLeft[Tree]((l, r) => q"""$l + " with " + $r""") - q"$pre + ${ - if (defs.isEmpty) "" else "{" + defs.mkString(";") + "}" - }" + if (parents.forall(_ =:= typeOf[AnyRef])) "" + else parents + .map(typePrintImplRec(c)(_, true)) + .reduceLeft[fansi.Str]((l, r) => l ++ " with " ++ r) + (pre + (if (defs.isEmpty) "" else "{" ++ defs.mkString(";") ++ "}"), WrapType.NoWrap) case ConstantType(value) => - q"$value.toString" + (value.toString, WrapType.NoWrap) } - lazy val cfgSym = c.freshName[TermName](TermName("cfg")) - val res = c.Expr[TPrint[T]](q"""_root_.pprint.TPrint.lambda{ - ($cfgSym: _root_.pprint.TPrintColors) => - ${rec0(tpe, end = true)} - }""") - // println("RES " + res) - res } } diff --git a/pprint/src-3/TPrintImpl.scala b/pprint/src-3/TPrintImpl.scala index 62879b2..0ef84cf 100644 --- a/pprint/src-3/TPrintImpl.scala +++ b/pprint/src-3/TPrintImpl.scala @@ -7,71 +7,67 @@ trait TPrintLowPri{ object TPrintLowPri{ import scala.quoted._ - import sourcecode.Text.generate - - extension (expr: Expr[String]) { - def +(other: Expr[String])(using Quotes): Expr[String] = - '{ $expr + $other } + sealed trait WrapType + object WrapType{ + case object NoWrap extends WrapType + case object Infix extends WrapType + case object Tuple extends WrapType } - extension (exprs: List[Expr[String]]) { - def mkStringExpr(sep: String)(using Quotes): Expr[String] = - exprs match { - case expr :: Nil => - expr - case _ => - exprs.reduceLeft { (l, r) => l + Expr(sep) + r } - } - } + val functionTypes = Range.inclusive(0, 22).map(i => s"scala.Function$i").toSet + val tupleTypes = Range.inclusive(0, 22).map(i => s"scala.Tuple$i").toSet def typePrintImpl[T](using Quotes, Type[T]): Expr[TPrint[T]] = { import quotes.reflect._ import util._ - def literalColor(cfg: Expr[TPrintColors], s: Expr[fansi.Str]) = { - '{ $cfg.typeColor($s).render } + def literalColor(s: fansi.Str): fansi.Str = { + fansi.Color.Green(s) } - def printSymString(cfg: Expr[TPrintColors], s: String) = + def printSymString(s: String) = if (s.toString.startsWith("_$")) "_" else s.toString.stripSuffix(".type") - def printBounds(cfg: Expr[TPrintColors])(lo: TypeRepr, hi: TypeRepr) = { + def printBounds(lo: TypeRepr, hi: TypeRepr): fansi.Str = { val loTree = - if (lo =:= TypeRepr.of[Nothing]) None else Some(Expr(" >: ") + rec0(cfg)(lo) ) + if (lo =:= TypeRepr.of[Nothing]) None else Some(fansi.Str(" >: ") ++ rec(lo) ) val hiTree = - if (hi =:= TypeRepr.of[Any]) None else Some(Expr(" <: ") + rec0(cfg)(hi) ) - val underscore = Expr("_") - loTree.orElse(hiTree).map(underscore + _).getOrElse(underscore) + if (hi =:= TypeRepr.of[Any]) None else Some(fansi.Str(" <: ") ++ rec(hi) ) + val underscore = fansi.Str("_") + loTree.orElse(hiTree).map(underscore ++ _).getOrElse(underscore) } - def printSym(cfg: Expr[TPrintColors], s: String): Expr[String] = { - val expr = Expr(s) - literalColor(cfg, '{ fansi.Str($expr) }) - } + def printSym(s: String): fansi.Str = literalColor(fansi.Str(s)) //TODO: We don't currently use this method - def prefixFor(cfg: Expr[TPrintColors])(pre: TypeTree, sym: String): Expr[String] = { + def prefixFor(pre: TypeTree, sym: String): fansi.Str = { // Depending on what the prefix is, you may use `#`, `.` // or even need to wrap the prefix in parentheses val sep = pre match { case x if x.toString.endsWith(".type") => - rec0(cfg)(pre.tpe) + Expr(".") + rec(pre.tpe) ++ "." } - sep + printSym(cfg, sym) + sep ++ printSym(sym) } - def printArgs(cfg: Expr[TPrintColors])(args: List[TypeRepr]): Expr[String] = { - val added = args.map { - case TypeBounds(lo, hi) => - printBounds(cfg)(lo, hi) - case tpe: TypeRepr => - rec0(cfg)(tpe, false) - }.mkStringExpr(", ") - Expr("[") + added + Expr("]") + def printArgs(args: List[TypeRepr]): fansi.Str = { + fansi.Str("[") ++ printArgs0(args) ++ "]" + } + + def printArgs0(args: List[TypeRepr]): fansi.Str = { + val added = fansi.Str.join( + args.map { + case TypeBounds(lo, hi) => + printBounds(lo, hi) + case tpe: TypeRepr => + rec(tpe, false) + }.flatMap(Seq(_, fansi.Str(", "))).dropRight(1):_* + ) + added } @@ -85,32 +81,49 @@ object TPrintLowPri{ } } - def rec0(cfg: Expr[TPrintColors])(tpe: TypeRepr, end: Boolean = false): Expr[String] = tpe match { + def rec(tpe: TypeRepr, end: Boolean = false): fansi.Str = rec0(tpe)._1 + def rec0(tpe: TypeRepr, end: Boolean = false): (fansi.Str, WrapType) = tpe match { case TypeRef(NoPrefix(), sym) => - printSym(cfg, sym) + (printSym(sym), WrapType.NoWrap) // TODO: Add prefix handling back in once it works! case TypeRef(_, sym) => - printSym(cfg, sym) + (printSym(sym), WrapType.NoWrap) case AppliedType(tpe, args) => - printSym(cfg, tpe.typeSymbol.name) + printArgs(cfg)(args) + if (functionTypes.contains(tpe.typeSymbol.fullName)) { + ( + if(args.size == 1 ) fansi.Str("() => ") ++ rec(args.last) + else{ + val (left, wrap) = rec0(args(0)) + if(args.size == 2 && wrap == WrapType.NoWrap){ + left ++ fansi.Str(" => ") ++ rec(args.last) + } + else fansi.Str("(") ++ printArgs0(args.dropRight(1)) ++ fansi.Str(") => ") ++ rec(args.last) + + }, + WrapType.Infix + ) + + } else if (tupleTypes.contains(tpe.typeSymbol.fullName)) + (fansi.Str("(") ++ printArgs0(args) ++ fansi.Str(")"), WrapType.Tuple) + else (printSym(tpe.typeSymbol.name) ++ printArgs(args), WrapType.NoWrap) case RefinedType(tpe, refinements) => - val pre = rec0(cfg)(tpe) - lazy val defs = refinements.collect { - case (name, tpe: TypeRepr) => - Expr("type " + name + " = ") + rec0(cfg)(tpe) - case (name, TypeBounds(lo, hi)) => - Expr("type " + name) + printBounds(cfg)(lo, hi) + rec0(cfg)(tpe) - }.mkStringExpr("; ") - pre + (if(refinements.isEmpty) '{ "" } else Expr("{") + defs + Expr("}")) + val pre = rec(tpe) + lazy val defs = fansi.Str.join( + refinements.collect { + case (name, tpe: TypeRepr) => + fansi.Str("type " + name + " = ") ++ rec(tpe) + case (name, TypeBounds(lo, hi)) => + fansi.Str("type " + name) ++ printBounds(lo, hi) ++ rec(tpe) + }.flatMap(Seq(_, fansi.Str("; "))).dropRight(1):_* + ) + (pre ++ (if(refinements.isEmpty) fansi.Str("") else fansi.Str("{") ++ defs ++ "}"), WrapType.NoWrap) case AnnotatedType(parent, annot) => - rec0(cfg)(parent, end) + (rec(parent, end), WrapType.NoWrap) case _ => - Expr(Type.show[T]) - } - '{ - new TPrint[T] { - final def render(implicit cfg: TPrintColors): String = ${ rec0('cfg)(TypeRepr.of[T]) } - } + (fansi.Str(Type.show[T]), WrapType.NoWrap) } + val value: fansi.Str = rec(TypeRepr.of[T]) + + '{TPrint.recolor(fansi.Str(${Expr(value.render)}))} } } diff --git a/pprint/src/pprint/TPrint.scala b/pprint/src/pprint/TPrint.scala index 3b4968a..80f6d60 100644 --- a/pprint/src/pprint/TPrint.scala +++ b/pprint/src/pprint/TPrint.scala @@ -9,21 +9,27 @@ package pprint * - Prefixed Types are printed un-qualified, according to * what's currently in scope */ -trait TPrint[T]{ - def render(implicit cfg: TPrintColors): String +trait TPrint[T] { + def render(implicit tpc: TPrintColors): fansi.Str + } -object TPrint extends TPrintGen[TPrint, TPrintColors] with TPrintLowPri{ - def literal[T](s: String) = new TPrint[T]{ - def render(implicit cfg: TPrintColors) = cfg.typeColor(s).toString - } - def lambda[T](f: TPrintColors => String) = new TPrint[T]{ - def render(implicit cfg: TPrintColors) = f(cfg) +object TPrint extends TPrintLowPri{ + def recolor[T](s: fansi.Str): TPrint[T] = { + new TPrint[T]{ + def render(implicit tpc: TPrintColors) = { + val colors = s.getColors + val updatedColors = colors.map{ + c => if (c == fansi.Color.Green.applyMask) tpc.typeColor.applyMask else 0L + } + fansi.Str.fromArrays(s.getChars, updatedColors) + } + } } - def make[T](f: TPrintColors => String) = TPrint.lambda[T](f) - def get[T](cfg: TPrintColors)(implicit t: TPrint[T]) = t.render(cfg) def implicitly[T](implicit t: TPrint[T]): TPrint[T] = t - implicit val NothingTPrint: TPrint[Nothing] = TPrint.literal("Nothing") + implicit val NothingTPrint: TPrint[Nothing] = + recolor[Nothing](fansi.Color.Green("Nothing")) + } case class TPrintColors(typeColor: fansi.Attrs) diff --git a/pprint/src/pprint/package.scala b/pprint/src/pprint/package.scala index fd56501..47205f5 100644 --- a/pprint/src/pprint/package.scala +++ b/pprint/src/pprint/package.scala @@ -6,6 +6,6 @@ */ package object pprint extends PPrinter{ def tprint[T: TPrint](implicit config: TPrintColors) = { - implicitly[TPrint[T]].render(config) + implicitly[TPrint[T]].render } } diff --git a/pprint/test/src-2/test/pprint/TPrintTests.scala b/pprint/test/src-2/test/pprint/TPrintTests.scala index 70c094e..600e173 100644 --- a/pprint/test/src-2/test/pprint/TPrintTests.scala +++ b/pprint/test/src-2/test/pprint/TPrintTests.scala @@ -1,4 +1,4 @@ -package pprint +package test.pprint import pprint.TPrint import utest._ @@ -11,15 +11,15 @@ object TPrintTests extends TestSuite{ // type X = scala.Int with scala.Predef.String{} val x = "" - test("plain"){ - def checkVal[T](expected: String, expr: => T)(implicit tprint: TPrint[T]) = { - check[T](expected)(tprint) - } + def checkVal[T](expected: String, expr: => T)(implicit tprint: TPrint[T]) = { + check[T](expected)(tprint) + } - def check[T](expected: String*)(implicit tprint: TPrint[T]) = { - val tprinted = tprint.render - assert(expected.contains(tprinted)) - } + def check[T](expected: String*)(implicit tprint: TPrint[T]) = { + val tprinted = tprint.render.render + assert(expected.contains(tprinted)) + } + test("plain"){ test("simple"){ check[X]("X") check[String]("String") @@ -139,25 +139,11 @@ object TPrintTests extends TestSuite{ } test("annotated"){ // Can't use the normal implicit method, because of SI-8079 - assert(TPrint.default[M@deprecated].render == "M @deprecated") + val rendered = TPrint.default[M@deprecated].render + assert(rendered.toString() == "M @deprecated") } class Custom - test("custom"){ - // Maybe we want to add some extra decoration - implicit def customTPrint: TPrint[Custom] = TPrint.lambda(cfg => "+++Custom+++") - check[Custom]("+++Custom+++") - check[List[Custom]]("List[+++Custom+++]") - - // Or make it look like F# - implicit def StreamTPrint[T: TPrint]: TPrint[Stream[T]] = TPrint.lambda( - c => implicitly[TPrint[T]].render(c) + " Stream" - ) - check[Stream[Int]]("Int Stream") - - // Note how it works recursively - check[Stream[Custom]]("+++Custom+++ Stream") - } test("complex"){ class A @@ -166,8 +152,7 @@ object TPrintTests extends TestSuite{ } check[(A with B)#C]("(A with B)#C") check[({type T = Int})#T]("Int") - implicit def customTPrint: TPrint[Custom] = TPrint.lambda(cfg => "+++Custom+++") - check[(Custom with B)#C]("(+++Custom+++ with B)#C") + check[(Custom with B)#C]("(Custom with B)#C") } test("higherKinded"){ @@ -175,9 +160,9 @@ object TPrintTests extends TestSuite{ check[C[List]]("C[List]") } test("byName"){ - check[(=> Int) => Double]("Function1[=> Int, Double]") + check[(=> Int) => Double]("(=> Int) => Double") check[(=> Int, String, => (=> Char) => Float) => Double]( - "Function3[=> Int, String, => Function1[=> Char, Float], Double]" + "(=> Int, String, => (=> Char) => Float) => Double" ) } test("range"){ @@ -190,7 +175,7 @@ object TPrintTests extends TestSuite{ test("colored"){ import pprint.TPrintColors.Colors._ def checkColor[T](expected: String)(implicit tprint: TPrint[T]) = { - val tprinted = tprint.render.replace( + val tprinted = tprint.render.toString.replace( fansi.Color.Green.escape, "<" ).replace( fansi.Color.Reset.escape, ">" @@ -214,6 +199,41 @@ object TPrintTests extends TestSuite{ "}" ) } - } + test("functionGrouping"){ + test("simple")( + check[(Int => Option[Int]) => Int]("(Int => Option[Int]) => Int") + ) + test("lazy")( + check[() => (Int => Option[Int])]("() => Int => Option[Int]") + ) + test("complex")( + check[ + (Int => Option[Int]) => + ((Int => String) => Int) => + (Int => Int) + ]("(Int => Option[Int]) => ((Int => String) => Int) => Int => Int") + ) + } + test("wildcards"){ + case class MyList[T <: String]() + check[MyList[_]]("MyList[_]") + } + test("weirdImplicits"){ + trait Lower { + implicit def monad[M[_],T](i: T): M[T] = ??? + implicit def preventNothing[T](i: T): Nothing = ??? + } + object Higher extends Lower{ + implicit def value[M[_],T](l: M[T]): T = ??? + } + import Higher._ + + check[Int]("Int") + } + test("nothingWithWeirdImport"){ + import scala.reflect.runtime.universe._ + check[Nothing]("Nothing") + } + } } diff --git a/pprint/test/src-3/test/src/pprint/TPrintTests.scala b/pprint/test/src-3/test/src/pprint/TPrintTests.scala index 61e3c60..79e5592 100644 --- a/pprint/test/src-3/test/src/pprint/TPrintTests.scala +++ b/pprint/test/src-3/test/src/pprint/TPrintTests.scala @@ -19,12 +19,12 @@ object TPrintTests extends TestSuite{ def check[T](expected: String*)(implicit tprint: TPrint[T]) = { val tprinted = tprint.render - assert(expected.contains(tprinted)) + assert(expected.contains(tprinted.render)) } test("simple"){ -// check[X]("X") + // check[X]("X") check[String]("String") check[java.lang.String]("String") check[Int]("Int") @@ -39,39 +39,39 @@ object TPrintTests extends TestSuite{ } test("nothing"){ - check[Nothing]("Nothing") + test - check[Nothing]("Nothing") //Inferred nothings behave weirdly, make sure it works! - check("Nothing") - checkVal("Nothing", throw new Exception()) - checkVal("Some[Nothing]", Some(???)) + test - check("Nothing") + test - checkVal("Nothing", throw new Exception()) + test - checkVal("Some[Nothing]", Some(???)) } test("singleton"){ - check[x.type]("x.type") - check[TPrintTests.this.M]("M") - // check[TPrintTests.type]("TPrintTests.type") + check[x.type]("x.type") + check[TPrintTests.this.M]("M") + // check[TPrintTests.type]("TPrintTests.type") } test("java"){ //check[java.util.Set[_]]("java.util.Set[_]") - // check[java.util.Set[_ <: Int]]("java.util.Set[_]") - // check[java.util.Set[_ <: String]]("java.util.Set[_] forSome { type _ <: String }") - // check[java.util.Set[_ <: String]]("java.util.Set[_] forSome { type _ <: String }") -// check[java.util.Set[String]]("java.util.Set[String]") + // check[java.util.Set[_ <: Int]]("java.util.Set[_]") + // check[java.util.Set[_ <: String]]("java.util.Set[_] forSome { type _ <: String }") + // check[java.util.Set[_ <: String]]("java.util.Set[_] forSome { type _ <: String }") + // check[java.util.Set[String]]("java.util.Set[String]") } test("mutable"){ - // check[collection.mutable.Buffer[Int]]("collection.mutable.Buffer[Int]") - import collection.mutable -// TPrint.default[mutable.Buffer[Int]] - //check[mutable.Buffer[Int]]("mutable.Buffer[Int]") - check[Seq[Int]]("Seq[Int]") + // check[collection.mutable.Buffer[Int]]("collection.mutable.Buffer[Int]") + import collection.mutable + // TPrint.default[mutable.Buffer[Int]] + //check[mutable.Buffer[Int]]("mutable.Buffer[Int]") + check[Seq[Int]]("Seq[Int]") - // can't use scala.util.Properties on Scala.JS - //val is213Plus = classOf[Seq[Int]].getName != "scala.collection.Seq" - // check[collection.Seq[Int]](if (is213Plus) "collection.Seq[Int]" else "Seq[Int]") - // check[collection.immutable.Seq[Int]](if (is213Plus) "Seq[Int]" else "collection.immutable.Seq[Int]") + // can't use scala.util.Properties on Scala.JS + //val is213Plus = classOf[Seq[Int]].getName != "scala.collection.Seq" + // check[collection.Seq[Int]](if (is213Plus) "collection.Seq[Int]" else "Seq[Int]") + // check[collection.immutable.Seq[Int]](if (is213Plus) "Seq[Int]" else "collection.immutable.Seq[Int]") } test("compound"){ @@ -84,7 +84,7 @@ object TPrintTests extends TestSuite{ check[(Int, Float) => (String, String)]("(Int, Float) => (String, String)") check[(Int, String)]("(Int, String)") check[(Int, String, (Int, String), Double)]("(Int, String, (Int, String), Double)") - //check[Int {val x: Int}]("Int{val x: Int}") + //check[Int {val x: Int}]("Int{val x: Int}") //check[Int & String]("Int with String") } test("existential") { @@ -92,50 +92,33 @@ object TPrintTests extends TestSuite{ // check[{type T = Int}]("{type T = Int}") check[Map[_, _]]("Map[_, _]") - } + } - // test("thisType"){ - // class T { - // check[T.this.type]("T.this.type") - // } - // new T() - // } - // test("annotated"){ - // // Can't use the normal implicit method, because of SI-8079 - // assert(TPrint.default[M@deprecated].render == "M @deprecated") - // } + // test("thisType"){ + // class T { + // check[T.this.type]("T.this.type") + // } + // new T() + // } + // test("annotated"){ + // // Can't use the normal implicit method, because of SI-8079 + // assert(TPrint.default[M@deprecated].render == "M @deprecated") + // } class Custom - test("custom"){ - // Maybe we want to add some extra decoration - implicit def customTPrint: TPrint[Custom] = TPrint.lambda(cfg => "+++Custom+++") - check[Custom]("+++Custom+++") - - //TODO: double check implicit - //check[List[Custom]]("List[+++Custom+++]") - - // Or make it look like F# - implicit def StreamTPrint[T: TPrint]: TPrint[Stream[T]] = TPrint.lambda( - c => implicitly[TPrint[T]].render(c) + " Stream" - ) - check[Stream[Int]]("Int Stream") - - // Note how it works recursively - check[Stream[Custom]]("+++Custom+++ Stream") - } - // test("complex"){ - // class A - // class B{ - // class C - // } - // check[(A with B)#C]("(A with B)#C") - // check[({type T = Int})#T]("Int") - // implicit def customTPrint: TPrint[Custom] = TPrint.lambda(cfg => "+++Custom+++") - // check[(Custom with B)#C]("(+++Custom+++ with B)#C") - - // } + // test("complex"){ + // class A + // class B{ + // class C + // } + // check[(A with B)#C]("(A with B)#C") + // check[({type T = Int})#T]("Int") + // implicit def customTPrint: TPrint[Custom] = TPrint.lambda(cfg => "+++Custom+++") + // check[(Custom with B)#C]("(+++Custom+++ with B)#C") + + // } test("higherKinded"){ //TODO: No idea why this breaks @@ -148,39 +131,39 @@ object TPrintTests extends TestSuite{ // "Function3[=> Int, String, => Function1[=> Char, Float], Double]" // ) // } - // test("range"){ - // check[Range]("Range") - // checkVal("Range.Inclusive", 0 to 10) - // checkVal("Range", 0 until 10) - // check[Range.Inclusive]("Range.Inclusive") - // } - // } - // test("colored"){ - // import pprint.TPrintColors.Colors._ - // def checkColor[T](expected: String)(implicit tprint: TPrint[T]) = { - // val tprinted = tprint.render.replace( - // fansi.Color.Green.escape, "<" - // ).replace( - // fansi.Color.Reset.escape, ">" - // ) - // assert(tprinted == expected) - // } - - // test - checkColor[String]("") - // test - checkColor[Map[Int, String]]("[, ]") - // test - checkColor[collection.mutable.Seq[Int]]("..[]") - // // Not going to bother coloring these for now, since they're quite uncommon - // // test - checkColor[{type T = Int; val x: String; def y: Char; var z: Double}]( - // // "{type = ; val : ; def : ; var : }" - // // ) - // // test - checkColor[Map[K, V] forSome { - // // type K <: Int; val x: Float; type V >: (String, Float with Double) - // // }]( - // // "[, ] forSome { " + - // // "type <: ; val : ; " + - // // "type >: (, with ) " + - // // "}" - // // ) + // test("range"){ + // check[Range]("Range") + // checkVal("Range.Inclusive", 0 to 10) + // checkVal("Range", 0 until 10) + // check[Range.Inclusive]("Range.Inclusive") + // } + // } + // test("colored"){ + // import pprint.TPrintColors.Colors._ + // def checkColor[T](expected: String)(implicit tprint: TPrint[T]) = { + // val tprinted = tprint.render.replace( + // fansi.Color.Green.escape, "<" + // ).replace( + // fansi.Color.Reset.escape, ">" + // ) + // assert(tprinted == expected) + // } + + // test - checkColor[String]("") + // test - checkColor[Map[Int, String]]("[, ]") + // test - checkColor[collection.mutable.Seq[Int]]("..[]") + // // Not going to bother coloring these for now, since they're quite uncommon + // // test - checkColor[{type T = Int; val x: String; def y: Char; var z: Double}]( + // // "{type = ; val : ; def : ; var : }" + // // ) + // // test - checkColor[Map[K, V] forSome { + // // type K <: Int; val x: Float; type V >: (String, Float with Double) + // // }]( + // // "[, ] forSome { " + + // // "type <: ; val : ; " + + // // "type >: (, with ) " + + // // "}" + // // ) } }