@@ -4,7 +4,7 @@ package cc
44
55import core .*
66import Types .* , Symbols .* , Contexts .* , Decorators .*
7- import util .{SimpleIdentitySet , Property }
7+ import util .{SimpleIdentitySet , EqHashMap }
88import typer .ErrorReporting .Addenda
99import util .common .alwaysTrue
1010import scala .collection .mutable
@@ -21,9 +21,14 @@ import annotation.constructorOnly
2121import ast .tpd
2222import printing .{Printer , Showable }
2323import printing .Texts .Text
24+ import reporting .Message
25+ import NameOps .isImpureFunction
2426import 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+
524811end Capabilities
0 commit comments