Skip to content

Commit cd9f0fb

Browse files
committed
Render classifier capabilities in scaladoc
1 parent d145f9e commit cd9f0fb

File tree

8 files changed

+263
-6
lines changed

8 files changed

+263
-6
lines changed

local/project/dummy/arrows.scala

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package dummy
2+
3+
import language.experimental.captureChecking
4+
import caps.*
5+
6+
trait Nested:
7+
val c: AnyRef^
8+
val next: Nested
9+
10+
trait Arrows:
11+
val a: AnyRef^
12+
val b: AnyRef^
13+
val c: AnyRef^
14+
15+
val purev: Int -> Int
16+
val purev2: Int ->{} Int
17+
val impurev: Int => Int
18+
val impurev2: Int ->{a,b,c} Int
19+
val impurev3: Int ->{a,b,c} Int => Int
20+
val impureCap: Int ->{cap} Int
21+
val impureCap2: Int ->{cap, a, b, c} Int
22+
val contextPureV: Int ?-> Int
23+
val contextPureV2: Int ?->{} Int
24+
val contextImpureV: Int ?=> Int
25+
val contextImpureV2: Int ?->{a,b,c} Int
26+
val contextImpureV3: Int ?->{a,b,c} Int ?=> Int
27+
val contextImpureCap: Int ?->{cap} Int
28+
val contextImpureCap2: Int ?->{cap, a, b, c} Int
29+
30+
def pure(f: Int -> Int): Int
31+
def pure2(f: Int ->{} Int): Int
32+
def impure(f: Int => Int): Int
33+
def impure2(f: Int ->{a,b,c} Int): Int
34+
def impure3(f: Int ->{a,b,c} Int => Int): Int
35+
36+
def consumes(@consume a: AnyRef^): Any
37+
def consumes2(@consume x: AnyRef^{a}, @consume y: AnyRef^{b}): Any
38+
39+
def reachThis: AnyRef^{this*}
40+
41+
def byNamePure(f: -> Int): Int
42+
def byNameImpure(f: ->{a,b,c} Int): Int
43+
def byNameImpure2(f: => Int): Int
44+
45+
def pathDependent(n: Nested^)(g: AnyRef^{n.c} => Any): Any
46+
def pathDependent2(n: Nested^)(g: AnyRef^{n.next.c} => Any): Any
47+
def pathDependent3(n: Nested^)(g: AnyRef^{n.c} => AnyRef^{n.next.c} ->{n.c} Any): Any
48+
def pathDependent4(n: Nested^)(g: AnyRef^{n.c} => AnyRef^{n.next.c} ->{n.c} Any): AnyRef^{n.next.next.c}
49+
def pathDependent5(n: Nested^)(g: AnyRef^{n.c} => AnyRef^{n.next.c} ->{n.c} Any): AnyRef^{n.next.next.c*, n.c, cap}
50+
51+
def contextPure(f: AnyRef^{a} ?-> Int): Int
52+
def contextImpure(f: AnyRef^{a} ?=> Int): Int
53+
def contextImpure2(f: AnyRef^{a} ?->{b,c} Int): Int
54+
def contextImpure3(f: AnyRef^{a} ?->{b,c} Int => AnyRef^{a} ?=> Int): Int
55+
56+
val noParams: () -> () -> Int
57+
val noParams2: () ->{} () ->{} Int
58+
val noParamsImpure: () => () => Int => Unit
59+
60+
val uncurried: (x: AnyRef^, y: AnyRef^) -> AnyRef^{x,y} => Int ->{x,y} Int
61+
val uncurried2: (x: AnyRef^, y: AnyRef^) -> AnyRef => Int ->{x,y} Int
62+
val uncurried3: (x: AnyRef^, y: AnyRef^) => AnyRef
63+
val uncurried4: (x: AnyRef^, y: AnyRef^) ->{a,b} AnyRef^ => Int ->{x,y} Int
64+
65+
val contextUncurried: (x: AnyRef^{a}, y: AnyRef^{b}) ?-> AnyRef^{x,y} ?-> Int ?->{x,y} Int
66+
val contextUncurried2: (x: AnyRef^{a}, y: AnyRef^{b}) ?-> AnyRef ?-> Int ?->{x,y} Int
67+
val contextUncurried3: (x: AnyRef^{a}, y: AnyRef^{b}) ?=> AnyRef
68+
val contextUncurried4: (x: AnyRef^{a}, y: AnyRef^{b}) ?->{a,b} AnyRef^ ?=> Int ?->{x,y} Int
69+
70+
def polyPure[A](f: A -> Int): Int
71+
def polyPure2[A](f: A ->{} Int): Int
72+
def polyImpure[A](f: A => Int): Int
73+
def polyImpure2[A](f: A ->{a,b,c} Int): Int
74+
def polyImpure3[A](f: A ->{a,b,c} Int => Int): Int
75+
76+
def polyContextPure[A](f: A ?-> Int): Int
77+
def polyContextPure2[A](f: A ?->{} Int): Int
78+
def polyContextImpure[A](f: A ?=> Int): Int
79+
def polyContextImpure2[A](f: A ?->{a,b,c} Int): Int
80+
def polyContextImpure3[A](f: A ?->{a,b,c} Int => Int): Int
81+
82+
val polyPureV: [A] => A -> Int
83+
val polyPureV2: [A] => Int => A ->{a,b,c} Int
84+
val polyImpureV: [A] -> A => Int
85+
val polyImpureV2: [A] -> A => Int

local/project/dummy/capturevars.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package dummy
2+
3+
import language.experimental.captureChecking
4+
import caps.*
5+
6+
trait Test:
7+
val a: AnyRef^
8+
val b: AnyRef^
9+
type Ordinary
10+
type Ordinary2 >: Int <: String
11+
type T[-C^ >: {a,b}]
12+
type U[+C^]
13+
type Foo = [C^ >: {a,b} <: {a,b,cap}] =>> AnyRef^{C}
14+
type C^
15+
type D^ >: {C} <: {a,b}
16+
type E^ <: C
17+
type F^ <: {D,E}
18+
type G^ = C
19+
type H^ = {C}
20+
def foo[C^ >: {a,b}](x: T[C]): Unit
21+
def bar(x: T[{a,b}]): Unit
22+
def baz(x: T[{a,b,caps.cap}]): Unit
23+
def foo2[C^](x: U[C]): Unit
24+
def bar2(x: U[{a,b,cap}]): Unit
25+
def baz2(x: U[{caps.cap}]): Unit
26+
def test[E^, F^ >: {caps.cap} <: {}, G <: [C^ >: {a,b} <: {a,b}] =>> AnyRef^{C}](x: T[{E,a,b}], y: U[F]): Unit
27+
val poly: [C^ >: {a,b}] => (f: () ->{C} Unit) -> Int ->{C} Unit
28+
29+
def readup[T^ <: {cap.rd}]() = ()
30+
def readlo[T^ >: {cap.rd}]() = ()

local/project/dummy/colltest.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package dummy
2+
3+
import language.experimental.captureChecking
4+
// Showing a problem with recursive references
5+
object CollectionStrawMan5 {
6+
7+
/** Base trait for generic collections */
8+
trait Iterable[+A] extends IterableLike[A] {
9+
def iterator: Iterator[A]^{this}
10+
def coll: Iterable[A]^{this} = this
11+
}
12+
13+
trait IterableLike[+A]:
14+
def coll: Iterable[A]^{this}
15+
def partition(p: A => Boolean): Unit =
16+
val pn = Partition(coll, p)
17+
()
18+
19+
/** Concrete collection type: View */
20+
trait View[+A] extends Iterable[A] with IterableLike[A]
21+
22+
case class Partition[A](val underlying: Iterable[A]^, p: A => Boolean) {
23+
24+
class Partitioned(expected: Boolean) extends View[A]:
25+
this: Partitioned^{Partition.this} =>
26+
def iterator: Iterator[A]^{this} =
27+
underlying.iterator.filter((x: A) => p(x) == expected)
28+
29+
val left: Partitioned^{Partition.this} = Partitioned(true)
30+
val right: Partitioned^{Partition.this} = Partitioned(false)
31+
}
32+
33+
}
34+
35+
object CollectionWithPureSelf {
36+
trait Concat[+A, CC[_]] {
37+
def concat[B >: A](other: CC[B]^): CC[B]^{this, other}
38+
def apply[B](f: A => B): CollectionStrawMan5.Iterable[B]^{f, this}
39+
}
40+
41+
trait Seq[+A] extends Concat[A, Seq] {
42+
self: Seq[A] =>
43+
}
44+
45+
trait ImmutSeq[+A] extends Seq[A] { }
46+
}

local/project/dummy/nocc.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dummy
2+
3+
trait NoCaptureChecking:
4+
def byName(f: => Int): Int
5+
def impure(f: Int => Int): Int
6+
def context(f: Int ?=> Int): Int
7+
def dependent(f: (x: Int) => x.type): Int

local/project/dummy/sep-pairs.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dummy
2+
import language.experimental.captureChecking
3+
import caps.Mutable
4+
import caps.{cap, consume, use}
5+
6+
class Ref extends Mutable:
7+
var x = 0
8+
def get: Int = x
9+
update def put(y: Int): Unit = x = y
10+
11+
case class Pair[+A, +B](fst: A, snd: B)
12+
13+
def mkPair: Pair[Ref^, Ref^] =
14+
val r1 = Ref()
15+
val r2 = Ref()
16+
val p_exact: Pair[Ref^{r1}, Ref^{r2}] = Pair(r1, r2)
17+
p_exact
18+
19+
def copyPair[C^, D^](@consume p: Pair[Ref^{C}, Ref^{D}]): Pair[Ref^{C}, Ref^{D}] =
20+
val x: Ref^{C} = p.fst
21+
val y: Ref^{D} = p.snd
22+
Pair[Ref^{C}, Ref^{D}](x, y)
23+
24+
trait TestRd:
25+
@consume def copyPair(p: Pair[Ref^, Ref^]): Pair[Ref^{p.fst*}, Ref^{p.snd*}]
26+
def rdPair(@consume p: Pair[Ref^, Ref^]): Int ->{p.fst*.rd} Int
27+
val rdPairV: (p: Pair[Ref^, Ref^]) => Int ->{p.fst*, p.snd*.rd} Int

mystuff.sbt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import sbt._
2+
import sbt.io.IO
3+
4+
import sbt.dsl.LinterLevel.Ignore
5+
6+
lazy val compileSrcTree = taskKey[Unit]("Example project")
7+
8+
compileSrcTree := {
9+
val log = streams.value.log
10+
val baseDir = baseDirectory.value
11+
val srcTreeDir = baseDir / "local" / "project"
12+
val outDir = baseDir / "local" / "out"
13+
14+
IO.delete(outDir)
15+
IO.createDirectory(outDir) // mkdir -p
16+
17+
val sources: Seq[String] =
18+
(srcTreeDir ** "*.scala").get.map(_.getPath) // find all .scala
19+
20+
if (sources.isEmpty)
21+
streams.value.log.warn(s"No .scala files found under $srcTreeDir")
22+
else {
23+
val cmd = ("scalac" +: "-d" +: outDir.getPath +: sources).mkString(" ")
24+
Command.process(cmd, state.value)
25+
}
26+
}
27+
28+
lazy val ensureApiDir = taskKey[Unit]("Create <repo>/local/api if it’s missing")
29+
30+
ensureApiDir := {
31+
val dir = (ThisBuild / baseDirectory).value / "local" / "api"
32+
IO.createDirectory(dir)
33+
}
34+
35+
addCommandAlias(
36+
"myrefresh",
37+
";compileSrcTree; ensureApiDir ; scaladoc/runMain dotty.tools.scaladoc.Main -siteroot /dev/null -project Foo -project-version 0.0.1 -d local/api local/out"
38+
)
39+
40+
addCommandAlias(
41+
"myscaladoc",
42+
"; ensureApiDir ; scaladoc/runMain dotty.tools.scaladoc.Main -siteroot /dev/null -project Foo -project-version 0.0.1 -d local/api local/out"
43+
)

scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ object CaptureDefs:
3636
qctx.reflect.Symbol.requiredClass("scala.annotation.internal.readOnlyCapability")
3737
def RequiresCapabilityAnnot(using qctx: Quotes) =
3838
qctx.reflect.Symbol.requiredClass("scala.annotation.internal.requiresCapability")
39+
def OnlyCapabilityAnnot(using qctx: Quotes) =
40+
qctx.reflect.Symbol.requiredClass("scala.annotation.internal.onlyCapability")
3941

4042
def LanguageExperimental(using qctx: Quotes) =
4143
qctx.reflect.Symbol.requiredPackage("scala.language.experimental")
@@ -71,6 +73,9 @@ extension (using qctx: Quotes)(ann: qctx.reflect.Symbol)
7173

7274
def isReadOnlyCapabilityAnnot: Boolean =
7375
ann == CaptureDefs.ReadOnlyCapabilityAnnot
76+
77+
def isOnlyCapabilityAnnot: Boolean =
78+
ann == CaptureDefs.OnlyCapabilityAnnot
7479
end extension
7580

7681
extension (using qctx: Quotes)(tpe: qctx.reflect.TypeRepr) // FIXME clean up and have versions on Symbol for those
@@ -171,6 +176,17 @@ object ReadOnlyCapability:
171176
case _ => None
172177
end ReadOnlyCapability
173178

179+
object OnlyCapability:
180+
def unapply(using qctx: Quotes)(ty: qctx.reflect.TypeRepr): Option[(qctx.reflect.TypeRepr, qctx.reflect.Symbol)] =
181+
import qctx.reflect._
182+
ty match
183+
case AnnotatedType(base, app @ Apply(TypeApply(Select(New(annot), _), _), Nil)) if annot.tpe.typeSymbol.isOnlyCapabilityAnnot =>
184+
app.tpe.typeArgs.head.classSymbol.match
185+
case Some(clazzsym) => Some((base, clazzsym))
186+
case None => None
187+
case _ => None
188+
end OnlyCapability
189+
174190
/** Decompose capture sets in the union-type-encoding into the sequence of atomic `TypeRepr`s.
175191
* Returns `None` if the type is not a capture set.
176192
*/
@@ -187,8 +203,9 @@ def decomposeCaptureRefs(using qctx: Quotes)(typ0: qctx.reflect.TypeRepr): Optio
187203
case t @ ParamRef(_, _) => include(t)
188204
case t @ ReachCapability(_) => include(t)
189205
case t @ ReadOnlyCapability(_) => include(t)
190-
case t : TypeRef => include(t) // FIXME: does this need a more refined check?
191-
case _ => report.warning(s"Unexpected type tree $typ while trying to extract capture references from $typ0"); false // TODO remove warning eventually
206+
case t @ OnlyCapability(_, _) => include(t)
207+
case t : TypeRef => include(t)
208+
case _ => report.warning(s"Unexpected type tree $typ while trying to extract capture references from $typ0"); false
192209
if traverse(typ0) then Some(buffer.toList) else None
193210
end decomposeCaptureRefs
194211

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import dotty.tools.scaladoc.cc.*
1010

1111
import NameNormalizer._
1212
import SyntheticsSupport._
13+
import java.awt.RenderingHints.Key
1314

1415
trait TypesSupport:
1516
self: TastyParser =>
@@ -514,10 +515,11 @@ trait TypesSupport:
514515
private def emitCapability(using Quotes)(ref: reflect.TypeRepr, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature =
515516
import reflect._
516517
ref match
517-
case ReachCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword("*")
518-
case ReadOnlyCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword(".rd")
519-
case ThisType(_) => List(Keyword("this"))
520-
case t => inner(t, skipThisTypePrefix)(using skipTypeSuffix = true, inCC = Some(Nil))
518+
case ReachCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword("*")
519+
case ReadOnlyCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword(".rd")
520+
case OnlyCapability(c, cls) => emitCapability(c, skipThisTypePrefix) ++ List(Plain("."), Keyword("only"), Plain("[")) ++ inner(cls.typeRef, skipThisTypePrefix) :+ Plain("]")
521+
case ThisType(_) => List(Keyword("this"))
522+
case t => inner(t, skipThisTypePrefix)(using skipTypeSuffix = true, inCC = Some(Nil))
521523

522524
private def emitCaptureSet(using Quotes)(refs: List[reflect.TypeRepr], skipThisTypePrefix: Boolean, omitCap: Boolean = true)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature =
523525
import reflect._

0 commit comments

Comments
 (0)