Skip to content

Commit f8ff12f

Browse files
committed
Nice capture-set rendering
1 parent b13135f commit f8ff12f

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
@@ -101,7 +101,16 @@ trait TypesSupport:
101101
inParens(inner(left), shouldWrapInParens(left, tp, true))
102102
++ keyword(" & ").l
103103
++ inParens(inner(right), shouldWrapInParens(right, tp, false))
104-
case ByNameType(tpe) => keyword("=> ") :: inner(tpe)
104+
case CapturingType(base, refs) =>
105+
inner(base) ++ renderCaptureSet(refs)
106+
case ByNameType(CapturingType(tpe, refs)) =>
107+
refs match
108+
case Nil => keyword("-> ") :: inner(tpe)
109+
case List(ref) if ref.isCaptureRoot =>
110+
keyword("=> ") :: inner(tpe)
111+
case refs =>
112+
keyword("->") :: (renderCaptureSet(refs) ++ inner(tpe))
113+
case ByNameType(tpe) => keyword("=> ") :: inner(tpe) // FIXME: does it need change for CC?
105114
case ConstantType(constant) =>
106115
plain(constant.show).l
107116
case ThisType(tpe) =>
@@ -114,8 +123,6 @@ trait TypesSupport:
114123
inner(tpe) :+ plain("*")
115124
case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) =>
116125
inner(tpe) :+ plain("*")
117-
case AnnotatedType(tpe, annotTerm) if annotTerm.isRetains =>
118-
inner(tpe) :+ plain(" @retains") // FIXME
119126
case AnnotatedType(tpe, _) =>
120127
inner(tpe)
121128
case tl @ TypeLambda(params, paramBounds, AppliedType(tpe, args))

0 commit comments

Comments
 (0)