From ab647515d3f9d4b0a38aa0b0391a6dfb82797cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Fornal?= <24961583+Florian3k@users.noreply.github.com> Date: Mon, 6 May 2024 16:22:05 +0200 Subject: [PATCH] Scaladoc: improve refined function types rendering (#20333) Fixes #19967 Improves rendering of refined function types - unneeded parameters names are omitted when possible [Cherry-picked 55025c22ab2145be6c883f085c47ebc0d3f8e417] --- .../src/tests/refinedFunctionTypes.scala | 37 ++++++++++++++++ scaladoc-testcases/src/tests/thisType.scala | 4 +- .../src/tests/typesSignatures.scala | 2 +- .../tools/scaladoc/tasty/TypesSupport.scala | 44 ++++++++++++++++--- .../TranslatableSignaturesTestCases.scala | 2 + 5 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 scaladoc-testcases/src/tests/refinedFunctionTypes.scala diff --git a/scaladoc-testcases/src/tests/refinedFunctionTypes.scala b/scaladoc-testcases/src/tests/refinedFunctionTypes.scala new file mode 100644 index 000000000000..d978a0ea2264 --- /dev/null +++ b/scaladoc-testcases/src/tests/refinedFunctionTypes.scala @@ -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 diff --git a/scaladoc-testcases/src/tests/thisType.scala b/scaladoc-testcases/src/tests/thisType.scala index 942e50af86ec..28cb55fcc49e 100644 --- a/scaladoc-testcases/src/tests/thisType.scala +++ b/scaladoc-testcases/src/tests/thisType.scala @@ -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] + = ??? diff --git a/scaladoc-testcases/src/tests/typesSignatures.scala b/scaladoc-testcases/src/tests/typesSignatures.scala index e7a29ad8c8e1..5b6281ec7cb5 100644 --- a/scaladoc-testcases/src/tests/typesSignatures.scala +++ b/scaladoc-testcases/src/tests/typesSignatures.scala @@ -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 diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 35cf1cb6eec3..47ec79d612ce 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -4,6 +4,7 @@ package tasty import scala.jdk.CollectionConverters._ import scala.quoted._ +import scala.util.control.NonFatal import NameNormalizer._ import SyntheticsSupport._ @@ -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 @@ -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") } @@ -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 => @@ -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 + + 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 diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala index 2b654d186aef..d60a4d82ff44 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/TranslatableSignaturesTestCases.scala @@ -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)