Skip to content

Commit 3ad644b

Browse files
bracevacnatsukagami
authored andcommitted
Nice capture-set rendering
1 parent 4cdcb29 commit 3ad644b

File tree

3 files changed

+90
-26
lines changed

3 files changed

+90
-26
lines changed
Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,86 @@
1-
package dotty.tools.scaladoc.cc
1+
package dotty.tools.scaladoc
2+
3+
package cc
24

35
import scala.quoted._
46

57
object CaptureDefs:
6-
/** The name of the `retains` annotation. */
7-
val RetainsName: String = "scala.annotation.retains"
8-
9-
/** The name of the `retainsCap` annotation. */
10-
val RetainsCapName: String = "scala.annotation.retainsCap"
8+
// these should become part of the reflect API in the distant future
9+
def retains(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.annotation.retains")
10+
def retainsCap(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.annotation.retainsCap")
11+
def retainsByName(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.annotation.retainsByName")
12+
def CapsModule(using qctx: Quotes) = qctx.reflect.Symbol.requiredPackage("scala.caps")
13+
def captureRoot(using qctx: Quotes) = qctx.reflect.Symbol.requiredPackage("scala.caps.cap")
14+
def Caps_Capability(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.caps.Capability")
15+
def Caps_CapSet(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.caps.CapSet")
16+
def Caps_Mutable(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.caps.Mutable")
17+
def Caps_SharedCapability(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.caps.SharedCapability")
1118

12-
/** The name of the `retainsByName` annotation. */
13-
val RetainsByNameName: String = "scala.annotation.retainsByName"
19+
def UseAnnot(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.caps.use")
20+
def ConsumeAnnot(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.caps.consume")
1421

15-
def retains(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass(RetainsName)
16-
def retainsCap(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass(RetainsCapName)
17-
def retainsByName(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass(RetainsByNameName)
1822
end CaptureDefs
1923

20-
extension (using qctx: Quotes)(term: qctx.reflect.Term)
21-
22-
/** Is this term a `retains`* annotations from capturing types? */
24+
extension (using qctx: Quotes)(ann: qctx.reflect.Symbol)
25+
/** This symbol is one of `retains` or `retainsCap` */
2326
def isRetains: Boolean =
24-
val sym = term.tpe match
25-
case qctx.reflect.AppliedType(base, _) => base.typeSymbol
26-
case other => other.typeSymbol
27-
sym == CaptureDefs.retains
28-
|| sym == CaptureDefs.retainsCap
29-
|| sym == CaptureDefs.retainsByName
27+
ann == CaptureDefs.retains || ann == CaptureDefs.retainsCap
28+
29+
/** This symbol is one of `retains`, `retainsCap`, or `retainsByName` */
30+
def isRetainsLike: Boolean =
31+
ann.isRetains || ann == CaptureDefs.retainsByName
32+
end extension
33+
34+
extension (using qctx: Quotes)(tpe: qctx.reflect.TypeRepr)
35+
def isCaptureRoot: Boolean = tpe.termSymbol == CaptureDefs.captureRoot
36+
end extension
37+
38+
/** Decompose capture sets in the union-type-encoding into the sequence of atomic `TypeRepr`s.
39+
* Returns `None` if the type is not a capture set.
40+
*/
41+
def decomposeCaptureRefs(using qctx: Quotes)(typ0: qctx.reflect.TypeRepr): Option[List[qctx.reflect.TypeRepr]] =
42+
import qctx.reflect._
43+
val buffer = collection.mutable.ListBuffer.empty[TypeRepr]
44+
def traverse(typ: TypeRepr): Boolean =
45+
typ match
46+
case OrType(t1, t2) => traverse(t1) && traverse(t2)
47+
case t @ ThisType(_) => buffer += t; true
48+
case t @ TermRef(_, _) => buffer += t; true
49+
case t @ ParamRef(_, _) => buffer += t; true
50+
// TODO: are atoms only ever the above? Then we could refine the return type
51+
case _ => report.warning(s"Unexpected type tree $typ while trying to extract capture references from $typ0"); System.exit(1); false // TODO remove warning eventually
52+
if traverse(typ0) then Some(buffer.toList) else None
53+
end decomposeCaptureRefs
54+
55+
object CaptureSetType:
56+
def unapply(using qctx: Quotes)(tt: qctx.reflect.TypeTree): Option[List[qctx.reflect.TypeRepr]] = decomposeCaptureRefs(tt.tpe)
57+
end CaptureSetType
58+
59+
object CapturingType:
60+
def unapply(using qctx: Quotes)(typ: qctx.reflect.TypeRepr): Option[(qctx.reflect.TypeRepr, List[qctx.reflect.TypeRepr])] =
61+
import qctx.reflect._
62+
typ match
63+
case AnnotatedType(base, Apply(TypeApply(Select(New(annot), _), List(CaptureSetType(refs))), Nil)) if annot.symbol.isRetainsLike =>
64+
Some((base, refs))
65+
case AnnotatedType(base, Apply(Select(New(annot), _), Nil)) if annot.symbol == CaptureDefs.retainsCap =>
66+
Some((base, List(CaptureDefs.captureRoot.termRef)))
67+
case _ => None
68+
end CapturingType
69+
70+
def renderCaptureSet(using qctx: Quotes)(refs: List[qctx.reflect.TypeRepr]): List[SignaturePart] =
71+
import dotty.tools.scaladoc.tasty.NameNormalizer._
72+
import qctx.reflect._
73+
refs match
74+
case List(ref) if ref.isCaptureRoot => List(Keyword("^"))
75+
case refs =>
76+
val res0 = refs.map { ref =>
77+
ref match
78+
case ThisType(_) => List(Keyword("this"))
79+
case TermRef(_, sym) => List(Plain(sym)) // FIXME: use type other than Plain, can we have clickable links to say, caps.cap and other things?
80+
case pf @ ParamRef(tpe, i) => List(Plain(tpe.asInstanceOf[MethodType].paramNames(i))) // FIXME: not sure if this covers all cases
81+
case _ => List(Plain("<unknown>"))
82+
}
83+
val res1 = res0 match
84+
case Nil => Nil
85+
case other => other.reduce((r, e) => r ++ (List(Plain(", ")) ++ e))
86+
Keyword("^") :: Plain("{") :: (res1 ++ List(Plain("}")))

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ object NameNormalizer {
1717
val escaped = escapedName(constructorNormalizedName)
1818
escaped
1919
}
20-
20+
2121
def ownerNameChain: List[String] = {
2222
import reflect.*
2323
if s.isNoSymbol then List.empty
2424
else if s == defn.EmptyPackageClass then List.empty
2525
else if s == defn.RootPackage then List.empty
2626
else if s == defn.RootClass then List.empty
2727
else s.owner.ownerNameChain :+ s.normalizedName
28-
}
29-
28+
}
29+
3030
def normalizedFullName: String =
3131
s.ownerNameChain.mkString(".")
3232

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,16 @@ trait TypesSupport:
107107
inParens(inner(left, skipThisTypePrefix), shouldWrapInParens(left, tp, true))
108108
++ keyword(" & ").l
109109
++ inParens(inner(right, skipThisTypePrefix), shouldWrapInParens(right, tp, false))
110-
case ByNameType(tpe) => keyword("=> ") :: inner(tpe, skipThisTypePrefix)
110+
case CapturingType(base, refs) =>
111+
inner(base, skipThisTypePrefix) ++ renderCaptureSet(refs)
112+
case ByNameType(CapturingType(tpe, refs)) =>
113+
refs match
114+
case Nil => keyword("-> ") :: inner(tpe, skipThisTypePrefix)
115+
case List(ref) if ref.isCaptureRoot =>
116+
keyword("=> ") :: inner(tpe, skipThisTypePrefix)
117+
case refs =>
118+
keyword("->") :: (renderCaptureSet(refs) ++ inner(tpe, skipThisTypePrefix))
119+
case ByNameType(tpe) => keyword("=> ") :: inner(tpe, skipThisTypePrefix) // FIXME: does it need change for CC?
111120
case ConstantType(constant) =>
112121
plain(constant.show).l
113122
case ThisType(tpe) =>
@@ -118,8 +127,6 @@ trait TypesSupport:
118127
inner(tpe, skipThisTypePrefix) :+ plain("*")
119128
case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) =>
120129
inner(tpe, skipThisTypePrefix) :+ plain("*")
121-
case AnnotatedType(tpe, annotTerm) if annotTerm.isRetains =>
122-
inner(tpe, skipThisTypePrefix) :+ plain(" @retains") // FIXME
123130
case AnnotatedType(tpe, _) =>
124131
inner(tpe, skipThisTypePrefix)
125132
case tl @ TypeLambda(params, paramBounds, AppliedType(tpe, args))

0 commit comments

Comments
 (0)