Skip to content

Fix presentation of signature types that may change in inherited classes due to different symbolic type names/partially applied types #11070

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions scala3doc-testcases/src/tests/typeAppliance.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package tests
package typeAppliance

trait AClass[A, B]:
def funASD[C, D](f: B => C): AClass[A, C]

trait BClass[A, B] extends AClass[A, B]:
override def funASD[X, D](f: B => X): BClass[A, X]
val f: (=> B) => String
= _ => "abc"


abstract class CClass[U] extends BClass[Int, U]:
def someFun(n: Int)(b: Int): Int
= 1

87 changes: 58 additions & 29 deletions scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ trait ClassLikeSupport:
else Kind.Class(Nil, Nil)

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

def parameterModifier(parameter: Symbol): String =
val fieldSymbol = classDef.symbol.declaredField(parameter.normalizedName)
Expand Down Expand Up @@ -122,7 +122,7 @@ trait ClassLikeSupport:
private def isDocumentableExtension(s: Symbol) =
!s.isHiddenByVisibility && !s.isSyntheticFunc && s.isExtensionMethod

private def parseMember(s: Tree): Option[Member] = processTreeOpt(s)(s match
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 target = ExtensionTarget(
Expand All @@ -131,14 +131,14 @@ trait ClassLikeSupport:
extSym.tpt.symbol.dri,
extSym.symbol.pos.get.start
)
parseMethod(dd.symbol,specificKind = Kind.Extension(target, _))
parseMethod(c, dd.symbol,specificKind = Kind.Extension(target, _))
}
// TODO check given methods?
case dd: DefDef if !dd.symbol.isHiddenByVisibility && dd.symbol.isGiven =>
Some(dd.symbol.owner.memberType(dd.name))
.filterNot(_.exists)
.map { _ =>
parseMethod(dd.symbol, specificKind =
parseMethod(c, dd.symbol, specificKind =
Kind.Given(_, getGivenInstance(dd).map(_.asSignature), None)
)
}
Expand All @@ -157,11 +157,11 @@ trait ClassLikeSupport:
case s: Select if s.symbol.isDefDef => s.symbol.dri
}.orElse(exportedTarget.map(_.qualifier.tpe.typeSymbol.dri))

Some(parseMethod(dd.symbol, specificKind = Kind.Exported(_))
Some(parseMethod(c, dd.symbol, specificKind = Kind.Exported(_))
.withOrigin(Origin.ExportedFrom(s"$instanceName.$functionName", dri)))

case dd: DefDef if !dd.symbol.isHiddenByVisibility && !dd.symbol.isGiven && !dd.symbol.isSyntheticFunc && !dd.symbol.isExtensionMethod =>
Some(parseMethod(dd.symbol))
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))
Expand All @@ -170,10 +170,10 @@ trait ClassLikeSupport:
&& (!vd.symbol.flags.is(Flags.Case) || !vd.symbol.flags.is(Flags.Enum))
&& vd.symbol.isGiven =>
val classDef = Some(vd.tpt.tpe).flatMap(_.classSymbol.map(_.tree.asInstanceOf[ClassDef]))
Some(classDef.filter(_.symbol.flags.is(Flags.Module)).fold[Member](parseValDef(vd))(parseGivenClasslike(_)))
Some(classDef.filter(_.symbol.flags.is(Flags.Module)).fold[Member](parseValDef(c, vd))(parseGivenClasslike(_)))

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

case c: ClassDef if c.symbol.owner.memberMethod(c.name).exists(_.flags.is(Flags.Given)) =>
Some(parseGivenClasslike(c))
Expand Down Expand Up @@ -207,9 +207,9 @@ trait ClassLikeSupport:
)
}

private def parseInheritedMember(s: Tree): Option[Member] = processTreeOpt(s)(s match
private def parseInheritedMember(c: ClassDef)(s: Tree): Option[Member] = processTreeOpt(s)(s match
case c: ClassDef if c.symbol.shouldDocumentClasslike && !c.symbol.isGiven => Some(parseClasslike(c, signatureOnly = true))
case other => parseMember(other)
case other => parseMember(c)(other)
).map(_.withInheritedFrom(InheritedFrom(s.symbol.owner.normalizedName, s.symbol.owner.dri)))

extension (c: ClassDef)
Expand All @@ -225,8 +225,8 @@ trait ClassLikeSupport:
case dd: DefDef if !dd.symbol.isClassConstructor && !(dd.symbol.isSuperBridgeMethod || dd.symbol.isDefaultHelperMethod) => dd
case other => other
}
c.membersToDocument.flatMap(parseMember) ++
inherited.flatMap(s => parseInheritedMember(s))
c.membersToDocument.flatMap(parseMember(c)) ++
inherited.flatMap(s => parseInheritedMember(c)(s))
}

/** Extracts members while taking Dotty logic for patching the stdlib into account. */
Expand All @@ -237,7 +237,7 @@ trait ClassLikeSupport:
val ownMemberDRIs = ownMembers.iterator.map(_.name).toSet + "experimental$"
sym.tree.asInstanceOf[ClassDef]
.membersToDocument.filterNot(m => ownMemberDRIs.contains(m.symbol.name))
.flatMap(parseMember)
.flatMap(parseMember(c))
}
c.symbol.fullName match {
case "scala.Predef$" =>
Expand Down Expand Up @@ -299,7 +299,7 @@ trait ClassLikeSupport:

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(_))
}.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
Expand All @@ -318,6 +318,7 @@ trait ClassLikeSupport:
classlikie.withNewMembers(cases).asInstanceOf[DClass]

def parseMethod(
c: ClassDef,
methodSymbol: Symbol,
emptyParamsList: Boolean = false,
paramPrefix: Symbol => String = _ => "",
Expand All @@ -333,9 +334,13 @@ trait ClassLikeSupport:
else method.paramss
val genericTypes = if (methodSymbol.isClassConstructor) Nil else method.typeParams

val memberInfo = unwrapMemberInfo(c, methodSymbol)

val basicKind: Kind.Def = Kind.Def(
genericTypes.map(mkTypeArgument),
paramLists.map(pList => ParametersList(pList.map(mkParameter(_, paramPrefix)), if isUsingModifier(pList) then "using " else ""))
genericTypes.map(mkTypeArgument(_, memberInfo.genericTypes)),
paramLists.zipWithIndex.map { (pList, index) =>
ParametersList(pList.map(mkParameter(_, paramPrefix, memberInfo = memberInfo.paramLists(index))), if isUsingModifier(pList) then "using " else "")
}
)

val methodKind =
Expand Down Expand Up @@ -365,7 +370,7 @@ trait ClassLikeSupport:
methodSymbol.getExtraModifiers(),
methodKind,
methodSymbol.getAnnotations(),
method.returnTpt.dokkaType.asSignature,
memberInfo.res.dokkaType.asSignature,
methodSymbol.source(using qctx),
origin
)
Expand All @@ -374,32 +379,33 @@ trait ClassLikeSupport:
def mkParameter(argument: ValDef,
prefix: Symbol => String = _ => "",
isExtendedSymbol: Boolean = false,
isGrouped: Boolean = false) =
isGrouped: Boolean = false,
memberInfo: Map[String, TypeRepr] = Map.empty) =
val inlinePrefix = if argument.symbol.flags.is(Flags.Inline) then "inline " else ""
val name = Option.when(!argument.symbol.flags.is(Flags.Synthetic))(argument.symbol.normalizedName)

val nameIfNotSynthetic = Option.when(!argument.symbol.flags.is(Flags.Synthetic))(argument.symbol.normalizedName)
val name = argument.symbol.normalizedName
Parameter(
argument.symbol.getAnnotations(),
inlinePrefix + prefix(argument.symbol),
name,
nameIfNotSynthetic,
argument.symbol.dri,
argument.tpt.dokkaType.asSignature,
memberInfo.get(name).fold(argument.tpt.dokkaType.asSignature)(_.dokkaType.asSignature),
isExtendedSymbol,
isGrouped
)

def mkTypeArgument(argument: TypeDef): TypeParameter =
def mkTypeArgument(argument: TypeDef, memberInfo: Map[String, TypeBounds] = 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
TypeParameter(
argument.symbol.getAnnotations(),
variancePrefix,
argument.symbol.normalizedName,
name,
argument.symbol.dri,
argument.rhs.dokkaType.asSignature
memberInfo.get(name).fold(argument.rhs.dokkaType.asSignature)(_.dokkaType.asSignature)
)

def parseTypeDef(typeDef: TypeDef): Member =
Expand All @@ -410,7 +416,7 @@ trait ClassLikeSupport:
}

val (generics, tpeTree) = typeDef.rhs match
case LambdaTypeTree(params, body) => (params.map(mkTypeArgument), body)
case LambdaTypeTree(params, body) => (params.map(mkTypeArgument(_)), body)
case tpe => (Nil, tpe)

mkMember(
Expand All @@ -425,8 +431,9 @@ trait ClassLikeSupport:
)
)

def parseValDef(valDef: ValDef): Member =
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 defaultKind
Expand All @@ -438,7 +445,7 @@ trait ClassLikeSupport:
valDef.symbol.getExtraModifiers(),
kind,
valDef.symbol.getAnnotations(),
valDef.tpt.tpe.dokkaType.asSignature,
memberInfo.res.dokkaType.asSignature,
valDef.symbol.source(using qctx)
)
)
Expand Down Expand Up @@ -469,5 +476,27 @@ trait ClassLikeSupport:
PropertyContainer.Companion.empty().plus(member.copy(rawDoc = symbol.documentation)).plus(compositeExt)
)

case class MemberInfo(genericTypes: Map[String, TypeBounds], paramLists: List[Map[String, TypeRepr]], res: TypeRepr)

def unwrapMemberInfo(c: ClassDef, symbol: Symbol): MemberInfo =
val baseTypeRepr = memberInfo(c, symbol)

def handlePolyType(polyType: PolyType): MemberInfo =
MemberInfo(polyType.paramNames.zip(polyType.paramBounds).toMap, List.empty, polyType.resType)

def handleMethodType(memberInfo: MemberInfo, methodType: MethodType): MemberInfo =
MemberInfo(memberInfo.genericTypes, memberInfo.paramLists ++ List(methodType.paramNames.zip(methodType.paramTypes).toMap), methodType.resType)

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

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

recursivelyCalculateMemberInfo(MemberInfo(Map.empty, List.empty, baseTypeRepr))

private def isUsingModifier(parameters: Seq[ValDef]): Boolean =
parameters.size > 0 && parameters(0).symbol.flags.is(Flags.Given)
9 changes: 9 additions & 0 deletions scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,12 @@ trait SyntheticsSupport:
given dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
val cSym = c.symbol.asInstanceOf[dotc.core.Symbols.Symbol]
cSym.typeRef.appliedTo(cSym.typeParams.map(_.typeRef)).asInstanceOf[TypeRepr]

def memberInfo(c: ClassDef, symbol: Symbol): TypeRepr =
import qctx.reflect._
import dotty.tools.dotc
given dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
typeForClass(c).asInstanceOf[dotc.core.Types.Type]
.memberInfo(symbol.asInstanceOf[dotc.core.Symbols.Symbol])
.asInstanceOf[TypeRepr]

26 changes: 20 additions & 6 deletions scala3doc/src/dotty/dokka/tasty/TypesSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,19 @@ trait TypesSupport:
case List(single) => single
case other => other.reduce((r, e) => r ++ texts(", ") ++ e)

private def isRepeated(tpeAnnotation: Term) =
// For some reason annotation.tpe.typeSymbol != defn.RepeatedParamClass
// annotation.tpe.typeSymbol prints 'class Repeated' and defn.RepeatedParamClass prints 'class <repeated>'
tpeAnnotation.tpe.typeSymbol.toString == "class Repeated"
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 == "<repeated>" && t.qualifier.match
case ThisType(tref: TypeRef) if tref.name == "scala" => true
case _ => false
case _ => false

// TODO #23 add support for all types signatures that makes sense
private def inner(tp: TypeRepr): List[JProjection] =
Expand All @@ -86,7 +95,9 @@ trait TypesSupport:
case ConstantType(constant) =>
texts(constant.show)
case ThisType(tpe) => inner(tpe)
case AnnotatedType(AppliedType(_, Seq(tpe)), annotation) if isRepeated(annotation) =>
case AnnotatedType(AppliedType(_, Seq(tpe)), annotation) if isRepeatedAnnotation(annotation) =>
inner(tpe) :+ text("*")
case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) =>
inner(tpe) :+ text("*")
case AnnotatedType(tpe, _) =>
inner(tpe)
Expand Down Expand Up @@ -168,7 +179,10 @@ trait TypesSupport:
case Seq(rtpe) =>
text("() => ") :: inner(rtpe)
case Seq(arg, rtpe) =>
inner(arg) ++ texts(" => ") ++ inner(rtpe)
val partOfSignature = arg match
case byName: ByNameType => texts("(") ++ inner(byName) ++ texts(")")
case _ => inner(arg)
partOfSignature ++ texts(" => ") ++ inner(rtpe)
case args =>
texts("(") ++ commas(args.init.map(inner)) ++ texts(") => ") ++ inner(args.last)
else if t.isTupleType then
Expand Down
5 changes: 4 additions & 1 deletion scala3doc/test/dotty/dokka/SignatureTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ abstract class SignatureTest(
case unexpectedRegex(signature) => findName(signature, kinds).map(Unexpected(_))
case expectedRegex(signature) => findName(signature, kinds).map(Expected(_, signature))
case signature =>
findName(signature, kinds).map(Expected(_, commentRegex.replaceAllIn(signature, "").compactWhitespaces))
findName(signature, kinds).map(
Expected(_, commentRegex.replaceAllIn(signature, "")
.compactWhitespaces.reverse.dropWhile(List('{', ':').contains(_)).reverse)
)
}

private def signaturesFromDocumentation(root: PageNode)(using DocContext): Seq[String] =
Expand Down
4 changes: 3 additions & 1 deletion scala3doc/test/dotty/dokka/SignatureTestCases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class FieldsSignatures extends SignatureTest("fieldsSignatures", SignatureTest.a

class NestedSignatures extends SignatureTest("nested", SignatureTest.all)

class TypeAppliacneSignatures extends SignatureTest("typeAppliance", SignatureTest.all)

class CompanionObjectSignatures extends SignatureTest("companionObjectSignatures", SignatureTest.all)

class PackageSymbolSignatures extends SignatureTest("packageSymbolSignatures", SignatureTest.all)
Expand Down Expand Up @@ -84,4 +86,4 @@ class ImplicitConversionsTest3 extends SignatureTest(
filterFunc = _.toString.endsWith("ClassWithConversionWithProperType.html")
)

class SpecializedSignature extends SignatureTest("specializedSignature", SignatureTest.all)
class SpecializedSignature extends SignatureTest("specializedSignature", SignatureTest.all)