private def delimiterAfter()

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
  }