Skip to content

Commit d925cd8

Browse files
committed
Integrate root.scala in Capability.scala
1 parent 6807a60 commit d925cd8

File tree

9 files changed

+326
-350
lines changed

9 files changed

+326
-350
lines changed

compiler/src/dotty/tools/dotc/cc/CCState.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ object CCState:
164164
/** Is `caps.cap` a root capability that is allowed to subsume other capabilities? */
165165
def capIsRoot(using Context): Boolean = ccState.capIsRoot
166166

167-
/** Run `op` under the assumption that all root.Fresh instances are equal.
167+
/** Run `op` under the assumption that all FreshCap instances are equal.
168168
* Needed to make override checking of types containing fresh work.
169169
* Asserted in override checking, tested in maxSubsumes.
170170
* Is this sound? Test case is neg-custom-args/captures/leaked-curried.scala.
@@ -177,7 +177,7 @@ object CCState:
177177
try op finally ccs.ignoreFreshLevels = saved
178178
else op
179179

180-
/** Should all root.Fresh instances be treated equal? */
180+
/** Should all FreshCap instances be treated equal? */
181181
def ignoreFreshLevels(using Context): Boolean = ccState.ignoreFreshLevels
182182

183183
end CCState

compiler/src/dotty/tools/dotc/cc/Capability.scala

Lines changed: 294 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package cc
44

55
import core.*
66
import Types.*, Symbols.*, Contexts.*, Decorators.*
7-
import util.{SimpleIdentitySet, Property}
7+
import util.{SimpleIdentitySet, EqHashMap}
88
import typer.ErrorReporting.Addenda
99
import util.common.alwaysTrue
1010
import scala.collection.mutable
@@ -21,9 +21,14 @@ import annotation.constructorOnly
2121
import ast.tpd
2222
import printing.{Printer, Showable}
2323
import printing.Texts.Text
24+
import reporting.Message
25+
import NameOps.isImpureFunction
2426
import annotation.internal.sharable
2527

26-
/** Capability --+-- RootCapabilty -----+-- GlobalCap
28+
/** Capabilities are members of capture sets. They partially overlap with types
29+
* as shown in the trait hierarchy below.
30+
*
31+
* Capability --+-- RootCapabilty -----+-- GlobalCap
2732
* | +-- FreshCap
2833
* | +-- ResultCap
2934
* |
@@ -47,7 +52,7 @@ object Capabilities:
4752
def currentId(using Context): Validity = validId(ctx.runId, ccState.iterationId)
4853
val invalid: Validity = validId(NoRunId, 0)
4954

50-
@sharable var nextRootId = 0
55+
@sharable private var nextRootId = 0
5156

5257
/** The base trait of all root capabilities */
5358
trait RootCapability extends Capability:
@@ -130,8 +135,7 @@ object Capabilities:
130135
* @param origin an indication where and why the FreshCap was created, used
131136
* for diagnostics
132137
*/
133-
case class FreshCap private (owner: Symbol, origin: root.Origin)(using @constructorOnly ctx: Context)
134-
extends RootCapability:
138+
case class FreshCap private (owner: Symbol, origin: Origin)(using @constructorOnly ctx: Context) extends RootCapability:
135139
val hiddenSet = CaptureSet.HiddenSet(owner)
136140
hiddenSet.owningCap = this
137141

@@ -140,10 +144,38 @@ object Capabilities:
140144
case _ => false
141145

142146
object FreshCap:
143-
def apply(origin: root.Origin)(using Context): FreshCap | GlobalCap.type =
147+
def apply(origin: Origin)(using Context): FreshCap | GlobalCap.type =
144148
if ccConfig.useSepChecks then FreshCap(ctx.owner, origin)
145149
else GlobalCap
146150

151+
/** A root capability associated with a function type. These are conceptually
152+
* existentially quantified over the function's result type.
153+
* @param binder The function type with which the capability is associated.
154+
* It is a MethodicType since we also have ResultCaps that are
155+
* associated with the ExprTypes of parameterless functions.
156+
* Currently we never create results over PolyTypes. TODO change this?
157+
* Setup:
158+
*
159+
* In the setup phase, `cap` instances in the result of a dependent function type
160+
* or method type such as `(x: T): C^{cap}` are converted to `ResultCap(binder)` instances,
161+
* where `binder` refers to the method type. Most other cap instances are mapped to
162+
* Fresh instances instead. For example the `cap` in the result of `T => C^{cap}`
163+
* is mapped to a Fresh instance.
164+
*
165+
* If one needs to use a dependent function type yet one still want to map `cap` to
166+
* a fresh instance instead an existential root, one can achieve that by the use
167+
* of a type alias. For instance, the following type creates an existential for `^`:
168+
*
169+
* (x: A) => (C^{x}, D^)
170+
*
171+
* By contrast, this variant creates a fresh instance instead:
172+
*
173+
* type F[X] = (x: A) => (C^{x}, X)
174+
* F[D^]
175+
*
176+
* The trick is that the argument D^ is mapped to D^{fresh} before the `F` alias
177+
* is expanded.
178+
*/
147179
case class ResultCap(binder: MethodicType) extends RootCapability:
148180
private var myOriginalBinder = binder
149181
def originalBinder: MethodicType = myOriginalBinder
@@ -283,7 +315,7 @@ object Capabilities:
283315
* - If it starts with a reference `r`, `r`'s owner.
284316
* - If it starts with cap, the `scala.caps` package class.
285317
* - If it starts with a fresh instance, its owner.
286-
* - If it starts with a ParamRef or a result root, NoSymbol.
318+
* - If it starts with a ParamRef or a ResultCap, NoSymbol.
287319
*/
288320
final def pathOwner(using Context): Symbol = pathRoot match
289321
case tp1: ThisType => tp1.cls
@@ -521,4 +553,259 @@ object Capabilities:
521553

522554
def toText(printer: Printer): Text = printer.toTextCapability(this)
523555
end Capability
556+
557+
/** The place of - and cause for - creating a fresh capability. Used for
558+
* error diagnostics
559+
*/
560+
enum Origin:
561+
case InDecl(sym: Symbol)
562+
case TypeArg(tp: Type)
563+
case UnsafeAssumePure
564+
case Formal(pref: ParamRef, app: tpd.Apply)
565+
case ResultInstance(methType: Type, meth: Symbol)
566+
case UnapplyInstance(info: MethodType)
567+
case NewMutable(tp: Type)
568+
case NewCapability(tp: Type)
569+
case LambdaExpected(respt: Type)
570+
case LambdaActual(restp: Type)
571+
case OverriddenType(member: Symbol)
572+
case DeepCS(ref: TypeRef)
573+
case Unknown
574+
575+
def explanation(using Context): String = this match
576+
case InDecl(sym: Symbol) =>
577+
if sym.is(Method) then i" in the result type of $sym"
578+
else if sym.exists then i" in the type of $sym"
579+
else ""
580+
case TypeArg(tp: Type) =>
581+
i" of type argument $tp"
582+
case UnsafeAssumePure =>
583+
" when instantiating argument of unsafeAssumePure"
584+
case Formal(pref, app) =>
585+
val meth = app.symbol
586+
if meth.exists
587+
then i" when checking argument to parameter ${pref.paramName} of $meth"
588+
else ""
589+
case ResultInstance(mt, meth) =>
590+
val methDescr = if meth.exists then i"$meth's type " else ""
591+
i" when instantiating $methDescr$mt"
592+
case UnapplyInstance(info) =>
593+
i" when instantiating argument of unapply with type $info"
594+
case NewMutable(tp) =>
595+
i" when constructing mutable $tp"
596+
case NewCapability(tp) =>
597+
i" when constructing Capability instance $tp"
598+
case LambdaExpected(respt) =>
599+
i" when instantiating expected result type $respt of lambda"
600+
case LambdaActual(restp: Type) =>
601+
i" when instantiating result type $restp of lambda"
602+
case OverriddenType(member: Symbol) =>
603+
i" when instantiating upper bound of member overridden by $member"
604+
case DeepCS(ref: TypeRef) =>
605+
i" when computing deep capture set of $ref"
606+
case Unknown =>
607+
""
608+
end Origin
609+
610+
// ---------- Maps between different kinds of root capabilities -----------------
611+
612+
613+
/** Map each occurrence of cap to a different Fresh instance
614+
* Exception: CapSet^ stays as it is.
615+
*/
616+
class CapToFresh(origin: Origin)(using Context) extends BiTypeMap, FollowAliasesMap:
617+
thisMap =>
618+
619+
override def apply(t: Type) =
620+
if variance <= 0 then t
621+
else t match
622+
case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet =>
623+
t
624+
case t @ CapturingType(_, _) =>
625+
mapOver(t)
626+
case t @ AnnotatedType(parent, ann) =>
627+
val parent1 = this(parent)
628+
if ann.symbol.isRetains && ann.tree.toCaptureSet.containsCap then
629+
this(CapturingType(parent1, ann.tree.toCaptureSet))
630+
else
631+
t.derivedAnnotatedType(parent1, ann)
632+
case _ =>
633+
mapFollowingAliases(t)
634+
635+
override def mapCapability(c: Capability, deep: Boolean): Capability = c match
636+
case GlobalCap => FreshCap(origin)
637+
case _ => super.mapCapability(c, deep)
638+
639+
override def fuse(next: BiTypeMap)(using Context) = next match
640+
case next: Inverse => assert(false); Some(IdentityTypeMap)
641+
case _ => None
642+
643+
override def toString = "CapToFresh"
644+
645+
class Inverse extends BiTypeMap, FollowAliasesMap:
646+
def apply(t: Type): Type = t match
647+
case t @ CapturingType(_, refs) => mapOver(t)
648+
case _ => mapFollowingAliases(t)
649+
650+
override def mapCapability(c: Capability, deep: Boolean): Capability = c match
651+
case _: FreshCap => GlobalCap
652+
case _ => super.mapCapability(c, deep)
653+
654+
def inverse = thisMap
655+
override def toString = thisMap.toString + ".inverse"
656+
657+
lazy val inverse = Inverse()
658+
659+
end CapToFresh
660+
661+
/** Maps cap to fresh. CapToFresh is a BiTypeMap since we don't want to
662+
* freeze a set when it is mapped. On the other hand, we do not want Fresh
663+
* values to flow back to cap since that would fail disallowRootCapability
664+
* tests elsewhere. We therefore use `withoutMappedFutureElems` to prevent
665+
* the map being installed for future use.
666+
*/
667+
def capToFresh(tp: Type, origin: Origin)(using Context): Type =
668+
if ccConfig.useSepChecks then
669+
ccState.withoutMappedFutureElems:
670+
CapToFresh(origin)(tp)
671+
else tp
672+
673+
/** Maps fresh to cap */
674+
def freshToCap(tp: Type)(using Context): Type =
675+
if ccConfig.useSepChecks then CapToFresh(Origin.Unknown).inverse(tp) else tp
676+
677+
/** Map top-level free existential variables one-to-one to Fresh instances */
678+
def resultToFresh(tp: Type, origin: Origin)(using Context): Type =
679+
val subst = new TypeMap:
680+
val seen = EqHashMap[ResultCap, FreshCap | GlobalCap.type]()
681+
var localBinders: SimpleIdentitySet[MethodType] = SimpleIdentitySet.empty
682+
683+
def apply(t: Type): Type = t match
684+
case t: MethodType =>
685+
// skip parameters
686+
val saved = localBinders
687+
if t.marksExistentialScope then localBinders = localBinders + t
688+
try t.derivedLambdaType(resType = this(t.resType))
689+
finally localBinders = saved
690+
case t: PolyType =>
691+
// skip parameters
692+
t.derivedLambdaType(resType = this(t.resType))
693+
case _ =>
694+
mapOver(t)
695+
696+
override def mapCapability(c: Capability, deep: Boolean) = c match
697+
case c @ ResultCap(binder) =>
698+
if localBinders.contains(binder) then c // keep bound references
699+
else seen.getOrElseUpdate(c, FreshCap(origin)) // map free references to FreshCap
700+
case _ => super.mapCapability(c, deep)
701+
end subst
702+
703+
subst(tp)
704+
end resultToFresh
705+
706+
/** Replace all occurrences of `cap` (or fresh) in parts of this type by an existentially bound
707+
* variable bound by `mt`.
708+
* Stop at function or method types since these have been mapped before.
709+
*/
710+
def toResult(tp: Type, mt: MethodicType, fail: Message => Unit)(using Context): Type =
711+
712+
abstract class CapMap extends BiTypeMap:
713+
override def mapOver(t: Type): Type = t match
714+
case t @ FunctionOrMethod(args, res) if variance > 0 && !t.isAliasFun =>
715+
t // `t` should be mapped in this case by a different call to `mapCap`.
716+
case t: (LazyRef | TypeVar) =>
717+
mapConserveSuper(t)
718+
case _ =>
719+
super.mapOver(t)
720+
721+
object toVar extends CapMap:
722+
private val seen = EqHashMap[RootCapability, ResultCap]()
723+
724+
def apply(t: Type) = t match
725+
case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction =>
726+
if variance > 0 then
727+
super.mapOver:
728+
defn.FunctionNOf(args, res, contextual)
729+
.capturing(ResultCap(mt).singletonCaptureSet)
730+
else mapOver(t)
731+
case _ =>
732+
mapOver(t)
733+
734+
override def mapCapability(c: Capability, deep: Boolean) = c match
735+
case c: (FreshCap | GlobalCap.type) =>
736+
if variance > 0 then
737+
seen.getOrElseUpdate(c, ResultCap(mt))
738+
else
739+
if variance == 0 then
740+
fail(em"""$tp captures the root capability `cap` in invariant position.
741+
|This capability cannot be converted to an existential in the result type of a function.""")
742+
// we accept variance < 0, and leave the cap as it is
743+
c
744+
case _ =>
745+
super.mapCapability(c, deep)
746+
747+
//.showing(i"mapcap $t = $result")
748+
override def toString = "toVar"
749+
750+
object inverse extends BiTypeMap:
751+
def apply(t: Type) = mapOver(t)
752+
753+
override def mapCapability(c: Capability, deep: Boolean) = c match
754+
case c @ ResultCap(`mt`) =>
755+
// do a reverse getOrElseUpdate on `seen` to produce the
756+
// `Fresh` assosicated with `t`
757+
val it = seen.iterator
758+
var ref: RootCapability | Null = null
759+
while it.hasNext && ref == null do
760+
val (k, v) = it.next
761+
if v eq c then ref = k
762+
if ref == null then
763+
ref = FreshCap(Origin.Unknown)
764+
seen(ref) = c
765+
ref
766+
case _ =>
767+
super.mapCapability(c, deep)
768+
769+
def inverse = toVar.this
770+
override def toString = "toVar.inverse"
771+
end inverse
772+
end toVar
773+
774+
toVar(tp)
775+
end toResult
776+
777+
/** Map global roots in function results to result roots. Also,
778+
* map roots in the types of parameterless def methods.
779+
*/
780+
def toResultInResults(sym: Symbol, fail: Message => Unit, keepAliases: Boolean = false)(tp: Type)(using Context): Type =
781+
val m = new TypeMap with FollowAliasesMap:
782+
def apply(t: Type): Type = t match
783+
case AnnotatedType(parent @ defn.RefinedFunctionOf(mt), ann) if ann.symbol == defn.InferredDepFunAnnot =>
784+
val mt1 = mapOver(mt).asInstanceOf[MethodType]
785+
if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true)
786+
else parent
787+
case defn.RefinedFunctionOf(mt) =>
788+
val mt1 = apply(mt)
789+
if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true)
790+
else t
791+
case t: MethodType if variance > 0 && t.marksExistentialScope =>
792+
val t1 = mapOver(t).asInstanceOf[MethodType]
793+
t1.derivedLambdaType(resType = toResult(t1.resType, t1, fail))
794+
case CapturingType(parent, refs) =>
795+
t.derivedCapturingType(this(parent), refs)
796+
case t: (LazyRef | TypeVar) =>
797+
mapConserveSuper(t)
798+
case _ =>
799+
try
800+
if keepAliases then mapOver(t)
801+
else mapFollowingAliases(t)
802+
catch case ex: AssertionError =>
803+
println(i"error while mapping $t")
804+
throw ex
805+
m(tp) match
806+
case tp1: ExprType if sym.is(Method, butNot = Accessor) =>
807+
tp1.derivedExprType(toResult(tp1.resType, tp1, fail))
808+
case tp1 => tp1
809+
end toResultInResults
810+
524811
end Capabilities

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ extension (tp: Type)
120120
false
121121

122122
/** The capture set of a type. This is:
123-
* - For trackable capabilities: The singleton capture set consisting of
123+
* - For object capabilities: The singleton capture set consisting of
124124
* just the reference, provided the underlying capture set of their info is not empty.
125125
* - For other capabilities: The capture set of their info
126126
* - For all other types: The result of CaptureSet.ofType
@@ -370,7 +370,7 @@ extension (tp: Type)
370370
// preceding arguments are known to be always pure
371371
t.derivedFunctionOrMethod(
372372
args,
373-
apply(root.resultToFresh(res, root.Origin.ResultInstance(t, NoSymbol))))
373+
apply(resultToFresh(res, Origin.ResultInstance(t, NoSymbol))))
374374
else
375375
t
376376
case _ =>
@@ -654,7 +654,7 @@ abstract class DeepTypeAccumulator[T](using Context) extends TypeAccumulator[T]:
654654
this(acc, parent)
655655
case t @ FunctionOrMethod(args, res) =>
656656
if args.forall(_.isAlwaysPure) then
657-
this(acc, root.resultToFresh(res, root.Origin.ResultInstance(t, NoSymbol)))
657+
this(acc, resultToFresh(res, Origin.ResultInstance(t, NoSymbol)))
658658
else acc
659659
case _ =>
660660
foldOver(acc, t)

0 commit comments

Comments
 (0)