From 4b51a63836fe1d2b7351d314e69c1006e32605a8 Mon Sep 17 00:00:00 2001 From: tgodzik Date: Fri, 4 Sep 2020 12:18:00 +0200 Subject: [PATCH] Remove dependency on pprint for Scala 3 and make it use default type printer an toString --- .github/workflows/ci.yml | 3 +- .scalafmt.conf | 4 + build.sbt | 12 +- .../mdoc/internal/io/ConsoleColors.scala | 11 + .../mdoc/internal/io/ConsoleColors.scala | 9 + .../mdoc/internal/markdown/Instrumenter.scala | 8 +- .../mdoc/internal/markdown/SectionInput.scala | 22 +- .../mdoc/internal/io/ConsoleReporter.scala | 9 +- .../internal/markdown/MarkdownBuilder.scala | 7 +- .../worksheets/WorksheetProvider.scala | 19 +- .../scala-0.26/pprint/SourceTypePrinter.scala | 1456 ++++++++++++++++ .../scala-0.27/pprint/SourceTypePrinter.scala | 1457 +++++++++++++++++ .../mdoc/internal/document/Compat.scala | 5 + .../mdoc/internal/document/Printing.scala | 31 + .../mdoc/internal/sourcecode/Compat.scala | 15 + .../mdoc/internal/sourcecode/Macros.scala | 31 + .../mdoc/internal/document/Compat.scala | 5 + .../mdoc/internal/document/Printing.scala | 16 + .../mdoc/internal/pprint/TypePrinter.scala | 24 + .../mdoc/internal/sourcecode/Macros.scala | 19 + .../src/main/scala/mdoc/document/Binder.scala | 16 +- .../main/scala/mdoc/document/Document.scala | 5 - .../internal/document/DocumentBuilder.scala | 20 +- .../internal/sourcecode/SourceContext.scala | 4 + .../src/main/scala/tests/BaseSuite.scala | 9 +- .../main/scala/tests/markdown/Compat.scala | 4 +- .../tests/worksheets/WorksheetSuite.scala | 118 +- 27 files changed, 3236 insertions(+), 103 deletions(-) create mode 100644 mdoc/src/main/scala-2/mdoc/internal/io/ConsoleColors.scala create mode 100644 mdoc/src/main/scala-3/mdoc/internal/io/ConsoleColors.scala create mode 100644 runtime/src/main/scala-0.26/pprint/SourceTypePrinter.scala create mode 100644 runtime/src/main/scala-0.27/pprint/SourceTypePrinter.scala create mode 100644 runtime/src/main/scala-2/mdoc/internal/document/Compat.scala create mode 100644 runtime/src/main/scala-2/mdoc/internal/document/Printing.scala create mode 100644 runtime/src/main/scala-2/mdoc/internal/sourcecode/Compat.scala create mode 100644 runtime/src/main/scala-2/mdoc/internal/sourcecode/Macros.scala create mode 100644 runtime/src/main/scala-3/mdoc/internal/document/Compat.scala create mode 100644 runtime/src/main/scala-3/mdoc/internal/document/Printing.scala create mode 100644 runtime/src/main/scala-3/mdoc/internal/pprint/TypePrinter.scala create mode 100644 runtime/src/main/scala-3/mdoc/internal/sourcecode/Macros.scala create mode 100644 runtime/src/main/scala/mdoc/internal/sourcecode/SourceContext.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bf233515..5d68dee3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,8 @@ jobs: - "'++2.11.12 test'" - "'++2.12.11 test' scripted" - "'++2.13.2 test'" - - "'++0.26.0-RC1 test'" + - "'++0.26.0 test'" + - "'++0.27.0-RC1 test'" steps: - uses: actions/checkout@v1 - uses: olafurpg/setup-scala@v7 diff --git a/.scalafmt.conf b/.scalafmt.conf index 696b59fc6..19fb989de 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -7,4 +7,8 @@ assumeStandardLibraryStripMargin = true project.excludeFilters = [ "mdoc/src/main/scala-3" + "runtime/src/main/scala-3" + "printing/src/main/scala-3" + "printing/src/main/scala-0.26" + "printing/src/main/scala-0.27" ] diff --git a/build.sbt b/build.sbt index e3f3c695c..152e27e12 100644 --- a/build.sbt +++ b/build.sbt @@ -65,6 +65,7 @@ def crossSetting[A]( inThisBuild( List( scalaVersion := scala212, + version := "2.2.7-SNAPSHOT", crossScalaVersions := List(scala212, scala211, scala213) ::: scala3, organization := "org.scalameta", licenses := Seq( @@ -110,7 +111,7 @@ lazy val sharedSettings = List( val V = new { val scalameta = "4.3.20" - val munit = "0.7.11" + val munit = "0.7.12" val coursier = "0.0.25" } @@ -121,7 +122,7 @@ lazy val pprintVersion = Def.setting { lazy val fansiVersion = Def.setting { if (scalaVersion.value.startsWith("2.11")) "0.2.6" - else "0.2.7" + else "0.2.9" } lazy val interfaces = project @@ -143,11 +144,13 @@ lazy val runtime = project .settings( sharedSettings, moduleName := "mdoc-runtime", - libraryDependencies += "com.lihaoyi" %% "pprint" % pprintVersion.value, + unmanagedSourceDirectories.in(Compile) ++= multiScalaDirectories("runtime").value, libraryDependencies ++= crossSetting( scalaVersion.value, if2 = List( - "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided + "com.lihaoyi" %% "pprint" % pprintVersion.value, + "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided, + "org.scala-lang" % "scala-compiler" % scalaVersion.value % Provided ) ) ) @@ -247,7 +250,6 @@ val jsdocs = project scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, - crossScalaVersions -= scala3, libraryDependencies ++= List( "org.scala-js" %%% "scalajs-dom" % scalajsDom ), diff --git a/mdoc/src/main/scala-2/mdoc/internal/io/ConsoleColors.scala b/mdoc/src/main/scala-2/mdoc/internal/io/ConsoleColors.scala new file mode 100644 index 000000000..cdfd0e6d4 --- /dev/null +++ b/mdoc/src/main/scala-2/mdoc/internal/io/ConsoleColors.scala @@ -0,0 +1,11 @@ +package mdoc.internal.io + +import fansi.Attrs +import fansi.Color._ + +case class ConsoleColors( + green: Attrs = Green, + blue: Attrs = Blue, + yellow: Attrs = Yellow, + red: Attrs = Red +) diff --git a/mdoc/src/main/scala-3/mdoc/internal/io/ConsoleColors.scala b/mdoc/src/main/scala-3/mdoc/internal/io/ConsoleColors.scala new file mode 100644 index 000000000..bc517be00 --- /dev/null +++ b/mdoc/src/main/scala-3/mdoc/internal/io/ConsoleColors.scala @@ -0,0 +1,9 @@ +package mdoc.internal.io + +case class ConsoleColors( + green: String => String = identity, + blue: String => String = identity, + yellow: String => String = identity, + red: String => String = identity +) + diff --git a/mdoc/src/main/scala-3/mdoc/internal/markdown/Instrumenter.scala b/mdoc/src/main/scala-3/mdoc/internal/markdown/Instrumenter.scala index 9d5fd02fa..c31ce1218 100644 --- a/mdoc/src/main/scala-3/mdoc/internal/markdown/Instrumenter.scala +++ b/mdoc/src/main/scala-3/mdoc/internal/markdown/Instrumenter.scala @@ -6,6 +6,7 @@ import java.nio.file.Path import dotty.tools.dotc.interfaces.SourcePosition import dotty.tools.dotc.ast.untpd._ +import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.ast.Trees import dotty.tools.dotc.core.Flags @@ -57,6 +58,7 @@ class Instrumenter( private def printAsScript(): Unit = { sections.zipWithIndex.foreach { case (section, i) => + import section.ctx printlnWithIndent("") printlnWithIndent("$doc.startSection();") section.stats.foreach { stat => @@ -79,7 +81,7 @@ class Instrumenter( m: Modifier, sb: PrintStream, section: SectionInput - ): Unit = { + )(using ctx: Context): Unit = { val binders = stat match { case Instrumenter.Binders(names) => names @@ -140,13 +142,13 @@ object Instrumenter { } object Binders { - private def fromPat(trees: List[Tree]) = { + private def fromPat(trees: List[Tree])(using ctx: Context) = { trees.map { case id: Ident => id.name.toString -> id.sourcePos } } - def unapply(tree: Tree): Option[List[(String, SourcePosition)]] = + def unapply(tree: Tree)(using ctx: Context): Option[List[(String, SourcePosition)]] = tree match { case df: Trees.ValDef[_] if df.mods.is(Flags.Lazy) => Some(Nil) case df: Trees.ValDef[_] => diff --git a/mdoc/src/main/scala-3/mdoc/internal/markdown/SectionInput.scala b/mdoc/src/main/scala-3/mdoc/internal/markdown/SectionInput.scala index c1c1471e6..c14cdd252 100644 --- a/mdoc/src/main/scala-3/mdoc/internal/markdown/SectionInput.scala +++ b/mdoc/src/main/scala-3/mdoc/internal/markdown/SectionInput.scala @@ -13,7 +13,7 @@ import scala.meta.Source import scala.meta.inputs.Input import scala.meta.Name import mdoc.internal.pos.TokenEditDistance - +import mdoc.internal.BuildInfo import dotty.tools.dotc.interactive.InteractiveDriver /* The class uses Scala 3 parser. @@ -28,9 +28,9 @@ case class SectionInput(input : Input, mod : Modifier, context : MContext){ |} |""".stripMargin private val filename = "Section.scala" - driver.run(java.net.URI.create("file:///Section.scala"), SourceFile.virtual(filename, sourceCode)) val source = driver.currentCtx.run.units.head.untpdTree + given ctx as Context = driver.currentCtx def stats : List[Tree] = { source match { case PackageDef(_, List(module @ _ : ModuleDef)) => @@ -42,14 +42,16 @@ case class SectionInput(input : Input, mod : Modifier, context : MContext){ def show(tree : Tree, currentIdent : Int) = { val str = tree.sourcePos.start val end = tree.sourcePos.end - // https://github.com/lampepfl/dotty/issues/9495 - val prefix = tree match { - case ext: ExtMethods if ext.tparams.nonEmpty => - "extension [" - case _: ExtMethods => - "extension (" - case _ => "" - } + // workaround should be removed once support for 0.26.0 is dropped + val prefix = if (BuildInfo.scalaBinaryVersion == "0.26") + tree match { + case ext: ExtMethods if ext.tparams.nonEmpty => + "extension [" + case _: ExtMethods => + "extension (" + case _ => "" + } + else "" val realIdent = " " * (currentIdent - wrapIdent.size) prefix + sourceCode.substring(str, end).replace("\n", "\n" + realIdent) } diff --git a/mdoc/src/main/scala/mdoc/internal/io/ConsoleReporter.scala b/mdoc/src/main/scala/mdoc/internal/io/ConsoleReporter.scala index 8b21595b9..6daffddcb 100644 --- a/mdoc/src/main/scala/mdoc/internal/io/ConsoleReporter.scala +++ b/mdoc/src/main/scala/mdoc/internal/io/ConsoleReporter.scala @@ -1,7 +1,5 @@ package mdoc.internal.io -import fansi.Attrs -import fansi.Color._ import java.io.PrintStream import java.util.concurrent.atomic.AtomicBoolean import scala.meta.Position @@ -11,12 +9,11 @@ import coursierapi.Logger class ConsoleReporter( ps: PrintStream, - green: Attrs = Green, - blue: Attrs = Blue, - yellow: Attrs = Yellow, - red: Attrs = Red + colors: ConsoleColors = ConsoleColors() ) extends Reporter { + import colors._ + def formatMessage(pos: Position, severity: String, message: String): String = pos.formatMessage("", message) diff --git a/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownBuilder.scala b/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownBuilder.scala index 4e1f7768c..71952a581 100644 --- a/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownBuilder.scala +++ b/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownBuilder.scala @@ -81,11 +81,11 @@ object MarkdownBuilder { val pathString = path.toString pathString.contains("scala-library") || pathString.contains("scala-reflect") || - pathString.contains("sourcecode") || pathString.contains("fansi") || pathString.contains("pprint") || pathString.contains("mdoc-interfaces") || - (pathString.contains("mdoc") && pathString.contains("runtime")) + (pathString.contains("mdoc") && pathString.contains("runtime")) || + (pathString.contains("mdoc") && pathString.contains("printing")) }) base ++ runtime } @@ -99,7 +99,8 @@ object MarkdownBuilder { .iterator .map(url => AbsolutePath(Paths.get(url.toURI))) .filter(p => fn(p.toNIO)) - Classpath(paths.toList) + .toList + Classpath(paths) } } diff --git a/mdoc/src/main/scala/mdoc/internal/worksheets/WorksheetProvider.scala b/mdoc/src/main/scala/mdoc/internal/worksheets/WorksheetProvider.scala index ef039c344..71a8070cb 100644 --- a/mdoc/src/main/scala/mdoc/internal/worksheets/WorksheetProvider.scala +++ b/mdoc/src/main/scala/mdoc/internal/worksheets/WorksheetProvider.scala @@ -7,11 +7,10 @@ import mdoc.internal.markdown.SectionInput import mdoc.internal.markdown.Modifier import mdoc.internal.markdown.Instrumenter import mdoc.internal.markdown.MarkdownBuilder +import mdoc.internal.document.Printing import mdoc.document.Statement import mdoc.document.RangePosition import mdoc.internal.cli.Settings -import pprint.TPrintColors -import pprint.PPrinter.BlackWhite import mdoc.internal.io.StoreReporter import mdoc.{interfaces => i} import java.{util => ju} @@ -94,9 +93,7 @@ class WorksheetProvider(settings: Settings) { .append(": ") .append(binder.tpeString) .append(" = ") - BlackWhite - .tokenize(binder.value, width = settings.screenWidth, height = settings.screenHeight) - .foreach(text => out.appendAll(text.getChars)) + Printing.print(binder.value, out, settings.screenWidth, settings.screenHeight) } } statement.out.linesIterator.foreach { line => @@ -140,16 +137,8 @@ class WorksheetProvider(settings: Settings) { .append(": ") .append(binder.tpeString) .append(" = ") - val chunk = BlackWhite - .tokenize(binder.value, width = margin - out.length) - .map(_.getChars) - .filterNot(_.iterator.forall(_.isWhitespace)) - .flatMap(_.iterator) - .filter { - case '\n' => false - case _ => true - } - out.appendAll(chunk) + + Printing.printOneLine(binder.value, out, width = margin - out.length) out.length > margin } } diff --git a/runtime/src/main/scala-0.26/pprint/SourceTypePrinter.scala b/runtime/src/main/scala-0.26/pprint/SourceTypePrinter.scala new file mode 100644 index 000000000..e2e30cb97 --- /dev/null +++ b/runtime/src/main/scala-0.26/pprint/SourceTypePrinter.scala @@ -0,0 +1,1456 @@ +package mdoc.internal.pprint + +import scala.annotation.switch +import scala.language.implicitConversions +import scala.quoted._ +import scala.quoted.show.SyntaxHighlight +import scala.tasty.Reflection +import scala.tasty.reflect.Printer + + +/** Printer for fully elaborated representation of the source code without the added prefix + * This is code is copied from Dotty and until https://github.com/lampepfl/dotty/issues/9716 + * is fixed it's the best way to print types we have. + */ +class SourceTypePrinter[R <: Reflection & Singleton](val tasty: R)(syntaxHighlight: SyntaxHighlight) extends Printer[R] { + import tasty._ + import syntaxHighlight._ + + def showTree(tree: Tree)(using ctx: Context): String = + (new Buffer).printTree(tree).result() + + def showTypeOrBounds(tpe: TypeOrBounds)(using ctx: Context): String = + (new Buffer).printTypeOrBound(tpe)(using None).result() + + def showConstant(const: Constant)(using ctx: Context): String = + (new Buffer).printConstant(const).result() + + def showSymbol(symbol: Symbol)(using ctx: Context): String = + symbol.fullName + + def showFlags(flags: Flags)(using ctx: Context): String = { + val flagList = List.newBuilder[String] + if (flags.is(Flags.Private)) flagList += "private" + if (flags.is(Flags.Protected)) flagList += "protected" + if (flags.is(Flags.Abstract)) flagList += "abstract" + if (flags.is(Flags.Final)) flagList += "final" + if (flags.is(Flags.Sealed)) flagList += "sealed" + if (flags.is(Flags.Case)) flagList += "case" + if (flags.is(Flags.Implicit)) flagList += "implicit" + if (flags.is(Flags.Erased)) flagList += "erased" + if (flags.is(Flags.Lazy)) flagList += "lazy" + if (flags.is(Flags.Override)) flagList += "override" + if (flags.is(Flags.Inline)) flagList += "inline" + if (flags.is(Flags.Macro)) flagList += "macro" + if (flags.is(Flags.JavaDefined)) flagList += "javaDefined" + if (flags.is(Flags.Static)) flagList += "javaStatic" + if (flags.is(Flags.Object)) flagList += "object" + if (flags.is(Flags.Trait)) flagList += "trait" + if (flags.is(Flags.Local)) flagList += "local" + if (flags.is(Flags.Synthetic)) flagList += "synthetic" + if (flags.is(Flags.Artifact)) flagList += "artifact" + if (flags.is(Flags.Mutable)) flagList += "mutable" + if (flags.is(Flags.FieldAccessor)) flagList += "accessor" + if (flags.is(Flags.CaseAcessor)) flagList += "caseAccessor" + if (flags.is(Flags.Covariant)) flagList += "covariant" + if (flags.is(Flags.Contravariant)) flagList += "contravariant" + if (flags.is(Flags.Scala2X)) flagList += "scala2x" + if (flags.is(Flags.HasDefault)) flagList += "hasDefault" + if (flags.is(Flags.StableRealizable)) flagList += "stableRealizable" + if (flags.is(Flags.Param)) flagList += "param" + if (flags.is(Flags.ParamAccessor)) flagList += "paramAccessor" + if (flags.is(Flags.Enum)) flagList += "enum" + if (flags.is(Flags.ModuleClass)) flagList += "moduleClass" + if (flags.is(Flags.PrivateLocal)) flagList += "private[this]" + if (flags.is(Flags.Package)) flagList += "package" + flagList.result().mkString("/*", " ", "*/") + } + + private class Buffer(using ctx: Context) { + + private[this] val sb: StringBuilder = new StringBuilder + + private[this] var indent: Int = 0 + def indented(printIndented: => Unit): Unit = { + indent += 1 + printIndented + indent -= 1 + } + + def inParens(body: => Unit): Buffer = { + this += "(" + body + this += ")" + } + + def inSquare(body: => Unit): Buffer = { + this += "[" + body + this += "]" + } + + def inBlock(body: => Unit): Buffer = { + this += " {" + indented { + this += lineBreak() + body + } + this += lineBreak() += "}" + } + + def result(): String = sb.result() + + def lineBreak(): String = "\n" + (" " * indent) + def doubleLineBreak(): String = "\n\n" + (" " * indent) + + def printTree(tree: Tree)(using elideThis: Option[Symbol] = None): Buffer = tree match { + case PackageObject(body)=> + printTree(body) // Print package object + + case PackageClause(Ident(name), (inner @ PackageClause(_, _)) :: Nil) if name != "" && PackageObject.unapply(inner).isEmpty => + // print inner package as `package outer.inner { ... }` + printTree(inner) + + case tree @ PackageClause(name, stats) => + val stats1 = stats.collect { + case stat: PackageClause => stat + case stat: Definition if !(stat.symbol.flags.is(Flags.Object) && stat.symbol.flags.is(Flags.Lazy)) => stat + case stat @ Import(_, _) => stat + } + name match { + case Ident("") => + printTrees(stats1, lineBreak()) + case _ => + this += "package " + printType(name.tpe) + inBlock(printTrees(stats1, lineBreak())) + } + + case Import(expr, selectors) => + this += "import " + printTree(expr) + this += "." + printImportSelectors(selectors) + + case cdef @ ClassDef(name, DefDef(_, targs, argss, _, _), parents, derived, self, stats) => + printDefAnnotations(cdef) + + val flags = cdef.symbol.flags + if (flags.is(Flags.Implicit)) this += highlightKeyword("implicit ") + if (flags.is(Flags.Sealed)) this += highlightKeyword("sealed ") + if (flags.is(Flags.Final) && !flags.is(Flags.Object)) this += highlightKeyword("final ") + if (flags.is(Flags.Case)) this += highlightKeyword("case ") + + if (name == "package$") { + this += highlightKeyword("package object ") += highlightTypeDef(cdef.symbol.owner.name.stripSuffix("$")) + } + else if (flags.is(Flags.Object)) this += highlightKeyword("object ") += highlightTypeDef(name.stripSuffix("$")) + else if (flags.is(Flags.Trait)) this += highlightKeyword("trait ") += highlightTypeDef(name) + else if (flags.is(Flags.Abstract)) this += highlightKeyword("abstract class ") += highlightTypeDef(name) + else this += highlightKeyword("class ") += highlightTypeDef(name) + + val typeParams = stats.collect { case targ: TypeDef => targ }.filter(_.symbol.isTypeParam).zip(targs) + if (!flags.is(Flags.Object)) { + printTargsDefs(typeParams) + val it = argss.iterator + while (it.hasNext) + printArgsDefs(it.next()) + } + + val parents1 = parents.filter { + case Apply(Select(New(tpt), _), _) => tpt.tpe.typeSymbol != ctx.requiredClass("java.lang.Object") + case TypeSelect(Select(Ident("_root_"), "scala"), "Product") => false + case TypeSelect(Select(Ident("_root_"), "scala"), "Serializable") => false + case _ => true + } + if (parents1.nonEmpty) + this += highlightKeyword(" extends ") + + def printParent(parent: Tree /* Term | TypeTree */, needEmptyParens: Boolean = false): Unit = parent match { + case parent: TypeTree => + printTypeTree(parent)(using Some(cdef.symbol)) + case TypeApply(fun, targs) => + printParent(fun) + case Apply(fun@Apply(_,_), args) => + printParent(fun, true) + if (!args.isEmpty || needEmptyParens) + inParens(printTrees(args, ", ")(using Some(cdef.symbol))) + case Apply(fun, args) => + printParent(fun) + if (!args.isEmpty || needEmptyParens) + inParens(printTrees(args, ", ")(using Some(cdef.symbol))) + case Select(newTree: New, _) => + printType(newTree.tpe)(using Some(cdef.symbol)) + case parent: Term => + throw new MatchError(parent.showExtractors) + } + + def printSeparated(list: List[Tree /* Term | TypeTree */]): Unit = list match { + case Nil => + case x :: Nil => printParent(x) + case x :: xs => + printParent(x) + this += highlightKeyword(" with ") + printSeparated(xs) + } + printSeparated(parents1) + + if (derived.nonEmpty) { + this += highlightKeyword(" derives ") + printTypeTrees(derived, ", ") + } + + def keepDefinition(d: Definition): Boolean = { + val flags = d.symbol.flags + def isUndecompilableCaseClassMethod: Boolean = { + // Currently the compiler does not allow overriding some of the methods generated for case classes + d.symbol.flags.is(Flags.Synthetic) && + (d match { + case DefDef("apply" | "unapply" | "writeReplace", _, _, _, _) if d.symbol.owner.flags.is(Flags.Object) => true + case DefDef(n, _, _, _, _) if d.symbol.owner.flags.is(Flags.Case) => + n == "copy" || + n.matches("copy\\$default\\$[1-9][0-9]*") || // default parameters for the copy method + n.matches("_[1-9][0-9]*") || // Getters from Product + n == "productElementName" + case _ => false + }) + } + def isInnerModuleObject = d.symbol.flags.is(Flags.Lazy) && d.symbol.flags.is(Flags.Object) + !flags.is(Flags.Param) && !flags.is(Flags.ParamAccessor) && !flags.is(Flags.FieldAccessor) && !isUndecompilableCaseClassMethod && !isInnerModuleObject + } + val stats1 = stats.collect { + case stat: Definition if keepDefinition(stat) => stat + case stat @ Import(_, _) => stat + case stat: Term => stat + } + + def printBody(printSelf: Boolean) = { + this += " {" + indented { + if (printSelf) { + val Some(ValDef(name, tpt, _)) = self + indented { + val name1 = if (name == "_") "this" else name + this += " " += highlightValDef(name1) += ": " + printTypeTree(tpt)(using Some(cdef.symbol)) + this += " =>" + } + } + this += lineBreak() + printTrees(stats1, lineBreak()) + } + this += lineBreak() += "}" + } + self match { + case Some(ValDef(_, Singleton(_), _)) => + if (stats1.nonEmpty) + printBody(printSelf = false) + case Some(ValDef(_, _, _)) => + printBody(printSelf = true) + case _ => + if (stats1.nonEmpty) + printBody(printSelf = false) + } + this + + case tdef @ TypeDef(name, rhs) => + printDefAnnotations(tdef) + this += highlightKeyword("type ") + printTargDef((tdef, tdef), isMember = true) + + case vdef @ ValDef(name, tpt, rhs) => + printDefAnnotations(vdef) + + val flags = vdef.symbol.flags + if (flags.is(Flags.Implicit)) this += highlightKeyword("implicit ") + if (flags.is(Flags.Override)) this += highlightKeyword("override ") + if (flags.is(Flags.Final) && !flags.is(Flags.Object)) this += highlightKeyword("final ") + + printProtectedOrPrivate(vdef) + + if (flags.is(Flags.Lazy)) this += highlightKeyword("lazy ") + if (vdef.symbol.flags.is(Flags.Mutable)) this += highlightKeyword("var ") + else this += highlightKeyword("val ") + + val name1 = splicedName(vdef.symbol).getOrElse(name) + this += highlightValDef(name1) += ": " + printTypeTree(tpt) + rhs match { + case Some(tree) => + this += " = " + printTree(tree) + case None => + this + } + + case While(cond, body) => + (cond, body) match { + case (Block(Block(Nil, body1) :: Nil, Block(Nil, cond1)), Literal(Constant(()))) => + this += highlightKeyword("do ") + printTree(body1) += highlightKeyword(" while ") + inParens(printTree(cond1)) + case _ => + this += highlightKeyword("while ") + inParens(printTree(cond)) += " " + printTree(body) + } + + case ddef @ DefDef(name, targs, argss, tpt, rhs) => + printDefAnnotations(ddef) + + val isConstructor = name == "" + + val flags = ddef.symbol.flags + if (flags.is(Flags.Implicit)) this += highlightKeyword("implicit ") + if (flags.is(Flags.Inline)) this += highlightKeyword("inline ") + if (flags.is(Flags.Override)) this += highlightKeyword("override ") + if (flags.is(Flags.Final) && !flags.is(Flags.Object)) this += highlightKeyword("final ") + + printProtectedOrPrivate(ddef) + + val name1: String = if (isConstructor) "this" else splicedName(ddef.symbol).getOrElse(name) + this += highlightKeyword("def ") += highlightValDef(name1) + printTargsDefs(targs.zip(targs)) + val it = argss.iterator + while (it.hasNext) + printArgsDefs(it.next()) + if (!isConstructor) { + this += ": " + printTypeTree(tpt) + } + rhs match { + case Some(tree) => + this += " = " + printTree(tree) + case None => + } + this + + case Ident("_") => + this += "_" + + case tree: Ident => + splicedName(tree.symbol) match { + case Some(name) => this += highlightTypeDef(name) + case _ => printType(tree.tpe) + } + + case Select(qual, name) => + printQualTree(qual) + if (name != "" && name != "package") + this += "." += name + this + + case Literal(const) => + printConstant(const) + + case This(id) => + id match { + case Some(x) => + this += x.name.stripSuffix("$") += "." + case None => + } + this += "this" + + case tree: New => + this += "new " + printType(tree.tpe) + + case NamedArg(name, arg) => + this += name += " = " + printTree(arg) + + case SpecialOp("throw", expr :: Nil) => + this += "throw " + printTree(expr) + + case Apply(fn, args) if fn.symbol == ctx.requiredMethod("scala.internal.Quoted.exprQuote") => + args.head match { + case Block(stats, expr) => + this += "'{" + indented { + this += lineBreak() + printFlatBlock(stats, expr) + } + this += lineBreak() += "}" + case _ => + this += "'{" + printTree(args.head) + this += "}" + } + + case TypeApply(fn, args) if fn.symbol == ctx.requiredMethod("scala.internal.Quoted.typeQuote") => + this += "'[" + printTypeTree(args.head) + this += "]" + + case Apply(fn, arg :: Nil) if fn.symbol == ctx.requiredMethod("scala.internal.Quoted.exprSplice") => + this += "${" + printTree(arg) + this += "}" + + case Apply(fn, args) => + var argsPrefix = "" + fn match { + case Select(This(_), "") => this += "this" // call to constructor inside a constructor + case Select(qual, "apply") => + if qual.tpe.isContextFunctionType then + argsPrefix += "using " + if qual.tpe.isErasedFunctionType then + argsPrefix += "erased " + printQualTree(fn) + case _ => printQualTree(fn) + } + val args1 = args match { + case init :+ Typed(Repeated(Nil, _), _) => init // drop empty var args at the end + case _ => args + } + + inParens { + this += argsPrefix + printTrees(args1, ", ") + } + + case TypeApply(fn, args) => + printQualTree(fn) + fn match { + case Select(New(Applied(_, _)), "") => + // type bounds already printed in `fn` + this + case _ => + inSquare(printTrees(args, ", ")) + } + + case Super(qual, idOpt) => + qual match { + case This(Some(Id(name))) => this += name += "." + case This(None) => + } + this += "super" + for (id <- idOpt) + inSquare(this += id.name) + this + + case Typed(term, tpt) => + tpt.tpe match { + case Types.Repeated(_) => + printTree(term) + term match { + case Repeated(_, _) | Inlined(None, Nil, Repeated(_, _)) => this + case _ => this += ": " += highlightTypeDef("_*") + } + case _ => + inParens { + printTree(term) + this += (if (scala.internal.Chars.isOperatorPart(sb.last)) " : " else ": ") + def printTypeOrAnnots(tpe: Type): Unit = tpe match { + case AnnotatedType(tp, annot) if tp == term.tpe => + printAnnotation(annot) + case AnnotatedType(tp, annot) => + printTypeOrAnnots(tp) + this += " " + printAnnotation(annot) + case tpe => + printType(tpe) + } + printTypeOrAnnots(tpt.tpe) + } + } + + case Assign(lhs, rhs) => + printTree(lhs) + this += " = " + printTree(rhs) + + case tree @ Lambda(params, body) => // must come before `Block` + inParens { + printArgsDefs(params) + this += (if tree.tpe.isContextFunctionType then " ?=> " else " => ") + printTree(body) + } + + case Block(stats0, expr) => + val stats = stats0.filter { + case tree: ValDef => !tree.symbol.flags.is(Flags.Object) + case _ => true + } + printFlatBlock(stats, expr) + + case Inlined(_, bindings, expansion) => + printFlatBlock(bindings, expansion) + + case If(cond, thenp, elsep) => + this += highlightKeyword("if ") + inParens(printTree(cond)) + this += " " + printTree(thenp) + this+= highlightKeyword(" else ") + printTree(elsep) + + case Match(selector, cases) => + printQualTree(selector) + this += highlightKeyword(" match") + inBlock(printCases(cases, lineBreak())) + + case GivenMatch(cases) => + this += highlightKeyword("given match") // TODO: drop + inBlock(printCases(cases, lineBreak())) + + case Try(body, cases, finallyOpt) => + this += highlightKeyword("try ") + printTree(body) + if (cases.nonEmpty) { + this += highlightKeyword(" catch") + inBlock(printCases(cases, lineBreak())) + } + finallyOpt match { + case Some(t) => + this += highlightKeyword(" finally ") + printTree(t) + case None => + this + } + + case Return(expr) => + this += "return " + printTree(expr) + + case Repeated(elems, _) => + printTrees(elems, ", ") + + case TypeBoundsTree(lo, hi) => + this += "_ >: " + printTypeTree(lo) + this += " <: " + printTypeTree(hi) + + case tpt: WildcardTypeTree => + printTypeOrBound(tpt.tpe) + + case tpt: TypeTree => + printTypeTree(tpt) + + case Closure(meth, _) => + printTree(meth) + + case _ => + throw new MatchError(tree.showExtractors) + + } + + def printQualTree(tree: Tree): Buffer = tree match { + case _: If | _: Match | _: While | _: Try | _: Return => + this += "(" + printTree(tree) + this += ")" + case _ => printTree(tree) + } + + def flatBlock(stats: List[Statement], expr: Term): (List[Statement], Term) = { + val flatStats = List.newBuilder[Statement] + def extractFlatStats(stat: Statement): Unit = stat match { + case Lambda(_, _) => // must come before `Block` + flatStats += stat + case Block(stats1, expr1) => + val it = stats1.iterator + while (it.hasNext) + extractFlatStats(it.next()) + extractFlatStats(expr1) + case Inlined(_, bindings, expansion) => + val it = bindings.iterator + while (it.hasNext) + extractFlatStats(it.next()) + extractFlatStats(expansion) + case Literal(Constant(())) => // ignore + case stat => flatStats += stat + } + def extractFlatExpr(term: Term): Term = term match { + case Lambda(_, _) => // must come before `Block` + term + case Block(stats1, expr1) => + val it = stats1.iterator + while (it.hasNext) + extractFlatStats(it.next()) + extractFlatExpr(expr1) + case Inlined(_, bindings, expansion) => + val it = bindings.iterator + while (it.hasNext) + extractFlatStats(it.next()) + extractFlatExpr(expansion) + case term => term + } + val it = stats.iterator + while (it.hasNext) + extractFlatStats(it.next()) + val flatExpr = extractFlatExpr(expr) + (flatStats.result(), flatExpr) + } + + def printFlatBlock(stats: List[Statement], expr: Term)(using elideThis: Option[Symbol]): Buffer = { + val (stats1, expr1) = flatBlock(stats, expr) + val stats2 = stats1.filter { + case tree: TypeDef => !tree.symbol.annots.exists(_.symbol.owner == ctx.requiredClass("scala.internal.Quoted.quoteTypeTag")) + case _ => true + } + if (stats2.isEmpty) { + printTree(expr1) + } else { + this += "{" + indented { + printStats(stats2, expr1) + } + this += lineBreak() += "}" + } + } + + def printStats(stats: List[Tree], expr: Tree)(using eliseThis: Option[Symbol]): Unit = { + def printSeparator(next: Tree): Unit = { + // Avoid accidental application of opening `{` on next line with a double break + def rec(next: Tree): Unit = next match { + case Lambda(_, _) => this += lineBreak() + case Block(stats, _) if stats.nonEmpty => this += doubleLineBreak() + case Inlined(_, bindings, _) if bindings.nonEmpty => this += doubleLineBreak() + case Select(qual, _) => rec(qual) + case Apply(fn, _) => rec(fn) + case TypeApply(fn, _) => rec(fn) + case Typed(_, _) => this += doubleLineBreak() + case _ => this += lineBreak() + } + next match { + case term: Term => + flatBlock(Nil, term) match { + case (next :: _, _) => rec(next) + case (Nil, next) => rec(next) + } + case _ => this += lineBreak() + } + } + def printSeparated(list: List[Tree]): Unit = list match { + case Nil => + printTree(expr) + case x :: xs => + printTree(x) + printSeparator(if (xs.isEmpty) expr else xs.head) + printSeparated(xs) + } + + this += lineBreak() + printSeparated(stats) + } + + def printList[T](xs: List[T], sep: String, print: T => Buffer): Buffer = { + def printSeparated(list: List[T]): Unit = list match { + case Nil => + case x :: Nil => print(x) + case x :: xs => + print(x) + this += sep + printSeparated(xs) + } + printSeparated(xs) + this + } + + def printTrees(trees: List[Tree], sep: String)(using elideThis: Option[Symbol]): Buffer = + printList(trees, sep, (t: Tree) => printTree(t)) + + def printTypeTrees(trees: List[TypeTree], sep: String)(using elideThis: Option[Symbol] = None): Buffer = + printList(trees, sep, (t: TypeTree) => printTypeTree(t)) + + def printTypes(trees: List[Type], sep: String)(using elideThis: Option[Symbol]): Buffer = { + def printSeparated(list: List[Type]): Unit = list match { + case Nil => + case x :: Nil => printType(x) + case x :: xs => + printType(x) + this += sep + printSeparated(xs) + } + printSeparated(trees) + this + } + + def printImportSelectors(selectors: List[ImportSelector]): Buffer = { + def printSeparated(list: List[ImportSelector]): Unit = list match { + case Nil => + case x :: Nil => printImportSelector(x) + case x :: xs => + printImportSelector(x) + this += ", " + printSeparated(xs) + } + this += "{" + printSeparated(selectors) + this += "}" + } + + def printCases(cases: List[CaseDef], sep: String): Buffer = { + def printSeparated(list: List[CaseDef]): Unit = list match { + case Nil => + case x :: Nil => printCaseDef(x) + case x :: xs => + printCaseDef(x) + this += sep + printSeparated(xs) + } + printSeparated(cases) + this + } + + def printTypeCases(cases: List[TypeCaseDef], sep: String): Buffer = { + def printSeparated(list: List[TypeCaseDef]): Unit = list match { + case Nil => + case x :: Nil => printTypeCaseDef(x) + case x :: xs => + printTypeCaseDef(x) + this += sep + printSeparated(xs) + } + printSeparated(cases) + this + } + + def printPatterns(cases: List[Tree], sep: String): Buffer = { + def printSeparated(list: List[Tree]): Unit = list match { + case Nil => + case x :: Nil => printPattern(x) + case x :: xs => + printPattern(x) + this += sep + printSeparated(xs) + } + printSeparated(cases) + this + } + + def printTypesOrBounds(types: List[TypeOrBounds], sep: String)(using elideThis: Option[Symbol]): Buffer = { + def printSeparated(list: List[TypeOrBounds]): Unit = list match { + case Nil => + case x :: Nil => printTypeOrBound(x) + case x :: xs => + printTypeOrBound(x) + this += sep + printSeparated(xs) + } + printSeparated(types) + this + } + + def printTargsDefs(targs: List[(TypeDef, TypeDef)], isDef:Boolean = true)(using elideThis: Option[Symbol]): Unit = { + if (!targs.isEmpty) { + def printSeparated(list: List[(TypeDef, TypeDef)]): Unit = list match { + case Nil => + case x :: Nil => printTargDef(x, isDef = isDef) + case x :: xs => + printTargDef(x, isDef = isDef) + this += ", " + printSeparated(xs) + } + + inSquare(printSeparated(targs)) + } + } + + def printTargDef(arg: (TypeDef, TypeDef), isMember: Boolean = false, isDef:Boolean = true)(using elideThis: Option[Symbol]): Buffer = { + val (argDef, argCons) = arg + + if (isDef) { + if (argDef.symbol.flags.is(Flags.Covariant)) { + this += highlightValDef("+") + } else if (argDef.symbol.flags.is(Flags.Contravariant)) { + this += highlightValDef("-") + } + } + + this += argCons.name + argCons.rhs match { + case rhs: TypeBoundsTree => printBoundsTree(rhs) + case rhs: WildcardTypeTree => + printTypeOrBound(rhs.tpe) + case rhs @ LambdaTypeTree(tparams, body) => + def printParam(t: Tree /*TypeTree | TypeBoundsTree*/): Unit = t match { + case t: TypeBoundsTree => printBoundsTree(t) + case t: TypeTree => printTypeTree(t) + } + def printSeparated(list: List[TypeDef]): Unit = list match { + case Nil => + case x :: Nil => + this += x.name + printParam(x.rhs) + case x :: xs => + this += x.name + printParam(x.rhs) + this += ", " + printSeparated(xs) + } + inSquare(printSeparated(tparams)) + if (isMember) { + body match { + case MatchTypeTree(Some(bound), _, _) => + this += " <: " + printTypeTree(bound) + case _ => + } + this += " = " + printTypeOrBoundsTree(body) + } + else this + case rhs: TypeTree => + this += " = " + printTypeTree(rhs) + } + } + + def printArgsDefs(args: List[ValDef])(using elideThis: Option[Symbol]): Unit = { + val argFlags = args match { + case Nil => Flags.EmptyFlags + case arg :: _ => arg.symbol.flags + } + if (argFlags.is(Flags.Erased | Flags.Given)) { + if (argFlags.is(Flags.Given)) this += " given" + if (argFlags.is(Flags.Erased)) this += " erased" + this += " " + } + inParens { + if (argFlags.is(Flags.Implicit) && !argFlags.is(Flags.Given)) this += "implicit " + + def printSeparated(list: List[ValDef]): Unit = list match { + case Nil => + case x :: Nil => printParamDef(x) + case x :: xs => + printParamDef(x) + this += ", " + printSeparated(xs) + } + + printSeparated(args) + } + } + + def printAnnotations(trees: List[Term])(using elideThis: Option[Symbol]): Buffer = { + def printSeparated(list: List[Term]): Unit = list match { + case Nil => + case x :: Nil => printAnnotation(x) + case x :: xs => + printAnnotation(x) + this += " " + printSeparated(xs) + } + printSeparated(trees) + this + } + + def printParamDef(arg: ValDef)(using elideThis: Option[Symbol]): Unit = { + val name = splicedName(arg.symbol).getOrElse(arg.symbol.name) + val sym = arg.symbol.owner + if sym.isDefDef && sym.name == "" then + val ClassDef(_, _, _, _, _, body) = sym.owner.tree + body.collectFirst { + case vdef @ ValDef(`name`, _, _) if vdef.symbol.flags.is(Flags.ParamAccessor) => + if (!vdef.symbol.flags.is(Flags.Local)) { + var printedPrefix = false + if (vdef.symbol.flags.is(Flags.Override)) { + this += "override " + printedPrefix = true + } + printedPrefix |= printProtectedOrPrivate(vdef) + if (vdef.symbol.flags.is(Flags.Mutable)) this += highlightValDef("var ") + else if (printedPrefix || !vdef.symbol.flags.is(Flags.CaseAcessor)) this += highlightValDef("val ") + } + } + end if + + this += highlightValDef(name) += ": " + printTypeTree(arg.tpt) + } + + def printCaseDef(caseDef: CaseDef): Buffer = { + this += highlightValDef("case ") + printPattern(caseDef.pattern) + caseDef.guard match { + case Some(t) => + this += " if " + printTree(t) + case None => + } + this += highlightValDef(" =>") + indented { + caseDef.rhs match { + case Block(stats, expr) => + printStats(stats, expr)(using None) + case body => + this += lineBreak() + printTree(body) + } + } + this + } + + def printTypeCaseDef(caseDef: TypeCaseDef): Buffer = { + this += highlightValDef("case ") + printTypeTree(caseDef.pattern) + this += highlightValDef(" => ") + printTypeTree(caseDef.rhs) + this + } + + def printPattern(pattern: Tree): Buffer = pattern match { + case Ident("_") => + this += "_" + + case Bind(name, Ident("_")) => + this += name + + case Bind(name, Typed(Ident("_"), tpt)) => + this += highlightValDef(name) += ": " + printTypeTree(tpt) + + case Bind(name, pattern) => + this += name += " @ " + printPattern(pattern) + + case Unapply(fun, implicits, patterns) => + val fun2 = fun match { + case TypeApply(fun2, _) => fun2 + case _ => fun + } + fun2 match { + case Select(extractor, "unapply" | "unapplySeq") => + printTree(extractor) + case Ident("unapply" | "unapplySeq") => + this += fun.symbol.owner.fullName.stripSuffix("$") + case _ => + throw new MatchError(fun.showExtractors) + } + inParens(printPatterns(patterns, ", ")) + + case Alternatives(trees) => + inParens(printPatterns(trees, " | ")) + + case Typed(Ident("_"), tpt) => + this += "_: " + printTypeOrBoundsTree(tpt) + + case v: Term => + printTree(v) + + case _ => + throw new MatchError(pattern.showExtractors) + + } + + inline private val qc = '\'' + inline private val qSc = '"' + + def printConstant(const: Constant): Buffer = const match { + case Constant(()) => this += highlightLiteral("()") + case Constant(null) => this += highlightLiteral("null") + case Constant(v: Boolean) => this += highlightLiteral(v.toString) + case Constant(v: Byte) => this += highlightLiteral(v.toString) + case Constant(v: Short) => this += highlightLiteral(v.toString) + case Constant(v: Int) => this += highlightLiteral(v.toString) + case Constant(v: Long) => this += highlightLiteral(v.toString + "L") + case Constant(v: Float) => this += highlightLiteral(v.toString + "f") + case Constant(v: Double) => this += highlightLiteral(v.toString) + case Constant(v: Char) => this += highlightString(s"${qc}${escapedChar(v)}${qc}") + case Constant(v: String) => this += highlightString(s"${qSc}${escapedString(v)}${qSc}") + case Constant.ClassTag(v) => + this += "classOf" + inSquare(printType(v)) + } + + def printTypeOrBoundsTree(tpt: Tree)(using elideThis: Option[Symbol] = None): Buffer = tpt match { + case TypeBoundsTree(lo, hi) => + this += "_ >: " + printTypeTree(lo) + this += " <: " + printTypeTree(hi) + case tpt: WildcardTypeTree => + printTypeOrBound(tpt.tpe) + case tpt: TypeTree => + printTypeTree(tpt) + } + + /** Print type tree + * + * @param elideThis Shoud printing elide `C.this` for the given class `C`? + * None means no eliding. + * + * Self type annotation and types in parent list should elide current class + * prefix `C.this` to avoid type checking errors. + */ + def printTypeTree(tree: TypeTree)(using elideThis: Option[Symbol] = None): Buffer = tree match { + case Inferred() => + // TODO try to move this logic into `printType` + def printTypeAndAnnots(tpe: Type): Buffer = tpe match { + case AnnotatedType(tp, annot) => + printTypeAndAnnots(tp) + this += " " + printAnnotation(annot) + case tpe: TypeRef if tpe.typeSymbol == ctx.requiredClass("scala.runtime.Null$") || tpe.typeSymbol == ctx.requiredClass("scala.runtime.Nothing$") => + // scala.runtime.Null$ and scala.runtime.Nothing$ are not modules, those are their actual names + printType(tpe) + case tpe: TermRef if tpe.termSymbol.isClassDef && tpe.termSymbol.name.endsWith("$") => + printType(tpe) + this += ".type" + case tpe: TypeRef if tpe.typeSymbol.isClassDef && tpe.typeSymbol.name.endsWith("$") => + printType(tpe) + this += ".type" + case tpe @ TermRef(sym, _) => + printType(tpe) + this += ".type" + case tpe => printType(tpe) + } + printTypeAndAnnots(tree.tpe) + + case TypeIdent(name) => + printType(tree.tpe) + + case TypeSelect(qual, name) => + printTree(qual) += "." += highlightTypeDef(name) + + case Projection(qual, name) => + printTypeTree(qual) += "#" += highlightTypeDef(name) + + case Singleton(ref) => + printTree(ref) + ref match { + case Literal(_) => this + case _ => this += ".type" + } + + case Refined(tpt, refinements) => + printTypeTree(tpt) + inBlock(printTrees(refinements, "; ")) + + case Applied(tpt, args) => + printTypeTree(tpt) + inSquare(printTrees(args, ", ")) + + case Annotated(tpt, annot) => + val Annotation(ref, args) = annot + ref.tpe match { + case tpe: TypeRef if tpe.typeSymbol == ctx.requiredClass("scala.annotation.internal.Repeated") => + val Types.Sequence(tp) = tpt.tpe + printType(tp) + this += highlightTypeDef("*") + case _ => + printTypeTree(tpt) + this += " " + printAnnotation(annot) + } + + case MatchTypeTree(bound, selector, cases) => + printTypeTree(selector) + this += highlightKeyword(" match ") + inBlock(printTypeCases(cases, lineBreak())) + + case ByName(result) => + this += highlightTypeDef("=> ") + printTypeTree(result) + + case LambdaTypeTree(tparams, body) => + printTargsDefs(tparams.zip(tparams), isDef = false) + this += highlightTypeDef(" => ") + printTypeOrBoundsTree(body) + + case TypeBind(name, _) => + this += highlightTypeDef(name) + + case TypeBlock(_, tpt) => + printTypeTree(tpt) + + case _ => + throw new MatchError(tree.showExtractors) + + } + + def printTypeOrBound(tpe: TypeOrBounds)(using elideThis: Option[Symbol]): Buffer = tpe match { + case tpe@TypeBounds(lo, hi) => + this += "_ >: " + printType(lo) + this += " <: " + printType(hi) + case tpe: Type => printType(tpe) + } + + /** Print type + * + * @param elideThis Shoud printing elide `C.this` for the given class `C`? + * None means no eliding. + * + * Self type annotation and types in parent list should elide current class + * prefix `C.this` to avoid type checking errors. + */ + def printType(tpe: Type)(using elideThis: Option[Symbol] = None): Buffer = tpe match { + case ConstantType(const) => + printConstant(const) + + case tpe: TypeRef => + val sym = tpe.typeSymbol + this += highlightTypeDef(sym.name.stripSuffix("$")) + + case TermRef(prefix, name) => + this += highlightTypeDef(name) + + case tpe @ Refinement(_, _, _) => + printRefinement(tpe) + + case AppliedType(tp, args) => + tp match { + case tp: TypeLambda => + this += "(" + printType(tp) + this += ")" + inSquare(printTypesOrBounds(args, ", ")) + case tp: TypeRef if tp.typeSymbol == ctx.requiredClass("scala.") => + this += "_*" + case _ => + printType(tp) + inSquare(printTypesOrBounds(args, ", ")) + } + + case AnnotatedType(tp, annot) => + val Annotation(ref, args) = annot + printType(tp) + this += " " + printAnnotation(annot) + + case AndType(left, right) => + printType(left) + this += highlightTypeDef(" & ") + printType(right) + + case OrType(left, right) => + printType(left) + this += highlightTypeDef(" | ") + printType(right) + + case MatchType(bound, scrutinee, cases) => + printType(scrutinee) + this += highlightKeyword(" match ") + inBlock(printTypes(cases, lineBreak())) + + case ByNameType(tp) => + this += highlightTypeDef(" => ") + printType(tp) + + case ThisType(tp) => + tp match { + case tp: TypeRef if !tp.typeSymbol.flags.is(Flags.Object) => + printFullClassName(tp) + this += highlightTypeDef(".this") + case TypeRef(prefix, name) if name.endsWith("$") => + this += highlightTypeDef(name.stripSuffix("$")) + case _ => + printType(tp) + } + + case SuperType(thistpe, supertpe) => + printType(supertpe) + this += highlightTypeDef(".super") + + case TypeLambda(paramNames, tparams, body) => + inSquare(printMethodicTypeParams(paramNames, tparams)) + this += highlightTypeDef(" => ") + printTypeOrBound(body) + + case ParamRef(lambda, idx) => + lambda match { + case MethodType(params, _, _) => this += params(idx) + case PolyType(params, _, _) => this += params(idx) + case TypeLambda(params, _, _) => this += params(idx) + } + + case RecursiveType(tpe) => + printType(tpe) + + case RecursiveThis(_) => + this += highlightTypeDef("this") + + case tpe: MethodType => + this += "(" + printList(tpe.paramNames.zip(tpe.paramTypes), ", ", + (x: (String, Type)) => (this += x._1 += ": ").printType(x._2)) + this += ")" + printType(tpe.resType) + + case tpe: PolyType => + this += "[" + printList(tpe.paramNames.zip(tpe.paramBounds), ", ", + (x: (String, TypeBounds)) => (this += x._1 += " ").printTypeOrBound(x._2)) + this += "]" + printType(tpe.resType) + + case tpe: TypeLambda => + this += "[" + printList(tpe.paramNames.zip(tpe.paramBounds), ", ", + (x: (String, TypeBounds)) => (this += x._1 += " ").printTypeOrBound(x._2)) + this += "] => " + printType(tpe.resType) + + case _ => + throw new MatchError(tpe.showExtractors) + } + + def printImportSelector(sel: ImportSelector): Buffer = sel match { + case SimpleSelector(Id(name)) => this += name + case OmitSelector(Id(name)) => this += name += " => _" + case RenameSelector(Id(name), Id(newName)) => this += name += " => " += newName + } + + def printDefinitionName(sym: Definition): Buffer = sym match { + case ValDef(name, _, _) => this += highlightValDef(name) + case DefDef(name, _, _, _, _) => this += highlightValDef(name) + case ClassDef(name, _, _, _, _, _) => this += highlightTypeDef(name.stripSuffix("$")) + case TypeDef(name, _) => this += highlightTypeDef(name) + case PackageDef(name, _) => this += highlightTypeDef(name) + } + + def printAnnotation(annot: Term)(using elideThis: Option[Symbol]): Buffer = { + val Annotation(ref, args) = annot + if (annot.symbol.maybeOwner == ctx.requiredClass("scala.internal.quoted.showName")) this + else { + this += "@" + printTypeTree(ref) + if (args.isEmpty) + this + else + inParens(printTrees(args, ", ")) + } + } + + def printDefAnnotations(definition: Definition)(using elideThis: Option[Symbol]): Buffer = { + val annots = definition.symbol.annots.filter { + case Annotation(annot, _) => + val sym = annot.tpe.typeSymbol + sym != ctx.requiredClass("scala.forceInline") && + sym.maybeOwner != ctx.requiredPackage("scala.annotation.internal") + case x => throw new MatchError(x.showExtractors) + } + printAnnotations(annots) + if (annots.nonEmpty) this += " " + else this + } + + def printRefinement(tpe: Type)(using elideThis: Option[Symbol]): Buffer = { + def printMethodicType(tp: TypeOrBounds): Unit = tp match { + case tp @ MethodType(paramNames, params, res) => + inParens(printMethodicTypeParams(paramNames, params)) + printMethodicType(res) + case tp @ TypeLambda(paramNames, params, res) => + inSquare(printMethodicTypeParams(paramNames, params)) + printMethodicType(res) + case ByNameType(t) => + this += ": " + printType(t) + case tp: Type => + this += ": " + printType(tp) + } + def rec(tp: Type): Unit = tp match { + case Refinement(parent, name, info) => + rec(parent) + indented { + this += lineBreak() + info match { + case info: TypeBounds => + this += highlightKeyword("type ") += highlightTypeDef(name) + printBounds(info) + case ByNameType(_) | MethodType(_, _, _) | TypeLambda(_, _, _) => + this += highlightKeyword("def ") += highlightTypeDef(name) + printMethodicType(info) + case info: Type => + this += highlightKeyword("val ") += highlightValDef(name) + printMethodicType(info) + } + } + case tp => + printType(tp) + this += " {" + } + rec(tpe) + this += lineBreak() += "}" + } + + def printMethodicTypeParams(paramNames: List[String], params: List[TypeOrBounds])(using elideThis: Option[Symbol]): Unit = { + def printInfo(info: TypeOrBounds) = info match { + case info: TypeBounds => printBounds(info) + case info: Type => + this += ": " + printType(info) + } + def printSeparated(list: List[(String, TypeOrBounds)]): Unit = list match { + case Nil => + case (name, info) :: Nil => + this += name + printInfo(info) + case (name, info) :: xs => + this += name + printInfo(info) + this += ", " + printSeparated(xs) + } + printSeparated(paramNames.zip(params)) + } + + def printBoundsTree(bounds: TypeBoundsTree)(using elideThis: Option[Symbol]): Buffer = { + bounds.low match { + case Inferred() => + case low => + this += " >: " + printTypeTree(low) + } + bounds.hi match { + case Inferred() => this + case hi => + this += " <: " + printTypeTree(hi) + } + } + + def printBounds(bounds: TypeBounds)(using elideThis: Option[Symbol]): Buffer = { + this += " >: " + printType(bounds.low) + this += " <: " + printType(bounds.hi) + } + + def printProtectedOrPrivate(definition: Definition): Boolean = { + var prefixWasPrinted = false + def printWithin(within: Type) = within match { + case TypeRef(_, name) => this += name + case _ => printFullClassName(within) + } + if (definition.symbol.flags.is(Flags.Protected)) { + this += highlightKeyword("protected") + definition.symbol.protectedWithin match { + case Some(within) => + inSquare(printWithin(within)) + case _ => + } + prefixWasPrinted = true + } else { + definition.symbol.privateWithin match { + case Some(within) => + this += highlightKeyword("private") + inSquare(printWithin(within)) + prefixWasPrinted = true + case _ => + } + } + if (prefixWasPrinted) + this += " " + prefixWasPrinted + } + + def printFullClassName(tp: TypeOrBounds): Unit = { + def printClassPrefix(prefix: TypeOrBounds): Unit = prefix match { + case TypeRef(prefix2, name) => + printClassPrefix(prefix2) + this += name += "." + case _ => + } + val TypeRef(prefix, name) = tp + printClassPrefix(prefix) + this += name + } + + def +=(x: Boolean): this.type = { sb.append(x); this } + def +=(x: Byte): this.type = { sb.append(x); this } + def +=(x: Short): this.type = { sb.append(x); this } + def +=(x: Int): this.type = { sb.append(x); this } + def +=(x: Long): this.type = { sb.append(x); this } + def +=(x: Float): this.type = { sb.append(x); this } + def +=(x: Double): this.type = { sb.append(x); this } + def +=(x: Char): this.type = { sb.append(x); this } + def +=(x: String): this.type = { sb.append(x); this } + + private def escapedChar(ch: Char): String = (ch: @switch) match { + case '\b' => "\\b" + case '\t' => "\\t" + case '\n' => "\\n" + case '\f' => "\\f" + case '\r' => "\\r" + case '"' => "\\\"" + case '\'' => "\\\'" + case '\\' => "\\\\" + case _ => if (ch.isControl) "\\0" + Integer.toOctalString(ch) else String.valueOf(ch) + } + + private def escapedString(str: String): String = str flatMap escapedChar + } + + private[this] val names = collection.mutable.Map.empty[Symbol, String] + private[this] val namesIndex = collection.mutable.Map.empty[String, Int] + + private def splicedName(sym: Symbol)(using ctx: Context): Option[String] = { + sym.annots.find(_.symbol.owner == ctx.requiredClass("scala.internal.quoted.showName")).flatMap { + case Apply(_, Literal(Constant(c: String)) :: Nil) => Some(c) + case Apply(_, Inlined(_, _, Literal(Constant(c: String))) :: Nil) => Some(c) + case annot => None + }.orElse { + if sym.owner.isClassDef then None + else names.get(sym).orElse { + val name0 = sym.name + val index = namesIndex.getOrElse(name0, 1) + namesIndex(name0) = index + 1 + val name = + if index == 1 then name0 + else s"`$name0${index.toString.toCharArray.map {x => (x - '0' + '₀').toChar}.mkString}`" + names(sym) = name + Some(name) + } + } + } + + private object SpecialOp { + def unapply(arg: Tree)(using ctx: Context): Option[(String, List[Term])] = arg match { + case arg @ Apply(fn, args) => + fn.tpe match { + case tpe @ TermRef(ThisType(TypeRef(_, name)), name2) if name == "" => + Some((name2, args)) + case _ => None + } + case _ => None + } + } + + private object Annotation { + def unapply(arg: Tree)(using ctx: Context): Option[(TypeTree, List[Term])] = arg match { + case New(annot) => Some((annot, Nil)) + case Apply(Select(New(annot), ""), args) => Some((annot, args)) + case Apply(TypeApply(Select(New(annot), ""), targs), args) => Some((annot, args)) + case _ => None + } + } + + // TODO Provide some of these in scala.tasty.Reflection.scala and implement them using checks on symbols for performance + private object Types { + + object Sequence { + def unapply(tpe: Type)(using ctx: Context): Option[Type] = tpe match { + case AppliedType(seq, (tp: Type) :: Nil) + if seq.typeSymbol == ctx.requiredClass("scala.collection.Seq") || seq.typeSymbol == ctx.requiredClass("scala.collection.immutable.Seq") => + Some(tp) + case _ => None + } + } + + object Repeated { + def unapply(tpe: Type)(using ctx: Context): Option[Type] = tpe match { + case AppliedType(rep, (tp: Type) :: Nil) if rep.typeSymbol == ctx.requiredClass("scala.") => Some(tp) + case _ => None + } + } + + } + + object PackageObject { + def unapply(tree: Tree)(using ctx: Context): Option[Tree] = tree match { + case PackageClause(_, ValDef("package", _, _) :: body :: Nil) => Some(body) + case _ => None + } + } + +} diff --git a/runtime/src/main/scala-0.27/pprint/SourceTypePrinter.scala b/runtime/src/main/scala-0.27/pprint/SourceTypePrinter.scala new file mode 100644 index 000000000..35453832a --- /dev/null +++ b/runtime/src/main/scala-0.27/pprint/SourceTypePrinter.scala @@ -0,0 +1,1457 @@ +package mdoc.internal.pprint + +import scala.annotation.switch +import scala.language.implicitConversions +import scala.quoted._ +import scala.quoted.show.SyntaxHighlight +import scala.tasty.Reflection +import scala.tasty.reflect.Printer + + +/** Printer for fully elaborated representation of the source code without the added prefix + * This is code is copied from Dotty and until https://github.com/lampepfl/dotty/issues/9716 + * is fixed it's the best way to print types we have. + */ +class SourceTypePrinter[R <: Reflection & Singleton](val tasty: R)(syntaxHighlight: SyntaxHighlight) extends Printer[R] { + import tasty._ + import syntaxHighlight._ + + def showTree(tree: Tree)(using ctx: Context): String = + (new Buffer).printTree(tree).result() + + def showTypeOrBounds(tpe: TypeOrBounds)(using ctx: Context): String = + (new Buffer).printTypeOrBound(tpe)(using None).result() + + def showConstant(const: Constant)(using ctx: Context): String = + (new Buffer).printConstant(const).result() + + def showSymbol(symbol: Symbol)(using ctx: Context): String = + symbol.fullName + + def showFlags(flags: Flags)(using ctx: Context): String = { + val flagList = List.newBuilder[String] + if (flags.is(Flags.Abstract)) flagList += "abstract" + if (flags.is(Flags.Artifact)) flagList += "artifact" + if (flags.is(Flags.Case)) flagList += "case" + if (flags.is(Flags.CaseAcessor)) flagList += "caseAccessor" + if (flags.is(Flags.Contravariant)) flagList += "contravariant" + if (flags.is(Flags.Covariant)) flagList += "covariant" + if (flags.is(Flags.Enum)) flagList += "enum" + if (flags.is(Flags.Erased)) flagList += "erased" + if (flags.is(Flags.ExtensionMethod)) flagList += "extension" + if (flags.is(Flags.FieldAccessor)) flagList += "accessor" + if (flags.is(Flags.Final)) flagList += "final" + if (flags.is(Flags.HasDefault)) flagList += "hasDefault" + if (flags.is(Flags.Implicit)) flagList += "implicit" + if (flags.is(Flags.Inline)) flagList += "inline" + if (flags.is(Flags.JavaDefined)) flagList += "javaDefined" + if (flags.is(Flags.Lazy)) flagList += "lazy" + if (flags.is(Flags.Local)) flagList += "local" + if (flags.is(Flags.Macro)) flagList += "macro" + if (flags.is(Flags.ModuleClass)) flagList += "moduleClass" + if (flags.is(Flags.Mutable)) flagList += "mutable" + if (flags.is(Flags.Object)) flagList += "object" + if (flags.is(Flags.Override)) flagList += "override" + if (flags.is(Flags.Package)) flagList += "package" + if (flags.is(Flags.Param)) flagList += "param" + if (flags.is(Flags.ParamAccessor)) flagList += "paramAccessor" + if (flags.is(Flags.Private)) flagList += "private" + if (flags.is(Flags.PrivateLocal)) flagList += "private[this]" + if (flags.is(Flags.Protected)) flagList += "protected" + if (flags.is(Flags.Scala2X)) flagList += "scala2x" + if (flags.is(Flags.Sealed)) flagList += "sealed" + if (flags.is(Flags.StableRealizable)) flagList += "stableRealizable" + if (flags.is(Flags.Static)) flagList += "javaStatic" + if (flags.is(Flags.Synthetic)) flagList += "synthetic" + if (flags.is(Flags.Trait)) flagList += "trait" + flagList.result().mkString("/*", " ", "*/") + } + + private class Buffer(using ctx: Context) { + + private[this] val sb: StringBuilder = new StringBuilder + + private[this] var indent: Int = 0 + def indented(printIndented: => Unit): Unit = { + indent += 1 + printIndented + indent -= 1 + } + + def inParens(body: => Unit): Buffer = { + this += "(" + body + this += ")" + } + + def inSquare(body: => Unit): Buffer = { + this += "[" + body + this += "]" + } + + def inBlock(body: => Unit): Buffer = { + this += " {" + indented { + this += lineBreak() + body + } + this += lineBreak() += "}" + } + + def result(): String = sb.result() + + def lineBreak(): String = "\n" + (" " * indent) + def doubleLineBreak(): String = "\n\n" + (" " * indent) + + def printTree(tree: Tree)(using elideThis: Option[Symbol] = None): Buffer = tree match { + case PackageObject(body)=> + printTree(body) // Print package object + + case PackageClause(Ident(name), (inner @ PackageClause(_, _)) :: Nil) if name != "" && PackageObject.unapply(inner).isEmpty => + // print inner package as `package outer.inner { ... }` + printTree(inner) + + case tree @ PackageClause(name, stats) => + val stats1 = stats.collect { + case stat: PackageClause => stat + case stat: Definition if !(stat.symbol.flags.is(Flags.Object) && stat.symbol.flags.is(Flags.Lazy)) => stat + case stat @ Import(_, _) => stat + } + name match { + case Ident("") => + printTrees(stats1, lineBreak()) + case _ => + this += "package " + printType(name.tpe) + inBlock(printTrees(stats1, lineBreak())) + } + + case Import(expr, selectors) => + this += "import " + printTree(expr) + this += "." + printImportSelectors(selectors) + + case cdef @ ClassDef(name, DefDef(_, targs, argss, _, _), parents, derived, self, stats) => + printDefAnnotations(cdef) + + val flags = cdef.symbol.flags + if (flags.is(Flags.Implicit)) this += highlightKeyword("implicit ") + if (flags.is(Flags.Sealed)) this += highlightKeyword("sealed ") + if (flags.is(Flags.Final) && !flags.is(Flags.Object)) this += highlightKeyword("final ") + if (flags.is(Flags.Case)) this += highlightKeyword("case ") + + if (name == "package$") { + this += highlightKeyword("package object ") += highlightTypeDef(cdef.symbol.owner.name.stripSuffix("$")) + } + else if (flags.is(Flags.Object)) this += highlightKeyword("object ") += highlightTypeDef(name.stripSuffix("$")) + else if (flags.is(Flags.Trait)) this += highlightKeyword("trait ") += highlightTypeDef(name) + else if (flags.is(Flags.Abstract)) this += highlightKeyword("abstract class ") += highlightTypeDef(name) + else this += highlightKeyword("class ") += highlightTypeDef(name) + + val typeParams = stats.collect { case targ: TypeDef => targ }.filter(_.symbol.isTypeParam).zip(targs) + if (!flags.is(Flags.Object)) { + printTargsDefs(typeParams) + val it = argss.iterator + while (it.hasNext) + printArgsDefs(it.next()) + } + + val parents1 = parents.filter { + case Apply(Select(New(tpt), _), _) => tpt.tpe.typeSymbol != Symbol.requiredClass("java.lang.Object") + case TypeSelect(Select(Ident("_root_"), "scala"), "Product") => false + case TypeSelect(Select(Ident("_root_"), "scala"), "Serializable") => false + case _ => true + } + if (parents1.nonEmpty) + this += highlightKeyword(" extends ") + + def printParent(parent: Tree /* Term | TypeTree */, needEmptyParens: Boolean = false): Unit = parent match { + case parent: TypeTree => + printTypeTree(parent)(using Some(cdef.symbol)) + case TypeApply(fun, targs) => + printParent(fun) + case Apply(fun@Apply(_,_), args) => + printParent(fun, true) + if (!args.isEmpty || needEmptyParens) + inParens(printTrees(args, ", ")(using Some(cdef.symbol))) + case Apply(fun, args) => + printParent(fun) + if (!args.isEmpty || needEmptyParens) + inParens(printTrees(args, ", ")(using Some(cdef.symbol))) + case Select(newTree: New, _) => + printType(newTree.tpe)(using Some(cdef.symbol)) + case parent: Term => + throw new MatchError(parent.showExtractors) + } + + def printSeparated(list: List[Tree /* Term | TypeTree */]): Unit = list match { + case Nil => + case x :: Nil => printParent(x) + case x :: xs => + printParent(x) + this += highlightKeyword(" with ") + printSeparated(xs) + } + printSeparated(parents1) + + if (derived.nonEmpty) { + this += highlightKeyword(" derives ") + printTypeTrees(derived, ", ") + } + + def keepDefinition(d: Definition): Boolean = { + val flags = d.symbol.flags + def isUndecompilableCaseClassMethod: Boolean = { + // Currently the compiler does not allow overriding some of the methods generated for case classes + d.symbol.flags.is(Flags.Synthetic) && + (d match { + case DefDef("apply" | "unapply" | "writeReplace", _, _, _, _) if d.symbol.owner.flags.is(Flags.Object) => true + case DefDef(n, _, _, _, _) if d.symbol.owner.flags.is(Flags.Case) => + n == "copy" || + n.matches("copy\\$default\\$[1-9][0-9]*") || // default parameters for the copy method + n.matches("_[1-9][0-9]*") || // Getters from Product + n == "productElementName" + case _ => false + }) + } + def isInnerModuleObject = d.symbol.flags.is(Flags.Lazy) && d.symbol.flags.is(Flags.Object) + !flags.is(Flags.Param) && !flags.is(Flags.ParamAccessor) && !flags.is(Flags.FieldAccessor) && !isUndecompilableCaseClassMethod && !isInnerModuleObject + } + val stats1 = stats.collect { + case stat: Definition if keepDefinition(stat) => stat + case stat @ Import(_, _) => stat + case stat: Term => stat + } + + def printBody(printSelf: Boolean) = { + this += " {" + indented { + if (printSelf) { + val Some(ValDef(name, tpt, _)) = self + indented { + val name1 = if (name == "_") "this" else name + this += " " += highlightValDef(name1) += ": " + printTypeTree(tpt)(using Some(cdef.symbol)) + this += " =>" + } + } + this += lineBreak() + printTrees(stats1, lineBreak()) + } + this += lineBreak() += "}" + } + self match { + case Some(ValDef(_, Singleton(_), _)) => + if (stats1.nonEmpty) + printBody(printSelf = false) + case Some(ValDef(_, _, _)) => + printBody(printSelf = true) + case _ => + if (stats1.nonEmpty) + printBody(printSelf = false) + } + this + + case tdef @ TypeDef(name, rhs) => + printDefAnnotations(tdef) + this += highlightKeyword("type ") + printTargDef((tdef, tdef), isMember = true) + + case vdef @ ValDef(name, tpt, rhs) => + printDefAnnotations(vdef) + + val flags = vdef.symbol.flags + if (flags.is(Flags.Implicit)) this += highlightKeyword("implicit ") + if (flags.is(Flags.Override)) this += highlightKeyword("override ") + if (flags.is(Flags.Final) && !flags.is(Flags.Object)) this += highlightKeyword("final ") + + printProtectedOrPrivate(vdef) + + if (flags.is(Flags.Lazy)) this += highlightKeyword("lazy ") + if (vdef.symbol.flags.is(Flags.Mutable)) this += highlightKeyword("var ") + else this += highlightKeyword("val ") + + val name1 = splicedName(vdef.symbol).getOrElse(name) + this += highlightValDef(name1) += ": " + printTypeTree(tpt) + rhs match { + case Some(tree) => + this += " = " + printTree(tree) + case None => + this + } + + case While(cond, body) => + (cond, body) match { + case (Block(Block(Nil, body1) :: Nil, Block(Nil, cond1)), Literal(Constant(()))) => + this += highlightKeyword("do ") + printTree(body1) += highlightKeyword(" while ") + inParens(printTree(cond1)) + case _ => + this += highlightKeyword("while ") + inParens(printTree(cond)) += " " + printTree(body) + } + + case ddef @ DefDef(name, targs, argss, tpt, rhs) => + printDefAnnotations(ddef) + + val isConstructor = name == "" + + val flags = ddef.symbol.flags + if (flags.is(Flags.Implicit)) this += highlightKeyword("implicit ") + if (flags.is(Flags.Inline)) this += highlightKeyword("inline ") + if (flags.is(Flags.Override)) this += highlightKeyword("override ") + if (flags.is(Flags.Final) && !flags.is(Flags.Object)) this += highlightKeyword("final ") + + printProtectedOrPrivate(ddef) + + val name1: String = if (isConstructor) "this" else splicedName(ddef.symbol).getOrElse(name) + this += highlightKeyword("def ") += highlightValDef(name1) + printTargsDefs(targs.zip(targs)) + val it = argss.iterator + while (it.hasNext) + printArgsDefs(it.next()) + if (!isConstructor) { + this += ": " + printTypeTree(tpt) + } + rhs match { + case Some(tree) => + this += " = " + printTree(tree) + case None => + } + this + + case Ident("_") => + this += "_" + + case tree: Ident => + splicedName(tree.symbol) match { + case Some(name) => this += highlightTypeDef(name) + case _ => printType(tree.tpe) + } + + case Select(qual, name) => + printQualTree(qual) + if (name != "" && name != "package") + this += "." += name + this + + case Literal(const) => + printConstant(const) + + case This(id) => + id match { + case Some(x) => + this += x.name.stripSuffix("$") += "." + case None => + } + this += "this" + + case tree: New => + this += "new " + printType(tree.tpe) + + case NamedArg(name, arg) => + this += name += " = " + printTree(arg) + + case SpecialOp("throw", expr :: Nil) => + this += "throw " + printTree(expr) + + case Apply(fn, args) if fn.symbol == Symbol.requiredMethod("scala.internal.Quoted.exprQuote") => + args.head match { + case Block(stats, expr) => + this += "'{" + indented { + this += lineBreak() + printFlatBlock(stats, expr) + } + this += lineBreak() += "}" + case _ => + this += "'{" + printTree(args.head) + this += "}" + } + + case TypeApply(fn, args) if fn.symbol == Symbol.requiredMethod("scala.internal.Quoted.typeQuote") => + this += "'[" + printTypeTree(args.head) + this += "]" + + case Apply(fn, arg :: Nil) if fn.symbol == Symbol.requiredMethod("scala.internal.Quoted.exprSplice") => + this += "${" + printTree(arg) + this += "}" + + case Apply(fn, args) => + var argsPrefix = "" + fn match { + case Select(This(_), "") => this += "this" // call to constructor inside a constructor + case Select(qual, "apply") => + if qual.tpe.isContextFunctionType then + argsPrefix += "using " + if qual.tpe.isErasedFunctionType then + argsPrefix += "erased " + printQualTree(fn) + case _ => printQualTree(fn) + } + val args1 = args match { + case init :+ Typed(Repeated(Nil, _), _) => init // drop empty var args at the end + case _ => args + } + + inParens { + this += argsPrefix + printTrees(args1, ", ") + } + + case TypeApply(fn, args) => + printQualTree(fn) + fn match { + case Select(New(Applied(_, _)), "") => + // type bounds already printed in `fn` + this + case _ => + inSquare(printTrees(args, ", ")) + } + + case Super(qual, idOpt) => + qual match { + case This(Some(Id(name))) => this += name += "." + case This(None) => + } + this += "super" + for (id <- idOpt) + inSquare(this += id.name) + this + + case Typed(term, tpt) => + tpt.tpe match { + case Types.Repeated(_) => + printTree(term) + term match { + case Repeated(_, _) | Inlined(None, Nil, Repeated(_, _)) => this + case _ => this += ": " += highlightTypeDef("_*") + } + case _ => + inParens { + printTree(term) + this += (if (scala.internal.Chars.isOperatorPart(sb.last)) " : " else ": ") + def printTypeOrAnnots(tpe: Type): Unit = tpe match { + case AnnotatedType(tp, annot) if tp == term.tpe => + printAnnotation(annot) + case AnnotatedType(tp, annot) => + printTypeOrAnnots(tp) + this += " " + printAnnotation(annot) + case tpe => + printType(tpe) + } + printTypeOrAnnots(tpt.tpe) + } + } + + case Assign(lhs, rhs) => + printTree(lhs) + this += " = " + printTree(rhs) + + case tree @ Lambda(params, body) => // must come before `Block` + inParens { + printArgsDefs(params) + this += (if tree.tpe.isContextFunctionType then " ?=> " else " => ") + printTree(body) + } + + case Block(stats0, expr) => + val stats = stats0.filter { + case tree: ValDef => !tree.symbol.flags.is(Flags.Object) + case _ => true + } + printFlatBlock(stats, expr) + + case Inlined(_, bindings, expansion) => + printFlatBlock(bindings, expansion) + + case If(cond, thenp, elsep) => + this += highlightKeyword("if ") + inParens(printTree(cond)) + this += " " + printTree(thenp) + this+= highlightKeyword(" else ") + printTree(elsep) + + case Match(selector, cases) => + printQualTree(selector) + this += highlightKeyword(" match") + inBlock(printCases(cases, lineBreak())) + + case GivenMatch(cases) => + this += highlightKeyword("given match") // TODO: drop + inBlock(printCases(cases, lineBreak())) + + case Try(body, cases, finallyOpt) => + this += highlightKeyword("try ") + printTree(body) + if (cases.nonEmpty) { + this += highlightKeyword(" catch") + inBlock(printCases(cases, lineBreak())) + } + finallyOpt match { + case Some(t) => + this += highlightKeyword(" finally ") + printTree(t) + case None => + this + } + + case Return(expr) => + this += "return " + printTree(expr) + + case Repeated(elems, _) => + printTrees(elems, ", ") + + case TypeBoundsTree(lo, hi) => + this += "_ >: " + printTypeTree(lo) + this += " <: " + printTypeTree(hi) + + case tpt: WildcardTypeTree => + printTypeOrBound(tpt.tpe) + + case tpt: TypeTree => + printTypeTree(tpt) + + case Closure(meth, _) => + printTree(meth) + + case _ => + throw new MatchError(tree.showExtractors) + + } + + def printQualTree(tree: Tree): Buffer = tree match { + case _: If | _: Match | _: While | _: Try | _: Return => + this += "(" + printTree(tree) + this += ")" + case _ => printTree(tree) + } + + def flatBlock(stats: List[Statement], expr: Term): (List[Statement], Term) = { + val flatStats = List.newBuilder[Statement] + def extractFlatStats(stat: Statement): Unit = stat match { + case Lambda(_, _) => // must come before `Block` + flatStats += stat + case Block(stats1, expr1) => + val it = stats1.iterator + while (it.hasNext) + extractFlatStats(it.next()) + extractFlatStats(expr1) + case Inlined(_, bindings, expansion) => + val it = bindings.iterator + while (it.hasNext) + extractFlatStats(it.next()) + extractFlatStats(expansion) + case Literal(Constant(())) => // ignore + case stat => flatStats += stat + } + def extractFlatExpr(term: Term): Term = term match { + case Lambda(_, _) => // must come before `Block` + term + case Block(stats1, expr1) => + val it = stats1.iterator + while (it.hasNext) + extractFlatStats(it.next()) + extractFlatExpr(expr1) + case Inlined(_, bindings, expansion) => + val it = bindings.iterator + while (it.hasNext) + extractFlatStats(it.next()) + extractFlatExpr(expansion) + case term => term + } + val it = stats.iterator + while (it.hasNext) + extractFlatStats(it.next()) + val flatExpr = extractFlatExpr(expr) + (flatStats.result(), flatExpr) + } + + def printFlatBlock(stats: List[Statement], expr: Term)(using elideThis: Option[Symbol]): Buffer = { + val (stats1, expr1) = flatBlock(stats, expr) + val stats2 = stats1.filter { + case tree: TypeDef => !tree.symbol.annots.exists(_.symbol.owner == Symbol.requiredClass("scala.internal.Quoted.quoteTypeTag")) + case _ => true + } + if (stats2.isEmpty) { + printTree(expr1) + } else { + this += "{" + indented { + printStats(stats2, expr1) + } + this += lineBreak() += "}" + } + } + + def printStats(stats: List[Tree], expr: Tree)(using eliseThis: Option[Symbol]): Unit = { + def printSeparator(next: Tree): Unit = { + // Avoid accidental application of opening `{` on next line with a double break + def rec(next: Tree): Unit = next match { + case Lambda(_, _) => this += lineBreak() + case Block(stats, _) if stats.nonEmpty => this += doubleLineBreak() + case Inlined(_, bindings, _) if bindings.nonEmpty => this += doubleLineBreak() + case Select(qual, _) => rec(qual) + case Apply(fn, _) => rec(fn) + case TypeApply(fn, _) => rec(fn) + case Typed(_, _) => this += doubleLineBreak() + case _ => this += lineBreak() + } + next match { + case term: Term => + flatBlock(Nil, term) match { + case (next :: _, _) => rec(next) + case (Nil, next) => rec(next) + } + case _ => this += lineBreak() + } + } + def printSeparated(list: List[Tree]): Unit = list match { + case Nil => + printTree(expr) + case x :: xs => + printTree(x) + printSeparator(if (xs.isEmpty) expr else xs.head) + printSeparated(xs) + } + + this += lineBreak() + printSeparated(stats) + } + + def printList[T](xs: List[T], sep: String, print: T => Buffer): Buffer = { + def printSeparated(list: List[T]): Unit = list match { + case Nil => + case x :: Nil => print(x) + case x :: xs => + print(x) + this += sep + printSeparated(xs) + } + printSeparated(xs) + this + } + + def printTrees(trees: List[Tree], sep: String)(using elideThis: Option[Symbol]): Buffer = + printList(trees, sep, (t: Tree) => printTree(t)) + + def printTypeTrees(trees: List[TypeTree], sep: String)(using elideThis: Option[Symbol] = None): Buffer = + printList(trees, sep, (t: TypeTree) => printTypeTree(t)) + + def printTypes(trees: List[Type], sep: String)(using elideThis: Option[Symbol]): Buffer = { + def printSeparated(list: List[Type]): Unit = list match { + case Nil => + case x :: Nil => printType(x) + case x :: xs => + printType(x) + this += sep + printSeparated(xs) + } + printSeparated(trees) + this + } + + def printImportSelectors(selectors: List[ImportSelector]): Buffer = { + def printSeparated(list: List[ImportSelector]): Unit = list match { + case Nil => + case x :: Nil => printImportSelector(x) + case x :: xs => + printImportSelector(x) + this += ", " + printSeparated(xs) + } + this += "{" + printSeparated(selectors) + this += "}" + } + + def printCases(cases: List[CaseDef], sep: String): Buffer = { + def printSeparated(list: List[CaseDef]): Unit = list match { + case Nil => + case x :: Nil => printCaseDef(x) + case x :: xs => + printCaseDef(x) + this += sep + printSeparated(xs) + } + printSeparated(cases) + this + } + + def printTypeCases(cases: List[TypeCaseDef], sep: String): Buffer = { + def printSeparated(list: List[TypeCaseDef]): Unit = list match { + case Nil => + case x :: Nil => printTypeCaseDef(x) + case x :: xs => + printTypeCaseDef(x) + this += sep + printSeparated(xs) + } + printSeparated(cases) + this + } + + def printPatterns(cases: List[Tree], sep: String): Buffer = { + def printSeparated(list: List[Tree]): Unit = list match { + case Nil => + case x :: Nil => printPattern(x) + case x :: xs => + printPattern(x) + this += sep + printSeparated(xs) + } + printSeparated(cases) + this + } + + def printTypesOrBounds(types: List[TypeOrBounds], sep: String)(using elideThis: Option[Symbol]): Buffer = { + def printSeparated(list: List[TypeOrBounds]): Unit = list match { + case Nil => + case x :: Nil => printTypeOrBound(x) + case x :: xs => + printTypeOrBound(x) + this += sep + printSeparated(xs) + } + printSeparated(types) + this + } + + def printTargsDefs(targs: List[(TypeDef, TypeDef)], isDef:Boolean = true)(using elideThis: Option[Symbol]): Unit = { + if (!targs.isEmpty) { + def printSeparated(list: List[(TypeDef, TypeDef)]): Unit = list match { + case Nil => + case x :: Nil => printTargDef(x, isDef = isDef) + case x :: xs => + printTargDef(x, isDef = isDef) + this += ", " + printSeparated(xs) + } + + inSquare(printSeparated(targs)) + } + } + + def printTargDef(arg: (TypeDef, TypeDef), isMember: Boolean = false, isDef:Boolean = true)(using elideThis: Option[Symbol]): Buffer = { + val (argDef, argCons) = arg + + if (isDef) { + if (argDef.symbol.flags.is(Flags.Covariant)) { + this += highlightValDef("+") + } else if (argDef.symbol.flags.is(Flags.Contravariant)) { + this += highlightValDef("-") + } + } + + this += argCons.name + argCons.rhs match { + case rhs: TypeBoundsTree => printBoundsTree(rhs) + case rhs: WildcardTypeTree => + printTypeOrBound(rhs.tpe) + case rhs @ LambdaTypeTree(tparams, body) => + def printParam(t: Tree /*TypeTree | TypeBoundsTree*/): Unit = t match { + case t: TypeBoundsTree => printBoundsTree(t) + case t: TypeTree => printTypeTree(t) + } + def printSeparated(list: List[TypeDef]): Unit = list match { + case Nil => + case x :: Nil => + this += x.name + printParam(x.rhs) + case x :: xs => + this += x.name + printParam(x.rhs) + this += ", " + printSeparated(xs) + } + inSquare(printSeparated(tparams)) + if (isMember) { + body match { + case MatchTypeTree(Some(bound), _, _) => + this += " <: " + printTypeTree(bound) + case _ => + } + this += " = " + printTypeOrBoundsTree(body) + } + else this + case rhs: TypeTree => + this += " = " + printTypeTree(rhs) + } + } + + def printArgsDefs(args: List[ValDef])(using elideThis: Option[Symbol]): Unit = { + val argFlags = args match { + case Nil => Flags.EmptyFlags + case arg :: _ => arg.symbol.flags + } + if (argFlags.is(Flags.Erased | Flags.Given)) { + if (argFlags.is(Flags.Given)) this += " given" + if (argFlags.is(Flags.Erased)) this += " erased" + this += " " + } + inParens { + if (argFlags.is(Flags.Implicit) && !argFlags.is(Flags.Given)) this += "implicit " + + def printSeparated(list: List[ValDef]): Unit = list match { + case Nil => + case x :: Nil => printParamDef(x) + case x :: xs => + printParamDef(x) + this += ", " + printSeparated(xs) + } + + printSeparated(args) + } + } + + def printAnnotations(trees: List[Term])(using elideThis: Option[Symbol]): Buffer = { + def printSeparated(list: List[Term]): Unit = list match { + case Nil => + case x :: Nil => printAnnotation(x) + case x :: xs => + printAnnotation(x) + this += " " + printSeparated(xs) + } + printSeparated(trees) + this + } + + def printParamDef(arg: ValDef)(using elideThis: Option[Symbol]): Unit = { + val name = splicedName(arg.symbol).getOrElse(arg.symbol.name) + val sym = arg.symbol.owner + if sym.isDefDef && sym.name == "" then + val ClassDef(_, _, _, _, _, body) = sym.owner.tree + body.collectFirst { + case vdef @ ValDef(`name`, _, _) if vdef.symbol.flags.is(Flags.ParamAccessor) => + if (!vdef.symbol.flags.is(Flags.Local)) { + var printedPrefix = false + if (vdef.symbol.flags.is(Flags.Override)) { + this += "override " + printedPrefix = true + } + printedPrefix |= printProtectedOrPrivate(vdef) + if (vdef.symbol.flags.is(Flags.Mutable)) this += highlightValDef("var ") + else if (printedPrefix || !vdef.symbol.flags.is(Flags.CaseAcessor)) this += highlightValDef("val ") + } + } + end if + + this += highlightValDef(name) += ": " + printTypeTree(arg.tpt) + } + + def printCaseDef(caseDef: CaseDef): Buffer = { + this += highlightValDef("case ") + printPattern(caseDef.pattern) + caseDef.guard match { + case Some(t) => + this += " if " + printTree(t) + case None => + } + this += highlightValDef(" =>") + indented { + caseDef.rhs match { + case Block(stats, expr) => + printStats(stats, expr)(using None) + case body => + this += lineBreak() + printTree(body) + } + } + this + } + + def printTypeCaseDef(caseDef: TypeCaseDef): Buffer = { + this += highlightValDef("case ") + printTypeTree(caseDef.pattern) + this += highlightValDef(" => ") + printTypeTree(caseDef.rhs) + this + } + + def printPattern(pattern: Tree): Buffer = pattern match { + case Ident("_") => + this += "_" + + case Bind(name, Ident("_")) => + this += name + + case Bind(name, Typed(Ident("_"), tpt)) => + this += highlightValDef(name) += ": " + printTypeTree(tpt) + + case Bind(name, pattern) => + this += name += " @ " + printPattern(pattern) + + case Unapply(fun, implicits, patterns) => + val fun2 = fun match { + case TypeApply(fun2, _) => fun2 + case _ => fun + } + fun2 match { + case Select(extractor, "unapply" | "unapplySeq") => + printTree(extractor) + case Ident("unapply" | "unapplySeq") => + this += fun.symbol.owner.fullName.stripSuffix("$") + case _ => + throw new MatchError(fun.showExtractors) + } + inParens(printPatterns(patterns, ", ")) + + case Alternatives(trees) => + inParens(printPatterns(trees, " | ")) + + case Typed(Ident("_"), tpt) => + this += "_: " + printTypeOrBoundsTree(tpt) + + case v: Term => + printTree(v) + + case _ => + throw new MatchError(pattern.showExtractors) + + } + + inline private val qc = '\'' + inline private val qSc = '"' + + def printConstant(const: Constant): Buffer = const match { + case Constant(()) => this += highlightLiteral("()") + case Constant(null) => this += highlightLiteral("null") + case Constant(v: Boolean) => this += highlightLiteral(v.toString) + case Constant(v: Byte) => this += highlightLiteral(v.toString) + case Constant(v: Short) => this += highlightLiteral(v.toString) + case Constant(v: Int) => this += highlightLiteral(v.toString) + case Constant(v: Long) => this += highlightLiteral(v.toString + "L") + case Constant(v: Float) => this += highlightLiteral(v.toString + "f") + case Constant(v: Double) => this += highlightLiteral(v.toString) + case Constant(v: Char) => this += highlightString(s"${qc}${escapedChar(v)}${qc}") + case Constant(v: String) => this += highlightString(s"${qSc}${escapedString(v)}${qSc}") + case Constant.ClassTag(v) => + this += "classOf" + inSquare(printType(v)) + } + + def printTypeOrBoundsTree(tpt: Tree)(using elideThis: Option[Symbol] = None): Buffer = tpt match { + case TypeBoundsTree(lo, hi) => + this += "_ >: " + printTypeTree(lo) + this += " <: " + printTypeTree(hi) + case tpt: WildcardTypeTree => + printTypeOrBound(tpt.tpe) + case tpt: TypeTree => + printTypeTree(tpt) + } + + /** Print type tree + * + * @param elideThis Shoud printing elide `C.this` for the given class `C`? + * None means no eliding. + * + * Self type annotation and types in parent list should elide current class + * prefix `C.this` to avoid type checking errors. + */ + def printTypeTree(tree: TypeTree)(using elideThis: Option[Symbol] = None): Buffer = tree match { + case Inferred() => + // TODO try to move this logic into `printType` + def printTypeAndAnnots(tpe: Type): Buffer = tpe match { + case AnnotatedType(tp, annot) => + printTypeAndAnnots(tp) + this += " " + printAnnotation(annot) + case tpe: TypeRef if tpe.typeSymbol == Symbol.requiredClass("scala.runtime.Null$") || tpe.typeSymbol == Symbol.requiredClass("scala.runtime.Nothing$") => + // scala.runtime.Null$ and scala.runtime.Nothing$ are not modules, those are their actual names + printType(tpe) + case tpe: TermRef if tpe.termSymbol.isClassDef && tpe.termSymbol.name.endsWith("$") => + printType(tpe) + this += ".type" + case tpe: TypeRef if tpe.typeSymbol.isClassDef && tpe.typeSymbol.name.endsWith("$") => + printType(tpe) + this += ".type" + case tpe @ TermRef(sym, _) => + printType(tpe) + this += ".type" + case tpe => printType(tpe) + } + printTypeAndAnnots(tree.tpe) + + case TypeIdent(name) => + printType(tree.tpe) + + case TypeSelect(qual, name) => + printTree(qual) += "." += highlightTypeDef(name) + + case Projection(qual, name) => + printTypeTree(qual) += "#" += highlightTypeDef(name) + + case Singleton(ref) => + printTree(ref) + ref match { + case Literal(_) => this + case _ => this += ".type" + } + + case Refined(tpt, refinements) => + printTypeTree(tpt) + inBlock(printTrees(refinements, "; ")) + + case Applied(tpt, args) => + printTypeTree(tpt) + inSquare(printTrees(args, ", ")) + + case Annotated(tpt, annot) => + val Annotation(ref, args) = annot + ref.tpe match { + case tpe: TypeRef if tpe.typeSymbol == Symbol.requiredClass("scala.annotation.internal.Repeated") => + val Types.Sequence(tp) = tpt.tpe + printType(tp) + this += highlightTypeDef("*") + case _ => + printTypeTree(tpt) + this += " " + printAnnotation(annot) + } + + case MatchTypeTree(bound, selector, cases) => + printTypeTree(selector) + this += highlightKeyword(" match ") + inBlock(printTypeCases(cases, lineBreak())) + + case ByName(result) => + this += highlightTypeDef("=> ") + printTypeTree(result) + + case LambdaTypeTree(tparams, body) => + printTargsDefs(tparams.zip(tparams), isDef = false) + this += highlightTypeDef(" => ") + printTypeOrBoundsTree(body) + + case TypeBind(name, _) => + this += highlightTypeDef(name) + + case TypeBlock(_, tpt) => + printTypeTree(tpt) + + case _ => + throw new MatchError(tree.showExtractors) + + } + + def printTypeOrBound(tpe: TypeOrBounds)(using elideThis: Option[Symbol]): Buffer = tpe match { + case tpe@TypeBounds(lo, hi) => + this += "_ >: " + printType(lo) + this += " <: " + printType(hi) + case tpe: Type => printType(tpe) + } + + /** Print type + * + * @param elideThis Shoud printing elide `C.this` for the given class `C`? + * None means no eliding. + * + * Self type annotation and types in parent list should elide current class + * prefix `C.this` to avoid type checking errors. + */ + def printType(tpe: Type)(using elideThis: Option[Symbol] = None): Buffer = tpe match { + case ConstantType(const) => + printConstant(const) + + case tpe: TypeRef => + val sym = tpe.typeSymbol + this += highlightTypeDef(sym.name.stripSuffix("$")) + + case TermRef(prefix, name) => + this += highlightTypeDef(name) + + case tpe @ Refinement(_, _, _) => + printRefinement(tpe) + + case AppliedType(tp, args) => + tp match { + case tp: TypeLambda => + this += "(" + printType(tp) + this += ")" + inSquare(printTypesOrBounds(args, ", ")) + case tp: TypeRef if tp.typeSymbol == Symbol.requiredClass("scala.") => + this += "_*" + case _ => + printType(tp) + inSquare(printTypesOrBounds(args, ", ")) + } + + case AnnotatedType(tp, annot) => + val Annotation(ref, args) = annot + printType(tp) + this += " " + printAnnotation(annot) + + case AndType(left, right) => + printType(left) + this += highlightTypeDef(" & ") + printType(right) + + case OrType(left, right) => + printType(left) + this += highlightTypeDef(" | ") + printType(right) + + case MatchType(bound, scrutinee, cases) => + printType(scrutinee) + this += highlightKeyword(" match ") + inBlock(printTypes(cases, lineBreak())) + + case ByNameType(tp) => + this += highlightTypeDef(" => ") + printType(tp) + + case ThisType(tp) => + tp match { + case tp: TypeRef if !tp.typeSymbol.flags.is(Flags.Object) => + printFullClassName(tp) + this += highlightTypeDef(".this") + case TypeRef(prefix, name) if name.endsWith("$") => + this += highlightTypeDef(name.stripSuffix("$")) + case _ => + printType(tp) + } + + case SuperType(thistpe, supertpe) => + printType(supertpe) + this += highlightTypeDef(".super") + + case TypeLambda(paramNames, tparams, body) => + inSquare(printMethodicTypeParams(paramNames, tparams)) + this += highlightTypeDef(" => ") + printTypeOrBound(body) + + case ParamRef(lambda, idx) => + lambda match { + case MethodType(params, _, _) => this += params(idx) + case PolyType(params, _, _) => this += params(idx) + case TypeLambda(params, _, _) => this += params(idx) + } + + case RecursiveType(tpe) => + printType(tpe) + + case RecursiveThis(_) => + this += highlightTypeDef("this") + + case tpe: MethodType => + this += "(" + printList(tpe.paramNames.zip(tpe.paramTypes), ", ", + (x: (String, Type)) => (this += x._1 += ": ").printType(x._2)) + this += ")" + printType(tpe.resType) + + case tpe: PolyType => + this += "[" + printList(tpe.paramNames.zip(tpe.paramBounds), ", ", + (x: (String, TypeBounds)) => (this += x._1 += " ").printTypeOrBound(x._2)) + this += "]" + printType(tpe.resType) + + case tpe: TypeLambda => + this += "[" + printList(tpe.paramNames.zip(tpe.paramBounds), ", ", + (x: (String, TypeBounds)) => (this += x._1 += " ").printTypeOrBound(x._2)) + this += "] => " + printType(tpe.resType) + + case _ => + throw new MatchError(tpe.showExtractors) + } + + def printImportSelector(sel: ImportSelector): Buffer = sel match { + case SimpleSelector(Id(name)) => this += name + case OmitSelector(Id(name)) => this += name += " => _" + case RenameSelector(Id(name), Id(newName)) => this += name += " => " += newName + } + + def printDefinitionName(sym: Definition): Buffer = sym match { + case ValDef(name, _, _) => this += highlightValDef(name) + case DefDef(name, _, _, _, _) => this += highlightValDef(name) + case ClassDef(name, _, _, _, _, _) => this += highlightTypeDef(name.stripSuffix("$")) + case TypeDef(name, _) => this += highlightTypeDef(name) + case PackageDef(name, _) => this += highlightTypeDef(name) + } + + def printAnnotation(annot: Term)(using elideThis: Option[Symbol]): Buffer = { + val Annotation(ref, args) = annot + if (annot.symbol.maybeOwner == Symbol.requiredClass("scala.internal.quoted.showName")) this + else { + this += "@" + printTypeTree(ref) + if (args.isEmpty) + this + else + inParens(printTrees(args, ", ")) + } + } + + def printDefAnnotations(definition: Definition)(using elideThis: Option[Symbol]): Buffer = { + val annots = definition.symbol.annots.filter { + case Annotation(annot, _) => + val sym = annot.tpe.typeSymbol + sym != Symbol.requiredClass("scala.forceInline") && + sym.maybeOwner != Symbol.requiredPackage("scala.annotation.internal") + case x => throw new MatchError(x.showExtractors) + } + printAnnotations(annots) + if (annots.nonEmpty) this += " " + else this + } + + def printRefinement(tpe: Type)(using elideThis: Option[Symbol]): Buffer = { + def printMethodicType(tp: TypeOrBounds): Unit = tp match { + case tp @ MethodType(paramNames, params, res) => + inParens(printMethodicTypeParams(paramNames, params)) + printMethodicType(res) + case tp @ TypeLambda(paramNames, params, res) => + inSquare(printMethodicTypeParams(paramNames, params)) + printMethodicType(res) + case ByNameType(t) => + this += ": " + printType(t) + case tp: Type => + this += ": " + printType(tp) + } + def rec(tp: Type): Unit = tp match { + case Refinement(parent, name, info) => + rec(parent) + indented { + this += lineBreak() + info match { + case info: TypeBounds => + this += highlightKeyword("type ") += highlightTypeDef(name) + printBounds(info) + case ByNameType(_) | MethodType(_, _, _) | TypeLambda(_, _, _) => + this += highlightKeyword("def ") += highlightTypeDef(name) + printMethodicType(info) + case info: Type => + this += highlightKeyword("val ") += highlightValDef(name) + printMethodicType(info) + } + } + case tp => + printType(tp) + this += " {" + } + rec(tpe) + this += lineBreak() += "}" + } + + def printMethodicTypeParams(paramNames: List[String], params: List[TypeOrBounds])(using elideThis: Option[Symbol]): Unit = { + def printInfo(info: TypeOrBounds) = info match { + case info: TypeBounds => printBounds(info) + case info: Type => + this += ": " + printType(info) + } + def printSeparated(list: List[(String, TypeOrBounds)]): Unit = list match { + case Nil => + case (name, info) :: Nil => + this += name + printInfo(info) + case (name, info) :: xs => + this += name + printInfo(info) + this += ", " + printSeparated(xs) + } + printSeparated(paramNames.zip(params)) + } + + def printBoundsTree(bounds: TypeBoundsTree)(using elideThis: Option[Symbol]): Buffer = { + bounds.low match { + case Inferred() => + case low => + this += " >: " + printTypeTree(low) + } + bounds.hi match { + case Inferred() => this + case hi => + this += " <: " + printTypeTree(hi) + } + } + + def printBounds(bounds: TypeBounds)(using elideThis: Option[Symbol]): Buffer = { + this += " >: " + printType(bounds.low) + this += " <: " + printType(bounds.hi) + } + + def printProtectedOrPrivate(definition: Definition): Boolean = { + var prefixWasPrinted = false + def printWithin(within: Type) = within match { + case TypeRef(_, name) => this += name + case _ => printFullClassName(within) + } + if (definition.symbol.flags.is(Flags.Protected)) { + this += highlightKeyword("protected") + definition.symbol.protectedWithin match { + case Some(within) => + inSquare(printWithin(within)) + case _ => + } + prefixWasPrinted = true + } else { + definition.symbol.privateWithin match { + case Some(within) => + this += highlightKeyword("private") + inSquare(printWithin(within)) + prefixWasPrinted = true + case _ => + } + } + if (prefixWasPrinted) + this += " " + prefixWasPrinted + } + + def printFullClassName(tp: TypeOrBounds): Unit = { + def printClassPrefix(prefix: TypeOrBounds): Unit = prefix match { + case TypeRef(prefix2, name) => + printClassPrefix(prefix2) + this += name += "." + case _ => + } + val TypeRef(prefix, name) = tp + printClassPrefix(prefix) + this += name + } + + def +=(x: Boolean): this.type = { sb.append(x); this } + def +=(x: Byte): this.type = { sb.append(x); this } + def +=(x: Short): this.type = { sb.append(x); this } + def +=(x: Int): this.type = { sb.append(x); this } + def +=(x: Long): this.type = { sb.append(x); this } + def +=(x: Float): this.type = { sb.append(x); this } + def +=(x: Double): this.type = { sb.append(x); this } + def +=(x: Char): this.type = { sb.append(x); this } + def +=(x: String): this.type = { sb.append(x); this } + + private def escapedChar(ch: Char): String = (ch: @switch) match { + case '\b' => "\\b" + case '\t' => "\\t" + case '\n' => "\\n" + case '\f' => "\\f" + case '\r' => "\\r" + case '"' => "\\\"" + case '\'' => "\\\'" + case '\\' => "\\\\" + case _ => if (ch.isControl) "\\0" + Integer.toOctalString(ch) else String.valueOf(ch) + } + + private def escapedString(str: String): String = str flatMap escapedChar + } + + private[this] val names = collection.mutable.Map.empty[Symbol, String] + private[this] val namesIndex = collection.mutable.Map.empty[String, Int] + + private def splicedName(sym: Symbol)(using ctx: Context): Option[String] = { + sym.annots.find(_.symbol.owner == Symbol.requiredClass("scala.internal.quoted.showName")).flatMap { + case Apply(_, Literal(Constant(c: String)) :: Nil) => Some(c) + case Apply(_, Inlined(_, _, Literal(Constant(c: String))) :: Nil) => Some(c) + case annot => None + }.orElse { + if sym.owner.isClassDef then None + else names.get(sym).orElse { + val name0 = sym.name + val index = namesIndex.getOrElse(name0, 1) + namesIndex(name0) = index + 1 + val name = + if index == 1 then name0 + else s"`$name0${index.toString.toCharArray.map {x => (x - '0' + '₀').toChar}.mkString}`" + names(sym) = name + Some(name) + } + } + } + + private object SpecialOp { + def unapply(arg: Tree)(using ctx: Context): Option[(String, List[Term])] = arg match { + case arg @ Apply(fn, args) => + fn.tpe match { + case tpe @ TermRef(ThisType(TypeRef(_, name)), name2) if name == "" => + Some((name2, args)) + case _ => None + } + case _ => None + } + } + + private object Annotation { + def unapply(arg: Tree)(using ctx: Context): Option[(TypeTree, List[Term])] = arg match { + case New(annot) => Some((annot, Nil)) + case Apply(Select(New(annot), ""), args) => Some((annot, args)) + case Apply(TypeApply(Select(New(annot), ""), targs), args) => Some((annot, args)) + case _ => None + } + } + + // TODO Provide some of these in scala.tasty.Reflection.scala and implement them using checks on symbols for performance + private object Types { + + object Sequence { + def unapply(tpe: Type)(using ctx: Context): Option[Type] = tpe match { + case AppliedType(seq, (tp: Type) :: Nil) + if seq.typeSymbol == Symbol.requiredClass("scala.collection.Seq") || seq.typeSymbol == Symbol.requiredClass("scala.collection.immutable.Seq") => + Some(tp) + case _ => None + } + } + + object Repeated { + def unapply(tpe: Type)(using ctx: Context): Option[Type] = tpe match { + case AppliedType(rep, (tp: Type) :: Nil) if rep.typeSymbol == Symbol.requiredClass("scala.") => Some(tp) + case _ => None + } + } + + } + + object PackageObject { + def unapply(tree: Tree)(using ctx: Context): Option[Tree] = tree match { + case PackageClause(_, ValDef("package", _, _) :: body :: Nil) => Some(body) + case _ => None + } + } + +} diff --git a/runtime/src/main/scala-2/mdoc/internal/document/Compat.scala b/runtime/src/main/scala-2/mdoc/internal/document/Compat.scala new file mode 100644 index 000000000..a5cd5664b --- /dev/null +++ b/runtime/src/main/scala-2/mdoc/internal/document/Compat.scala @@ -0,0 +1,5 @@ +package mdoc.internal.document + +object Compat { + type TPrint[T] = pprint.TPrint[T] +} diff --git a/runtime/src/main/scala-2/mdoc/internal/document/Printing.scala b/runtime/src/main/scala-2/mdoc/internal/document/Printing.scala new file mode 100644 index 000000000..7097ccfac --- /dev/null +++ b/runtime/src/main/scala-2/mdoc/internal/document/Printing.scala @@ -0,0 +1,31 @@ +package mdoc.internal.document + +import pprint.TPrintColors +import pprint.PPrinter.BlackWhite +import pprint.PPrinter +import Compat.TPrint + +object Printing { + + def stringValue[T](value: T) = PPrinter.BlackWhite.apply(value) + def typeString[T](tprint: TPrint[T]) = tprint.render(TPrintColors.BlackWhite) + def print[T](value: T, out: StringBuilder, width: Int, height: Int) = { + BlackWhite + .tokenize(value, width, height) + .foreach(text => out.appendAll(text.getChars)) + } + + def printOneLine[T](value: T, out: StringBuilder, width: Int) = { + val chunk = BlackWhite + .tokenize(value, width) + .map(_.getChars) + .filterNot(_.iterator.forall(_.isWhitespace)) + .flatMap(_.iterator) + .filter { + case '\n' => false + case _ => true + } + out.appendAll(chunk) + + } +} diff --git a/runtime/src/main/scala-2/mdoc/internal/sourcecode/Compat.scala b/runtime/src/main/scala-2/mdoc/internal/sourcecode/Compat.scala new file mode 100644 index 000000000..5ecca0125 --- /dev/null +++ b/runtime/src/main/scala-2/mdoc/internal/sourcecode/Compat.scala @@ -0,0 +1,15 @@ +package mdoc.internal.sourcecode + +object Compat { + type Context = scala.reflect.macros.blackbox.Context + def enclosingOwner(c: Context) = c.internal.enclosingOwner + + def enclosingParamList(c: Context): List[List[c.Symbol]] = { + def nearestEnclosingMethod(owner: c.Symbol): c.Symbol = + if (owner.isMethod) owner + else if (owner.isClass) owner.asClass.primaryConstructor + else nearestEnclosingMethod(owner.owner) + + nearestEnclosingMethod(enclosingOwner(c)).asMethod.paramLists + } +} diff --git a/runtime/src/main/scala-2/mdoc/internal/sourcecode/Macros.scala b/runtime/src/main/scala-2/mdoc/internal/sourcecode/Macros.scala new file mode 100644 index 000000000..9f4571f05 --- /dev/null +++ b/runtime/src/main/scala-2/mdoc/internal/sourcecode/Macros.scala @@ -0,0 +1,31 @@ +package mdoc.internal.sourcecode + +import language.experimental.macros + +trait StatementMacro { + implicit def generate[T](v: T): SourceStatement[T] = macro Macros.text[T] + def apply[T](v: T): SourceStatement[T] = macro Macros.text[T] + +} + +object Macros { + + def text[T: c.WeakTypeTag](c: Compat.Context)(v: c.Expr[T]): c.Expr[SourceStatement[T]] = { + import c.universe._ + val fileContent = new String(v.tree.pos.source.content) + val start = v.tree.collect { + case treeVal => + treeVal.pos match { + case NoPosition ⇒ Int.MaxValue + case p ⇒ p.startOrPoint + } + }.min + val g = c.asInstanceOf[reflect.macros.runtime.Context].global + val parser = g.newUnitParser(fileContent.drop(start)) + parser.expr() + val end = parser.in.lastOffset + val txt = fileContent.slice(start, start + end) + val tree = q"""${c.prefix}(${v.tree}, $txt)""" + c.Expr[SourceStatement[T]](tree) + } +} diff --git a/runtime/src/main/scala-3/mdoc/internal/document/Compat.scala b/runtime/src/main/scala-3/mdoc/internal/document/Compat.scala new file mode 100644 index 000000000..2895d9e53 --- /dev/null +++ b/runtime/src/main/scala-3/mdoc/internal/document/Compat.scala @@ -0,0 +1,5 @@ +package mdoc.internal.document + +object Compat { + type TPrint[T] = mdoc.internal.pprint.TPrint[T] +} diff --git a/runtime/src/main/scala-3/mdoc/internal/document/Printing.scala b/runtime/src/main/scala-3/mdoc/internal/document/Printing.scala new file mode 100644 index 000000000..9a941ef98 --- /dev/null +++ b/runtime/src/main/scala-3/mdoc/internal/document/Printing.scala @@ -0,0 +1,16 @@ +package mdoc.internal.document + +import Compat.TPrint + +object Printing { + inline def stringValue[T](value: T) = value.toString + inline def typeString[T](tprint: TPrint[T]) = tprint.render + + inline def print[T](value: T, out : StringBuilder, width: Int, height: Int) = { + out.append(value.toString) + } + + inline def printOneLine[T](value: T, out : StringBuilder, width: Int) = { + out.append(value.toString.replace("\n","")) + } +} diff --git a/runtime/src/main/scala-3/mdoc/internal/pprint/TypePrinter.scala b/runtime/src/main/scala-3/mdoc/internal/pprint/TypePrinter.scala new file mode 100644 index 000000000..113fdf768 --- /dev/null +++ b/runtime/src/main/scala-3/mdoc/internal/pprint/TypePrinter.scala @@ -0,0 +1,24 @@ +package mdoc.internal.pprint + +import scala.language.implicitConversions +import scala.quoted._ +import scala.quoted.show.SyntaxHighlight + +trait TPrint[T]{ + def render: String +} + +object TPrint { + inline given default[T] as TPrint[T] = ${ TypePrinter.typeString[T] } +} + +object TypePrinter{ + + def typeString[T](using ctx: QuoteContext, tpe: Type[T]): Expr[TPrint[T]] = { + import ctx.tasty._ + + val typePrinter = new SourceTypePrinter(ctx.tasty)(SyntaxHighlight.plain) + + '{ new TPrint[T]{ def render: String = ${ Expr(typePrinter.showTypeOrBounds(tpe.unseal.tpe)) } } } + } +} diff --git a/runtime/src/main/scala-3/mdoc/internal/sourcecode/Macros.scala b/runtime/src/main/scala-3/mdoc/internal/sourcecode/Macros.scala new file mode 100644 index 000000000..2a0b2de65 --- /dev/null +++ b/runtime/src/main/scala-3/mdoc/internal/sourcecode/Macros.scala @@ -0,0 +1,19 @@ +package mdoc.internal.sourcecode + +import scala.language.implicitConversions +import scala.quoted._ +import scala.tasty.Reflection + +trait StatementMacro { + inline implicit def generate[T](v: => T): SourceStatement[T] = ${ Macros.text('v) } + inline def apply[T](v: => T): SourceStatement[T] = ${ Macros.text('v) } +} + +object Macros{ + + def text[T: Type](v: Expr[T])(using ctx: QuoteContext): Expr[SourceStatement[T]] = { + import ctx.tasty._ + val txt = v.unseal.pos.sourceCode + '{SourceStatement[T]($v, ${Expr(txt)})} + } +} \ No newline at end of file diff --git a/runtime/src/main/scala/mdoc/document/Binder.scala b/runtime/src/main/scala/mdoc/document/Binder.scala index 1853f33e4..031135eb0 100644 --- a/runtime/src/main/scala/mdoc/document/Binder.scala +++ b/runtime/src/main/scala/mdoc/document/Binder.scala @@ -1,20 +1,20 @@ package mdoc.document -import pprint.PPrinter -import pprint.TPrint -import pprint.TPrintColors -import sourcecode.Text +import mdoc.internal.sourcecode.SourceStatement +import mdoc.internal.document.Printing +import mdoc.internal.document.Compat.TPrint final class Binder[T](val value: T, val name: String, val tpe: TPrint[T], val pos: RangePosition) { override def toString: String = { - val valueString = PPrinter.BlackWhite.apply(value) - val tpeString = tpe.render(TPrintColors.BlackWhite) + val valueString = Printing.stringValue(value) s"""Binder($valueString, "$name", "$tpeString")""" } - def tpeString = tpe.render(TPrintColors.BlackWhite) + def tpeString = Printing.typeString(tpe) } object Binder { - def generate[A](e: Text[A], pos: RangePosition)(implicit tprint: TPrint[A]): Binder[A] = + def generate[A](e: SourceStatement[A], pos: RangePosition)(implicit + tprint: TPrint[A] + ): Binder[A] = new Binder(e.value, e.source, tprint, pos: RangePosition) } diff --git a/runtime/src/main/scala/mdoc/document/Document.scala b/runtime/src/main/scala/mdoc/document/Document.scala index 9825ea3bf..d4f457529 100644 --- a/runtime/src/main/scala/mdoc/document/Document.scala +++ b/runtime/src/main/scala/mdoc/document/Document.scala @@ -1,10 +1,5 @@ package mdoc.document -import pprint.PPrinter -import pprint.TPrint -import pprint.TPrintColors -import sourcecode.Text - final case class InstrumentedInput(filename: String, text: String) object InstrumentedInput { val empty = InstrumentedInput("", "") diff --git a/runtime/src/main/scala/mdoc/internal/document/DocumentBuilder.scala b/runtime/src/main/scala/mdoc/internal/document/DocumentBuilder.scala index a425f7052..78bbf7579 100644 --- a/runtime/src/main/scala/mdoc/internal/document/DocumentBuilder.scala +++ b/runtime/src/main/scala/mdoc/internal/document/DocumentBuilder.scala @@ -3,11 +3,11 @@ package mdoc.internal.document import java.io.ByteArrayOutputStream import java.io.PrintStream import mdoc.document._ -import pprint.TPrint +import mdoc.internal.document.Compat.TPrint import scala.collection.mutable.ArrayBuffer import scala.language.experimental.macros import scala.util.control.NonFatal -import sourcecode.Text +import mdoc.internal.sourcecode.SourceStatement trait DocumentBuilder { @@ -35,8 +35,14 @@ trait DocumentBuilder { pos } - def binder[A](e: Text[A], startLine: Int, startColumn: Int, endLine: Int, endColumn: Int)( - implicit tprint: TPrint[A] + def binder[A]( + e: SourceStatement[A], + startLine: Int, + startColumn: Int, + endLine: Int, + endColumn: Int + )(implicit + tprint: TPrint[A] ): A = { val pos = position(startLine, startColumn, endLine, endColumn) myBinders.append(Binder.generate(e, pos)) @@ -62,9 +68,9 @@ trait DocumentBuilder { def crash(startLine: Int, startColumn: Int, endLine: Int, endColumn: Int)( thunk: => Any - ): Unit = { + )(implicit tprint: TPrint[CrashResult]): Unit = { val pos = new RangePosition(startLine, startColumn, endLine, endColumn) - val result = + val result: CrashResult = try { thunk CrashResult.Success(pos) @@ -72,7 +78,7 @@ trait DocumentBuilder { case MdocNonFatal(e) => CrashResult.Crashed(e, pos) } - myBinders.append(Binder.generate(result, pos)) + myBinders.append(new Binder(result, "result", tprint, pos)) } def build(input: InstrumentedInput): Document = { diff --git a/runtime/src/main/scala/mdoc/internal/sourcecode/SourceContext.scala b/runtime/src/main/scala/mdoc/internal/sourcecode/SourceContext.scala new file mode 100644 index 000000000..b2d92404e --- /dev/null +++ b/runtime/src/main/scala/mdoc/internal/sourcecode/SourceContext.scala @@ -0,0 +1,4 @@ +package mdoc.internal.sourcecode + +case class SourceStatement[T](value: T, source: String) +object SourceStatement extends StatementMacro diff --git a/tests/tests/src/main/scala/tests/BaseSuite.scala b/tests/tests/src/main/scala/tests/BaseSuite.scala index 10527394a..733bcbe6b 100644 --- a/tests/tests/src/main/scala/tests/BaseSuite.scala +++ b/tests/tests/src/main/scala/tests/BaseSuite.scala @@ -18,11 +18,12 @@ class BaseSuite extends FunSuite { } object OnlyScala213 extends munit.Tag("OnlyScala213") object OnlyScala3 extends munit.Tag("OnlyScala3") + object SkipScala3 extends munit.Tag("SkipScala3") object SkipScala211 extends munit.Tag("SkipScala211") override def munitTestTransforms: List[TestTransform] = super.munitTestTransforms ++ List( new TestTransform( - OnlyScala213.value, + "ScalaVersions", { test => val binaryVersion = tests.BuildInfo.scalaBinaryVersion if (test.tags(OnlyScala213) && binaryVersion != "2.13") @@ -33,6 +34,12 @@ class BaseSuite extends FunSuite { .startsWith("3.")) ) test.tag(munit.Ignore) + else if ( + test + .tags(SkipScala3) && (binaryVersion.startsWith("0.") || binaryVersion + .startsWith("3.")) + ) + test.tag(munit.Ignore) else if (test.tags(SkipScala211) && binaryVersion == "2.11") test.tag(munit.Ignore) else test diff --git a/tests/tests/src/main/scala/tests/markdown/Compat.scala b/tests/tests/src/main/scala/tests/markdown/Compat.scala index 6e10bdde5..5d0b9cf20 100644 --- a/tests/tests/src/main/scala/tests/markdown/Compat.scala +++ b/tests/tests/src/main/scala/tests/markdown/Compat.scala @@ -20,8 +20,8 @@ object Compat { postProcess: Map[String, String => String] = Map.empty ): String = { val result = compat - .get(BuildInfo.scalaVersion) - .orElse(compat.get(BuildInfo.scalaBinaryVersion)) + .collect { case (key, value) if BuildInfo.scalaVersion.startsWith(key) => value } + .headOption .orElse(compat.get("all")) .getOrElse( BuildInfo.scalaBinaryVersion match { diff --git a/tests/worksheets/src/test/scala/tests/worksheets/WorksheetSuite.scala b/tests/worksheets/src/test/scala/tests/worksheets/WorksheetSuite.scala index c931ea737..1c0735c5c 100644 --- a/tests/worksheets/src/test/scala/tests/worksheets/WorksheetSuite.scala +++ b/tests/worksheets/src/test/scala/tests/worksheets/WorksheetSuite.scala @@ -28,7 +28,11 @@ class WorksheetSuite extends BaseSuite { .withClasspath( CompatClassloader .getURLs(this.getClass().getClassLoader()) - .collect { case url if url.toString.contains("dotty-library") => Paths.get(url.toURI()) } + .collect { + case url + if url.toString.contains("dotty-library") || url.toString.contains("scala-library") => + Paths.get(url.toURI()) + } .asJava ) @@ -83,7 +87,12 @@ class WorksheetSuite extends BaseSuite { | 11, | 12, |... - |""".stripMargin + |""".stripMargin, + compat = Map( + "0." -> """| // : Stream[Int] = Stre... + |res0: Stream[Int] = Stream(10, ) + |""".stripMargin + ) ) checkDecorations( @@ -99,6 +108,27 @@ class WorksheetSuite extends BaseSuite { |""".stripMargin ) + checkDecorations( + "list", + """ + |val list = List(1,2,3) + |list.tail + |""".stripMargin, + """| // : List[Int] = List(1... + |list: List[Int] = List(1, 2, 3) + | // : List[Int] = List(2,... + |res0: List[Int] = List(2, 3) + |""".stripMargin, + compat = Map( + "0.2" -> + """| // : List[Int] = List(1... + |list: List[Int] = List(1, 2, 3) + | // : List[Int @unchecked... + |res0: List[Int @uncheckedVariance] = List(2, 3) + |""".stripMargin + ) + ) + checkDecorations( "stdout+value", """ @@ -145,13 +175,7 @@ class WorksheetSuite extends BaseSuite { |""".stripMargin ) - // From 2.13 we get `name =` part - val definitionCompat = - """|case class User(name: String) - | // : User = User(name =... - |n: User = User(name = "Susan") - |""".stripMargin - + // In pprint for 2.13 we get `name =` part checkDecorations( "definition", """case class User(name: String) @@ -162,8 +186,14 @@ class WorksheetSuite extends BaseSuite { |n: User = User("Susan") |""".stripMargin, compat = Map( - "0.26" -> definitionCompat, - "2.13" -> definitionCompat + "0." -> """|case class User(name: String) + | // : User = User(Susan) + |n: User = User(Susan) + |""".stripMargin, + "2.13" -> """|case class User(name: String) + | // : User = User(name =... + |n: User = User(name = "Susan") + |""".stripMargin ) ) @@ -179,7 +209,7 @@ class WorksheetSuite extends BaseSuite { | ^^^^^^^^^^^ |""".stripMargin, compat = Map( - "0.26" -> + "0.2" -> """|type-error:2:21: error: Found: ("not found" : String) |Required: Int |val filename: Int = "not found" @@ -204,7 +234,7 @@ class WorksheetSuite extends BaseSuite { |^^^^^^^^^^^^^^^ |""".stripMargin, compat = Map( - "0.26" -> + "0.2" -> """|crash:4:1: error: java.lang.RuntimeException: boom | at repl.MdocSession$App.crash(crash.scala:8) | at repl.MdocSession$App.(crash.scala:16) @@ -226,12 +256,17 @@ class WorksheetSuite extends BaseSuite { """| | // : String = "foo" |x: String = "foo" - |""".stripMargin + |""".stripMargin, + compat = Map( + "0." -> + """| // : String = foo + |x: String = foo + |""".stripMargin + ) ) - // test doesn't actually work currently checkDecorations( - "fastparse".ignore, + "fastparse".tag(SkipScala3).tag(SkipScala211), """ |import $dep.`com.lihaoyi::fastparse:2.3.0` |import fastparse._, MultiLineWhitespace._ @@ -241,9 +276,17 @@ class WorksheetSuite extends BaseSuite { """|import $dep.`com.lihaoyi::fastparse:2.3.0` |import fastparse._, MultiLineWhitespace._ |def p[_:P] = P("a") - | // Success((), 1) + | // : Parsed[Unit] = Suc... |res0: Parsed[Unit] = Success((), 1) - |""".stripMargin + |""".stripMargin, + compat = Map( + "2.13" -> """|import $dep.`com.lihaoyi::fastparse:2.3.0` + |import fastparse._, MultiLineWhitespace._ + |def p[_:P] = P("a") + | // : Parsed[Unit] = Suc... + |res0: Parsed[Unit] = Success(value = (), index = 1) + |""".stripMargin + ) ) checkDecorations( @@ -260,12 +303,8 @@ class WorksheetSuite extends BaseSuite { """|case class Circle(x: Double, y: Double, radius: Double) |extension (c: Circle) | def circumference: Double = c.radius * math.Pi * 2 - | // : Circle = Circle(x ... - |circle: Circle = Circle( - | x = 0.0, - | y = 0.0, - | radius = 2.0 - |) + | // : Circle = Circle(0.... + |circle: Circle = Circle(0.0,0.0,2.0) | // : Double = 12.566370... |res0: Double = 12.566370614359172 |extension [T](xs: List[T]) @@ -277,20 +316,25 @@ class WorksheetSuite extends BaseSuite { checkDecorations( "dotty-imports".tag(OnlyScala3), - """|import $dep.`org.json4s:json4s-native_2.13:3.6.9` - |import org.json4s._ - |import org.json4s.native.JsonMethods._ - |parse("{ \"numbers\" : [1, 2, 3, 4] }") + """|import $dep.`com.lihaoyi:scalatags_2.13:0.9.1` + |import scalatags.Text.all._ + |val htmlFile = html( + | body( + | p("This is a big paragraph of text") + | ) + |) + |htmlFile.render |""".stripMargin, - """|import $dep.`org.json4s:json4s-native_2.13:3.6.9` - |import org.json4s._ - |import org.json4s.native.JsonMethods._ - | // : JValue = JObject(o... - |res0: JValue = JObject( - | obj = List( - | ( - | "numbers", - |... + """|import $dep.`com.lihaoyi:scalatags_2.13:0.9.1` + |import scalatags.Text.all._ + | // : TypedTag[String] =

This is a big paragraph of text

+ | // : String =

This is a big paragraph of text

|""".stripMargin )