in scala/tasty-reader/src/TreePrinter.scala [548:743]
private def delimiterAfter(prefix: Node): String =
if (prefix.isTypeTree || prefix.is(APPLIEDtype, TYPEREF, TYPEREFsymbol, TYPEREFdirect)) "#" else "."
private def textOfType(node: Node, parens: Int = 0)(using parent: Option[Node] = None): String = {
val withDotTypeSuffix =
parent.forall(_.is(SINGLETONtpt, APPLIEDtype, ANDtype, ORtype)) && node.is(TERMREF, TERMREFsymbol, TERMREFdirect, SELECT) ||
parent.isEmpty && node.is(THIS)
if (node.isSharedType) {
val fromCache = sharedTypes.get(node.addr)
fromCache match {
case Some(cached) =>
val res =
if (withDotTypeSuffix) cached + ".type"
else cached
return res
case None =>
}
}
// Extract method?
given Option[Node] = Some(node)
val text = node match { // Proper settings?
case Node3(IDENTtpt, _, Seq(tail)) => textOfType(tail)
case Node3(SINGLETONtpt, _, Seq(tail)) =>
val literal = textOfConstant(tail)
if (literal.nonEmpty) literal
else {
val tailText = textOfType(tail)
tailText + (if (tail.is(THIS, QUALTHIS)) ".type" else "")
}
case Node3(TYPEREF, Seq(name), Seq(prefix)) =>
val prefixText = textOfType(prefix)
val delimiter = if (prefixText.endsWith("$")) "." else delimiterAfter(prefix) // Foo[ModuleClass] name
val s = withNonEmptyPrefixWith(delimiter, prefixText.stripSuffix("$"), name)
if (s == "_root_.`<special-ops>`.`<FromJavaObject>`") "_root_.scala.AnyRef" else s
case Node3(TERMREF, Seq(name), Seq(prefix)) =>
// Why there's "package" in some cases?
val prefixText = textOfType(prefix)
if (name == ScalaBytecodeConstants.PackageObjectClassName ||
name.endsWith(ScalaBytecodeConstants.TopLevelDefinitionsClassNameSuffix))
prefixText
else {
val prefixWithName = withNonEmptyPrefixWith(".", prefixText, name)
// Why there is sometimes no SINGLETONtpt? (add RHS?)
val typeSuffix = if (withDotTypeSuffix) ".type" else ""
prefixWithName + typeSuffix
}
case Node3(THIS, _, Seq(tail)) =>
val qualifier = textOfType(tail)
if (qualifier.endsWith(ScalaBytecodeConstants.PackageObjectSingletonClassName)) {
val i = qualifier.lastIndexOf('.')
qualifier.substring(0, if (i == -1) qualifier.length - 8 else i)
}
else if (qualifier.endsWith("$"))
qualifier.substring(0, qualifier.length - 1) // What is the semantics of "this" when referring to external module classes?
else if (qualifier == "_root_.`<empty>`")
""
else
val typeSuffix = if (withDotTypeSuffix) ".type" else ""
qualifier.split('.').last + ".this" + typeSuffix
case Node3(QUALTHIS, _, Seq(tail)) =>
val qualifier = textOfType(tail)
qualifier.split('.').last + ".this" // Simplify Foo.this in Foo?
case Node3(TYPEREFsymbol | TYPEREFdirect | TERMREFsymbol | TERMREFdirect, _, tail) =>
val prefix = if (node.refTag.contains(TYPEPARAM)) "" else tail.headOption.map(textOfType(_)).getOrElse("")
val name = {
val s = node.refName.getOrElse("")
val isSynthetic = node.refTag.contains(TYPEPARAM) && s.startsWith("_$")
if (isSynthetic) {
compilerOptions = compilerOptions.copy(kindProjector = true)
}
if (isSynthetic) "*" else s
}
if (name == ScalaBytecodeConstants.PackageObjectClassName || name.endsWith(ScalaBytecodeConstants.TopLevelDefinitionsClassNameSuffix))
prefix
else {
// Rely on name kind?
val part1 = withNonEmptyPrefixWith(tail.headOption.map(delimiterAfter).getOrElse(""), prefix, name)
val part2 = if (withDotTypeSuffix) ".type" else ""
part1 + part2
}
case Node3(SELECTtpt | SELECT, Seq(name), Seq(tail)) =>
val selector = if (node.tag == SELECTtpt && node.children.headOption.exists(it => isTypeTreeTag(it.tag))) "#" else "."
val qualifier = textOfType(tail)
val qualifierInParens = if (selector == "#" && tail.is(REFINEDtpt)) "(" + qualifier + ")" else qualifier
val res =
if (qualifier.nonEmpty) qualifierInParens + selector + id(name)
else id(name)
if (withDotTypeSuffix) res + ".type"
else res
case Node2(TERMREFpkg | TYPEREFpkg, Seq(name)) => if (name == "_root_") name else "_root_." + name.split('.').map(id(_)).mkString(".")
case Node3(APPLIEDtpt | APPLIEDtype, _, Seq(constructor, arguments*)) =>
val base = textOfType(constructor)
val simpleBase = if (infixTypes) simple0(base) else base
val isInfix = infixTypes && simpleBase.forall(!_.isLetterOrDigit) && arguments.length == 2
val isWith = legacySyntax && base == "_root_.scala.&"
if (isInfix || isWith) {
val components = {
val cs = arguments.map(it => simple(textOfType(it, parens = if (isWith) 0 else 1)))
if (base == "_root_.scala.&" || base == "_root_.scala.|") cs.distinct else cs
}
val s = components.mkString(" " + (if (isWith) "with" else simpleBase) + " ")
if (parens > 0) "(" + s + ")" else s
} else if (base == "_root_.scala.`<repeated>`") {
textOfType(arguments.head, parens = 1) + "*" // Why repeated parameters in aliases are encoded differently?
} else if (base.startsWith("_root_.scala.Tuple") && base != "_root_.scala.Tuple1" && !base.substring(18).contains(".")) { // Use regex?
val s = arguments.map(it => simple(textOfType(it))).mkString("(", ", ", ")")
if (parens > 1) "(" + s + ")" else s
} else if (base.startsWith("_root_.scala.Function") || base.startsWith("_root_.scala.ImpureFunction") || base.startsWith("_root_.scala.ContextFunction") || base.startsWith("_root_.scala.ImpureContextFunction")) {
val arrow = if (base.startsWith("_root_.scala.Function") || base.startsWith("_root_.scala.ImpureFunction")) " => " else " ?=> "
val s = (if (arguments.length == 2) simple(textOfType(arguments.head, parens = 2)) else arguments.init.map(it => simple(textOfType(it))).mkString("(", ", ", ")")) + arrow + simple(textOfType(arguments.last))
if (parens > 0) "(" + s + ")" else s
} else {
simpleBase + "[" + arguments.map(it => simple(textOfType(it))).mkString(", ") + "]"
}
case Node3(ANDtype | ORtype, _, Seq(left, right)) =>
val l = simple(textOfType(left))
val r = simple(textOfType(right))
if (l != r) {
if (infixTypes) {
val s = l + (if (node.is(ANDtype)) " & " else " | ") + r
if (parens > 0) "(" + s + ")" else s
} else {
"_root_.scala." + (if (node.is(ANDtype)) "&" else "|") + "[" + l + ", " + r + "]"
}
} else {
l
}
case Node3(ANNOTATEDtpt | ANNOTATEDtype, _, Seq(tpe, annotation)) =>
annotation match {
case Node3(APPLY, _, Seq(Node3(SELECTin, _, Seq(Node3(NEW, _, Seq(tpe0, _*)), _*)), _*)) =>
val s = textOfType(tpe0)
if (s == "_root_.scala.annotation.internal.Repeated") textOfType(tpe.children(1), parens = 1) + "*"
else if (s != "_root_.scala.annotation.internal.InlineParam") textOfType(tpe) // SCL-21207
else textOfType(tpe) + " " + "@" + simple(s) + {
val args = annotation.children.map(textOfConstantOrArray).filter(_.nonEmpty).mkString(", ")
if (args.nonEmpty) "(" + args + ")" else ""
}
case _ => textOfType(tpe)
}
case Node3(BYNAMEtpt, _, Seq(tpe)) =>
val s = "=> " + simple(textOfType(tpe))
if (parens > 1) "(" + s + ")" else s
case Node3(MATCHtpt, _, children) =>
val (tpe, cases) = children match {
case tpe :: (cases @ Seq(Node1(CASEDEF), _*)) => (tpe, cases)
case _ :: tpe :: (cases @ Seq(Node1(CASEDEF), _*)) => (tpe, cases)
}
val cs = cases.map {
case Node3(CASEDEF, _, Seq(t1, t2)) => "case " + simple(textOfType(t1)) + " => " + simple(textOfType(t2))
}
simple(textOfType(tpe)) + " match { " + cs.mkString(" ") + " }"
case Node1(BIND) => if (node.name.startsWith("_$")) "_" else id(node.name)
case Node1(TYPEBOUNDStpt | TYPEBOUNDS) =>
val sb1 = new StringBuilder() // Reuse?
boundsIn(sb1, node)
(if (legacySyntax) "_" else "?") + sb1.toString
case Node3(LAMBDAtpt, _, children) =>
val sb1 = new StringBuilder() // Reuse?
parametersIn(sb1, node, withSynthetic = false)
if (sb1.nonEmpty) {
sb1 ++= " =>> "
}
sb1 ++= children.lastOption.map(textOfType(_)).getOrElse("") // Check tree?
sb1.toString
case Node3(TYPELAMBDAtype, _, Seq(Node3(APPLIEDtype, _, Seq(tail, _*)), _*)) => textOfType(tail)
case Node3(REFINEDtpt, _, Seq(tr @ Node1(TYPEREF), Node3(DEFDEF, Seq(name), children), _*)) if textOfType(tr) == "_root_.scala.PolyFunction" && name == "apply" => // Check tree?
val (typeParams, tail1) = children.span(_.is(TYPEPARAM))
val (valueParams, tails2) = tail1.span(_.is(PARAM))
val s = typeParams.map(tp => id(tp.name)).mkString("[", ", ", "]") + " => " + {
val params = valueParams.flatMap(_.children.headOption.map(tpe => simple(textOfType(tpe)))).mkString(", ")
if (valueParams.length == 1) params else "(" + params + ")"
} + " => " + tails2.headOption.map(tpe => simple(textOfType(tpe))).getOrElse("")
if (parens > 0) "(" + s + ")" else s
case Node3(REFINEDtpt, _, Seq(tpe, members*)) =>
val prefix = textOfType(tpe)
(if (prefix == "_root_.scala.AnyRef" || prefix == "_root_.java.lang.Object") "" else simple(prefix) + " ") + "{ " + members.map(it => { val sb = new StringBuilder(); textOfMember(sb, "", it); sb.toString }).mkString("; ") + " }" // Use sb directly?
case _ => // Exhaustive match?
textOfConstant(node)
}
sharedTypes.put(node.addr, text.stripSuffix(".type"))
text
}