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..bd55798d000c --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala @@ -0,0 +1,191 @@ +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 = + import qctx.reflect.* + tpe match + case TermRef(ThisType(TypeRef(NoPrefix(), "caps")), "cap") => true + case TermRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "caps"), "cap") => true + case TermRef(TermRef(TermRef(TermRef(NoPrefix(), "_root_"), "scala"), "caps"), "cap") => true + case _ => false + + // 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 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 99aac7010d8b..00635951fb69 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -3,6 +3,8 @@ package dotty.tools.scaladoc.tasty import dotty.tools.scaladoc._ import dotty.tools.scaladoc.{Signature => DSignature} +import dotty.tools.scaladoc.cc.* + import scala.quoted._ import SymOps._ @@ -465,6 +467,8 @@ trait ClassLikeSupport: else "" val name = symbol.normalizedName + val isCaptureVar = ccEnabled && argument.derivesFromCapSet + val normalizedName = if name.matches("_\\$\\d*") then "_" else name val boundsSignature = argument.rhs.asSignature(classDef, symbol.owner) val signature = boundsSignature ++ contextBounds.flatMap(tr => @@ -479,7 +483,8 @@ trait ClassLikeSupport: variancePrefix, normalizedName, symbol.dri, - signature + signature, + isCaptureVar, ) def parseTypeDef(typeDef: TypeDef, classDef: ClassDef): Member = @@ -489,6 +494,9 @@ 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) @@ -528,7 +536,10 @@ trait ClassLikeSupport: case _ => symbol.getExtraModifiers() mkMember(symbol, kind, sig)( - 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 = symbol.isDeprecated(), experimental = 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 30a5ac22be0d..92ea42dab101 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, originalOwner: reflect.Symbol, skipThisTypePrefix: Boolean): SSignature = import reflect._ tpeTree match - case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe, skipThisTypePrefix)(using elideThis, originalOwner) + case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe, skipThisTypePrefix)(using elideThis, originalOwner, inCC = None) case tpeTree: TypeTree => topLevelProcess(tpeTree.tpe, skipThisTypePrefix)(using elideThis, originalOwner) case term: Term => topLevelProcess(term.tpe, skipThisTypePrefix)(using elideThis, originalOwner) def asSignature(elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = @@ -37,19 +39,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) protected 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 @@ -82,7 +95,7 @@ trait TypesSupport: // TODO #23 add support for all types signatures that make sense private def inner( - using Quotes, + using qctx: Quotes, )( tp: reflect.TypeRepr, skipThisTypePrefix: Boolean @@ -91,6 +104,8 @@ trait TypesSupport: originalOwner: reflect.Symbol, 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 = @@ -105,7 +120,10 @@ trait TypesSupport: inParens(inner(left, skipThisTypePrefix), shouldWrapInParens(left, tp, true)) ++ keyword(" & ").l ++ inParens(inner(right, skipThisTypePrefix), shouldWrapInParens(right, tp, false)) - case ByNameType(tpe) => keyword("=> ") :: inner(tpe, skipThisTypePrefix) + case ByNameType(CapturingType(tpe, refs)) => + emitByNameArrow(using qctx)(Some(refs), skipThisTypePrefix) ++ (plain(" ") :: inner(tpe, skipThisTypePrefix)) + case ByNameType(tpe) => + emitByNameArrow(using qctx)(None, skipThisTypePrefix) ++ (plain(" ") :: inner(tpe, skipThisTypePrefix)) case ConstantType(constant) => plain(constant.show).l case ThisType(tpe) => @@ -116,6 +134,14 @@ trait TypesSupport: inner(tpe, skipThisTypePrefix) :+ plain("*") case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) => inner(tpe, skipThisTypePrefix) :+ plain("*") + case CapturingType(base, refs) => + base match + case t @ AppliedType(base, args) if t.isFunctionType => + functionType(base, args, skipThisTypePrefix)(using inCC = Some(refs)) + case t : Refinement if t.isFunctionType => + inner(base, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = skipTypeSuffix, inCC = Some(refs)) + case t if t.isCapSet => emitCaptureSet(refs, skipThisTypePrefix, omitCap = false) + case t => inner(base, skipThisTypePrefix) ++ emitCapturing(refs, skipThisTypePrefix) case AnnotatedType(tpe, _) => inner(tpe, skipThisTypePrefix) case tl @ TypeLambda(params, paramBounds, AppliedType(tpe, args)) @@ -127,7 +153,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, skipThisTypePrefix) + val suffix = if ccEnabled && typ.derivesFrom(CaptureDefs.Caps_CapSet) then List(Keyword("^")) else Nil + tpe(normalizedName).l ++ suffix ++ inner(typ, skipThisTypePrefix) }) ++ plain("]").l ++ keyword(" =>> ").l ++ inner(resType, skipThisTypePrefix) @@ -139,14 +166,19 @@ trait TypesSupport: inner(Refinement(at, "apply", mt), skipThisTypePrefix) 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(_, skipThisTypePrefix))) - .map(b => tpe(b(0)).l ++ b(1)) + t.paramNames.zip(t.paramBounds.map(inner(_, skipThisTypePrefix))).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 = @@ -188,9 +220,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, skipThisTypePrefix) + else keyword(arrPrefix + "=>").l val resType = inner(m.resType, skipThisTypePrefix) - 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), skipThisTypePrefix) @@ -233,18 +272,7 @@ trait TypesSupport: ++ inParens(inner(rhs, skipThisTypePrefix), 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, skipThisTypePrefix) - 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, skipThisTypePrefix), wrapInParens) ++ keyword(arrow).l ++ inner(rtpe, skipThisTypePrefix) - case _ => - plain("(").l ++ commas(args.init.map(inner(_, skipThisTypePrefix))) ++ plain(")").l ++ keyword(arrow).l ++ inner(args.last, skipThisTypePrefix) + functionType(tpe, args, skipThisTypePrefix) case t @ AppliedType(tpe, typeList) => inner(tpe, skipThisTypePrefix) ++ plain("[").l ++ commas(typeList.map { t => t match @@ -252,6 +280,8 @@ trait TypesSupport: case _ => topLevelProcess(t, skipThisTypePrefix) }) ++ plain("]").l + case t : TypeRef if t.isCapSet => emitCaptureSet(Nil, skipThisTypePrefix) + case tp @ TypeRef(qual, typeName) => qual match { case r: RecursiveThis => tpe(s"this.$typeName").l @@ -269,14 +299,14 @@ trait TypesSupport: if skipPrefix(qual, elideThis, originalOwner, skipThisTypePrefix) then tpe(tp.typeSymbol) else - val sig = inParens(inner(qual, skipThisTypePrefix)(using skipTypeSuffix = true), shouldWrapInParens(qual, tp, true)) + val sig = inParens(inner(qual, skipThisTypePrefix)(using skipTypeSuffix = true, inCC = inCC), shouldWrapInParens(qual, tp, true)) sig ++ plain(".").l ++ tpe(tp.typeSymbol) case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) => tpe(tp.typeSymbol) case _: TermRef | _: ParamRef => val suffix = if tp.typeSymbol == Symbol.noSymbol then tpe(typeName).l else tpe(tp.typeSymbol) - inner(qual, skipThisTypePrefix)(using skipTypeSuffix = true) ++ plain(".").l ++ suffix + inner(qual, skipThisTypePrefix)(using skipTypeSuffix = true, inCC = inCC) ++ plain(".").l ++ suffix case _ => val sig = inParens(inner(qual, skipThisTypePrefix), shouldWrapInParens(qual, tp, true)) sig ++ keyword("#").l ++ tpe(tp.typeSymbol) @@ -285,7 +315,7 @@ trait TypesSupport: case tr @ TermRef(qual, typeName) => val prefix = qual match case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) => Nil - case tp => inner(tp, skipThisTypePrefix)(using skipTypeSuffix = true) ++ plain(".").l + case tp => inner(tp, skipThisTypePrefix)(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) => @@ -304,9 +334,9 @@ trait TypesSupport: val spaces = " " * (indent) val casesTexts = cases.flatMap { case MatchCase(from, to) => - keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(to, skipThisTypePrefix)(using indent = indent + 2) ++ plain("\n").l + keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(to, skipThisTypePrefix)(using indent = indent + 2, inCC = inCC) ++ plain("\n").l case TypeLambda(_, _, MatchCase(from, to)) => - keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(to, skipThisTypePrefix)(using indent = indent + 2) ++ plain("\n").l + keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(to, skipThisTypePrefix)(using indent = indent + 2, inCC = inCC) ++ plain("\n").l } inner(sc, skipThisTypePrefix) ++ keyword(" match ").l ++ plain("{\n").l ++ casesTexts ++ plain(spaces + "}").l @@ -332,9 +362,34 @@ trait TypesSupport: s"${tpe.show(using Printer.TypeReprStructure)}" throw MatchError(msg) + private def functionType(using qctx: Quotes)(funTy: reflect.TypeRepr, args: List[reflect.TypeRepr], skipThisTypePrefix: Boolean)(using + elideThis: reflect.ClassDef, + originalOwner: reflect.Symbol, + indent: Int, + skipTypeSuffix: Boolean, + inCC: Option[List[reflect.TypeRepr]], + ): SSignature = + import reflect._ + val arrow = plain(" ") :: (emitFunctionArrow(using qctx)(funTy, inCC, skipThisTypePrefix) ++ 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, skipThisTypePrefix) + 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, skipThisTypePrefix), wrapInParens) ++ arrow ++ inner(rtpe, skipThisTypePrefix) + case _ => + plain("(").l ++ commas(args.init.map(inner(_, skipThisTypePrefix))) ++ plain(")").l ++ arrow ++ inner(args.last, skipThisTypePrefix) + private def typeBound(using Quotes)(t: reflect.TypeRepr, low: Boolean, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol) = 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, skipThisTypePrefix)(using elideThis, originalOwner)) @@ -344,18 +399,18 @@ trait TypesSupport: } private def typeBoundsTreeOfHigherKindedType(using Quotes)(low: reflect.TypeRepr, high: reflect.TypeRepr, skipThisTypePrefix: Boolean)( - using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol + using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol, inCC: Option[List[reflect.TypeRepr]] ) = import reflect._ def regularTypeBounds(low: TypeRepr, high: TypeRepr) = - if low == high then keyword(" = ").l ++ inner(low, skipThisTypePrefix)(using elideThis, originalOwner) + if low == high then keyword(" = ").l ++ inner(low, skipThisTypePrefix)(using elideThis, originalOwner, inCC = inCC) else typeBound(low, low = true, skipThisTypePrefix)(using elideThis, originalOwner) ++ typeBound(high, low = false, skipThisTypePrefix)(using elideThis, originalOwner) 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, skipThisTypePrefix)(using elideThis, originalOwner) + tpe(normalizedName)(using inCC).l ++ inner(typ, skipThisTypePrefix)(using elideThis, originalOwner, inCC = inCC) }) ++ plain("]").l else regularTypeBounds(low, high) @@ -437,3 +492,54 @@ trait TypesSupport: tr match case AnnotatedType(tr, _) => stripAnnotated(tr) case other => other + + private def emitCapability(using Quotes)(ref: reflect.TypeRepr, skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + ref match + case ReachCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword("*") + case ReadOnlyCapability(c) => emitCapability(c, skipThisTypePrefix) :+ Keyword(".rd") + case ThisType(_) => List(Keyword("this")) + case t => inner(t, skipThisTypePrefix)(using skipTypeSuffix = true, inCC = Some(Nil)) + + private def emitCaptureSet(using Quotes)(refs: List[reflect.TypeRepr], skipThisTypePrefix: Boolean, omitCap: Boolean = true)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + refs match + case List(ref) if omitCap && ref.isCaptureRoot => Nil + case refs => + val res0 = refs.map(x => emitCapability(x, skipThisTypePrefix)) + 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], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + import reflect._ + Keyword("^") :: emitCaptureSet(refs, skipThisTypePrefix) + + private def emitFunctionArrow(using Quotes)(funTy: reflect.TypeRepr, captures: Option[List[reflect.TypeRepr]], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): 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, skipThisTypePrefix) + + private def emitByNameArrow(using Quotes)(captures: Option[List[reflect.TypeRepr]], skipThisTypePrefix: Boolean)(using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol): SSignature = + emitFunctionArrow(CaptureDefs.Function1.typeRef, captures, skipThisTypePrefix) \ 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]) =