diff --git a/local/project/dummy/arrows.scala b/local/project/dummy/arrows.scala new file mode 100644 index 000000000000..c367bbc78f13 --- /dev/null +++ b/local/project/dummy/arrows.scala @@ -0,0 +1,93 @@ +package dummy + +import language.experimental.captureChecking +import caps.* + +trait Nested: + val c: AnyRef^ + val next: Nested + +trait Arrows: + val a: AnyRef^ + val b: AnyRef^ + val c: AnyRef^ + + val purev: Int -> Int + val purev2: Int ->{} Int + val impurev: Int => Int + val impurev2: Int ->{a,b,c} Int + val impurev3: Int ->{a,b,c} Int => Int + val impureCap: Int ->{cap} Int + val impureCap2: Int ->{cap, a, b, c} Int + val contextPureV: Int ?-> Int + val contextPureV2: Int ?->{} Int + val contextImpureV: Int ?=> Int + val contextImpureV2: Int ?->{a,b,c} Int + val contextImpureV3: Int ?->{a,b,c} Int ?=> Int + val contextImpureCap: Int ?->{cap} Int + val contextImpureCap2: Int ?->{cap, a, b, c} Int + + def pure(f: Int -> Int): Int + def pure2(f: Int ->{} Int): Int + def impure(f: Int => Int): Int + def impure2(f: Int ->{a,b,c} Int): Int + def impure3(f: Int ->{a,b,c} Int => Int): Int + + def uses(@use a: AnyRef^): Any + def uses2(@use x: AnyRef^{a}, @use y: AnyRef^{b}): Any + + def consumes(@consume a: AnyRef^): Any + def consumes2(@consume x: AnyRef^{a}, @consume y: AnyRef^{b}): Any + + def usesAndConsumes(@use a: AnyRef^, @consume b: AnyRef^): Any + def usesAndConsumes2(@use @consume x: AnyRef^{a}): Any + def consumesAndUses(@consume @use x: AnyRef^{a}): Any + def consumesAndUses2(@consume @use x: List[AnyRef^]): Array[AnyRef^{x*}] + + def reachThis: AnyRef^{this*} + + def byNamePure(f: -> Int): Int + def byNameImpure(f: ->{a,b,c} Int): Int + def byNameImpure2(f: => Int): Int + + def pathDependent(n: Nested^)(g: AnyRef^{n.c} => Any): Any + def pathDependent2(n: Nested^)(g: AnyRef^{n.next.c} => Any): Any + def pathDependent3(n: Nested^)(g: AnyRef^{n.c} => AnyRef^{n.next.c} ->{n.c} Any): Any + def pathDependent4(n: Nested^)(g: AnyRef^{n.c} => AnyRef^{n.next.c} ->{n.c} Any): AnyRef^{n.next.next.c} + def pathDependent5(n: Nested^)(g: AnyRef^{n.c} => AnyRef^{n.next.c} ->{n.c} Any): AnyRef^{n.next.next.c*, n.c, cap} + + def contextPure(f: AnyRef^{a} ?-> Int): Int + def contextImpure(f: AnyRef^{a} ?=> Int): Int + def contextImpure2(f: AnyRef^{a} ?->{b,c} Int): Int + def contextImpure3(f: AnyRef^{a} ?->{b,c} Int => AnyRef^{a} ?=> Int): Int + + val noParams: () -> () -> Int + val noParams2: () ->{} () ->{} Int + val noParamsImpure: () => () => Int => Unit + + val uncurried: (x: AnyRef^, y: AnyRef^) -> AnyRef^{x,y} => Int ->{x,y} Int + val uncurried2: (x: AnyRef^, y: AnyRef^) -> AnyRef => Int ->{x,y} Int + val uncurried3: (x: AnyRef^, y: AnyRef^) => AnyRef + val uncurried4: (x: AnyRef^, y: AnyRef^) ->{a,b} AnyRef^ => Int ->{x,y} Int + + val contextUncurried: (x: AnyRef^{a}, y: AnyRef^{b}) ?-> AnyRef^{x,y} ?-> Int ?->{x,y} Int + val contextUncurried2: (x: AnyRef^{a}, y: AnyRef^{b}) ?-> AnyRef ?-> Int ?->{x,y} Int + val contextUncurried3: (x: AnyRef^{a}, y: AnyRef^{b}) ?=> AnyRef + val contextUncurried4: (x: AnyRef^{a}, y: AnyRef^{b}) ?->{a,b} AnyRef^ ?=> Int ?->{x,y} Int + + def polyPure[A](f: A -> Int): Int + def polyPure2[A](f: A ->{} Int): Int + def polyImpure[A](f: A => Int): Int + def polyImpure2[A](f: A ->{a,b,c} Int): Int + def polyImpure3[A](f: A ->{a,b,c} Int => Int): Int + + def polyContextPure[A](f: A ?-> Int): Int + def polyContextPure2[A](f: A ?->{} Int): Int + def polyContextImpure[A](f: A ?=> Int): Int + def polyContextImpure2[A](f: A ?->{a,b,c} Int): Int + def polyContextImpure3[A](f: A ?->{a,b,c} Int => Int): Int + + val polyPureV: [A] => A -> Int + val polyPureV2: [A] => Int => A ->{a,b,c} Int + val polyImpureV: [A] -> A => Int + val polyImpureV2: [A] -> A => Int \ No newline at end of file diff --git a/local/project/dummy/capturevars.scala b/local/project/dummy/capturevars.scala new file mode 100644 index 000000000000..ea351e889dfe --- /dev/null +++ b/local/project/dummy/capturevars.scala @@ -0,0 +1,27 @@ +package dummy + +import language.experimental.captureChecking +import caps.* + +trait Test: + val a: AnyRef^ + val b: AnyRef^ + type Ordinary + type Ordinary2 >: Int <: String + type T[-C^ >: {a,b}] + type U[+C^] + type Foo = [C^ >: {a,b} <: {a,b,cap}] =>> AnyRef^{C} + type C^ + type D^ >: {C} <: {a,b} + type E^ <: C + type F^ <: {D,E} + type G^ = C + type H^ = {C} + def foo[C^ >: {a,b}](x: T[C]): Unit + def bar(x: T[{a,b}]): Unit + def baz(x: T[{a,b,caps.cap}]): Unit + def foo2[C^](x: U[C]): Unit + def bar2(x: U[{a,b,cap}]): Unit + def baz2(x: U[{caps.cap}]): Unit + def test[E^, F^ >: {caps.cap} <: {}, G <: [C^ >: {a,b} <: {a,b}] =>> AnyRef^{C}](x: T[{E,a,b}], y: U[F]): Unit + val poly: [C^ >: {a,b}] => (f: () ->{C} Unit) -> Int ->{C} Unit diff --git a/local/project/dummy/colltest.scala b/local/project/dummy/colltest.scala new file mode 100644 index 000000000000..32561aad6754 --- /dev/null +++ b/local/project/dummy/colltest.scala @@ -0,0 +1,34 @@ +package dummy + +import language.experimental.captureChecking +// Showing a problem with recursive references +object CollectionStrawMan5 { + + /** Base trait for generic collections */ + trait Iterable[+A] extends IterableLike[A] { + def iterator: Iterator[A]^{this} + def coll: Iterable[A]^{this} = this + } + + trait IterableLike[+A]: + def coll: Iterable[A]^{this} + def partition(p: A => Boolean): Unit = + val pn = Partition(coll, p) + () + + /** Concrete collection type: View */ + trait View[+A] extends Iterable[A] with IterableLike[A] + + case class Partition[A](val underlying: Iterable[A]^, p: A => Boolean) { + + class Partitioned(expected: Boolean) extends View[A]: + this: Partitioned^{Partition.this} => + def iterator: Iterator[A]^{this} = + underlying.iterator.filter((x: A) => p(x) == expected) + + val left: Partitioned^{Partition.this} = Partitioned(true) + val right: Partitioned^{Partition.this} = Partitioned(false) + } + + +} \ No newline at end of file diff --git a/local/project/dummy/nocc.scala b/local/project/dummy/nocc.scala new file mode 100644 index 000000000000..2f34304c7015 --- /dev/null +++ b/local/project/dummy/nocc.scala @@ -0,0 +1,7 @@ +package dummy + +trait NoCaptureChecking: + def byName(f: => Int): Int + def impure(f: Int => Int): Int + def context(f: Int ?=> Int): Int + def dependent(f: (x: Int) => x.type): Int diff --git a/local/project/dummy/sep-pairs.scala b/local/project/dummy/sep-pairs.scala new file mode 100644 index 000000000000..bf02242e8aa3 --- /dev/null +++ b/local/project/dummy/sep-pairs.scala @@ -0,0 +1,27 @@ +package dummy +import language.experimental.captureChecking +import caps.Mutable +import caps.{cap, consume, use} + +class Ref extends Mutable: + var x = 0 + def get: Int = x + update def put(y: Int): Unit = x = y + +case class Pair[+A, +B](fst: A, snd: B) + +def mkPair: Pair[Ref^, Ref^] = + val r1 = Ref() + val r2 = Ref() + val p_exact: Pair[Ref^{r1}, Ref^{r2}] = Pair(r1, r2) + p_exact + +def copyPair(@consume @use p: Pair[Ref^, Ref^]): Pair[Ref^, Ref^] = + val x: Ref^{p.fst*} = p.fst + val y: Ref^{p.snd*} = p.snd + Pair(x, y) + +trait TestRd: + @consume def copyPair(@use p: Pair[Ref^, Ref^]): Pair[Ref^{p.fst*}, Ref^{p.snd*}] + def rdPair(@consume p: Pair[Ref^, Ref^]): Int ->{p.fst*.rd} Int + val rdPairV: (p: Pair[Ref^, Ref^]) => Int ->{p.fst*, p.snd*.rd} Int diff --git a/mystuff.sbt b/mystuff.sbt new file mode 100644 index 000000000000..30da6fd54da1 --- /dev/null +++ b/mystuff.sbt @@ -0,0 +1,43 @@ +import sbt._ +import sbt.io.IO + +import sbt.dsl.LinterLevel.Ignore + +lazy val compileSrcTree = taskKey[Unit]("Example project") + +compileSrcTree := { + val log = streams.value.log + val baseDir = baseDirectory.value + val srcTreeDir = baseDir / "local" / "project" + val outDir = baseDir / "local" / "out" + + IO.delete(outDir) + IO.createDirectory(outDir) // mkdir -p + + val sources: Seq[String] = + (srcTreeDir ** "*.scala").get.map(_.getPath) // find all .scala + + if (sources.isEmpty) + streams.value.log.warn(s"No .scala files found under $srcTreeDir") + else { + val cmd = ("scalac" +: "-d" +: outDir.getPath +: sources).mkString(" ") + Command.process(cmd, state.value) + } +} + +lazy val ensureApiDir = taskKey[Unit]("Create /local/api if it’s missing") + +ensureApiDir := { + val dir = (ThisBuild / baseDirectory).value / "local" / "api" + IO.createDirectory(dir) +} + +addCommandAlias( + "myrefresh", + ";compileSrcTree; ensureApiDir ; scaladoc/runMain dotty.tools.scaladoc.Main -siteroot /dev/null -project Foo -project-version 0.0.1 -d local/api local/out" +) + +addCommandAlias( + "myscaladoc", + "; ensureApiDir ; scaladoc/runMain dotty.tools.scaladoc.Main -siteroot /dev/null -project Foo -project-version 0.0.1 -d local/api local/out" +) \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index a0a8e1d2acea..aa721568716b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2584,7 +2584,7 @@ object ScaladocConfigs { def defaultSourceLinks(version: String = dottyNonBootstrappedVersion, refVersion: String = dottyVersion) = Def.task { def stdLibVersion = stdlibVersion(NonBootstrapped) - def srcManaged(v: String, s: String) = s"out/bootstrap/scala2-library-bootstrapped/scala-$v/src_managed/main/$s-library-src" + def srcManaged(v: String, s: String) = s"out/bootstrap/scala2-library-cc/scala-$v/src_managed/main/$s-library-src" SourceLinks( List( scalaSrcLink(stdLibVersion, srcManaged(version, "scala") + "="), @@ -2673,7 +2673,7 @@ object ScaladocConfigs { lazy val Scala3 = Def.task { val dottyJars: Seq[java.io.File] = Seq( - (`scala2-library-bootstrapped`/Compile/products).value, + (`scala2-library-cc`/Compile/products).value, (`scala3-library-bootstrapped`/Compile/products).value, (`scala3-interfaces`/Compile/products).value, (`tasty-core-bootstrapped`/Compile/products).value, @@ -2682,7 +2682,7 @@ object ScaladocConfigs { val roots = dottyJars.map(_.getAbsolutePath) val managedSources = - (`scala2-library-bootstrapped`/Compile/sourceManaged).value / "scala-library-src" + (`scala2-library-cc`/Compile/sourceManaged).value / "scala-library-src" val projectRoot = (ThisBuild/baseDirectory).value.toPath val stdLibRoot = projectRoot.relativize(managedSources.toPath.normalize()) val docRootFile = stdLibRoot.resolve("rootdoc.txt") @@ -2718,7 +2718,7 @@ object ScaladocConfigs { } def stableScala3(version: String) = Def.task { - val scalaLibrarySrc = s"out/bootstrap/scala2-library-bootstrapped/scala-$version-bin-SNAPSHOT-nonbootstrapped/src_managed" + val scalaLibrarySrc = s"out/bootstrap/scala2-library-cc/scala-$version-bin-SNAPSHOT-nonbootstrapped/src_managed" val dottyLibrarySrc = "library/src" Scala3.value .add(defaultSourceLinks(version + "-bin-SNAPSHOT-nonbootstrapped", version).value) @@ -2739,7 +2739,7 @@ object ScaladocConfigs { .add(DocRootContent(s"$scalaLibrarySrc/rootdoc.txt")) .withTargets( Seq( - s"out/bootstrap/scala2-library-bootstrapped/scala-$version-bin-SNAPSHOT-nonbootstrapped/classes", + s"out/bootstrap/scala2-library-cc/scala-$version-bin-SNAPSHOT-nonbootstrapped/classes", s"out/bootstrap/scala3-library-bootstrapped/scala-$version-bin-SNAPSHOT-nonbootstrapped/classes", s"tmp/interfaces/target/classes", s"out/bootstrap/tasty-core-bootstrapped/scala-$version-bin-SNAPSHOT-nonbootstrapped/classes" diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index b39fdf157347..41ccd8fb2280 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -44,6 +44,7 @@ enum Modifier(val name: String, val prefix: Boolean): case Transparent extends Modifier("transparent", true) case Infix extends Modifier("infix", true) case AbsOverride extends Modifier("abstract override", true) + case Update extends Modifier("update", true) case class ExtensionTarget(name: String, typeParams: Seq[TypeParameter], argsLists: Seq[TermParameterList], signature: Signature, dri: DRI, position: Long) case class ImplicitConversion(from: DRI, to: DRI) @@ -69,7 +70,7 @@ enum Kind(val name: String): case Var extends Kind("var") case Val extends Kind("val") case Exported(base: Kind) extends Kind("export") - case Type(concreate: Boolean, opaque: Boolean, typeParams: Seq[TypeParameter]) + case Type(concreate: Boolean, opaque: Boolean, typeParams: Seq[TypeParameter], isCaptureVar: Boolean = false) extends Kind("type") // should we handle opaque as modifier? case Given(kind: Def | Class | Val.type, as: Option[Signature], conversion: Option[ImplicitConversion]) extends Kind("given") with ImplicitConversionProvider @@ -120,7 +121,8 @@ case class TypeParameter( variance: "" | "+" | "-", name: String, dri: DRI, - signature: Signature + signature: Signature, + isCaptureVar: Boolean = false // under capture checking ) case class Link(name: String, dri: DRI) diff --git a/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala new file mode 100644 index 000000000000..9ddba3b028b9 --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala @@ -0,0 +1,185 @@ +package dotty.tools.scaladoc + +package cc + +import scala.quoted._ + +object CaptureDefs: + // these should become part of the reflect API in the distant future + def retains(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.retains") + def retainsCap(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.retainsCap") + def retainsByName(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.retainsByName") + def CapsModule(using qctx: Quotes) = + qctx.reflect.Symbol.requiredPackage("scala.caps") + def captureRoot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredPackage("scala.caps.cap") + def Caps_Capability(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.Capability") + def Caps_CapSet(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.CapSet") + def Caps_Mutable(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.Mutable") + def Caps_SharedCapability(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.SharedCapability") + def UseAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.use") + def ConsumeAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.consume") + def ReachCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.reachCapability") + def RootCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.caps.internal.rootCapability") + def ReadOnlyCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.readOnlyCapability") + def RequiresCapabilityAnnot(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.annotation.internal.requiresCapability") + + def LanguageExperimental(using qctx: Quotes) = + qctx.reflect.Symbol.requiredPackage("scala.language.experimental") + + def ImpureFunction1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.ImpureFunction1") + + def ImpureContextFunction1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.ImpureContextFunction1") + + def Function1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.Function1") + + def ContextFunction1(using qctx: Quotes) = + qctx.reflect.Symbol.requiredClass("scala.ContextFunction1") + + val useAnnotFullName: String = "scala.caps.use." + val consumeAnnotFullName: String = "scala.caps.consume." + val ccImportSelector = "captureChecking" +end CaptureDefs + +extension (using qctx: Quotes)(ann: qctx.reflect.Symbol) + /** This symbol is one of `retains` or `retainsCap` */ + def isRetains: Boolean = + ann == CaptureDefs.retains || ann == CaptureDefs.retainsCap + + /** This symbol is one of `retains`, `retainsCap`, or `retainsByName` */ + def isRetainsLike: Boolean = + ann.isRetains || ann == CaptureDefs.retainsByName + + def isReachCapabilityAnnot: Boolean = + ann == CaptureDefs.ReachCapabilityAnnot + + def isReadOnlyCapabilityAnnot: Boolean = + ann == CaptureDefs.ReadOnlyCapabilityAnnot +end extension + +extension (using qctx: Quotes)(tpe: qctx.reflect.TypeRepr) // FIXME clean up and have versions on Symbol for those + def isCaptureRoot: Boolean = tpe.termSymbol == CaptureDefs.captureRoot + + // NOTE: There's something horribly broken with Symbols, and we can't rely on tests like .isContextFunctionType either, + // so we do these lame string comparisons instead. + def isImpureFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ImpureFunction1" + + def isImpureContextFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ImpureContextFunction1" + + def isFunction1: Boolean = tpe.typeSymbol.fullName == "scala.Function1" + + def isContextFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ContextFunction1" + + def isAnyImpureFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ImpureFunction") + + def isAnyImpureContextFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ImpureContextFunction") + + def isAnyFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.Function") + + def isAnyContextFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ContextFunction") + + def isCapSet: Boolean = tpe.typeSymbol == CaptureDefs.Caps_CapSet + + def isCapSetPure: Boolean = + tpe.isCapSet && tpe.match + case CapturingType(_, refs) => refs.isEmpty + case _ => true + + def isCapSetCap: Boolean = + tpe.isCapSet && tpe.match + case CapturingType(_, List(ref)) => ref.isCaptureRoot + case _ => false +end extension + +extension (using qctx: Quotes)(typedef: qctx.reflect.TypeDef) + def derivesFromCapSet: Boolean = + import qctx.reflect.* + typedef.rhs.match + case t: TypeTree => t.tpe.derivesFrom(CaptureDefs.Caps_CapSet) + case t: TypeBoundsTree => t.tpe.derivesFrom(CaptureDefs.Caps_CapSet) + case _ => false +end extension + +/** Matches `import scala.language.experimental.captureChecking` */ +object CCImport: + def unapply(using qctx: Quotes)(tree: qctx.reflect.Tree): Boolean = + import qctx.reflect._ + tree match + case imprt: Import if imprt.expr.tpe.termSymbol == CaptureDefs.LanguageExperimental => + imprt.selectors.exists { + case SimpleSelector(s) if s == CaptureDefs.ccImportSelector => true + case _ => false + } + case _ => false + end unapply +end CCImport + +object ReachCapability: + def unapply(using qctx: Quotes)(ty: qctx.reflect.TypeRepr): Option[qctx.reflect.TypeRepr] = + import qctx.reflect._ + ty match + case AnnotatedType(base, Apply(Select(New(annot), _), Nil)) if annot.symbol.isReachCapabilityAnnot => + Some(base) + case _ => None +end ReachCapability + +object ReadOnlyCapability: + def unapply(using qctx: Quotes)(ty: qctx.reflect.TypeRepr): Option[qctx.reflect.TypeRepr] = + import qctx.reflect._ + ty match + case AnnotatedType(base, Apply(Select(New(annot), _), Nil)) if annot.symbol.isReadOnlyCapabilityAnnot => + Some(base) + case _ => None +end ReadOnlyCapability + +/** Decompose capture sets in the union-type-encoding into the sequence of atomic `TypeRepr`s. + * Returns `None` if the type is not a capture set. +*/ +def decomposeCaptureRefs(using qctx: Quotes)(typ0: qctx.reflect.TypeRepr): Option[List[qctx.reflect.TypeRepr]] = + import qctx.reflect._ + val buffer = collection.mutable.ListBuffer.empty[TypeRepr] + def include(t: TypeRepr): Boolean = { buffer += t; true } + def traverse(typ: TypeRepr): Boolean = + typ match + case t if t.typeSymbol == defn.NothingClass => true + case OrType(t1, t2) => traverse(t1) && traverse(t2) + case t @ ThisType(_) => include(t) + case t @ TermRef(_, _) => include(t) + case t @ ParamRef(_, _) => include(t) + case t @ ReachCapability(_) => include(t) + case t @ ReadOnlyCapability(_) => include(t) + case t : TypeRef => include(t) // FIXME: does this need a more refined check? + case _ => report.warning(s"Unexpected type tree $typ while trying to extract capture references from $typ0"); false // TODO remove warning eventually + if traverse(typ0) then Some(buffer.toList) else None +end decomposeCaptureRefs + +object CaptureSetType: + def unapply(using qctx: Quotes)(tt: qctx.reflect.TypeTree): Option[List[qctx.reflect.TypeRepr]] = decomposeCaptureRefs(tt.tpe) +end CaptureSetType + +object CapturingType: + def unapply(using qctx: Quotes)(typ: qctx.reflect.TypeRepr): Option[(qctx.reflect.TypeRepr, List[qctx.reflect.TypeRepr])] = + import qctx.reflect._ + typ match + case AnnotatedType(base, Apply(TypeApply(Select(New(annot), _), List(CaptureSetType(refs))), Nil)) if annot.symbol.isRetainsLike => + Some((base, refs)) + case AnnotatedType(base, Apply(Select(New(annot), _), Nil)) if annot.symbol == CaptureDefs.retainsCap => + Some((base, List(CaptureDefs.captureRoot.termRef))) + case _ => None +end CapturingType \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala index 81415377beeb..81309018718c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala @@ -3,6 +3,7 @@ package tasty import scala.jdk.CollectionConverters._ import dotty.tools.scaladoc._ +import dotty.tools.scaladoc.cc.CaptureDefs import scala.quoted._ import SymOps._ @@ -52,7 +53,9 @@ trait BasicSupport: "scala.annotation.static", "scala.annotation.targetName", "scala.annotation.threadUnsafe", - "scala.annotation.varargs" + "scala.annotation.varargs", + CaptureDefs.useAnnotFullName, + CaptureDefs.consumeAnnotFullName, ) val documentedSymbol = summon[Quotes].reflect.Symbol.requiredClass("java.lang.annotation.Documented") val annotations = sym.annotations.filter { a => diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index ee12755c7f98..bf448800c4f1 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -5,6 +5,8 @@ import dotty.tools.scaladoc._ import dotty.tools.scaladoc.{Signature => DSignature} import dotty.tools.scaladoc.Inkuire +import dotty.tools.scaladoc.cc.* + import scala.quoted._ import SymOps._ @@ -464,6 +466,8 @@ trait ClassLikeSupport: else if argument.symbol.flags.is(Flags.Contravariant) then "-" else "" + val isCaptureVar = ccEnabled && argument.derivesFromCapSet + val name = argument.symbol.normalizedName val normalizedName = if name.matches("_\\$\\d*") then "_" else name val boundsSignature = memberInfo.get(name).fold(argument.rhs.asSignature(classDef))(_.asSignature(classDef)) @@ -477,7 +481,8 @@ trait ClassLikeSupport: variancePrefix, normalizedName, argument.symbol.dri, - signature + signature, + isCaptureVar, ) def parseTypeDef(typeDef: TypeDef, classDef: ClassDef): Member = @@ -486,11 +491,14 @@ trait ClassLikeSupport: case LambdaTypeTree(params, body) => isTreeAbstract(body) case _ => false } + + val isCaptureVar = ccEnabled && typeDef.derivesFromCapSet + val (generics, tpeTree) = typeDef.rhs match case LambdaTypeTree(params, body) => (params.map(mkTypeArgument(_, classDef)), body) case tpe => (Nil, tpe) - val defaultKind = Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque, generics).asInstanceOf[Kind.Type] + val defaultKind = Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque, generics, isCaptureVar).asInstanceOf[Kind.Type] val kind = if typeDef.symbol.flags.is(Flags.Enum) then Kind.EnumCase(defaultKind) else defaultKind @@ -524,7 +532,10 @@ trait ClassLikeSupport: case _ => valDef.symbol.getExtraModifiers() mkMember(valDef.symbol, kind, memberInfo.res.asSignature(c))( - modifiers = modifiers, + // Due to how capture checking encodes update methods (recycling the mutable flag for methods), + // we need to filter out the update modifier here. Otherwise, mutable fields will + // be documented as having the update modifier, which is not correct. + modifiers = modifiers.filterNot(_ == Modifier.Update), deprecated = valDef.symbol.isDeprecated(), experimental = valDef.symbol.isExperimental() ) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala index 196c3e056b36..8f42a28c2c35 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala @@ -17,7 +17,7 @@ object NameNormalizer { val escaped = escapedName(constructorNormalizedName) escaped } - + def ownerNameChain: List[String] = { import reflect.* if s.isNoSymbol then List.empty @@ -25,8 +25,8 @@ object NameNormalizer { else if s == defn.RootPackage then List.empty else if s == defn.RootClass then List.empty else s.owner.ownerNameChain :+ s.normalizedName - } - + } + def normalizedFullName: String = s.ownerNameChain.mkString(".") diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala index c0308336a2bf..8de2ab6b8539 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala @@ -5,6 +5,8 @@ import scala.jdk.CollectionConverters._ import SymOps._ +import dotty.tools.scaladoc.cc.CCImport + trait PackageSupport: self: TastyParser => import qctx.reflect._ @@ -13,6 +15,11 @@ trait PackageSupport: def parsePackage(pck: PackageClause): (String, Member) = val name = pck.symbol.fullName + ccFlag = false // FIXME: would be better if we had access to the tasty attribute + pck.stats.foreach { + case CCImport() => ccFlag = true + case _ => + } (name, Member(name, "", pck.symbol.dri, Kind.Package)) def parsePackageObject(pckObj: ClassDef): (String, Member) = diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index 969b1d6462c2..0464da450f05 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -100,6 +100,7 @@ object SymOps: Flags.Case -> Modifier.Case, Flags.Opaque -> Modifier.Opaque, Flags.AbsOverride -> Modifier.AbsOverride, + Flags.Mutable -> Modifier.Update, // under CC ).collect { case (flag, mod) if sym.flags.is(flag) => mod } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala index 1a8337e0c6b7..741147ebfe2e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala @@ -187,6 +187,9 @@ case class TastyParser( private given qctx.type = qctx + protected var ccFlag: Boolean = false + def ccEnabled: Boolean = ccFlag + val intrinsicClassDefs = Set( defn.AnyClass, defn.MatchableClass, diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 110ee498a3ac..1cfc7abbaa9d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -6,6 +6,8 @@ import scala.jdk.CollectionConverters._ import scala.quoted._ import scala.util.control.NonFatal +import dotty.tools.scaladoc.cc.* + import NameNormalizer._ import SyntheticsSupport._ @@ -19,7 +21,7 @@ trait TypesSupport: def asSignature(elideThis: reflect.ClassDef): SSignature = import reflect._ tpeTree match - case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe)(using elideThis) + case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe)(using elideThis, inCC = None) case tpeTree: TypeTree => topLevelProcess(tpeTree.tpe)(using elideThis) case term: Term => topLevelProcess(term.tpe)(using elideThis) @@ -33,19 +35,30 @@ trait TypesSupport: private def keyword(str: String): SignaturePart = Keyword(str) - private def tpe(str: String, dri: DRI): SignaturePart = dotty.tools.scaladoc.Type(str, Some(dri)) + private def tpe(str: String, dri: DRI)(using inCC: Option[Any]): SignaturePart = + if inCC.isDefined then + dotty.tools.scaladoc.Plain(str) + else + dotty.tools.scaladoc.Type(str, Some(dri)) - private def tpe(str: String): SignaturePart = dotty.tools.scaladoc.Type(str, None) + private def tpe(str: String)(using inCC: Option[Any]): SignaturePart = + if inCC.isDefined then + dotty.tools.scaladoc.Plain(str) + else + dotty.tools.scaladoc.Type(str, None) private def inParens(s: SSignature, wrap: Boolean = true) = if wrap then plain("(").l ++ s ++ plain(")").l else s extension (on: SignaturePart) def l: List[SignaturePart] = List(on) - private def tpe(using Quotes)(symbol: reflect.Symbol): SSignature = + private def tpe(using Quotes)(symbol: reflect.Symbol)(using inCC: Option[Any]): SSignature = import SymOps._ val dri: Option[DRI] = Option(symbol).filterNot(_.isHiddenByVisibility).map(_.dri) - dotty.tools.scaladoc.Type(symbol.normalizedName, dri).l + if inCC.isDefined then // we are in the context of a capture set and want paths to be rendered plainly + dotty.tools.scaladoc.Plain(symbol.normalizedName).l + else + dotty.tools.scaladoc.Type(symbol.normalizedName, dri).l private def commas(lists: List[SSignature]) = lists match case List(single) => single @@ -78,13 +91,15 @@ trait TypesSupport: // TODO #23 add support for all types signatures that make sense private def inner( - using Quotes, + using qctx: Quotes, )( tp: reflect.TypeRepr, )(using elideThis: reflect.ClassDef, indent: Int = 0, skipTypeSuffix: Boolean = false, + // inCC means in capture-checking context. If defined, it carries the current capture-set contents. + inCC: Option[List[reflect.TypeRepr]] = None, ): SSignature = import reflect._ def noSupported(name: String): SSignature = @@ -99,7 +114,10 @@ trait TypesSupport: inParens(inner(left), shouldWrapInParens(left, tp, true)) ++ keyword(" & ").l ++ inParens(inner(right), shouldWrapInParens(right, tp, false)) - case ByNameType(tpe) => keyword("=> ") :: inner(tpe) + case ByNameType(CapturingType(tpe, refs)) => + emitByNameArrow(using qctx)(Some(refs)) ++ (plain(" ") :: inner(tpe)) + case ByNameType(tpe) => + emitByNameArrow(using qctx)(None) ++ (plain(" ") :: inner(tpe)) case ConstantType(constant) => plain(constant.show).l case ThisType(tpe) => @@ -112,6 +130,14 @@ trait TypesSupport: inner(tpe) :+ plain("*") case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) => inner(tpe) :+ plain("*") + case CapturingType(base, refs) => + base match + case t @ AppliedType(base, args) if t.isFunctionType => + functionType(base, args)(using inCC = Some(refs)) + case t : Refinement if t.isFunctionType => + inner(base)(using indent = indent, skipTypeSuffix = skipTypeSuffix, inCC = Some(refs)) + case t if t.isCapSet => emitCaptureSet(refs, omitCap = false) + case t => inner(base) ++ emitCapturing(refs) case AnnotatedType(tpe, _) => inner(tpe) case tl @ TypeLambda(params, paramBounds, AppliedType(tpe, args)) @@ -120,7 +146,8 @@ trait TypesSupport: case tl @ TypeLambda(params, paramBounds, resType) => plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name - tpe(normalizedName).l ++ inner(typ) + val suffix = if ccEnabled && typ.derivesFrom(CaptureDefs.Caps_CapSet) then List(Keyword("^")) else Nil + tpe(normalizedName).l ++ suffix ++ inner(typ) }) ++ plain("]").l ++ keyword(" =>> ").l ++ inner(resType) @@ -132,14 +159,19 @@ trait TypesSupport: inner(Refinement(at, "apply", mt)) case r: Refinement => { //(parent, name, info) + val inCC0 = inCC + given Option[List[TypeRepr]] = None // do not propagate capture set beyond this point def getRefinementInformation(t: TypeRepr): List[TypeRepr] = t match { case r: Refinement => getRefinementInformation(r.parent) :+ r case t => List(t) } def getParamBounds(t: PolyType): SSignature = commas( - t.paramNames.zip(t.paramBounds.map(inner(_))) - .map(b => tpe(b(0)).l ++ b(1)) + t.paramNames.zip(t.paramBounds.map(inner(_))).zipWithIndex + .map { case ((name, bound), idx) => + val suffix = if ccEnabled && t.param(idx).derivesFrom(CaptureDefs.Caps_CapSet) then List(Keyword("^")) else Nil + tpe(name).l ++ suffix ++ bound + } ) def getParamList(m: MethodType): SSignature = @@ -181,9 +213,16 @@ trait TypesSupport: val isCtx = isContextualMethod(m) if isDependentMethod(m) then val paramList = getParamList(m) - val arrow = keyword(if isCtx then " ?=> " else " => ").l + val arrPrefix = if isCtx then "?" else "" + val arrow = + if ccEnabled then + inCC0 match + case None | Some(Nil) => keyword(arrPrefix + "->").l + case Some(List(c)) if c.isCaptureRoot => keyword(arrPrefix + "=>").l + case Some(refs) => keyword(arrPrefix + "->") :: emitCaptureSet(refs) + else keyword(arrPrefix + "=>").l val resType = inner(m.resType) - paramList ++ arrow ++ resType + paramList ++ (plain(" ") :: arrow) ++ (plain(" ") :: resType) else val sym = defn.FunctionClass(m.paramTypes.length, isCtx) inner(sym.typeRef.appliedTo(m.paramTypes :+ m.resType)) @@ -226,18 +265,7 @@ trait TypesSupport: ++ inParens(inner(rhs), shouldWrapInParens(rhs, t, false)) case t @ AppliedType(tpe, args) if t.isFunctionType => - val arrow = if t.isContextFunctionType then " ?=> " else " => " - args match - case Nil => Nil - case List(rtpe) => plain("()").l ++ keyword(arrow).l ++ inner(rtpe) - case List(arg, rtpe) => - val wrapInParens = stripAnnotated(arg) match - case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => false - case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => false - case _ => true - inParens(inner(arg), wrapInParens) ++ keyword(arrow).l ++ inner(rtpe) - case _ => - plain("(").l ++ commas(args.init.map(inner(_))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last) + functionType(tpe, args) case t @ AppliedType(tpe, typeList) => inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match @@ -245,6 +273,8 @@ trait TypesSupport: case _ => topLevelProcess(t) }) ++ plain("]").l + case t : TypeRef if t.isCapSet => emitCaptureSet(Nil) + case tp @ TypeRef(qual, typeName) => qual match { case r: RecursiveThis => tpe(s"this.$typeName").l @@ -252,7 +282,7 @@ trait TypesSupport: tpe(tp.typeSymbol) case _: TermRef | _: ParamRef => val suffix = if tp.typeSymbol == Symbol.noSymbol then tpe(typeName).l else tpe(tp.typeSymbol) - inner(qual)(using skipTypeSuffix = true) ++ plain(".").l ++ suffix + inner(qual)(using skipTypeSuffix = true, inCC = inCC) ++ plain(".").l ++ suffix case ThisType(tr) => findSupertype(elideThis, tr.typeSymbol) match case Some((sym, AppliedType(tr2, args))) => @@ -266,7 +296,7 @@ trait TypesSupport: case _ => tpe(tp.typeSymbol) case Some(_) => tpe(tp.typeSymbol) case None => - val sig = inParens(inner(qual)(using skipTypeSuffix = true), shouldWrapInParens(qual, tp, true)) + val sig = inParens(inner(qual)(using skipTypeSuffix = true, inCC = inCC), shouldWrapInParens(qual, tp, true)) sig ++ plain(".").l ++ tpe(tp.typeSymbol) case _ => val sig = inParens(inner(qual), shouldWrapInParens(qual, tp, true)) @@ -276,7 +306,7 @@ trait TypesSupport: case tr @ TermRef(qual, typeName) => val prefix = qual match case t if skipPrefix(t, elideThis) => Nil - case tp => inner(tp)(using skipTypeSuffix = true) ++ plain(".").l + case tp => inner(tp)(using skipTypeSuffix = true, inCC = inCC) ++ plain(".").l val suffix = if skipTypeSuffix then Nil else List(plain("."), keyword("type")) val typeSig = tr.termSymbol.tree match case vd: ValDef if tr.termSymbol.flags.is(Flags.Module) => @@ -295,9 +325,9 @@ trait TypesSupport: val spaces = " " * (indent) val casesTexts = cases.flatMap { case MatchCase(from, to) => - keyword(caseSpaces + "case ").l ++ inner(from) ++ keyword(" => ").l ++ inner(to)(using indent = indent + 2) ++ plain("\n").l + keyword(caseSpaces + "case ").l ++ inner(from) ++ keyword(" => ").l ++ inner(to)(using indent = indent + 2, inCC = inCC) ++ plain("\n").l case TypeLambda(_, _, MatchCase(from, to)) => - keyword(caseSpaces + "case ").l ++ inner(from) ++ keyword(" => ").l ++ inner(to)(using indent = indent + 2) ++ plain("\n").l + keyword(caseSpaces + "case ").l ++ inner(from) ++ keyword(" => ").l ++ inner(to)(using indent = indent + 2, inCC = inCC) ++ plain("\n").l } inner(sc) ++ keyword(" match ").l ++ plain("{\n").l ++ casesTexts ++ plain(spaces + "}").l @@ -323,9 +353,33 @@ trait TypesSupport: s"${tpe.show(using Printer.TypeReprStructure)}" throw MatchError(msg) + private def functionType(using qctx: Quotes)(funTy: reflect.TypeRepr, args: List[reflect.TypeRepr])(using + elideThis: reflect.ClassDef, + indent: Int, + skipTypeSuffix: Boolean, + inCC: Option[List[reflect.TypeRepr]], + ): SSignature = + import reflect._ + val arrow = plain(" ") :: (emitFunctionArrow(using qctx)(funTy, inCC) ++ plain(" ").l) + given Option[List[TypeRepr]] = None // do not propagate capture set beyond this point + args match + case Nil => Nil + case List(rtpe) => plain("()").l ++ arrow ++ inner(rtpe) + case List(arg, rtpe) => + val wrapInParens = stripAnnotated(arg) match + case _: TermRef | _: TypeRef | _: ConstantType | _: ParamRef => false + case at: AppliedType if !isInfix(at) && !at.isFunctionType && !at.isTupleN => false + case _ => true + inParens(inner(arg), wrapInParens) ++ arrow ++ inner(rtpe) + case _ => + plain("(").l ++ commas(args.init.map(inner(_))) ++ plain(")").l ++ arrow ++ inner(args.last) + private def typeBound(using Quotes)(t: reflect.TypeRepr, low: Boolean)(using elideThis: reflect.ClassDef) = import reflect._ - val ignore = if (low) t.typeSymbol == defn.NothingClass else t.typeSymbol == defn.AnyClass + val ignore = low && (ccEnabled && t.isCapSetPure + || t.typeSymbol == defn.NothingClass) + || !low && (ccEnabled && t.isCapSetCap + || t.typeSymbol == defn.AnyClass) val prefix = keyword(if low then " >: " else " <: ") t match { case l: TypeLambda => prefix :: inParens(inner(l)(using elideThis)) @@ -334,17 +388,17 @@ trait TypesSupport: case _ => Nil } - private def typeBoundsTreeOfHigherKindedType(using Quotes)(low: reflect.TypeRepr, high: reflect.TypeRepr)(using elideThis: reflect.ClassDef) = + private def typeBoundsTreeOfHigherKindedType(using Quotes)(low: reflect.TypeRepr, high: reflect.TypeRepr)(using elideThis: reflect.ClassDef, inCC: Option[List[reflect.TypeRepr]]) = import reflect._ def regularTypeBounds(low: TypeRepr, high: TypeRepr) = - if low == high then keyword(" = ").l ++ inner(low)(using elideThis) + if low == high then keyword(" = ").l ++ inner(low)(using elideThis, inCC = inCC) else typeBound(low, low = true)(using elideThis) ++ typeBound(high, low = false)(using elideThis) high.match case TypeLambda(params, paramBounds, resType) => if resType.typeSymbol == defn.AnyClass then plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name - tpe(normalizedName).l ++ inner(typ)(using elideThis) + tpe(normalizedName)(using inCC).l ++ inner(typ)(using elideThis, inCC = inCC) }) ++ plain("]").l else regularTypeBounds(low, high) @@ -424,3 +478,54 @@ trait TypesSupport: tr match case AnnotatedType(tr, _) => stripAnnotated(tr) case other => other + + private def emitCapability(using Quotes)(ref: reflect.TypeRepr)(using elideThis: reflect.ClassDef): SSignature = + import reflect._ + ref match + case ReachCapability(c) => emitCapability(c) :+ Keyword("*") + case ReadOnlyCapability(c) => emitCapability(c) :+ Keyword(".rd") + case ThisType(_) => List(Keyword("this")) + case t => inner(t)(using skipTypeSuffix = true, inCC = Some(Nil)) + + private def emitCaptureSet(using Quotes)(refs: List[reflect.TypeRepr], omitCap: Boolean = true)(using elideThis: reflect.ClassDef): SSignature = + import reflect._ + refs match + case List(ref) if omitCap && ref.isCaptureRoot => Nil + case refs => + val res0 = refs.map(emitCapability) + val res1 = res0 match + case Nil => Nil + case other => other.reduce((r, e) => r ++ (List(Plain(", ")) ++ e)) + Plain("{") :: (res1 ++ List(Plain("}"))) + + private def emitCapturing(using Quotes)(refs: List[reflect.TypeRepr])(using elideThis: reflect.ClassDef): SSignature = + import reflect._ + Keyword("^") :: emitCaptureSet(refs) + + private def emitFunctionArrow(using Quotes)(funTy: reflect.TypeRepr, captures: Option[List[reflect.TypeRepr]])(using elideThis: reflect.ClassDef): SSignature = + import reflect._ + val isContextFun = funTy.isAnyContextFunction || funTy.isAnyImpureContextFunction + val prefix = if isContextFun then "?" else "" + if !ccEnabled then + List(Keyword(prefix + "=>")) + else + val isPureFun = funTy.isAnyFunction || funTy.isAnyContextFunction + val isImpureFun = funTy.isAnyImpureFunction || funTy.isAnyImpureContextFunction + captures match + case None => // means an explicit retains* annotation is missing + if isPureFun then + List(Keyword(prefix + "->")) + else if isImpureFun then + List(Keyword(prefix + "=>")) + else + report.error(s"Cannot emit function arrow: expected a (Context)Function* or Impure(Context)Function*, but got: ${funTy.show}") + Nil + case Some(refs) => + // there is some capture set + refs match + case Nil => List(Keyword(prefix + "->")) + case List(ref) if ref.isCaptureRoot => List(Keyword(prefix + "=>")) + case refs => Keyword(prefix + "->") :: emitCaptureSet(refs) + + private def emitByNameArrow(using Quotes)(captures: Option[List[reflect.TypeRepr]])(using elideThis: reflect.ClassDef): SSignature = + emitFunctionArrow(CaptureDefs.Function1.typeRef, captures) \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala index a3ce15d70c64..d62ce4693575 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala @@ -149,7 +149,7 @@ class ScalaSignatureProvider: MemberSignature( builder.modifiersAndVisibility(typeDef), builder.kind(tpe), - builder.name(typeDef.name, typeDef.dri), + builder.name(typeDef.name, typeDef.dri, isCaptureVar = tpe.isCaptureVar), builder.typeParamList(tpe.typeParams).pipe { bdr => if (!tpe.opaque) { (if tpe.concreate then bdr.plain(" = ") else bdr) diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala index d28dd6ca18fe..7b3f2fa44acf 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala @@ -3,8 +3,12 @@ package translators case class SignatureBuilder(content: Signature = Nil) extends ScalaSignatureUtils: def plain(str: String): SignatureBuilder = copy(content = content :+ Plain(str)) - def name(str: String, dri: DRI): SignatureBuilder = copy(content = content :+ Name(str, dri)) - def tpe(text: String, dri: Option[DRI]): SignatureBuilder = copy(content = content :+ Type(text, dri)) + def name(str: String, dri: DRI, isCaptureVar: Boolean = false/*under CC*/): SignatureBuilder = + val suffix = if isCaptureVar then List(Keyword("^")) else Nil + copy(content = content ++ (Name(str, dri) :: suffix)) + def tpe(text: String, dri: Option[DRI], isCaptureVar: Boolean = false/*under CC*/): SignatureBuilder = + val suffix = if isCaptureVar then List(Keyword("^")) else Nil + copy(content = content ++ (Type(text, dri) :: suffix)) def keyword(str: String): SignatureBuilder = copy(content = content :+ Keyword(str)) def tpe(text: String, dri: DRI): SignatureBuilder = copy(content = content :+ Type(text, Some(dri))) def signature(s: Signature): SignatureBuilder = copy(content = content ++ s) @@ -90,7 +94,7 @@ case class SignatureBuilder(content: Signature = Nil) extends ScalaSignatureUtil } def typeParamList(on: TypeParameterList) = list(on.toList, List(Plain("[")), List(Plain("]"))){ (bdr, e) => - bdr.annotationsInline(e).keyword(e.variance).tpe(e.name, Some(e.dri)).signature(e.signature) + bdr.annotationsInline(e).keyword(e.variance).tpe(e.name, Some(e.dri), e.isCaptureVar).signature(e.signature) } def functionTermParameters(paramss: Seq[TermParameterList]) =