Skip to content

Scaladoc: improve refined function types rendering #20333

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 1 commit into from
May 6, 2024
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
37 changes: 37 additions & 0 deletions scaladoc-testcases/src/tests/refinedFunctionTypes.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package tests
package refinedFunctionTypes

import annotation.experimental

@experimental
infix type $throws[R, +E <: Exception] = CanThrow[E] ?=> R

@experimental
infix type $throws2[+E <: Exception] = (c: CanThrow[E]) ?=> c.type

@experimental
infix type $throws3[+E <: Exception] = [T] => (c: CanThrow[E]) ?=> c.type

@experimental
infix type $throws4[+E <: Exception] = [T] => (c: CanThrow[E]) ?=> T //expected: infix type $throws4[+E <: Exception] = [T] => CanThrow[E] ?=> T

type TA1 = (a: Int, b: (Boolean, String)) => List[(a.type, b.type)]

type TA2 = (a: Int, b: (Boolean, String)) ?=> List[Boolean]

@experimental
type TB0 = [R, E <: Exception] =>> PolyFunction { def apply[T](c: CanThrow[E]): R; } //expected: type TB0[R, E <: Exception] = [T] => CanThrow[E] => R

@experimental
type TB1 = [R, E <: Exception] =>> PolyFunction { def apply[T](c: CanThrow[E], y: c.type): R; } //expected: type TB1[R, E <: Exception] = [T] => (c: CanThrow[E], y: c.type) => R

@experimental
type TB2 = [R, E <: Exception] =>> PolyFunction { def apply[T](using c: CanThrow[E]): c.type; } //expected: type TB2[R, E <: Exception] = [T] => (c: CanThrow[E]) ?=> c.type

type TC1 = [T] => (a: T) => T //expected: type TC1 = [T] => T => T

type TC2 = [T] => (a: T) ?=> T //expected: type TC2 = [T] => T ?=> T

type TC3 = [T] => (a: T) => a.type

type TC4 = [T] => (a: T) ?=> a.type
4 changes: 2 additions & 2 deletions scaladoc-testcases/src/tests/thisType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ package thisType
// issue 16024

class X[Map[_, _[_]]]:
inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] = //expected: inline def map[F[_]](f: [t] => (x$1: t) => F[t]): Map[this.type, F]
???
inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F]
= ???
2 changes: 1 addition & 1 deletion scaladoc-testcases/src/tests/typesSignatures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Base
// Tests do not support multiline signatures
type Elem[X] = X match { case String => Char case Array[t] => t case Iterable[t] => t }

type F = [X] => (x: X) => List[X]
type F = [X] => (x: X) => List[X] //expected: type F = [X] => X => List[X]

type G = Int => Int

Expand Down
44 changes: 38 additions & 6 deletions scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package tasty
import scala.jdk.CollectionConverters._

import scala.quoted._
import scala.util.control.NonFatal

import NameNormalizer._
import SyntheticsSupport._
Expand Down Expand Up @@ -124,6 +125,12 @@ trait TypesSupport:
++ keyword(" =>> ").l
++ inner(resType)

case Refinement(parent, "apply", mt : MethodType) if isPolyOrEreased(parent) =>
val isCtx = isContextualMethod(mt)
val sym = defn.FunctionClass(mt.paramTypes.length, isCtx)
val at = sym.typeRef.appliedTo(mt.paramTypes :+ mt.resType)
inner(Refinement(at, "apply", mt))

case r: Refinement => { //(parent, name, info)
def getRefinementInformation(t: TypeRepr): List[TypeRepr] = t match {
case r: Refinement => getRefinementInformation(r.parent) :+ r
Expand Down Expand Up @@ -164,16 +171,22 @@ trait TypesSupport:
case t: PolyType =>
val paramBounds = getParamBounds(t)
val method = t.resType.asInstanceOf[MethodType]
val paramList = getParamList(method)
val resType = inner(method.resType)
plain("[").l ++ paramBounds ++ plain("]").l ++ keyword(" => ").l ++ paramList ++ keyword(" => ").l ++ resType
val rest = parseDependentFunctionType(method)
plain("[").l ++ paramBounds ++ plain("]").l ++ keyword(" => ").l ++ rest
case other => noSupported(s"Not supported type in refinement $info")
}

def parseDependentFunctionType(info: TypeRepr): SSignature = info match {
case m: MethodType =>
val paramList = getParamList(m)
paramList ++ keyword(" => ").l ++ inner(m.resType)
val isCtx = isContextualMethod(m)
if isDependentMethod(m) then
val paramList = getParamList(m)
val arrow = keyword(if isCtx then " ?=> " else " => ").l
val resType = inner(m.resType)
paramList ++ arrow ++ resType
else
val sym = defn.FunctionClass(m.paramTypes.length, isCtx)
inner(sym.typeRef.appliedTo(m.paramTypes :+ m.resType))
case other => noSupported("Dependent function type without MethodType refinement")
}

Expand Down Expand Up @@ -213,8 +226,9 @@ trait TypesSupport:
case Seq(rtpe) =>
plain("()").l ++ keyword(arrow).l ++ inner(rtpe)
case Seq(arg, rtpe) =>
val partOfSignature = arg match
val partOfSignature = stripAnnotated(arg) match
case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => inner(arg)
case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => inner(arg)
case _ => inParens(inner(arg))
partOfSignature ++ keyword(arrow).l ++ inner(rtpe)
case args =>
Expand Down Expand Up @@ -385,3 +399,21 @@ trait TypesSupport:
case _ => false

at.args.size == 2 && (!at.typeSymbol.name.forall(isIdentifierPart) || infixAnnot)

private def isPolyOrEreased(using Quotes)(tr: reflect.TypeRepr) =
Set("scala.PolyFunction", "scala.runtime.ErasedFunction")
.contains(tr.typeSymbol.fullName)

private def isContextualMethod(using Quotes)(mt: reflect.MethodType) =
mt.asInstanceOf[dotty.tools.dotc.core.Types.MethodType].isContextualMethod
Comment on lines +407 to +408
Copy link
Contributor

Choose a reason for hiding this comment

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

As a curiosity I will mention we've just added a MethodTypeKind to quotes api for that (but I think using the compiler directly here is better, and avoiding MethodTypeKind should allow us to use this scaladoc for LTS api).


private def isDependentMethod(using Quotes)(mt: reflect.MethodType) =
val method = mt.asInstanceOf[dotty.tools.dotc.core.Types.MethodType]
try method.isParamDependent || method.isResultDependent
catch case NonFatal(_) => true

private def stripAnnotated(using Quotes)(tr: reflect.TypeRepr): reflect.TypeRepr =
import reflect.*
tr match
case AnnotatedType(tr, _) => stripAnnotated(tr)
case other => other
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,5 @@ class MatchTypeTuple extends SignatureTest("matchTypeTuple", SignatureTest.all)
class InfixTypes extends SignatureTest("infixTypes", SignatureTest.all)

class ExtendsCall extends SignatureTest("extendsCall", SignatureTest.all)

class RefinedFunctionTypes extends SignatureTest("refinedFunctionTypes", SignatureTest.all)
Loading