@@ -4,7 +4,7 @@ package cc
4
4
5
5
import core .*
6
6
import Types .* , Symbols .* , Contexts .* , Decorators .*
7
- import util .{SimpleIdentitySet , Property }
7
+ import util .{SimpleIdentitySet , EqHashMap }
8
8
import typer .ErrorReporting .Addenda
9
9
import util .common .alwaysTrue
10
10
import scala .collection .mutable
@@ -21,9 +21,14 @@ import annotation.constructorOnly
21
21
import ast .tpd
22
22
import printing .{Printer , Showable }
23
23
import printing .Texts .Text
24
+ import reporting .Message
25
+ import NameOps .isImpureFunction
24
26
import annotation .internal .sharable
25
27
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
27
32
* | +-- FreshCap
28
33
* | +-- ResultCap
29
34
* |
@@ -47,7 +52,7 @@ object Capabilities:
47
52
def currentId (using Context ): Validity = validId(ctx.runId, ccState.iterationId)
48
53
val invalid : Validity = validId(NoRunId , 0 )
49
54
50
- @ sharable var nextRootId = 0
55
+ @ sharable private var nextRootId = 0
51
56
52
57
/** The base trait of all root capabilities */
53
58
trait RootCapability extends Capability :
@@ -130,8 +135,7 @@ object Capabilities:
130
135
* @param origin an indication where and why the FreshCap was created, used
131
136
* for diagnostics
132
137
*/
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 :
135
139
val hiddenSet = CaptureSet .HiddenSet (owner)
136
140
hiddenSet.owningCap = this
137
141
@@ -140,10 +144,38 @@ object Capabilities:
140
144
case _ => false
141
145
142
146
object FreshCap :
143
- def apply (origin : root. Origin )(using Context ): FreshCap | GlobalCap .type =
147
+ def apply (origin : Origin )(using Context ): FreshCap | GlobalCap .type =
144
148
if ccConfig.useSepChecks then FreshCap (ctx.owner, origin)
145
149
else GlobalCap
146
150
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
+ */
147
179
case class ResultCap (binder : MethodicType ) extends RootCapability :
148
180
private var myOriginalBinder = binder
149
181
def originalBinder : MethodicType = myOriginalBinder
@@ -283,7 +315,7 @@ object Capabilities:
283
315
* - If it starts with a reference `r`, `r`'s owner.
284
316
* - If it starts with cap, the `scala.caps` package class.
285
317
* - 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.
287
319
*/
288
320
final def pathOwner (using Context ): Symbol = pathRoot match
289
321
case tp1 : ThisType => tp1.cls
@@ -521,4 +553,259 @@ object Capabilities:
521
553
522
554
def toText (printer : Printer ): Text = printer.toTextCapability(this )
523
555
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
+
524
811
end Capabilities
0 commit comments