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

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

There is a newer version: 3.6.3-RC1-bin-20241114-6a7d5d3-NIGHTLY
Show 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 scala.quoted._

import SymOps._
import NameNormalizer._
import SyntheticsSupport._
import dotty.tools.dotc.core.NameKinds

// Please use this only for things defined in the api.scala file
import dotty.tools.{scaladoc => api}

trait ClassLikeSupport:
  self: TastyParser =>
  import qctx.reflect._

  private given qctx.type = qctx

  private def bareClasslikeKind(using Quotes)(symbol: reflect.Symbol): Kind =
    import reflect._
    if symbol.flags.is(Flags.Module) then Kind.Object
    else if symbol.flags.is(Flags.Trait) then  Kind.Trait(Nil, Nil)
    else if symbol.flags.is(Flags.Enum) then Kind.Enum(Nil, Nil)
    else if symbol.flags.is(Flags.Enum) && symbol.flags.is(Flags.Case) then Kind.EnumCase(Kind.Object)
    else Kind.Class(Nil, Nil)

  private def kindForClasslike(classDef: ClassDef): Kind =
    def typeArgs = classDef.getTypeParams.map(mkTypeArgument(_, classDef))

    def parameterModifier(parameter: Symbol): String =
      val fieldSymbol = classDef.symbol.declaredField(parameter.normalizedName)
      def isVal = fieldSymbol.flags.is(Flags.ParamAccessor) &&
        !classDef.symbol.flags.is(Flags.Case) &&
        !fieldSymbol.flags.is(Flags.Private)

      if fieldSymbol.flags.is(Flags.Mutable) then "var "
      else if isVal then "val "
      else ""

    def args = if constructorWithoutParamLists(classDef) then Nil else
      val constr =
        Some(classDef.constructor.symbol)
          .filter(s => s.exists && !s.isHiddenByVisibility)
          .map( _.tree.asInstanceOf[DefDef])

      constr.fold(Nil)(_.termParamss.map(pList =>
        api.TermParameterList(
          pList.params.map(p => mkParameter(p, classDef, parameterModifier)),
          paramListModifier(pList.params),
        )
      ))

    if classDef.symbol.flags.is(Flags.Module) then Kind.Object
    else if classDef.symbol.flags.is(Flags.Trait) then
      Kind.Trait(typeArgs, args)
    else if classDef.symbol.flags.is(Flags.Enum) && classDef.symbol.flags.is(Flags.Case) then Kind.EnumCase(Kind.Class(typeArgs, args))
    else if classDef.symbol.flags.is(Flags.Enum) then Kind.Enum(typeArgs, args)
    else Kind.Class(typeArgs, args)

  def mkClass(classDef: ClassDef)(
    dri: DRI = classDef.symbol.dri,
    name: String = classDef.symbol.normalizedName,
    signatureOnly: Boolean = false,
    modifiers: Seq[Modifier] = classDef.symbol.getExtraModifiers(),
  ): Member =
    def unpackTreeToClassDef(tree: Tree): ClassDef =
      def unpackApply(a: Apply) =
        a.symbol.owner.tree match
          case tree: ClassDef => tree

      tree match
        case tree: ClassDef => tree
        case TypeDef(_, tbt: TypeBoundsTree) => unpackTreeToClassDef(tbt.tpe.typeSymbol.tree)
        case TypeDef(_, tt: TypeTree) => unpackTreeToClassDef(tt.tpe.typeSymbol.tree)
        case c: Apply => unpackApply(c)
        case Block(_, c: Apply) => unpackApply(c)
        case tt: TypeTree => unpackTreeToClassDef(tt.tpe.typeSymbol.tree)

    def signatureWithName(s: dotty.tools.scaladoc.Signature): dotty.tools.scaladoc.Signature =
      s match
          case dotty.tools.scaladoc.Type(n, Some(dri)) :: tail => dotty.tools.scaladoc.Name(n, dri) :: tail
          case other => other

    def getSupertypesGraph(link: LinkToType, to: Seq[Tree]): Seq[(LinkToType, LinkToType)] =
      to.flatMap { case tree =>
        val symbol = if tree.symbol.isClassConstructor then tree.symbol.owner else tree.symbol
        val signature = signatureWithName(tree.asSignature(classDef))
        val superLink = LinkToType(signature, symbol.dri, bareClasslikeKind(symbol))
        val nextTo = unpackTreeToClassDef(tree).parents
        if symbol.isHiddenByVisibility then getSupertypesGraph(link, nextTo)
        else Seq(link -> superLink) ++ getSupertypesGraph(superLink, nextTo)
      }

    val supertypes = getSupertypes(using qctx)(classDef)
      .filterNot((s, t) => s.isHiddenByVisibility)
      .map {
        case (symbol, tpe) =>
          val signature = signatureWithName(tpe.asSignature(classDef))
          LinkToType(signature, symbol.dri, bareClasslikeKind(symbol))
      }
    val selfType = classDef.self.map { (valdef: ValDef) =>
      val symbol = valdef.symbol
      val tpe = valdef.tpt.tpe
      val signature = signatureWithName(tpe.asSignature(classDef))
      LinkToType(signature, symbol.dri, Kind.Type(false, false, Seq.empty))
    }
    val selfSignature: DSignature = signatureWithName(typeForClass(classDef).asSignature(classDef))

    val graph = HierarchyGraph.withEdges(
      getSupertypesGraph(LinkToType(selfSignature, classDef.symbol.dri, bareClasslikeKind(classDef.symbol)), unpackTreeToClassDef(classDef).parents)
    )

    val kind = if intrinsicClassDefs.contains(classDef.symbol) then Kind.Class(Nil, Nil) else kindForClasslike(classDef)

    val baseMember = mkMember(classDef.symbol, kind, selfSignature)(
      modifiers = modifiers,
      graph = graph,
      deprecated = classDef.symbol.isDeprecated(),
      experimental = classDef.symbol.isExperimental()
    ).copy(
      directParents = classDef.getParentsAsLinkToTypes,
      parents = supertypes,
    )

    if summon[DocContext].args.generateInkuire then doInkuireStuff(classDef)

    if signatureOnly then baseMember else baseMember.copy(
        members = classDef.extractPatchedMembers.sortBy(m => (m.name, m.kind.name)),
        selfType = selfType,
        companion = classDef.getCompanion
    )

  private val conversionSymbol = Symbol.requiredClass("scala.Conversion")

  def extractImplicitConversion(tpe: TypeRepr): Option[ImplicitConversion] =
      if tpe.derivesFrom(conversionSymbol) then tpe.baseType(conversionSymbol) match
        case AppliedType(tpe, List(from: TypeRepr, to: TypeRepr)) =>
          Some(ImplicitConversion(from.typeSymbol.dri, to.typeSymbol.dri))
        case _ =>
          None
      else None

  private def isDocumentableExtension(s: Symbol) =
    !s.isHiddenByVisibility && !s.isSyntheticFunc && s.isExtensionMethod

  private def parseMember(c: ClassDef)(s: Tree): Option[Member] = processTreeOpt(s) { s match
      case dd: DefDef if isDocumentableExtension(dd.symbol) =>
        dd.symbol.extendedSymbol.map { extSym =>
          val memberInfo = unwrapMemberInfo(c, dd.symbol)
          val typeParams = dd.symbol.extendedTypeParams.map(mkTypeArgument(_, c, memberInfo.genericTypes))
          val termParams = dd.symbol.extendedTermParamLists.zipWithIndex.flatMap { case (termParamList, index) =>
            memberInfo.termParamLists(index) match
              case MemberInfo.EvidenceOnlyParameterList => None
              case MemberInfo.RegularParameterList(info) =>
                Some(api.TermParameterList(termParamList.params.map(mkParameter(_, c, memberInfo = info)), paramListModifier(termParamList.params)))
              case _ => assert(false, "memberInfo.termParamLists contains a type parameter list !")
          }
          val target = ExtensionTarget(
            extSym.symbol.normalizedName,
            typeParams,
            termParams,
            extSym.tpt.asSignature(c),
            extSym.tpt.symbol.dri,
            extSym.symbol.pos.get.start
          )
          parseMethod(c, dd.symbol,specificKind = Kind.Extension(target, _))
        }

      case dd: DefDef if !dd.symbol.isHiddenByVisibility && dd.symbol.isExported && !dd.symbol.isArtifact =>
        dd.rhs.map {
          case TypeApply(rhs, _) => rhs
          case Apply(TypeApply(rhs, _), _) => rhs
          case rhs => rhs
        }.map(_.tpe.termSymbol).filter(_.exists).map(_.tree).map {
          case v: ValDef if v.symbol.flags.is(Flags.Module) && !v.symbol.flags.is(Flags.Synthetic) =>
            v.symbol.owner -> Symbol.newVal(c.symbol, dd.name, v.tpt.tpe, Flags.Final, Symbol.noSymbol).tree
          case other => other.symbol.owner -> other
        }.flatMap { (originalOwner, tree) =>
          parseMember(c)(tree)
            .map { m => m
              .withDRI(dd.symbol.dri)
              .withName(dd.symbol.normalizedName)
              .withKind(Kind.Exported(m.kind))
              .withOrigin(Origin.ExportedFrom(Some(Link(originalOwner.normalizedName, originalOwner.dri))))
            }
        }

      case dd: DefDef if !dd.symbol.isHiddenByVisibility && !dd.symbol.isSyntheticFunc && !dd.symbol.isExtensionMethod && !dd.symbol.isArtifact =>
        Some(parseMethod(c, dd.symbol))

      case td: TypeDef if !td.symbol.flags.is(Flags.Synthetic) && (!td.symbol.flags.is(Flags.Case) || !td.symbol.flags.is(Flags.Enum)) =>
        Some(parseTypeDef(td, c))

      case vd: ValDef if !isSyntheticField(vd.symbol) && (!vd.symbol.flags.is(Flags.Case) || !vd.symbol.flags.is(Flags.Enum)) =>
        Some(parseValDef(c, vd))

      case c: ClassDef if c.symbol.shouldDocumentClasslike =>
        Some(parseClasslike(c))

      case _ => None
  }

  private def parseInheritedMember(c: ClassDef)(s: Tree): Option[Member] =
    def inheritance = Some(InheritedFrom(s.symbol.owner.normalizedName, s.symbol.dri, s.symbol.owner.isHiddenByVisibility))
    processTreeOpt(s)(s match
      case c: ClassDef if c.symbol.shouldDocumentClasslike => Some(parseClasslike(c, signatureOnly = true))
      case other => {
        val parsed = parseMember(c)(other)
        parsed.map(p =>
          val parentDRI = c.symbol.dri
          p.copy(
            dri = p.dri.copy(
              location = parentDRI.location,
              externalLink = None
            )
          )
        )
      }
    ).map(_.copy(inheritedFrom = inheritance))

  extension (using Quotes)(c: reflect.ClassDef)

    def membersToDocument = c.body.filterNot(_.symbol.isHiddenByVisibility)

    def getNonTrivialInheritedMemberTrees =
      c.symbol.getmembers.filterNot(s => s.isHiddenByVisibility || s.maybeOwner == c.symbol)
        .filter(s => s.maybeOwner != defn.ObjectClass && s.maybeOwner != defn.AnyClass)
        .map(_.tree)

  extension (c: ClassDef)
    def extractMembers: Seq[Member] = {
      val inherited = c.getNonTrivialInheritedMemberTrees.collect {
        case dd: DefDef if !dd.symbol.isClassConstructor && !(dd.symbol.isSuperAccessor || dd.symbol.isDefaultHelperMethod) => dd
        case other => other
      }
      c.membersToDocument.flatMap(parseMember(c)) ++
        inherited.flatMap(s => parseInheritedMember(c)(s))
    }

    /** Extracts members while taking Dotty logic for patching the stdlib into account. */
    def extractPatchedMembers: Seq[Member] = {
      val ownMembers = c.extractMembers
      def extractPatchMembers(sym: Symbol) = {
        // NOTE for some reason scala.language$.experimental$ class doesn't show up here, so we manually add the name
        val ownMemberDRIs = ownMembers.iterator.map(_.name).toSet + "experimental$"
        sym.tree.asInstanceOf[ClassDef]
          .membersToDocument.filterNot(m => ownMemberDRIs.contains(m.symbol.name))
          .flatMap(parseMember(c))
      }
      c.symbol.fullName match {
        case "scala.Predef$" =>
          ownMembers ++
          extractPatchMembers(qctx.reflect.Symbol.requiredClass("scala.runtime.stdLibPatches.Predef$"))
        case "scala.language$" =>
          ownMembers ++
          extractPatchMembers(qctx.reflect.Symbol.requiredModule("scala.runtime.stdLibPatches.language").moduleClass)
        case "scala.language$.experimental$" =>
          ownMembers ++
          extractPatchMembers(qctx.reflect.Symbol.requiredModule("scala.runtime.stdLibPatches.language.experimental").moduleClass)
        case _ => ownMembers
      }

    }

    def getTreeOfFirstParent: Option[Tree] =
      c.getParentsAsTreeSymbolTuples.headOption.map(_._1)

    def getParentsAsLinkToTypes: List[LinkToType] =
      c.getParentsAsTreeSymbolTuples.map {
        (tree, symbol) => LinkToType(tree.asSignature(c), symbol.dri, bareClasslikeKind(symbol))
      }

    def getParentsAsTreeSymbolTuples: List[(Tree, Symbol)] =
      if noPosClassDefs.contains(c.symbol) then Nil
      else for
        // TODO: add exists function to position methods in Quotes and replace the condition here for checking the JPath
        parentTree <- c.parents if parentTree.pos.sourceFile.getJPath.isDefined && parentTree.pos.start != parentTree.pos.end // We assume here that order is correct
        parentSymbol = parentTree match
          case t: TypeTree => t.tpe.typeSymbol
          case tree if tree.symbol.isClassConstructor => tree.symbol.owner
          case tree => tree.symbol
        if parentSymbol != defn.ObjectClass && parentSymbol != defn.AnyClass && !parentSymbol.isHiddenByVisibility
      yield (parentTree, parentSymbol)

    def getConstructors: List[Symbol] = c.membersToDocument.collect {
      case d: DefDef if d.symbol.isClassConstructor && c.constructor.symbol != d.symbol => d.symbol
    }.toList

    def getParameterModifier(parameter: Symbol): String =
      val fieldSymbol = c.symbol.declaredField(parameter.normalizedName)
      if fieldSymbol.flags.is(Flags.Mutable) then "var "
      else if fieldSymbol.flags.is(Flags.ParamAccessor) && !c.symbol.flags.is(Flags.Case) && !fieldSymbol.flags.is(Flags.Private) then "val "
      else ""

    def getTypeParams: List[TypeDef] =
      c.body.collect { case targ: TypeDef => targ  }.filter(_.symbol.isTypeParam)

    def getCompanion: Option[(Kind, DRI)] = c.symbol.getCompanionSymbol
      .filter(!_.flags.is(Flags.Synthetic))
      .filterNot(_.isHiddenByVisibility)
      .map(s => (bareClasslikeKind(s), s.dri))


  def parseClasslike(classDef: ClassDef, signatureOnly: Boolean = false): Member = classDef match
    case c: ClassDef if classDef.symbol.flags.is(Flags.Module) => parseObject(c, signatureOnly)
    case c: ClassDef if classDef.symbol.flags.is(Flags.Enum) && !classDef.symbol.flags.is(Flags.Case) => parseEnum(c, signatureOnly)
    case clazz => mkClass(classDef)(signatureOnly = signatureOnly)

  def parseObject(classDef: ClassDef, signatureOnly: Boolean = false): Member =
    mkClass(classDef)(
      // All objects are final so we do not need final modifier!
      modifiers = classDef.symbol.getExtraModifiers().filter(_ != Modifier.Final),
      signatureOnly = signatureOnly
    )

  def parseEnum(classDef: ClassDef, signatureOnly: Boolean = false): Member =
    val extraModifiers = classDef.symbol.getExtraModifiers().filter(_ != Modifier.Sealed).filter(_ != Modifier.Abstract)
    val companion = classDef.symbol.getCompanionSymbol.map(_.tree.asInstanceOf[ClassDef]).get

    val enumVals = companion.membersToDocument.collect {
      case vd: ValDef if !isSyntheticField(vd.symbol) && vd.symbol.flags.is(Flags.Enum) && vd.symbol.flags.is(Flags.Case) => vd
    }.toList.map(parseValDef(classDef, _))

    val enumTypes = companion.membersToDocument.collect {
      case td: TypeDef if !td.symbol.flags.is(Flags.Synthetic) && td.symbol.flags.is(Flags.Enum) && td.symbol.flags.is(Flags.Case) => td
    }.toList.map(parseTypeDef(_, classDef))

    val enumNested = companion.membersToDocument.collect {
      case c: ClassDef if c.symbol.flags.is(Flags.Case) && c.symbol.flags.is(Flags.Enum) => processTree(c)(parseClasslike(c))
    }.flatten

    val enumClass = mkClass(classDef)(modifiers = extraModifiers, signatureOnly = signatureOnly)

    val cases = (
      enumNested ++
      enumTypes ++
      enumVals.map(m => m.copy(dri = m.dri.copy(location = enumClass.dri.location)))
    )

    enumClass.withMembers(cases)

  def parseMethod(
      c: ClassDef,
      methodSymbol: Symbol,
      paramPrefix: Symbol => String = _ => "",
      specificKind: (Kind.Def => Kind) = identity
    ): Member =
    val method = methodSymbol.tree.asInstanceOf[DefDef]
    val paramLists = methodSymbol.nonExtensionParamLists

    val memberInfo = unwrapMemberInfo(c, methodSymbol)

    val unshuffledMemberInfoParamLists =
      if methodSymbol.isExtensionMethod && methodSymbol.isRightAssoc then
        // Taken from RefinedPrinter.scala
        // If you change the names of the clauses below, also change them in right-associative-extension-methods.md
        val (leftTyParams, rest1) = memberInfo.paramLists match
          case fst :: tail if fst.isType => (List(fst), tail)
          case other => (List(), other)
        val (leadingUsing, rest2) = rest1.span(_.isUsing)
        val (rightTyParams, rest3) = rest2.span(_.isType)
        val (rightParam, rest4) = rest3.splitAt(1)
        val (leftParam, rest5) = rest4.splitAt(1)
        val (trailingUsing, rest6) = rest5.span(_.isUsing)
        if leftParam.nonEmpty then
          // leftTyParams ::: leadingUsing ::: leftParam ::: trailingUsing ::: rightTyParams ::: rightParam ::: rest6
          // because of takeRight after, this is equivalent to the following:
          rightTyParams ::: rightParam ::: rest6
        else
          memberInfo.paramLists // it wasn't a binary operator, after all.
      else
        memberInfo.paramLists

    val croppedUnshuffledMemberInfoParamLists = unshuffledMemberInfoParamLists.takeRight(paramLists.length)

    val basicDefKind: Kind.Def = Kind.Def(
      paramLists.zip(croppedUnshuffledMemberInfoParamLists).flatMap{
        case (_: TermParamClause, MemberInfo.EvidenceOnlyParameterList) => Nil
        case (pList: TermParamClause, MemberInfo.RegularParameterList(info)) =>
            Some(Left(api.TermParameterList(pList.params.map(
              mkParameter(_, c, paramPrefix, memberInfo = info)), paramListModifier(pList.params)
            )))
        case (TypeParamClause(genericTypeList), MemberInfo.TypeParameterList(memInfoTypes)) =>
          Some(Right(genericTypeList.map(mkTypeArgument(_, c, memInfoTypes, memberInfo.contextBounds))))
        case (_,_) =>
          assert(false, s"croppedUnshuffledMemberInfoParamLists and SymOps.nonExtensionParamLists disagree on whether this clause is a type or term one")
      }
    )

    val methodKind =
      if methodSymbol.isClassConstructor then Kind.Constructor(basicDefKind)
      else if methodSymbol.flags.is(Flags.Implicit) then
        val termParamLists: List[TermParamClause] = methodSymbol.nonExtensionTermParamLists
        extractImplicitConversion(method.returnTpt.tpe) match
          case Some(conversion) if termParamLists.size == 0 || (termParamLists.size == 1 && termParamLists.head.params.size == 0) =>
            Kind.Implicit(basicDefKind, Some(conversion))
          case None if termParamLists.size == 1 && termParamLists(0).params.size == 1 =>
            Kind.Implicit(basicDefKind, Some(
              ImplicitConversion(
                termParamLists(0).params(0).tpt.tpe.typeSymbol.dri,
                method.returnTpt.tpe.typeSymbol.dri
              )
            ))
          case _ =>
            Kind.Implicit(basicDefKind, None)
      else if methodSymbol.flags.is(Flags.Given) then Kind.Given(basicDefKind, Some(method.returnTpt.tpe.asSignature(c)), extractImplicitConversion(method.returnTpt.tpe))
      else specificKind(basicDefKind)

    val origin = if !methodSymbol.isOverridden then Origin.RegularlyDefined else
      val overriddenSyms = methodSymbol.allOverriddenSymbols.map(_.owner)
      Origin.Overrides(overriddenSyms.map(s => Overridden(s.name, s.dri)).toSeq)

    val modifiers = methodKind match
      case _: Kind.Given => methodSymbol
        .getExtraModifiers()
        .filterNot(m => m == Modifier.Lazy || m == Modifier.Final)
      case _ => methodSymbol.getExtraModifiers()

    mkMember(
      methodSymbol,
      methodKind,
      method.returnTpt.tpe.asSignature(c),
    )(
      modifiers = modifiers,
      origin = origin,
      deprecated = methodSymbol.isDeprecated(),
      experimental = methodSymbol.isExperimental()
    )

  def mkParameter(
    argument: ValDef,
    classDef: ClassDef,
    prefix: Symbol => String = _ => "",
    isExtendedSymbol: Boolean = false,
    isGrouped: Boolean = false,
    memberInfo: Map[String, TypeRepr] = Map.empty,
  ) =
    val inlinePrefix = if argument.symbol.flags.is(Flags.Inline) then "inline " else ""
    val nameIfNotSynthetic = Option.when(!argument.symbol.flags.is(Flags.Synthetic))(argument.symbol.normalizedName)
    val name = argument.symbol.normalizedName
    api.TermParameter(
      argument.symbol.getAnnotations(),
      inlinePrefix + prefix(argument.symbol),
      nameIfNotSynthetic,
      argument.symbol.dri,
      memberInfo.get(name).fold(argument.tpt.asSignature(classDef))(_.asSignature(classDef)),
      isExtendedSymbol,
      isGrouped
    )

  def mkTypeArgument(
    argument: TypeDef,
    classDef: ClassDef,
    memberInfo: Map[String, TypeBounds] = Map.empty,
    contextBounds: Map[String, DSignature] = Map.empty,
  ): TypeParameter =
    val variancePrefix: "+" | "-" | "" =
      if  argument.symbol.flags.is(Flags.Covariant) then "+"
      else if argument.symbol.flags.is(Flags.Contravariant) then "-"
      else ""

    val name = argument.symbol.normalizedName
    val normalizedName = if name.matches("_\\$\\d*") then "_" else name
    val boundsSignature = memberInfo.get(name).fold(argument.rhs.asSignature(classDef))(_.asSignature(classDef))
    val signature = contextBounds.get(name) match
      case None => boundsSignature
      case Some(contextBoundsSignature) =>
        boundsSignature ++ DSignature(Plain(" : ")) ++ contextBoundsSignature

    TypeParameter(
      argument.symbol.getAnnotations(),
      variancePrefix,
      normalizedName,
      argument.symbol.dri,
      signature
    )

  def parseTypeDef(typeDef: TypeDef, classDef: ClassDef): Member =
    def isTreeAbstract(typ: Tree): Boolean = typ match {
      case TypeBoundsTree(_, _) => true
      case LambdaTypeTree(params, body) => isTreeAbstract(body)
      case _ => false
    }
    val (generics, tpeTree) = typeDef.rhs match
      case LambdaTypeTree(params, body) => (params.map(mkTypeArgument(_, classDef)), body)
      case tpe => (Nil, tpe)

    val defaultKind = Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque, generics).asInstanceOf[Kind.Type]
    val kind = if typeDef.symbol.flags.is(Flags.Enum) then Kind.EnumCase(defaultKind)
      else defaultKind

    if typeDef.symbol.flags.is(Flags.Exported)
    then {
      val origin = Some(tpeTree).flatMap {
        case TypeBoundsTree(l: TypeTree, h: TypeTree) if l.tpe == h.tpe =>
          Some(Link(l.tpe.typeSymbol.owner.name, l.tpe.typeSymbol.owner.dri))
        case _ => None
      }
      mkMember(typeDef.symbol, Kind.Exported(kind), tpeTree.asSignature(classDef))(
        deprecated = typeDef.symbol.isDeprecated(),
        origin = Origin.ExportedFrom(origin),
        experimental = typeDef.symbol.isExperimental()
      )
    }
    else mkMember(typeDef.symbol, kind, tpeTree.asSignature(classDef))(deprecated = typeDef.symbol.isDeprecated())

  def parseValDef(c: ClassDef, valDef: ValDef): Member =
    def defaultKind = if valDef.symbol.flags.is(Flags.Mutable) then Kind.Var else Kind.Val
    val memberInfo = unwrapMemberInfo(c, valDef.symbol)
    val kind = if valDef.symbol.flags.is(Flags.Implicit) then Kind.Implicit(Kind.Val, extractImplicitConversion(valDef.tpt.tpe))
      else if valDef.symbol.flags.is(Flags.Given) then Kind.Given(Kind.Val, Some(memberInfo.res.asSignature(c)), extractImplicitConversion(valDef.tpt.tpe))
      else if valDef.symbol.flags.is(Flags.Enum) then Kind.EnumCase(Kind.Val)
      else defaultKind

    val modifiers = kind match
      case _: Kind.Given => valDef.symbol
        .getExtraModifiers()
        .filterNot(m => m == Modifier.Lazy || m == Modifier.Final)
      case _ => valDef.symbol.getExtraModifiers()

    mkMember(valDef.symbol, kind, memberInfo.res.asSignature(c))(
      modifiers = modifiers,
      deprecated = valDef.symbol.isDeprecated(),
      experimental = valDef.symbol.isExperimental()
    )

  def mkMember(symbol: Symbol, kind: Kind, signature: DSignature)(
    modifiers: Seq[Modifier] = symbol.getExtraModifiers(),
    origin: Origin = Origin.RegularlyDefined,
    inheritedFrom: Option[InheritedFrom] = None,
    graph: HierarchyGraph = HierarchyGraph.empty,
    deprecated: Option[Annotation] = None,
    experimental: Option[Annotation] = None
  ) = Member(
    name = symbol.normalizedName,
    fullName = symbol.normalizedFullName,
    dri = symbol.dri,
    kind = kind,
    visibility = symbol.getVisibility(),
    modifiers = modifiers,
    annotations = symbol.getAnnotations(),
    signature = signature,
    sources = symbol.source,
    origin = origin,
    inheritedFrom = inheritedFrom,
    graph = graph,
    docs = symbol.documentation,
    deprecated = deprecated,
    experimental = experimental
  )


  case class MemberInfo(
    paramLists: List[MemberInfo.ParameterList],
    res: TypeRepr,
    contextBounds: Map[String, DSignature] = Map.empty,
  ){
    val genericTypes: Map[String, TypeBounds] = paramLists.collect{ case MemberInfo.TypeParameterList(types) => types }.headOption.getOrElse(Map())

    val termParamLists: List[MemberInfo.ParameterList] = paramLists.filter(_.isTerm)
  }

  object MemberInfo:
    enum ParameterList(val isTerm: Boolean, val isUsing: Boolean):
      inline def isType = !isTerm
      case EvidenceOnlyParameterList                                         extends ParameterList(isTerm = true, isUsing = false)
      case RegularParameterList(m: Map[String, TypeRepr])(isUsing: Boolean)  extends ParameterList(isTerm = true, isUsing)
      case TypeParameterList(m: Map[String, TypeBounds])                     extends ParameterList(isTerm = false, isUsing = false)

    export ParameterList.{RegularParameterList, EvidenceOnlyParameterList, TypeParameterList}



  def unwrapMemberInfo(c: ClassDef, symbol: Symbol): MemberInfo =
    val baseTypeRepr = typeForClass(c).memberType(symbol)

    def isSyntheticEvidence(name: String) =
      if !name.startsWith(NameKinds.ContextBoundParamName.separator) then false else
        // This assumes that every parameter that starts with `evidence$` and is implicit is generated by compiler to desugar context bound.
        // Howrever, this is just a heuristic, so
        // `def foo[A](evidence$1: ClassTag[A]) = 1`
        // will be documented as
        // `def foo[A: ClassTag] = 1`.
        // Scala spec states that `$` should not be used in names and behaviour may be undefiend in such case.
        // Documenting method slightly different then its definition is withing the 'undefiend behaviour'.
        symbol.paramSymss.flatten.find(_.name == name).exists(p =>
          p.flags.is(Flags.Given) || p.flags.is(Flags.Implicit))

    def handlePolyType(memberInfo: MemberInfo, polyType: PolyType): MemberInfo =
      val typeParamList = MemberInfo.TypeParameterList(polyType.paramNames.zip(polyType.paramBounds).toMap)
      MemberInfo(memberInfo.paramLists :+ typeParamList, polyType.resType)

    def handleMethodType(memberInfo: MemberInfo, methodType: MethodType): MemberInfo =
      val rawParams = methodType.paramNames.zip(methodType.paramTypes).toMap
      val isUsing = methodType.isImplicit
      val (evidences, notEvidences) = rawParams.partition(e => isSyntheticEvidence(e._1))

      def findParamRefs(t: TypeRepr): Seq[ParamRef] = t match
        case paramRef: ParamRef => Seq(paramRef)
        case AppliedType(_, args) => args.flatMap(findParamRefs)
        case MatchType(bound, scrutinee,  cases) =>
            findParamRefs(bound) ++ findParamRefs(scrutinee)
        case _ => Nil

      def nameForRef(ref: ParamRef): String =
        val PolyType(names, _, _) = ref.binder: @unchecked
        names(ref.paramNum)

      val (paramsThatLookLikeContextBounds, contextBounds) =
        evidences.partitionMap {
          case (_, AppliedType(tpe, List(typeParam: ParamRef))) =>
            Right(nameForRef(typeParam) -> tpe.asSignature(c))
          case (name, original) =>
            findParamRefs(original) match
              case Nil => Left((name, original))
              case typeParam :: _ =>
                val name = nameForRef(typeParam)
                val signature = Seq(
                  Plain("(["),
                  dotty.tools.scaladoc.Type(name, None),
                  Plain("]"),
                  Keyword(" =>> "),
                ) ++ original.asSignature(c) ++ Seq(Plain(")"))
                Right(name -> signature.toList)
        }

      val newParams = notEvidences ++ paramsThatLookLikeContextBounds

      val termParamList = if newParams.isEmpty && contextBounds.nonEmpty
        then MemberInfo.EvidenceOnlyParameterList
        else MemberInfo.RegularParameterList(newParams)(isUsing)


      MemberInfo(memberInfo.paramLists :+ termParamList, methodType.resType, contextBounds.toMap)

    def handleByNameType(memberInfo: MemberInfo, byNameType: ByNameType): MemberInfo =
      MemberInfo(memberInfo.paramLists, byNameType.underlying)

    def recursivelyCalculateMemberInfo(memberInfo: MemberInfo): MemberInfo = memberInfo.res match
      case p: PolyType => recursivelyCalculateMemberInfo(handlePolyType(memberInfo, p))
      case m: MethodType => recursivelyCalculateMemberInfo(handleMethodType(memberInfo, m))
      case b: ByNameType => handleByNameType(memberInfo, b)
      case _ => memberInfo

    recursivelyCalculateMemberInfo(MemberInfo(List.empty, baseTypeRepr))

  private def paramListModifier(parameters: Seq[ValDef]): String =
    if parameters.size > 0 then
      if parameters(0).symbol.flags.is(Flags.Given) then "using "
      else if parameters(0).symbol.flags.is(Flags.Implicit) then "implicit "
      else ""
    else ""




© 2015 - 2024 Weber Informatics LLC | Privacy Policy