Skip to content

Commit ab602f6

Browse files
committed
Implement getScaladoc2Type without using tree show
Showing the tree or type does not return a reliable representation. The only way to create this correctly is be creating a custom printer that prints in the required format. Two bugs where found: * If the definition was annotated the result was nonsensical * `def f(implicit ly: T): R` and `def f(implicitly: T): R` produced the same anchor (`f(implicitly:T):R`).
1 parent c466fa0 commit ab602f6

File tree

2 files changed

+65
-8
lines changed

2 files changed

+65
-8
lines changed

scaladoc/src/dotty/tools/scaladoc/tasty/Scaladoc2AnchorCreator.scala

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,67 @@ import scala.util.matching.Regex
77

88
object Scaladoc2AnchorCreator:
99

10-
def getScaladoc2Type(using Quotes)(t: reflect.Tree) =
11-
import reflect.*
12-
val regex = t match
13-
case d: DefDef => "def"
14-
case t: TypeDef => "type"
15-
case v: ValDef => "val|var"
16-
t.show(using Printer.TreeShortCode).split(regex, 2)(1).replace(" ","")
10+
def getScaladoc2Type(using Quotes)(sym: quotes.reflect.Symbol) = signatureAnchor(sym)
11+
12+
/** Creates the signature anchor
13+
*
14+
* - `X` for a `type X ...`
15+
* - `x:X` for a `val x: X`
16+
* - `f[U1,...](x1:T1,...)...:R` for a `def f[U1, ...](x1: T1, ...)...: R`
17+
*
18+
* Types are printed without their paths. No spaces are printed in the output
19+
* except for the one after `(implicit`.
20+
*/
21+
private def signatureAnchor(using Quotes)(sym: quotes.reflect.Symbol): String =
22+
import quotes.reflect.*
23+
def signatureType(tp: quotes.reflect.TypeRepr): String =
24+
tp match
25+
case mt @ MethodType(paramNames, paramTypes, res) =>
26+
val openClause = if mt.isImplicit then "(implicit " else "("
27+
val closeClause = res match
28+
case _: MethodOrPoly => ")"
29+
case _ => "):"
30+
paramNames.zip(paramTypes.map(signatureType))
31+
.map((name, tpe) => s"$name:$tpe")
32+
.mkString(openClause, ",", closeClause) + signatureType(res)
33+
case PolyType(paramNames, paramBounds, res) =>
34+
val closeClause = res match
35+
case _: MethodOrPoly => "]"
36+
case _ => "]:"
37+
paramNames.zip(paramBounds.map(signatureType))
38+
.map((name, tpe) => s"$name$tpe")
39+
.mkString("[", ",", closeClause) + signatureType(res)
40+
case TypeLambda(paramNames, paramBounds, res) =>
41+
paramNames.zip(paramBounds.map(signatureType))
42+
.map((name, tpe) => s"$name$tpe")
43+
.mkString("[", ",", "]") + "=>" + signatureType(res)
44+
case ByNameType(tp) =>
45+
":" + signatureType(tp)
46+
case TypeBounds(low, hi) =>
47+
val lowBound = if low =:= defn.NothingClass.typeRef then "" else ">:" + signatureType(low)
48+
val hiBound = if low =:= defn.AnyClass.typeRef then "" else "<:" + signatureType(hi)
49+
lowBound + hiBound
50+
case tp: ParamRef =>
51+
tp.binder match
52+
case binder: MethodType => binder.paramNames(tp.paramNum) + ".type"
53+
case binder: PolyType => binder.paramNames(tp.paramNum)
54+
case binder: LambdaType => binder.paramNames(tp.paramNum)
55+
case AppliedType(tycon, args) =>
56+
args.map {
57+
case tp: TypeBounds => "_" + signatureType(tp)
58+
case tp => signatureType(tp)
59+
}.mkString(signatureType(tycon) + "[", ",", "]")
60+
case tp: AnnotatedType =>
61+
signatureType(tp.underlying) + "@" + tp.annotation.symbol.owner.name
62+
case tp: ThisType =>
63+
signatureType(tp.tref) + ".this"
64+
case tp: TypeRef =>
65+
tp.name
66+
case tp =>
67+
// TODO handle other cases without using show (show does not have a stable representation)
68+
tp.show(using Printer.TypeReprShortCode).replace(" ","")
69+
70+
sym match
71+
case sym if sym.isType => sym.name
72+
case sym if sym.flags.is(Flags.Method) => sym.name + signatureType(sym.info)
73+
case sym => sym.name + ":" + signatureType(sym.info)

scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ class SymOpsWithLinkCache:
235235
def constructPathForScaladoc2: String =
236236
val l = escapeUrl(location.mkString("/"))
237237
val scaladoc2Anchor = if anchor.isDefined then {
238-
"#" + getScaladoc2Type(sym.tree)
238+
"#" + getScaladoc2Type(sym)
239239
} else ""
240240
docURL + l + extension + scaladoc2Anchor
241241

0 commit comments

Comments
 (0)