Skip to content

Scaladoc: new heuristic for extension method parameter extraction #14810

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 12 commits into from
Jan 5, 2023
48 changes: 40 additions & 8 deletions scaladoc-testcases/src/tests/extensionParams.scala
Original file line number Diff line number Diff line change
@@ -1,22 +1,54 @@
package tests.extensionParams

trait Animal

extension [A](thiz: A)
def toTuple2[B](that: B): (A, B) = thiz -> that
def toTuple2[B](that: B): (A, B)
= thiz -> that

extension [A](a: A)(using Int)
def f[B](b: B): (A, B) = ???
def f1[B](b: B): (A, B)
= ???

extension [A](a: A)(using Int)
def ff(b: A): (A, A) = ???
def f2(b: A): (A, A)
= ???

extension [A](a: A)(using Int)
def fff(using String)(b: A): (A, A) = ???
def f3(using String)(b: A): (A, A)
= ???

extension (a: Char)(using Int)
def ffff(using String)(b: Int): Unit = ???
def f4(using String)(b: Int): Unit
= ???

extension (a: Char)(using Int)
def fffff[B](using String)(b: B): Unit = ???
def f5[B](using String)(b: B): Unit
= ???

extension [A <: List[Char]](a: Int)(using Int)
def f6[B](b: B): (A, B)
= ???

extension [A <: List[Char]](using String)(using Unit)(a: A)(using Int)(using Number)
def f7[B, C](b: B)(c: C): (A, B)
= ???

extension [A <: List[Char]](using String)(using Unit)(a: A)(using Int)(using Number)
def f8(b: Any)(c: Any): Any
= ???

extension [A <: List[Char]](using String)(using Unit)(a: A)(using Int)(using Number)
def f9[B, C](using Int)(b: B)(c: C): (A, B)
= ???

extension [A <: List[Char]](using String)(using Unit)(a: A)(using Int)(using Number)
def f10(using Int)(b: Any)(c: Any): Any
= ???

def f12(using Int)(b: A)(c: String): Number
= ???

extension [A <: List[Char]](a: A)(using Int)
def ffffff[B](b: B): (A, B) = ???
extension (using String)(using Unit)(a: Animal)(using Int)(using Number)
def f11(b: Any)(c: Any): Any
= ???
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do these tests work? I see test “fixtures” but no test specification.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had answered in person, but I'll put it here for posterity:
This works by comparing the text of the file with the output of scala-meta, similarly to what is done in the compiler with the tests/pos, tests/neg folders.
This is therefore both the declaration and the specification for the tests !

3 changes: 1 addition & 2 deletions scaladoc/resources/dotty_res/styles/scalastyle.css
Original file line number Diff line number Diff line change
Expand Up @@ -988,8 +988,7 @@ footer .socials {
color: var(--type);
}

.signature *[t="t"] {
/* Types with links */
.signature *[t="t"] { /* Types with links */
color: var(--type-link);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,8 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext
.functionParameters(on.argsLists)
.content
val sig = typeSig ++ Signature(Plain(s"(${on.name}: ")) ++ on.signature ++ Signature(Plain(")")) ++ argsSig
MGroup(span(cls := "groupHeader")(sig.map(renderElement(_))), members.sortBy(_.name).toSeq, on.name)
}.toSeq
MGroup(span(cls := "groupHeader")(sig.map(renderElement(_))), members.sortBy(_.name).toSeq, on.name) -> on.position
}.toSeq.sortBy(_._2).map(_._1)

div(cls := "membersList expand")(
h2(cls := "h500")("Members list"),
Expand Down
10 changes: 9 additions & 1 deletion scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,15 @@ trait Resources(using ctx: DocContext) extends Locations, Writer:

def extensionTarget(member: Member): String =
member.kind match
case Kind.Extension(on, _) => flattenToText(on.signature)
case Kind.Extension(on, _) =>
val typeSig = SignatureBuilder()
.keyword("extension ")
.generics(on.typeParams)
.content
val argsSig = SignatureBuilder()
.functionParameters(on.argsLists)
.content
flattenToText(typeSig ++ argsSig)
case _ => ""

def processPage(page: Page, pageFQName: List[String]): Seq[(JSON, Seq[String])] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,14 +519,13 @@ trait ClassLikeSupport:
// Documenting method slightly different then its definition is withing the 'undefiend behaviour'.
symbol.paramSymss.flatten.find(_.name == name).exists(_.flags.is(Flags.Implicit))

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

def handleMethodType(memberInfo: MemberInfo, methodType: MethodType): MemberInfo =
val rawParams = methodType.paramNames.zip(methodType.paramTypes).toMap
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)
Expand Down Expand Up @@ -563,7 +562,7 @@ trait ClassLikeSupport:
MemberInfo(memberInfo.genericTypes, memberInfo.paramLists, byNameType.underlying)

def recursivelyCalculateMemberInfo(memberInfo: MemberInfo): MemberInfo = memberInfo.res match
case p: PolyType => recursivelyCalculateMemberInfo(handlePolyType(p))
case p: PolyType => recursivelyCalculateMemberInfo(handlePolyType(memberInfo, p))
case m: MethodType => recursivelyCalculateMemberInfo(handleMethodType(memberInfo, m))
case b: ByNameType => handleByNameType(memberInfo, b)
case _ => memberInfo
Expand Down
54 changes: 31 additions & 23 deletions scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -147,28 +147,43 @@ object SymOps:

def extendedSymbol: Option[reflect.ValDef] =
import reflect.*
Option.when(sym.isExtensionMethod){
val termParamss = sym.tree.asInstanceOf[DefDef].termParamss
if sym.isLeftAssoc || termParamss.size == 1 then termParamss(0).params(0)
else termParamss(1).params(0)
if sym.isExtensionMethod then
sym.extendedTermParamLists.find(param => !param.isImplicit && !param.isGiven).flatMap(_.params.headOption)
else None

def splitExtensionParamList: (List[reflect.ParamClause], List[reflect.ParamClause]) =
import reflect.*

def getPositionStartOption(pos: Option[Position]): Option[Int] = pos.flatMap {
case dotty.tools.dotc.util.NoSourcePosition => None
case pos: Position => Some(pos.start)
}

def comparePositionStarts(posA: Option[Position], posB: Option[Position]): Option[Boolean] =
for {
startA <- getPositionStartOption(posA)
startB <- getPositionStartOption(posB)
} yield startA < startB

sym.tree match
case tree: DefDef =>
tree.paramss.partition(_.params.headOption.flatMap(param =>
comparePositionStarts(param.symbol.pos, tree.symbol.pos)).getOrElse(false)
)
case _ => Nil -> Nil

def extendedTypeParams: List[reflect.TypeDef] =
import reflect.*
val method = sym.tree.asInstanceOf[DefDef]
method.leadingTypeParams
sym.tree match
case tree: DefDef =>
tree.leadingTypeParams
case _ => Nil

def extendedTermParamLists: List[reflect.TermParamClause] =
import reflect.*
if sym.nonExtensionLeadingTypeParams.nonEmpty then
sym.nonExtensionParamLists.takeWhile {
case _: TypeParamClause => false
case _ => true
}.collect {
case tpc: TermParamClause => tpc
}
else
List.empty
sym.splitExtensionParamList._1.collect {
case tpc: TermParamClause => tpc
}

def nonExtensionTermParamLists: List[reflect.TermParamClause] =
import reflect.*
Expand All @@ -185,14 +200,7 @@ object SymOps:
}

def nonExtensionParamLists: List[reflect.ParamClause] =
import reflect.*
val method = sym.tree.asInstanceOf[DefDef]
if sym.isExtensionMethod then
val params = method.paramss
val toDrop = if method.leadingTypeParams.nonEmpty then 2 else 1
if sym.isLeftAssoc || params.size == 1 then params.drop(toDrop)
else params.head :: params.tail.drop(toDrop)
else method.paramss
sym.splitExtensionParamList._2

def nonExtensionLeadingTypeParams: List[reflect.TypeDef] =
import reflect.*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class MergedPackageSignatures extends SignatureTest("mergedPackage", SignatureTe

class ExtensionMethodSignature extends SignatureTest("extensionMethodSignatures", SignatureTest.all)

class ExtensionMethodParamsSignature extends SignatureTest("extensionParams", SignatureTest.all)

class ClassModifiers extends SignatureTest("classModifiers", SignatureTest.classlikeKinds)

class EnumSignatures extends SignatureTest("enumSignatures", SignatureTest.all)
Expand Down