Skip to content

Commit

Permalink
Add a multiline printer and showMultiline methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
sjrd committed Nov 21, 2023
1 parent 814580e commit 791897a
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 0 deletions.
35 changes: 35 additions & 0 deletions tasty-query/shared/src/main/scala/tastyquery/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,41 @@ private[tastyquery] object Printers:
out.write(str)
end Printer

/** A `Printer` that prints blocks with line separators and indentation. */
class MultilinePrinter(out: Writer) extends Printer(out):
private var spaces: String = " " // 40 spaces
private var currentIndent: Int = 0

private final val IndentWidth = 2

protected final def printNewLine(): Unit =
print("\n")
out.write(spaces, 0, currentIndent)

protected final def printNewLineAndIndent(): Unit =
currentIndent += IndentWidth
if spaces.length() < currentIndent then spaces = spaces + spaces
printNewLine()

protected final def printNewLineAndOutdent(): Unit =
if currentIndent < IndentWidth then throw IllegalStateException("Trying to outdent more than indent")
currentIndent -= IndentWidth
printNewLine()

override def printBlock[A](elems: List[A])(printElem: A => Unit): Unit =
if elems.isEmpty then print(" {}")
else
print(" {")
printNewLineAndIndent()
printElem(elems.head)
for elem <- elems.tail do
printNewLine()
printElem(elem)
printNewLineAndOutdent()
print("}")
end printBlock
end MultilinePrinter

private def isSyntacticNothing(tpe: Type): Boolean = tpe match
case tpe: TypeRef => tpe.name == tpnme.Nothing && isScalaPackageRef(tpe.prefix)
case _ => false
Expand Down
3 changes: 3 additions & 0 deletions tasty-query/shared/src/main/scala/tastyquery/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ object Trees {

final def showBasic: String =
Printers.withWriterToString(writer => new Printers.Printer(writer).printAnyTree(this))

final def showMultiline: String =
Printers.withWriterToString(writer => new Printers.MultilinePrinter(writer).printAnyTree(this))
}

sealed abstract class TopLevelTree(pos: SourcePosition) extends Tree(pos):
Expand Down
3 changes: 3 additions & 0 deletions tasty-query/shared/src/main/scala/tastyquery/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ object Types {
def showBasic: String =
Printers.withWriterToString(writer => new Printers.Printer(writer).print(this))

final def showMultiline: String =
Printers.withWriterToString(writer => new Printers.MultilinePrinter(writer).print(this))

def requireType: Type = this match
case self: Type => self
case self => throw InvalidProgramStructureException(s"Unexpected non-type $self")
Expand Down
88 changes: 88 additions & 0 deletions tasty-query/shared/src/test/scala/tastyquery/PrintersTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,92 @@ class PrintersTest extends UnrestrictedUnpicklingSuite:
)
testShowBasicMember(MatchTypeClass, typeName("MTWithBind"), "type MTWithBind[X] = X match { case List[t] => t }")
}

testWithContext("multiline tree printers") {
def testShowMultiline(tree: Tree, expected: String)(using munit.Location): Unit =
assert(clue(tree.showMultiline) == clue(expected))

def testShowMultilineMember(cls: ClassSymbol, memberName: Name, expected: String)(using munit.Location): Unit =
val member = memberName match
case memberName: UnsignedTermName => cls.findNonOverloadedDecl(memberName)
case _ => cls.findMember(memberName)
testShowMultiline(member.tree.get, expected)

val GenericClassWithNestedGenericClass = ctx.findTopLevelClass("simple_trees.GenericClassWithNestedGeneric")
testShowMultiline(
GenericClassWithNestedGenericClass.tree.get,
"""class GenericClassWithNestedGeneric[T] extends java.lang.Object() {
| def <init>[T](): scala.Unit
| class NestedGeneric[U] extends java.lang.Object() {
| def <init>[U](): scala.Unit
| }
|}""".stripMargin
)

val PatternMatchingOnCaseClassClass = ctx.findTopLevelClass("simple_trees.PatternMatchingOnCaseClass")
testShowMultilineMember(
PatternMatchingOnCaseClassClass,
termName("caseMatching"),
"""def caseMatching(c: AbstractForCaseClasses): Int = (c match {
| case FirstCase(x @ _): simple_trees.FirstCase => {
| (x: scala.Int)
| }
| case SecondCase(y @ _): simple_trees.SecondCase => {
| (y: scala.Int)
| }
| case _ => {
| 0
| }
|})""".stripMargin
)

val ClassWithSelfClass = ctx.findTopLevelClass("simple_trees.ClassWithSelf")
testShowMultiline(
ClassWithSelfClass.tree.get,
"""class ClassWithSelf extends java.lang.Object() with TraitWithSelf {
| self: simple_trees.ClassWithSelf =>
| def <init>(): scala.Unit
|}""".stripMargin
)

val HigherKindedClass = ctx.findTopLevelClass("simple_trees.HigherKinded")
testShowMultiline(
HigherKindedClass.tree.get,
"""trait HigherKinded[A <: ([_$1] =>> scala.Any)] extends java.lang.Object {
| def <init>[A[_$1]](): scala.Unit
| def m[T](x: A[T]): A[T]
| def f[B[_$2], T](x: B[T]): B[T]
|}""".stripMargin
)

val MatchTypeClass = ctx.findTopLevelClass("simple_trees.MatchType")
testShowMultilineMember(
MatchTypeClass,
typeName("MT"),
"""type MT[X] = X match {
| case Int => String
|}""".stripMargin
)
testShowMultilineMember(
MatchTypeClass,
typeName("MTWithBound"),
"""type MTWithBound[X] <: Product = X match {
| case Int => Some[Int]
|}""".stripMargin
)
testShowMultilineMember(
MatchTypeClass,
typeName("MTWithWildcard"),
"""type MTWithWildcard[X] = X match {
| case _ => Int
|}""".stripMargin
)
testShowMultilineMember(
MatchTypeClass,
typeName("MTWithBind"),
"""type MTWithBind[X] = X match {
| case List[t] => t
|}""".stripMargin
)
}
end PrintersTest

0 comments on commit 791897a

Please sign in to comment.