All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dotty.tools.scaladoc.tasty.InkuireSupport.scala Maven / Gradle / Ivy

The newest version!
package dotty.tools.scaladoc.tasty

import scala.jdk.CollectionConverters._
import dotty.tools.scaladoc._
import dotty.tools.scaladoc.{Signature => DSignature}
import dotty.tools.scaladoc.Inkuire
import dotty.tools.scaladoc.renderers.Resources

import scala.util.Random
import scala.quoted._
import scala.util.chaining._

import SymOps._
import NameNormalizer._
import SyntheticsSupport._

trait InkuireSupport(using DocContext) extends Resources:
  self: TastyParser =>
  import qctx.reflect._

  // Unused in InkuireSupport, required for Resources
  override def effectiveMembers: Map[DRI, Member] = Map.empty

  private given qctx.type = qctx

  def doInkuireStuff(classDef: ClassDef): Unit = {
    val classType: Inkuire.Type = classDef.asInkuire(Set.empty).asInstanceOf[Inkuire.Type]

    val variableNames: Set[String] = classType.params.map(_.typ)
      .flatMap(varName(_).toList).toSet

    val parents: Seq[Inkuire.Type] = classDef.parents.map(_.asInkuire(variableNames).asInstanceOf[Inkuire.Type])

    val isModule = classDef.symbol.flags.is(Flags.Module)

    if !isModule then Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(classType.itid.get, (classType, parents)))

    classDef.symbol.declaredTypes
      .filter(viableSymbol)
      .foreach {
        case typeSymbol: Symbol if typeSymbol.flags.is(Flags.Opaque) =>
          val typ = typeSymbol.tree.asInkuire(variableNames)
          if typ.isInstanceOf[Inkuire.Type] then {
            val t = typ.asInstanceOf[Inkuire.Type]
            Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(t.itid.get, (t, Seq.empty)))
          }
        case typeSymbol: Symbol if !typeSymbol.isClassDef =>
          val typeDef = typeSymbol.tree.asInstanceOf[TypeDef]
          val typ = typeSymbol.tree.asInkuire(variableNames)
          if typ.isInstanceOf[Inkuire.Type] then {
            val t = typ.asInstanceOf[Inkuire.Type]
            val rhsTypeLike = typeDef.rhs.asInkuire(variableNames)
            val withType = Inkuire.db.copy(
              types = Inkuire.db.types.updated(t.itid.get, (t, Seq.empty))
            )
            typeDef.rhs match {
              case TypeBoundsTree(_, r) if r.asInkuire(variableNames).isInstanceOf[Inkuire.Type] =>
                Inkuire.db = Inkuire.db.copy(
                  types = Inkuire.db.types.updated(t.itid.get, (t, Seq(r.asInkuire(variableNames).asInstanceOf[Inkuire.Type])))
                )
              case _ =>
                Inkuire.db = withType.copy(
                  types = Inkuire.db.types.updated(t.itid.get, (t, Seq.empty)),
                  typeAliases = Inkuire.db.typeAliases.updated(t.itid.get, rhsTypeLike)
                )
            }
          }
          if typeDef.rhs.symbol.flags.is(Flags.JavaDefined) then
            val typJava = typeDef.rhs.asInkuire(variableNames)
            if typJava.isInstanceOf[Inkuire.Type] then {
              val tJava = typJava.asInstanceOf[Inkuire.Type]
              Inkuire.db = Inkuire.db.copy(types = Inkuire.db.types.updated(tJava.itid.get, (tJava, Seq.empty)))
            }
        case _ =>
    }

    if classDef.symbol.isImplicitClass then // Implicit classes <- synthetic method with the same name
      classDef.symbol.maybeOwner.declarations
        .filter { methodSymbol =>
          methodSymbol.name == classDef.symbol.name && methodSymbol.flags.is(Flags.Implicit) && methodSymbol.flags.is(Flags.Method)
        }
        .foreach(handleImplicitConversion(_, variableNames))

    classDef.symbol.declaredMethods
      .filter(viableSymbol)
      .tap { _.foreach { // Loop for implicit conversions
        case implicitConversion: Symbol if implicitConversion.flags.is(Flags.Implicit) || implicitConversion.flags.is(Flags.Given) =>
          handleImplicitConversion(implicitConversion, variableNames)
        case _ =>
      }}
      .tap { _.foreach { // Loop for functions and vals
        case methodSymbol: Symbol =>
          val defdef = methodSymbol.tree.asInstanceOf[DefDef]
          val methodVars = defdef.paramss.flatMap(_.params).collect {
            case TypeDef(name, _) => name
          }
          val vars = variableNames ++ methodVars
          val (receiver, preArgs): (Option[Inkuire.TypeLike], Seq[Inkuire.TypeLike]) = Some(classType).filter(_ => !isModule) match {
            case None => (methodSymbol.extendedSymbol.flatMap(s => partialAsInkuire(vars).lift(s.tpt)), Seq.empty)
            case rcvr => (rcvr, methodSymbol.extendedSymbol.flatMap(s => partialAsInkuire(vars).lift(s.tpt)).toSeq)
          }
          val (name, ownerName) = nameAndOwnerName(classDef, methodSymbol)
          val sgn = Inkuire.ExternalSignature(
            signature = Inkuire.Signature(
              receiver = receiver,
              arguments = preArgs ++ methodSymbol.nonExtensionTermParamLists.collect {
                case tpc@TermParamClause(params) if !tpc.isImplicit && !tpc.isGiven => params //TODO [Inkuire] Implicit parameters
              }.flatten.map(_.tpt.asInkuire(vars)),
              result = defdef.returnTpt.asInkuire(vars),
              context = Inkuire.SignatureContext(
                vars = vars.toSet,
                constraints = Map.empty //TODO [Inkuire] Type bounds
              )
            ),
            name = name,
            packageName = ownerName,
            uri = methodSymbol.dri.externalLink.getOrElse(escapedAbsolutePathWithAnchor(methodSymbol.dri)),
            isLocationExternal = methodSymbol.dri.externalLink.isDefined,
            entryType = "def"
          )
          val curriedSgn = sgn.copy(signature = Inkuire.curry(sgn.signature))
          Inkuire.db = Inkuire.db.copy(functions = Inkuire.db.functions :+ curriedSgn)
    }}

    classDef.symbol.declaredFields
      .filter(viableSymbol)
      .foreach {
        case valSymbol: Symbol =>
          val valdef = valSymbol.tree.asInstanceOf[ValDef]
          val receiver: Option[Inkuire.TypeLike] =
            Some(classType)
              .filter(_ => !isModule)
          val (name, ownerName) = nameAndOwnerName(classDef, valSymbol)
          val sgn = Inkuire.ExternalSignature(
            signature = Inkuire.Signature(
              receiver = receiver,
              arguments = Seq.empty,
              result = valdef.tpt.asInkuire(variableNames),
              context = Inkuire.SignatureContext(
                vars = variableNames.toSet,
                constraints = Map.empty //TODO [Inkuire] Type bounds
              )
            ),
            name = name,
            packageName = ownerName,
            uri = valSymbol.dri.externalLink.getOrElse(escapedAbsolutePathWithAnchor(valSymbol.dri)),
            isLocationExternal = valSymbol.dri.externalLink.isDefined,
            entryType = "val"
          )
          val curriedSgn = sgn.copy(signature = Inkuire.curry(sgn.signature))
          Inkuire.db = Inkuire.db.copy(functions = Inkuire.db.functions :+ curriedSgn)
      }
  }

  private def handleImplicitConversion(implicitConversion: Symbol, variableNames: Set[String]) = {
    val defdef = implicitConversion.tree.asInstanceOf[DefDef]
    val methodVars = defdef.paramss.flatMap(_.params).collect {
      case TypeDef(name, _) => name
    }
    val vars = variableNames ++ methodVars
    val to = defdef.returnTpt.asInkuire(vars)
    val from = defdef.paramss.flatMap(_.params).collectFirst {
      case v: ValDef => v.tpt.asInkuire(vars)
    }
    (from, to) match
      case (from, to: Inkuire.Type) => Inkuire.implicitConversions = Inkuire.implicitConversions :+ (from -> to)
      case _ =>
  }

  private def nameAndOwnerName(classDef: ClassDef, symbol: Symbol): (String, String) =
    if classDef.symbol.flags.is(Flags.Module) || Seq("apply", "unapply").contains(symbol.name) then
      symbol.maybeOwner.normalizedName + "." + symbol.name ->
        ownerNameChain(classDef.symbol.maybeOwner).mkString(".")
    else
      symbol.name ->
        ownerNameChain(classDef.symbol).mkString(".")

  private def ownerNameChain(sym: Symbol): List[String] =
    if sym.isNoSymbol then List.empty
    else if sym == defn.EmptyPackageClass then List.empty
    else if sym == defn.RootPackage then List.empty
    else if sym == defn.RootClass then List.empty
    else if sym.normalizedName.contains("$package") then ownerNameChain(sym.owner)
    else ownerNameChain(sym.owner) :+ sym.normalizedName

  private def viableSymbol(s: Symbol): Boolean =
    !s.flags.is(Flags.Private) &&
      !s.flags.is(Flags.Protected) &&
      !s.flags.is(Flags.Override) &&
      !s.flags.is(Flags.Synthetic)

  private def varName(t: Inkuire.TypeLike): Option[String] = t match {
    case tpe: Inkuire.Type      => Some(tpe.name.name)
    case tl: Inkuire.TypeLambda => varName(tl.result)
    case _                      => None
  }

  private def paramsForClass(classDef: ClassDef, vars: Set[String]): Seq[Inkuire.Variance] =
    classDef.getTypeParams.map(mkTypeArgumentInkuire)

  given TreeSyntaxInkuire: AnyRef with
    extension (tpeTree: Tree)
      def asInkuire(vars: Set[String]): Inkuire.TypeLike =
        partialAsInkuire(vars)(tpeTree)

  private def partialAsInkuire(vars: Set[String]): PartialFunction[Tree, Inkuire.TypeLike] = {
    case tpeTree: TypeBoundsTree => inner(tpeTree.tpe, vars)
    case tpeTree: Applied =>
      inner(tpeTree.tpe, vars)
    case tpeTree: TypeTree =>
      inner(tpeTree.tpe, vars)
    case term:  Term => inner(term.tpe, vars)
    case classDef: ClassDef => mkTypeFromClassDef(classDef, vars)
    case typeDef: TypeDef => mkTypeDef(typeDef)
  }

  private def mkTypeDef(typeDef: TypeDef): Inkuire.Type = typeDef.rhs match {
    case LambdaTypeTree(paramsDefs, _) =>
      val name = typeDef.symbol.normalizedName
      val normalizedName = if name.matches("_\\$\\d*") then "_" else name
      val params = paramsDefs.map(_.name).map(Inkuire.TypeLambda.argument)
      Inkuire.Type(
        name = Inkuire.TypeName(normalizedName),
        itid = typeDef.symbol.itid,
        params = params.map(Inkuire.Invariance(_))
      )
    case _ =>
      Inkuire.Type(
        name = Inkuire.TypeName(typeDef.name),
        itid = typeDef.symbol.itid
      )
  }

  private def mkTypeFromClassDef(classDef: ClassDef, vars: Set[String]): Inkuire.Type = {
    Inkuire.Type(
      name = Inkuire.TypeName(classDef.name),
      itid = classDef.symbol.itid,
      params = paramsForClass(classDef, vars)
    )
  }

  given SymbolSyntaxInkuire: AnyRef with
    extension (symbol: Symbol)
      def itid(using dctx: DocContext): Option[Inkuire.ITID] = Some(Inkuire.ITID(symbol.dri.symbolUUID, isParsed = false))

  given TypeSyntaxInkuire: AnyRef with
    extension (tpe: TypeRepr)
      def asInkuire(vars: Set[String]): Inkuire.TypeLike = inner(tpe, vars)

  private def genSyntheticTypeArgs(n: Int, resSymbol: Symbol) =
    1.to(n).map { i =>
      val uuid = s"synthetic-arg$i${resSymbol.hashCode}"
      val name = s"X$i"
      Inkuire.Type(
        name = Inkuire.TypeName(name),
        itid = Some(Inkuire.ITID(uuid, isParsed = false)),
        isVariable = true
      )
    }

  private def mkTypeArgumentInkuire(argument: TypeDef): Inkuire.Variance =
    //TODO [Inkuire] Type bounds (other than just HKTs)
    val name = argument.symbol.normalizedName
    val normalizedName = if name.matches("_\\$\\d*") then "_" else name
    val params = genSyntheticTypeArgs(typeVariableDeclarationParamsNo(argument), argument.symbol)
    val res = Inkuire.Type(
      name = Inkuire.TypeName(normalizedName),
      itid = argument.symbol.itid,
      isVariable = true,
      params = params.map(Inkuire.Invariance(_))
    )
    val t = params.toList match
      case Nil => res
      case _ => Inkuire.TypeLambda(params, res)
    if argument.symbol.flags.is(Flags.Covariant) then Inkuire.Covariance(t)
    else if argument.symbol.flags.is(Flags.Contravariant) then Inkuire.Contravariance(t)
    else Inkuire.Invariance(t)

  private def typeVariableDeclarationParamsNo(argument: TypeDef): Int =
    argument.rhs match
      case t: TypeTree => t.tpe match
        case TypeBounds(_, TypeLambda(names, _, _)) => names.size
        case _ => 0
      case _ => 0

  private def isRepeatedAnnotation(term: Term) =
    term.tpe match
      case t: TypeRef => t.name == "Repeated" && t.qualifier.match
        case ThisType(tref: TypeRef) if tref.name == "internal" => true
        case _ => false
      case _ => false

  private def isRepeated(typeRepr: TypeRepr) =
    typeRepr match
      case t: TypeRef => t.name == "" && t.qualifier.match
        case ThisType(tref: TypeRef) if tref.name == "scala" => true
        case _ => false
      case _ => false

  private def inner(tp: TypeRepr, vars: Set[String]): Inkuire.TypeLike =
    tp match
      case OrType(left, right) => Inkuire.OrType(inner(left, vars), inner(right, vars))
      case AndType(left, right) => Inkuire.AndType(inner(left, vars), inner(right, vars))
      case ByNameType(tpe) => inner(tpe, vars)
      case ConstantType(constant) =>
        Inkuire.Type(
          name = Inkuire.TypeName(constant.toString),
          params = Seq.empty,
          itid = Some(Inkuire.ITID(constant.toString, isParsed = false))
        )
      case ThisType(tpe) => inner(tpe, vars)
      case AnnotatedType(AppliedType(_, Seq(tpe)), annotation) if isRepeatedAnnotation(annotation) =>
        inner(tpe, vars) //TODO [Inkuire] Repeated types
      case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) =>
        inner(tpe, vars) //TODO [Inkuire] Repeated types
      case AnnotatedType(tpe, _) =>
        inner(tpe, vars)
      case tl @ TypeLambda(paramNames, _, resType) =>
        Inkuire.TypeLambda(paramNames.map(Inkuire.TypeLambda.argument), inner(resType, vars)) //TODO [Inkuire] Type bounds
      case pt @ PolyType(paramNames, _, resType) =>
        Inkuire.TypeLambda(paramNames.map(Inkuire.TypeLambda.argument), inner(resType, vars)) //TODO [Inkuire] Type bounds
      case r: Refinement =>
        inner(r.info, vars) //TODO [Inkuire] Refinements
      case t @ AppliedType(tpe, typeList) =>
        import dotty.tools.dotc.util.Chars._
        if t.isFunctionType then
          val name = s"Function${typeList.size-1}"
          Inkuire.Type(
            name = Inkuire.TypeName(name),
            params = typeList.init.map(p => Inkuire.Contravariance(inner(p, vars))) :+ Inkuire.Covariance(inner(typeList.last, vars)),
            itid = Some(Inkuire.ITID(s"${name}scala.${name}//[]", isParsed = false))
          )
        else if t.isTupleN then
          val name = s"Tuple${typeList.size}"
          Inkuire.Type(
            name = Inkuire.TypeName(name),
            params = typeList.map(p => Inkuire.Covariance(inner(p, vars))),
            itid = Some(Inkuire.ITID(s"${name}scala.${name}//[]", isParsed = false))
          )
        else
          inner(tpe, vars).asInstanceOf[Inkuire.Type].copy(
            params = typeList.map(p => Inkuire.Invariance(inner(p, vars)))
          )
      case tp: TypeRef =>
        Inkuire.Type(
          name = Inkuire.TypeName(tp.name),
          itid = tp.typeSymbol.itid,
          params = Seq.empty,
          isVariable = vars.contains(tp.name)
        )
      case tr @ TermRef(qual, typeName) =>
        inner(qual, vars)
      case tb@TypeBounds(low, hi) =>
        if low.typeSymbol != defn.NothingClass || hi.typeSymbol == defn.AnyClass then
          inner(low, vars) //TODO [Inkuire] Type bounds
        else
          inner(hi, vars)
      case NoPrefix() =>
        Inkuire.Type.unresolved //TODO [Inkuire] <- should be handled by Singleton case, but didn't work
      case MatchType(bond, sc, cases) =>
        inner(sc, vars)
      case ParamRef(binder: LambdaType, i) =>
        Inkuire.TypeLambda.argument(binder.paramNames(i))
      case RecursiveType(tp) =>
        inner(tp, vars)
      case m@MethodType(_, typeList, resType) =>
        val name = s"Function${typeList.size-1}"
        Inkuire.Type(
          name = Inkuire.TypeName(name),
          params = typeList.map(p => Inkuire.Contravariance(inner(p, vars))) :+ Inkuire.Covariance(inner(resType, vars)),
          itid = Some(Inkuire.ITID(s"${name}scala.${name}//[]", isParsed = false))
        )




© 2015 - 2024 Weber Informatics LLC | Privacy Policy