diff --git a/scala3doc-testcases/src/tests/typeAppliance.scala b/scala3doc-testcases/src/tests/typeAppliance.scala new file mode 100644 index 000000000000..23a8325328ea --- /dev/null +++ b/scala3doc-testcases/src/tests/typeAppliance.scala @@ -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 + diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index b823d6f6d7d7..c8758103c80a 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -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) @@ -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( @@ -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) ) } @@ -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)) @@ -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)) @@ -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) @@ -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. */ @@ -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$" => @@ -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 @@ -318,6 +318,7 @@ trait ClassLikeSupport: classlikie.withNewMembers(cases).asInstanceOf[DClass] def parseMethod( + c: ClassDef, methodSymbol: Symbol, emptyParamsList: Boolean = false, paramPrefix: Symbol => String = _ => "", @@ -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 = @@ -365,7 +370,7 @@ trait ClassLikeSupport: methodSymbol.getExtraModifiers(), methodKind, methodSymbol.getAnnotations(), - method.returnTpt.dokkaType.asSignature, + memberInfo.res.dokkaType.asSignature, methodSymbol.source(using qctx), origin ) @@ -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 = @@ -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( @@ -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 @@ -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) ) ) @@ -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) diff --git a/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala b/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala index 7e923b5007a9..6aecd7ef5a19 100644 --- a/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala @@ -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] + diff --git a/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala b/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala index fbbe095b93bf..d12e0f53b3a5 100644 --- a/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala @@ -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 ' - 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 == "" && 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] = @@ -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) @@ -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 diff --git a/scala3doc/test/dotty/dokka/SignatureTest.scala b/scala3doc/test/dotty/dokka/SignatureTest.scala index 1df3e6592ccb..3a82ccfc53e2 100644 --- a/scala3doc/test/dotty/dokka/SignatureTest.scala +++ b/scala3doc/test/dotty/dokka/SignatureTest.scala @@ -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] = diff --git a/scala3doc/test/dotty/dokka/SignatureTestCases.scala b/scala3doc/test/dotty/dokka/SignatureTestCases.scala index 2320fe51fe3b..2538332c1989 100644 --- a/scala3doc/test/dotty/dokka/SignatureTestCases.scala +++ b/scala3doc/test/dotty/dokka/SignatureTestCases.scala @@ -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) @@ -84,4 +86,4 @@ class ImplicitConversionsTest3 extends SignatureTest( filterFunc = _.toString.endsWith("ClassWithConversionWithProperType.html") ) -class SpecializedSignature extends SignatureTest("specializedSignature", SignatureTest.all) \ No newline at end of file +class SpecializedSignature extends SignatureTest("specializedSignature", SignatureTest.all)